GregorianCalendar无法增加97天?

时间:2018-12-05 20:58:37

标签: java scala date gregorian-calendar

我正在创建3个GregorianCalendar对象:

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

第一天和第二天之间的天差为96天。第一天和第三个天之间的天差是... 96天。说吧?

为Scala代码道歉,但您的Java负责人应该能够了解正在发生的事情:

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

怎么了?

4 个答案:

答案 0 :(得分:7)

您必须位于美国的语言环境(或与美国同时开始夏令时的语言环境)。在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

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

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

  

计算返回一个整数,代表两个时间点之间的完整单位数。例如,时间11:30到13:29之间的小时数将只有1小时,因为它比2小时少了1分钟。

因此,相差96天23小时(由于DST而不是97天)返回为96

答案 1 :(得分:2)

那是因为您所在的时区。如果您通过比较第二个值

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秒,这意味着夏令时发生了变化。

答案 2 :(得分:2)

正如其他人已经说过的那样,请尽可能避免使用GregorianCalendar类。它存在一些设计问题,现在已经过时了。

但是,您的特定问题不是GregorianCalendar类。考虑到夏令时(DST)和其他所有因素,它可以在您的代码中很好地增加97天。正如其他人正确指出的那样,在今年12月4日之前增加97天可以使您在3月11日这一天,在使用夏令时的北美时区中,夏令时才刚刚开始(第11个月作为构造函数参数,意味着12月,另一件令人困惑的事情关于GregorianCalendar)。因此添加的最后一天只有23小时。

您真正遇到的问题是ChronoUnit.DAYSInstant的组合。 Instant是没有时区的时间点,没有天数的概念。取而代之的是,您得到的结果与从小时数中得到的结果相同,除以24,然后舍去其余部分。很少有用。由于添加的最后一天只有23小时,因此不计算在内。取而代之的是在ZonedDateTimeLocalDate实例之间或者在名称上带有“ date”的其他java.time类之间计算天数。示例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对其进行转换以获取{{1} }。这将为您提供ZonedDateTime中的所有相关信息,包括其时区和日历日期。换句话说,您不应像在问题代码中那样使用其GregorianCalendar方法。示例2:

toInstant

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

  

97

我都用America / New_York作为我的默认时区运行了这两个代码片段。

答案 3 :(得分:1)

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日越过了夏令时。