При решении данной задачи нужно учитывать, что нельзя просто взять и вычесть количество миллисекунд одной даты из другой. Таким образом мы не можем в точности посчитать ничего, т.к. существует переход на летнее/зимнее время. Например, мы вычтем кол-во мс из даты, созданной на 30 марта 2009 года(установим также 0 часов, минут, секунд и миллисекунд) и 29 марта 2009 года(также обнулим лишние поля). Если перевести получившееся количество миллисекунд в часы, то мы получим 23 часа, а должно быть 24. Почему? Потому что именно в это время у нас переводят на зимнее время. Соответственно, необходимо учитывать данный факт.
То есть для вычисления мс, прошедших между двумя датами нужно использовать следующий метод:
public static long getDiffInMillis(Calendar c1, Calendar c2) { // учитываем перевод времени long time1 = c1.getTimeInMillis() + c1.getTimeZone().getOffset(c1.getTimeInMillis()); long time2 = c2.getTimeInMillis() + c2.getTimeZone().getOffset(c2.getTimeInMillis()); return Math.abs(time1 - time2); }
Таким образом мы легко получим кол-во прошедших часов, минут и секунд. Но если мы хотим получить кол-во прошедших дней, не учитывая часы, минуты и прочее, то нам необходим следующий метод:
public static int getDiffInDays(Calendar c1, Calendar c2) { Calendar gc1 = (Calendar)c1.clone(); Calendar gc2 = (Calendar)c2.clone(); // обнуляем часы, минуты и секунды gc1.set(Calendar.HOUR, 0); gc2.set(Calendar.HOUR, 0); gc1.set(Calendar.SECOND, 0); gc2.set(Calendar.SECOND, 0); gc1.set(Calendar.MILLISECOND, 0); gc2.set(Calendar.MILLISECOND, 0); return (int)(getDiffInMillis(c1, c2) / MILLISECONDS_IN_A_DAY); }
Но используя данную методику нельзя сказать сколько прошло месяцев и лет. Для этого мы будем использовать следующий метод:
/** *Как мы видим, данный метод просто пытается прибавить 1 к заданному временному промежутку. В clearFields мы задаем поля, которые необходимо обнулить. Таким образом мы можем найти разницу не только в годах и месяцах, но и в чем угодно, но это будет неэффективно. Следующей задачей является вывод нужного слова для временной единицы. Например, мы говорим прошло "5 лет", "2 месяца", "1 год", "11 секунд" и т.д. Оказывается, что все названия временных промежутков подчиняются единому закону. Достаточно всего лишь трех слов :). Этот закон выражен данным кодом:Высчитывает разницу между двумя временами, в зависимости от значения field. * field - это константное значение из java.util.Calendar, обозначающее * конкретную временную метрику(год, месяц и т.д.).
* *Устанавливает поля, заданные в массиве clearFields равными 0.
* * @param field * константное значение из java.util.Calendar(YEAR, MONTH...) * @param c1 * первая дата * @param c2 * вторая дата * @param clearFields * поля даты, которые не учитываются при сравнении. Задаются массивом констант из из java.util.Calendar. * Например, можно не учитывать миллисекунды, секунды и часы. Данные поля будут обнулены. * @return * разницу между c1 и c2 в метрике, заданной field */ public static int getTimeDifference(int field, Calendar c1, Calendar c2, int... clearFields) { Calendar gc1, gc2; if (c2.after(c1)) { gc1 = (Calendar)c1.clone(); gc2 = (Calendar)c2.clone(); } else { gc1 = (Calendar)c2.clone(); gc2 = (Calendar)c1.clone(); } if (clearFields != null) { // очищаем поля, которые мы не будем учитывать при сравнении дат for (int clearField : clearFields) { gc1.clear(clearField); gc2.clear(clearField); } } int count = 0; for (gc1.add(field, 1); gc1.compareTo(gc2) <= 0; gc1.add(field, 1)) { count++; } return count; }
/** * Возвращает слово, добавляющееся к временной единице, когда указывается сколько прошло * временных единиц. * В русском языке для временных единиц достаточно трех слов. * Значение time берется по модулю. * * @param time * количество временной единицы * @param timeUnitName1 * Название временной единицы, которое используется с 1. Например, 1 "минута". * @param timeUnitName2 * Название временной единицы, которое используется с 2. Например, 2 "минуты". * @param timeUnitName5 * Название временной единицы, которое используется с 5. Например, 5 "минут". * @return * слово для единицы времени */ public static String getTimeUnitPeriodName(long time, String timeUnitName1, String timeUnitName2, String timeUnitName5) { String result; time = Math.abs(time); // для отрицательных чисел int small = (int)(time % 10); int middle = (int)(time % 100); // если заканчивается на 11, то всегда оформляется словом timeUnitName5 if (small == 1 && middle != 11) { result = timeUnitName1; } // если оканчиваются на 2, 3, 4, за исключением 12, 13 и 14 else if (small >= 2 && small <= 4 && (middle < 12 || middle > 14)) { result = timeUnitName2; } else { result = timeUnitName5; } return result; }Оказалось, что так устроен наш язык, что особыми временными единицами являются 11, 12, 13 и 14(точнее оканчивающимся на эти числа). Остальное подчиняется общим правилам. То ли дело в английском языке. Теперь мы можем считать промежуток между двумя датами в любой временной единице и находить для данной единицы нужное слово. Осталась ещё одна нерешенная задача: как выводить временной промежуток в формате "X дней Y месяцев Z лет", но делать это так, чтобы у нас не было ситуации "12 дней 13 месяцев 1 год", а было например так "12 дней 1 месяц 2 года". Т.е. у нас не может быть больше 12 месяцев или больше 31 дня. Данную задачу мы решим следующим образом. Будем находить разницу в годах и затем вычитать данную разницу из конца промежутка. Дальше поступим также для месяца. Оставшиеся дни же просто выведем. Вот кусок кода(from и to - экземпляры Calendar):
// если конечная дата больше, то меняем их местами if (from.after(to)) { to = from; } int diffInYears = DateUtils.getDiffInYears(from, to); to.add(Calendar.YEAR, -diffInYears); // вычитаем учтенные года int diffInMonths = DateUtils.getDiffInMonths(from, to); to.add(Calendar.MONTH, -diffInMonths); // вычитаем учтенные месяцы int diffInDays = DateUtils.getDiffInDays(from, to);Не забудьте, что вы вычитаете из даты, поэтому нужно клонировать переданный экземпляр Calendar. Ну а далее форматируем полученные числа по своему желанию.
p.s. также не забывайте указывать первый день недели на понедельник(в России) - calendar.setFirstDayOfWeek(Calendar.MONDAY)
Так по простому, через миллисекунды делать нельзя. Не особо вникал - високосные года учитываются? Плюс, еще есть замечательной свойство когда раз в несколько лет то ли секунду, то ли миллисекунду добавляют.
ОтветитьУдалитьЛучше всего для этих целей использовать проверенные решения вроде joda times.
joda-time.sourceforge.net/faq.html#datediff
ОтветитьУдалить