在计算1918-03-24之后的天数时,Joda-time一个一个错误

时间:2017-10-10 09:29:59

标签: java datetime jodatime java-time date-difference

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

输出:

  乔达1918-03-24 6656
      Java 1918-03-24 6656

     乔达1918-03-25 6656
      Java 1918-03-25 6657

     乔达1918-03-26 6657
      Java 1918-03-26 6658

     乔达2017-10-10 43015
      Java 2017-10-10 43016

4 个答案:

答案 0 :(得分:8)

问题是您的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,显然你不需要完全限定它。)

答案 1 :(得分:4)

@Jon Skeet's answer是正确且直接的。我只想添加更多有关正在发生的事情的详细信息以及为什么会得到这些结果(正如您asked in the comments - 而且Jon也回复了一个提示,这也是正确的。)

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

您在Joda中创建的开始日期为1900-01-01T00:00Z 1月1日 st 1900年午夜在UTC )。但是,结束日期仅使用年,月和日创建。但DateTime也需要时间(小时/分钟/秒/毫秒)和时区。由于未指定,因此在JVM默认时区设置为午夜(不保证是UTC - 取决于JVM配置,您可以获得不同的结果)。

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

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

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

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

没有直接相关,但在Joda-Time中你可以使用常量DateTimeZone.UTC来引用UTC - 调用forID("UTC")是多余的,因为它总是返回常量(DateTimeZone.forID("UTC")==DateTimeZone.UTC返回{ {1}})。

答案 2 :(得分:0)

  

在计算1918-03-24之后的天数时,Joda-off-by-one错误

试试这个,我更正了你的程序它提供了与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");
}

答案 3 :(得分:0)

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,它也适用于这个例子,但第一个解决方案应该是一个更好的案例。