Java GregorianCalendar DST问题

Java GregorianCalendar DST问题,java,calendar,dst,gregorian-calendar,Java,Calendar,Dst,Gregorian Calendar,如果我把秋日光时间转换结束时的时间(2014-10-26丹麦CET时间02:00:00)减去一小时(因此我希望返回CEST时间02:00),然后将分钟数设为零,我会得到一些奇怪的结果: Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("CET")); cal.set(Calendar.YEAR, 2014); cal.set(Calendar.MONTH, Calendar.OCTOBER); cal.set(Calendar.

如果我把秋日光时间转换结束时的时间(2014-10-26丹麦CET时间02:00:00)减去一小时(因此我希望返回CEST时间02:00),然后将分钟数设为零,我会得到一些奇怪的结果:

Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("CET"));

cal.set(Calendar.YEAR, 2014);
cal.set(Calendar.MONTH, Calendar.OCTOBER);
cal.set(Calendar.DAY_OF_MONTH, 26);
cal.set(Calendar.HOUR_OF_DAY, 2);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);

System.out.println(cal.getTimeInMillis()); // 1414285200000 : 01:00:00 UTC

cal.add(Calendar.HOUR_OF_DAY, -1);

System.out.println(cal.getTimeInMillis()); // 1414281600000 : 00:00:00 UTC (as expected)

cal.set(Calendar.MINUTE, 0);
// or: cal.set(Calendar.MINUTE, cal.get(Calendar.MINUTE));
// both should be NOPs.

System.out.println(cal.getTimeInMillis()); // 1414285200000 : 01:00:00 UTC (Why?!)
这是虫子吗?我知道Java日历有一些奇怪的约定,但我看不出这怎么可能是正确的

使用Calendar类减去一小时并将分钟设置为0的正确方法是什么?

方法必须如下所示:

根据日历规则向给定日历字段添加或减去指定的时间量

所以我们面临着神秘的日历规则。通过
calendar.getInstance(TimeZone.getTimeZone(“CET”))
我们得到的日历类型是什么

执行

System.out.println(Calendar.getInstance(TimeZone.getTimeZone("CET")).getClass());
我得到了答案:
类java.util.GregorianCalendar
。让我们试着找出
gregorianalendar
的规则。我在(为什么在这里?)。其中一条规则适合你的情况

添加规则2。如果预期较小的字段是不变的,但由于字段更改后其最小值或最大值的变化,它不可能等于其先前的值,则其值将调整为尽可能接近其预期值

01:00:00和23:00:00具有相同的距离,因此根据这些规则,两者都是有效的

另外,请注意规范中的下一行

添加指定的(已签名)时间量

它说您使用它错误地调用了
cal.add(Calendar.HOUR\u OF_DAY,-1)因此,您的
add
仅在下一次
set
调用时显示结果,这并没有什么可指责的。但它不知怎么起作用了;你可以用它

在这里做什么?如果你认为2014-10-26 02:00:00 CET减去一小时等于它本身,那么你可以在你的
添加
之后添加愚蠢的
设置
。就像这样:

    cal.add(Calendar.HOUR_OF_DAY, -1);
    cal.set(Calendar.HOUR, cal.get(Calendar.HOUR)); //apply changes
    cal.set(Calendar.MINUTE, 0);
如果您想在
-1小时操作后看到差异,我建议您更改时区<代码>时区。getTimeZone(“GMT”)
(例如)没有夏令时偏移。

中的链接解释了问题并提出了解决方案。我确实试着用艰难的方式来审视代码

如果我们在设置之前和之后检查DST_偏移:

System.out.println(cal.get(Calendar.DST_OFFSET));
cal.set(Calendar.MINUTE, 0);
System.out.println(cal.get(Calendar.DST_OFFSET));
它打印:

3600000
0
查看methodgetTimeInMillis,我们看到他们在重新计算时间(以毫秒为单位)时使用一个标志(isTimeSet)进行检查:

public long getTimeInMillis() {
   if (!isTimeSet) {
       updateTime();
   }
   return time;
}
调用set时,标志istimet重置为false。来源:

A设置值:

internalSet(DST_OFFSET, zoneOffsets[1]); 
以毫秒为单位的时间在下一次调用getTimeInMillis时更改

因为我们不应该在初始化日期后使用set,否则我们会强制进行某种日期重置。这就是OpenJDK的bug跟踪器中的帖子所建议的

cal.add(Calendar.MINUTE, -cal.get(Calendar.MINUTE));
更好的选择是利用乔达时间。这个库的作者一直在开发新的Java 8的日期时间api,我希望没有这种问题。

以下是Java Bug追踪器的:

在“回退”期间,日历不支持消除歧义,给定的本地时间被解释为标准时间

要避免标准时间的意外DST更改,请调用add()重置该值

这意味着您应该将
set()
替换为

cal.add(Calendar.MINUTE, -cal.get(Calendar.MINUTE));

真奇怪!刚试过,结果完全一样!这就像在我启动ide进行检查时,它会记住关于joda time的setrequired注释之后的第一个值。我认为它是在向前滚动小时值(或拉日历的默认设置状态?。反转命令(设置分钟,然后添加负小时)看起来很有效,但对于这样一个明显的问题来说,这是一个解决办法。类似的问题。
cal.add(Calendar.MINUTE, -cal.get(Calendar.MINUTE));
cal.add(Calendar.MINUTE, -cal.get(Calendar.MINUTE));