【问题标题】:Joda-time off-by-one error when counting days after 1918-03-24计算 1918 年 3 月 24 日之后的天数时,Joda-time off-by-one 错误
【发布时间】:2017-10-10 09:29:58
【问题描述】:

使用 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

【问题讨论】:

  • 使用 Java 8?和使用 Joda-time v2.9.9?,在此之前,您需要在完全了解它的工作原理后实现任何第三方库。我没有发现使用 Java 8 的任何问题?并使用 Joda-time v2.9.9,除了您在堆栈溢出时的特殊实现发布

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


【解决方案1】:

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

【讨论】:

  • 仍然没有解释为什么 1918-03-25,只有那一天,在原版中被搞砸了。将开始日期更改为该日期之前的任何时间仍会导致相同的问题。不过感谢您的修复。
  • @Chro:嗯,我们不知道您使用的是哪个时区,这无济于事 - 但 1918 年的英国 DST 过渡是在 3 月 24 日,这可能是相关的。如果您要求 1918 年 12 月的日期,两个结果匹配,而在 1919 年 6 月它们又不匹配(至少在我的机器上)。
  • 啊,是的,英国。所以这一定意味着当英国过渡时,那 1 个小时就永远失去了(或其他什么)。
  • @Chro 我已经在下面发布了an answer,并提供了更多详细信息。
【解决方案2】:

@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:00ZUTC 时间 1 月 1 日st 1900 年午夜)。但是,结束日期只能通过使用年、月和日来创建。但是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 - 在 DST 期间,伦敦使用偏移量 +01:00,这意味着它比 UTC提前 小时(所以到此结束日期相当于 1918-03-24T23:00Z - 或 1981 年 3 月 24 日th UTC 时间晚上 11 点)。

所以,小时差是 159767,不足以完成 6657 天,所以差是 6656 天(四舍五入总是到最小值 - 差必须至少 159768 小时才能完成 6657 天) .

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


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


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

【讨论】:

    【解决方案3】:

    在计算 1918-03-24 之后的天数时,Joda-time 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");
    }
    

    【讨论】:

      【解决方案4】:
      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,它也适用于本示例,但第一个解决方案应该是更好的情况。

      【讨论】:

        猜你喜欢
        • 2019-05-02
        • 2011-10-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-17
        • 2014-05-12
        • 2012-05-10
        • 2020-12-25
        相关资源
        最近更新 更多