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");
}
Другие вопросы по тегам