Чому різниця між 30 березня та 1 березня 2020 року помилково дає 28 днів замість 29?


124
TimeUnit.DAYS.convert(
   Math.abs(
      new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").parse("30-03-2020 00:00:00").getTime() - 
      new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").parse("1-03-2020 00:00:00").getTime()
   ),
   TimeUnit.MILLISECONDS)

Результат - 28, тоді як він повинен бути 29.

Чи може бути проблемою часовий пояс / місцеположення?


17
Примітка. Будь ласка, більше не використовуйте SimpleDateFormat, оскільки це застаріло. Використовуйте java.timeзамість пакунків . У SimpleDateFormatтакому випадку використовуйте DateTimeFormatter. У випадку з Java 7 див. Коментар Енді Тернера нижче.
MC Імператор

28
Не робіть математику часом. Використовуйте належну бібліотеку часу ( java.time[хоча зауважу, ви перебуваєте на Java 7], ThreeTenBp , Joda ).
Енді Тернер

14
Мені б хотілося, щоб я був на зустрічі, куди хтось їде "Гаразд, тепер, коли у нас з'явилися часові пояси, я щось зрозумів, давайте перейдемо до режиму 100% попу і реалізуємо цю річ під назвою економія денного світла, яка прийшла мені уві сні після моєї подорожі з кислотою" минулої ночі."
MonkeyZeus

5
@gmauch TimeUnitне претендує на те, що знає нічого про DST. Як говорить javadoc: Наносекунда визначається як одна тисячна частина мікросекунди, мікросекунда - одна тисячна частки мілісекунди, мілісекунда - одна тисячна частина секунди, хвилина шістдесят секунд, година шістдесят хвилин, а день як двадцять чотири години . --- Оскільки DST призводить до того, що 2 дні в році не становлять рівно 24 годин, TimeUnitпомиляється, коли задіяний DST.
Андреас

1
Ця проблема виникає лише на комп'ютерах, які знаходяться в часових поясах із економією денного світла. Це дає правильну кількість днів (29) у часових поясах, де немає економії денного світла!
Гопінат

Відповіді:


207

Проблема полягає в тому, що через зміну літнього часу (у неділю, 8 березня 2020 р.) Між цими датами проходить 28 днів і 23 години . TimeUnit.DAYS.convert(...) обрізає результат до 28 днів.

Щоб побачити проблему (я в східному часовому поясі США):

SimpleDateFormat fmt = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
long diff = fmt.parse("30-03-2020 00:00:00").getTime() -
            fmt.parse("1-03-2020 00:00:00").getTime();

System.out.println(diff);
System.out.println("Days: " + TimeUnit.DAYS.convert(Math.abs(diff), TimeUnit.MILLISECONDS));
System.out.println("Hours: " + TimeUnit.HOURS.convert(Math.abs(diff), TimeUnit.MILLISECONDS));
System.out.println("Days: " + TimeUnit.HOURS.convert(Math.abs(diff), TimeUnit.MILLISECONDS) / 24.0);

Вихідні дані

2502000000
Days: 28
Hours: 695
Days: 28.958333333333332

Для виправлення використовуйте часовий пояс, який не має DST, наприклад, UTC :

SimpleDateFormat fmt = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
long diff = fmt.parse("30-03-2020 00:00:00").getTime() -
            fmt.parse("1-03-2020 00:00:00").getTime();

Вихідні дані

2505600000
Days: 29
Hours: 696
Days: 29.0

121
"Для виправлення" не робіть математики з часом. Використовуйте належну бібліотеку дати / часу.
Енді Тернер

62
@AndyTurner Щоб виправити, використовуючи вбудовані API Java 7. Оскільки це можна виправити, показуючи, як є правильною відповіддю. Змусити когось включити повну бібліотеку (Joda-Time, ThreeTen тощо) просто для цього, один розрахунок буде надмірним. Звичайно, використання бібліотеки було б рекомендовано, але це не потрібно .
Андреас

38
Я з повагою не згоден. Якщо ви хочете зробити розрахунок, зробіть це правильно; оплатити витрати, щоб зробити це правильно.
Енді Тернер

16
Мої 0,02 євро: "Правильним" способом зробити в Java 7 без будь-якої зовнішньої бібліотеки було б використовувати GregorianCalendarоб'єкт і додати 1 день за один раз до досягнення кінцевої дати. Я б заплатив високу ціну, щоб уникнути цього. І додавання бекпорту бібліотеки, яка вже є частиною Java 8, 9, 10, 11, 12, 13,…, це не висока ціна. Навпаки, наступного разу, коли вам доведеться щось робити з датою чи часом, це вже буде виграшем.
Оле ВВ

27
Навіть у UTC останній день червня періодично буває однією секундою занадто короткою, без реального попередження чи передбачуваності. Завжди використовуйте бібліотеку дат і часу.
Трапляється

41

Причина цієї проблеми вже згадується у відповіді Андреаса .

Питання в тому, що саме ви хочете порахувати. Той факт, що ви заявляєте, що фактична різниця повинна бути 29, а не 28, і запитуєте, чи "час розташування / зони може бути проблемою" , виявляє, що ви насправді хочете порахувати. Мабуть, ви хочете позбутися будь-якої різниці часових поясів.

Я припускаю, що ви хочете розраховувати лише дні, без часу та часового поясу.

Java 8

Нижче, на прикладі того, як кількість днів між ними можна було правильно обчислити, я використовую клас, який представляє саме це - дату без часу та часового поясу - LocalDate.

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d-MM-yyyy HH:mm:ss");
LocalDate start = LocalDate.parse("1-03-2020 00:00:00", formatter);
LocalDate end = LocalDate.parse("30-03-2020 00:00:00", formatter);

long daysBetween = ChronoUnit.DAYS.between(start, end);

Зауважте ChronoUnit, DateTimeFormatterі LocalDateвимагайте принаймні Java 8, яка вам недоступна, згідно з тегом . Однак, можливо, це і майбутнім читачам.

Як згадував Ole VV, є також ThreeTen Backport , який підтримує функціональність API Java 8 Дата і Час на Java 6 і 7.


2
@ OleV.V. Я знаю, що існує ThreeTen, деякі користувачі, можливо, згадували про це кілька разів. (Я збирався посилатись на запит SEDE, який повертав усі повідомлення та коментарі користувача 5772882, що містять текст ThreeTen;-), але, на жаль, на момент написання повідомлення в автономному режимі.) Я оновлю публікацію.
MC Імператор

2
@OleVV Це зовсім не погано. Я думаю, що більшість людей просто не знають java.time, тому що в школі вони все ще використовують старі класи. Але API 8 Дата і Час дуже добре розроблений - було б втратою не використовувати його.
MC Імператор
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.