繁体   English   中英

GregorianCalendar DST问题

[英]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的正确方法是什么?

以下是来自Java Bug跟踪器的类似“bug”注释

在“后退”期间,日历不支持消歧,并且给定的本地时间被解释为标准时间。

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

这意味着你应该用set()替换set()

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

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM