[英]GregorianCalendar DST problems
如果我在秋季结束时点亮时间(2014-10-26 02:00:00 CET in Denmark)并减去一小时(所以我希望回到02:00 CEST)然后将分钟设置为零,我得到一些奇怪的结果:
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有一些奇怪的约定,但我看不出这是如何正确的。
使用Calendar类减去一小时并将分钟设置为0的正确方法是什么?
apangin响应中的链接解释了问题并提出了解决方案。 我确实试图通过代码查看它。
如果我们在设置之前和之后检查DST_OFFSET:
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时,标志isTimeSet被重置为false。 从GregorianCalendar的来源:
public void set(int field, int value)
{
if (isLenient() && areFieldsSet && !areAllFieldsSet) {
computeFields();
}
internalSet(field, value);
isTimeSet = false; // <-------------- here
areFieldsSet = false;
isSet[field] = true;
stamp[field] = nextStamp++;
if (nextStamp == Integer.MAX_VALUE) {
adjustStamp();
}
}
因此设置重置DST偏移。 此行获取偏移量:
zoneOffset = ((ZoneInfo)tz).getOffsets(time, zoneOffsets);
稍微降低值设置:
internalSet(DST_OFFSET, zoneOffsets[1]);
以毫秒为单位的时间在下一次调用getTimeInMillis时被更改。
正如apangin建议我们不应该在初始化日期后使用set ,否则我们会强制重置日期。 这是OpenJDK的bug跟踪器中的帖子中的建议。
cal.add(Calendar.MINUTE, -cal.get(Calendar.MINUTE));
更好的选择是使用Joda Time。 这个库的作者一直在研究新的Java 8的日期时间api,我希望不会有这样的问题。
Calendar.add
方法必须如下操作:
根据日历的规则 ,将指定的时间量添加或减去给定的日历字段。
所以我们在这里遇到了神秘的日历规则。 Calendar.getInstance(TimeZone.getTimeZone("CET"))
我们得到的日历类型是什么?
执行
System.out.println(Calendar.getInstance(TimeZone.getTimeZone("CET")).getClass());
我有答案: class java.util.GregorianCalendar
。 让我们试着找到GregorianCalendar
规则。 我在add method
找到了它们(为什么在这里?)。 其中一条规则适合您的情况。
添加规则2.如果预期较小的字段不变,但由于字段更改后其最小值或最大值的变化,它不可能等于其先前值,则将其值调整为尽可能接近可能达到预期值
01:00:00和23:00:00具有相同的距离,因此这两个规则都有效。
另请注意GregorianCalendar.add
规范中的下一行
添加指定(签名)的时间量
它说你用错了调用cal.add(Calendar.HOUR_OF_DAY, -1);
因此,没有人可以责怪你的add
只显示下一set
调用的结果。 但它有点工作; 你可以用它!
这是做什么的? 如果你确定那2014年10月26日02:00:00 CET减1小时等于本身-那么你可以只添加愚蠢set
的后add
。 像那样:
cal.add(Calendar.HOUR_OF_DAY, -1);
cal.set(Calendar.HOUR, cal.get(Calendar.HOUR)); //apply changes
cal.set(Calendar.MINUTE, 0);
如果你想在-1 hour
操作后看到差异,那么我建议你改变TimeZone。 TimeZone.getTimeZone("GMT")
(例如)没有夏令时偏移量。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.