Joda-Time在计算1918年3月24日后的天数时出现了一个偏差错误。

7

使用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年3月24日 6656
Java 1918年3月24日 6656

Joda 1918年3月25日 6656
Java 1918年3月25日 6657

Joda 1918年3月26日 6657
Java 1918年3月26日 6658

Joda 2017年10月10日 43015
Java 2017年10月10日 43016


使用Java 8?和Joda-time v2.9.9?在此之前,您需要在充分了解其工作原理后实现任何第三方库。我没有发现任何问题使用Java 8?和Joda-time v2.9.9,除非您在Stack Overflow上发布了异常的实现。 - user4856296
4个回答

10
问题在于您的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年3月25日出现了问题。将开始日期更改为该日期之前的任何时间仍会导致相同的问题。感谢修复。 - Chro
2
@Chro:我们不知道你使用的时区,这并没有帮助——但是1918年英国夏令时转换是在3月24日,这可能是相关的。如果你要求1918年12月的日期,两个结果是匹配的,在1919年6月它们再次不匹配(至少在我的机器上)。 - Jon Skeet
啊,是的,英国。这意味着当英国过渡时,1小时就永远丢失了(或者类似的情况)。 - Chro
1
@Chro,我在下面发布了答案,其中包含更多关于此事的详细信息。 - user7605325

4

@Jon Skeet的答案是正确的并且直接到点。我想补充更多关于发生了什么以及为什么会得到这些结果的细节(正如您在评论中提出的问题 - Jon 也回答了一个提示,那也是正确的)。

你的JVM默认时区可能是Europe/London(或者任何一个在1918年3月24日有夏令时改变的DST的时区)。您可以通过在Joda-Time中使用DateTimeZone.getDefault()和在Java 8中使用ZoneId.systemDefault()来检查。

你在Joda中创建的开始日期是1900-01-01T00:00ZUTC时间下的1900年1月1日午夜)。然而,结束日期只是使用年、月和日创建的。但是DateTime还需要时间(小时/分钟/秒/毫秒)和时区。由于这些没有指定,它被设置为JVM默认时区的午夜(这不能保证是UTC - 取决于JVM配置,您可能会得到不同的结果)。

假设您的默认时区是伦敦(这就是我能复现问题的方式 - 在我的JVM默认时区(America/Sao_Paulo),它不会发生)。在1981年3月25日,伦敦处于DST状态,因此当您使用1918-03-25创建结束日期时,结果是伦敦时区中的1981年3月25日午夜 - 但由于DST更改,结果是1918-03-25T00:00+01:00 - 在DST期间,伦敦使用偏移量+01:00,这意味着它比UTC快一小时(因此这个结束日期等价于1918-03-24T23:00Z - 或者UTC时间下的1981年3月24日晚上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)。

0
Joda-Time 在计算 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");
}

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,这对于这个例子也有效,但第一种解决方案应该是更好的情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接