Joda-time-off-one ошибка при подсчете дней после 1918-03-24
Расчет количества дней между 1900-01-01
и дата после 1918-03-24
использование Joda-Time, похоже, дает один за другим результат.
Использование Java 8 java.time
дает правильный результат. В чем причина того, что Joda-Time не считает 1918-03-25
?
Использование Joda-Time v2.9.9.
public static void main(String[] args) {
jodaDiff("1918-03-24");
javaDiff("1918-03-24");
jodaDiff("1918-03-25");
javaDiff("1918-03-25");
jodaDiff("1918-03-26");
javaDiff("1918-03-26");
jodaDiff("2017-10-10");
javaDiff("2017-10-10");
}
private static void jodaDiff(String date) {
DateTime start = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeZone.forID("UTC"));
DateTimeFormatter dateDecoder = DateTimeFormat.forPattern("YYYY-MM-dd");
DateTime end = dateDecoder.parseDateTime(date);
int diff = Days.daysBetween(start, end).getDays();
System.out.println("Joda " + date + " " + diff);
}
private static void javaDiff(String date) {
LocalDate start = LocalDate.parse("1900-01-01");
LocalDate end = LocalDate.parse(date);
int diff = (int) ChronoUnit.DAYS.between(start, end);
System.out.println("Java " + date + " " + diff + "\n");
}
Выход:
Йода 1918-03-24 6656
Ява 1918-03-24 6656Йода 1918-03-25 6656
Ява 1918-03-25 6657Йода 1918-03-26 6657
Ява 1918-03-26 6658Йода 2017-10-10 43015
Ява 2017-10-10 43016
4 ответа
Проблема в том, что ваш DateTimeFormatter
использует системный часовой пояс по умолчанию. В идеале вы должны разобрать LocalDate
значения вместо DateTime
, но вы все равно можете это исправить, используя UTC для форматера:
DateTimeFormatter dateDecoder = DateTimeFormat.forPattern("YYYY-MM-dd").withZoneUTC();
Разобрать с LocalDate
вместо этого просто используйте:
org.joda.time.LocalDate start = new org.joda.time.LocalDate(1900, 1, 1);
DateTimeFormatter dateDecoder = DateTimeFormat.forPattern("YYYY-MM-dd");
org.joda.time.LocalDate end = dateDecoder.parseLocalDate(date);
(Очевидно, вам не нужно полностью его квалифицировать, если вы не используете Java 8.)
Ответ @Jon Skeet является правильным и прямым к сути. Я просто хотел бы добавить больше информации о том, что происходит и почему вы получаете эти результаты (как вы просили в комментариях - и Джон также ответил намеком, это также правильно).
Ваш часовой пояс JVM по умолчанию, вероятно, Europe/London
(или любой другой, у которого есть изменение летнего времени 24 марта 1918). Вы можете проверить это с помощью DateTimeZone.getDefault()
в Йода-Тайм и ZoneId.systemDefault()
в Java 8.
Дата начала, которую вы создали в Joda: 1900-01-01T00:00Z
(1 января 1900 года в полночь по UTC). Дата окончания, тем не менее, создается только с использованием года, месяца и дня. Но DateTime
также необходимо время (час / минута / секунда / миллисекунда) и часовой пояс. Так как они не указаны, для часового пояса JVM по умолчанию установлено значение " полночь" (который не обязательно будет UTC - в зависимости от конфигурации JVM вы можете получить другой результат).
Предполагая, что вашим часовым поясом по умолчанию является Лондон (именно так я мог воспроизвести проблему - в моем часовом поясе JVM по умолчанию (America/Sao_Paulo
) это не происходит). 25 марта 1981 года Лондон был в летнее время, поэтому, когда вы создадите дату окончания 1918-03-25
результат - 25 марта 1981 года в полночь лондонского часового пояса, но из-за изменения летнего времени результат 1918-03-25T00:00+01:00
- во время летнего времени Лондон использует смещение +01:00
, это означает, что это на один час впереди UTC (так что эта дата окончания эквивалентна 1918-03-24T23:00Z
- или 24 марта 1981 года в 23:00 по Гринвичу).
Таким образом, разница в часах составляет 159767, что недостаточно для завершения 6657 дней, поэтому разница составляет 6656 дней (округление всегда до минимального значения - разница должна составлять не менее 159768 часов, чтобы завершить 6657 дней).
Когда вы используете LocalDate
время и эффекты летнего времени не учитываются LocalDate
есть только день, месяц и год), и вы получите правильную разницу. Если вы также установите дату окончания в UTC, вы также получите правильные результаты, потому что в UTC нет изменений летнего времени.
Кстати, если вы используете Java 8 ZonedDateTime
и используйте дату начала с UTC и дату окончания с лондонским часовым поясом (вместо использования LocalDate
), вы получите такую же разницу в результатах.
Не связано напрямую, но в Joda-Time вы можете использовать постоянную DateTimeZone.UTC
ссылаться на UTC - звонить forID("UTC")
избыточен, так как в любом случае возвращает константу (DateTimeZone.forID("UTC")==DateTimeZone.UTC
возвращается true
).
DateTime start = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeZone.forID("UTC"));
DateTimeFormatter dateDecoder = DateTimeFormat.forPattern("YYYY-MM-dd").withZone(DateTimeZone.forID("UTC"));
Когда вы создаете первую дату, вы фиксируете зону, а вторая - нет. Так что либо удалите зону, либо установите одинаковое в обоих местах.
Вы также можете конвертировать оба DateTime в LocalDate, это также работает для этого примера, но первое решение должно быть лучшим вариантом.
Joda-time-off-one ошибка при подсчете дней после 1918-03-24
Попробуйте это, я исправил вашу программу Это дает вам исключительный ответ по сравнению с Java
public static void main(String[] args) {
jodaDiff("1918-03-24");
javaDiff("1918-03-24");
jodaDiff("1918-03-25");
javaDiff("1918-03-25");
jodaDiff("1918-03-26");
javaDiff("1918-03-26");
jodaDiff("2017-10-10");
javaDiff("2017-10-10");
}
private static void jodaDiff(String date) {
DateTime start = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeZone.forID("UTC"));
DateTime end = new DateTime(date);
int diff = Days.daysBetween(start.toLocalDate(), end.toLocalDate()).getDays();
System.out.println("Joda " + date + " " + diff);
}
private static void javaDiff(String date) {
LocalDate start = LocalDate.parse("1900-01-01");
LocalDate end = LocalDate.parse(date);
int diff = (int) ChronoUnit.DAYS.between(start, end);
System.out.println("Java " + date + " " + diff + "\n");
}