1976年3月28日到3月29日之间,java.util.GregorianCalendar发生了什么?

20

我正在尝试使用GregorianCalendar,但在计算自特定日期以来的天数时遇到了奇点问题。 在Scala解释器中,我输入了:

scala>import java.util.GregorianCalendar
scala>import java.util.Calendar
scala>val dateToday = new GregorianCalendar(2012,Calendar.MAY,22).getTimeInMillis()
dateToday: Long = 1337637600000
scala>val days1 = (dateToday - (new GregorianCalendar(1976,Calendar.MARCH,28).getTimeInMillis())) / (1000*3600*24)
days1: Long = 13203
scala>val days2 = (dateToday - (new GregorianCalendar(1976,Calendar.MARCH,29).getTimeInMillis())) / (1000*3600*24)
days2: Long = 13203

我不知道1976年是闰年是否有影响,但days1和days2应该相差1天。这是自1970年以来唯一发生这种情况的时刻。

想知道发生了什么,我计算了先前提到的两个日期之间的差异,结果只有23小时的差异!那天发生了什么?维基百科显然没有提到任何关于它的内容。

更重要的是,如何计算自特定日期以来的实际天数?


你应该将 dateToday 定义为一个 def,否则随着实验的进行,偏移量会变得越来越奇怪。 - Debilski
放心,这只是为了确保日期对每个人都是正确的。在原始代码中,我写了Calendar.getInstance().getTimeInMillis()。 - Mikaël Mayer
我的意思是你应该为我们SO上的测试人员将其制作成一个“def”。 :) - Debilski
好的,我明白了,我应该将dateToday重命名为dateWhereIFoundTheBigDateBug - Mikaël Mayer
3个回答

46

问题

夏令时的一天只有23小时。

根据这个网站,至少在巴黎,1976年3月28日是夏令时。那一天凌晨1点至2点之间的一个小时被直接跳过了。

尽管这可能是由于本地设置引起的问题,因为在我的电脑上,显示的时间是正确的:

scala> val days1 = (dateToday - (new GregorianCalendar(1976, Calendar.MARCH, 28).getTimeInMillis())) / (1000 * 3600 * 24)
days1: Long = 13203

scala> val days2 = (dateToday - (new GregorianCalendar(1976, Calendar.MARCH, 29).getTimeInMillis())) / (1000 * 3600 * 24)
days2: Long = 13202

我不在巴黎或其他发生时区变化的地方;时间变化发生在1976年4月25日,而我就在那里。因此,在那一天我会遇到“跳过一天”的情况。

scala> val days3 = (dateToday - (new GregorianCalendar(1976, Calendar.APRIL, 25).getTimeInMillis())) / (1000 * 3600 * 24)
days3: Long = 13175

scala> val days4 = (dateToday - (new GregorianCalendar(1976, Calendar.APRIL, 26).getTimeInMillis())) / (1000 * 3600 * 24)
days4: Long = 13175

Erwin在评论中指出,你唯一注意到这里日期差异不正确的原因可能是所有其他夏令时日都受到那些年份中也发生的25小时日偏移量的影响,当夏令时日被校正时。

解决方法

使用更好的日期处理库。 joda-time库可以正确处理(而且整体上是一个更好的日期/时间框架):

import org.joda.time.Days
import org.joda.time.DateTimeConstants
import org.joda.time.DateMidnight

val d1 = new DateMidnight(1976, DateTimeConstants.APRIL, 25)
val d2 = new DateMidnight(1976, DateTimeConstants.APRIL, 26)
val x = Days.daysBetween(d1, d2).getDays()
println(x) // 1

1
那么,为什么只在这个日期上?自那以后有很多夏令时日。如何解决这个问题来计算自那以后的天数? - Mikaël Mayer
5
也许其他日期缺失或增加的1个小时被抵消了(1-1+1-1+1-1+1=1)。 - Erwin Mayer
1
@meak:这件事情应该发生在此日期之后。然而,这是夏令时在法国(重新)引入的第一天,因此不应该在那之前发生。 - Debilski

4

如果没有浮点除法选项,我能想到的最好答案是在计算两天之间的差异时添加一个小时(1000*3600):

scala> (dateToday - (new GregorianCalendar(1976, Calendar.MARCH, 28).getTimeInMillis()) + 1000*3600) / (1000 * 3600 * 24)
days1: Long = 13204
scala> (dateToday - (new GregorianCalendar(1976, Calendar.MARCH, 29).getTimeInMillis()) + 1000*3600) / (1000 * 3600 * 24)
days1: Long = 13203

现在,由于不再存在闰秒问题,所以此方法适用于任何日期。


GregorianCalendar 处理闰秒吗? - Debilski
1
闰秒?如果你指的是夏令时(更有可能的罪魁祸首),并不是所有实行夏令时的国家都在午夜进行。 - Clockwork-Muse
@X-Zero 跳秒。每隔几年,会增加或减少一秒来纠正地球轨道的变化。 - Daniel C. Sobral
是的,但是CalendarGregorianCalendar没有提到它们,所以我怀疑这是否是正确的。Date提到了它们,但似乎只是作为旁注,因为它们指出“大多数计算机时钟不够准确,无法反映闰秒差异。”因此,使用和实现取决于具体实现(文档中给出的示例在我的机器上不会产生闰秒)。 - Clockwork-Muse
我记得在Java中,使用(或不使用)闰秒是实现相关的。 - Callum Rogers
这个答案(https://dev59.com/I2gv5IYBdhLWcg3wTvE-) 应该被采纳。 - Basil Bourque

3

dhg指出了我认为的:这一天是一个“夏令时”日。

由于这一天只有23小时长,除以一天的欧几里得除法等于0。

事实上,使用GregorianCalendar对象仅使用日期作为毫秒,因此将其作为整数除以一天只会截断结果。

不要使用欧几里得(整数)除法,尝试进行浮点除法,然后四舍五入结果。


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