GregorianCalendar 不能加上 97 天?

GregorianCalendar can't add 97 days?

我正在创建 3 个 GregorianCalendar 对象:

  1. 2018 年 12 月 4 日
  2. 2018 年 12 月 4 日 + 96 天
  3. 2018 年 12 月 4 日 + 97 天

第一次和第二次相差96天。第一天和第三天相差……96天。嗯?

对 Scala 代码表示歉意,但您 Java-heads 应该能够了解正在发生的事情:

def test(): Unit = {
    val start = new GregorianCalendar(2018, 11, 4)
    val laterA = new GregorianCalendar(2018, 11, 4)
    laterA.add(Calendar.DATE, 96)
    val laterB = new GregorianCalendar(2018, 11, 4)
    laterB.add(Calendar.DATE, 97)
    println(ChronoUnit.DAYS.between(start.toInstant, laterA.toInstant))
    println(ChronoUnit.DAYS.between(start.toInstant, laterB.toInstant))
  }

以上打印如下:

96
96

怎么回事?

您必须位于美国的某个地区(或与美国同时开始夏令时的地区)。 2019 年,DST starts at 2:00 am Sunday, March 10, 2019.

从 2018 年 12 月 4 日开始加上 96 天得到 2019 年 3 月 10 日。从 2018 年 12 月 4 日开始加上 97 天得到 2019 年 3 月 11 日。

格式化和输出 laterAlaterB 产量:

2019-03-10 00:00:00
2019-03-11 00:00:00

请注意,由于夏令时(在美国),这两个日期之间有 23 小时。

但是 between method 会截断小数单位。

The calculation returns a whole number, representing the number of complete units between the two temporals. For example, the amount in hours between the times 11:30 and 13:29 will only be one hour as it is one minute short of two hours.

因此返回 96 天 23 小时(由于 DST 而不是 97 天)的差异 96

jshell> System.out.println(laterB.toInstant()); 2019-03-11T04:00:00Z

jshell> System.out.println(laterA.toInstant()); 2019-03-10T05:00:00Z

请注意,相差不到 24 小时。为什么?您在 3 月 10 日跨越了夏令时界限。

那是因为你所在的时区。如果您通过

比较第二个值
System.out.println(ChronoUnit.SECONDS.between(start.toInstant(), laterA.toInstant()));
System.out.println(ChronoUnit.SECONDS.between(start.toInstant(), laterB.toInstant()));

你会看到 Europe/London 你会得到

8294400
8380800

但是 America/New_York 它将是

8294400
8377200

秒的差异恰好是 3600 秒,这意味着夏令时更改。

正如其他人已经说过的,如果可以的话,请避免使用 GregorianCalendar class。它有一些设计问题,现在已经过时了。

但是,您的特定问题不在于 GregorianCalendar class。它在您的代码中很好地添加了 97 天,将夏令时 (DST) 和所有内容都考虑在内。正如其他人正确指出的那样,今年的 12 月 4 日加上 97 天就是 3 月 11 日,在使用夏令时的北美时区,夏令时刚刚开始的那一天(11 月作为构造函数参数表示 12 月,这是另一个令人困惑的事情约 GregorianCalendar)。因此添加的最后一天只有 23 小时。

您的真正问题在于 ChronoUnit.DAYSInstant 的组合。 Instant 是一个没有时区的时间点,没有天的概念。相反,您得到的结果与计算小时数的结果相同,除以 24 并丢弃余数。这很少有用。由于最后一天添加的时间仅为 23 小时,因此未被计算在内。而是计算 ZonedDateTimeLocalDate 实例之间的天数,或其他一些 java.time class 名称中包含“日期”的天数。示例1(Java代码,你能自己翻译吗?):

    LocalDate start = LocalDate.of(2018, Month.DECEMBER, 4);
    LocalDate laterB = start.plusDays(97);
    System.out.println(ChronoUnit.DAYS.between(start, laterB));

输出:

97

如果您无法避免从旧版 API 中获得一个您现在无力升级的 GregorianCalendar,您应该做的第一件事就是使用 toZonedDateTime 将其转换为得到一个ZonedDateTime。这将为您提供 GregorianCalendar 的所有相关信息,包括其时区和日历日期。换句话说,您不应该像在问题代码中那样使用它的 toInstant 方法。示例 2:

    GregorianCalendar start = new GregorianCalendar(2018, Calendar.DECEMBER, 4);
    GregorianCalendar laterB = new GregorianCalendar(2018, Calendar.DECEMBER, 4);
    laterB.add(Calendar.DATE, 97);
    System.out.println(ChronoUnit.DAYS.between(start.toZonedDateTime(),
                                               laterB.toZonedDateTime()));

现在的输出是您所期望的:

97

我有 运行 两个片段 America/New_York 作为我的默认时区。