Java GregorianCalendar DST问题
如果我把秋日光时间转换结束时的时间(2014-10-26丹麦CET时间02:00:00)减去一小时(因此我希望返回CEST时间02:00),然后将分钟数设为零,我会得到一些奇怪的结果: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.
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));