计算 1918-03-24 之后的天数时,Joda 时间差一错误

Joda-time off-by-one error when counting days after 1918-03-24

使用 Joda-Time 计算 1900-01-011918-03-24 之后的日期之间的天数似乎给出了差一的结果。

使用 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");
}

输出:

Joda 1918-03-24 6656
Java 1918-03-24 6656

Joda 1918-03-25 6656
Java 1918-03-25 6657

Joda 1918-03-26 6657
Java 1918-03-26 6658

Joda 2017-10-10 43015
Java 2017-10-10 43016

问题是您的 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,则无需完全限定它。)

Joda-time off-by-one error when counting days after 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");
}
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,它也适用于此示例,但第一个解决方案应该更好。

is correct and direct to the point. I'd just like to add more details about what's happening and why you get these results (as you - Jon 也回复了提示,这也是正确的)。

您的 JVM 默认时区可能是 Europe/London(或任何其他具有 DST change in March 24th 1918 的时区)。您可以通过在 Joda-Time 中使用 DateTimeZone.getDefault() 和在 Java 中使用 ZoneId.systemDefault() 来检查 8.

您在 Joda 中创建的开始日期是 1900-01-01T00:00Z1 月 1 日st 1900 年午夜 UTC)。但是,结束日期只能通过使用年、月和日来创建。但是 DateTime 还需要时间 (hour/minute/second/millisecond) 和时区。由于未指定,它被设置为 JVM 默认时区 的午夜(不保证是 UTC - 根据 JVM 配置,您可以获得不同的结果)。

假设您的默认时区是伦敦(这就是我重现问题的方式 - 在我的 JVM 默认时区 (America/Sao_Paulo) 中它不会发生)。 1981 年 3 月 25,伦敦采用夏令时,因此当您使用 1918-03-25 创建结束日期时,结果是 3 月 25 th 1981 年伦敦时区午夜 - 但由于 DST 更改,结果为 1918-03-25T00:00+01:00 - 在 DST 期间,伦敦使用偏移量 +01:00,这意味着它是一个UTC 的 提前 小时(因此此结束日期相当于 1918-03-24T23:00Z - 或 March 24th 1981 at 11下午 (UTC)).

因此,小时差为 159767,不足以完成 6657 天,因此差值为 6656 天(四舍五入始终为最低值 - 差值必须至少为 159768 小时才能完成 6657天)。

当您使用 LocalDate 时,不考虑时间和夏令时的影响(LocalDate 只有日、月和年),您会得到正确的差异。如果您也将结束日期设置为 UTC,您也会得到正确的结果,因为 UTC 没有 DST 更改。


顺便说一句,如果您使用 Java 8 ZonedDateTime,并使用 UTC 开始日期和伦敦时区结束日期(而不是使用 LocalDate),您获得相同的结果差异。


没有直接关系,但在 Joda-Time 中,您可以使用常量 DateTimeZone.UTC 来引用 UTC - 调用 forID("UTC") 是多余的,因为它 returns 无论如何都是常量(DateTimeZone.forID("UTC")==DateTimeZone.UTC returns true).