GregorianCalendar DST问题

Ras*_*ber 14 java calendar gregorian-calendar dst

如果我在秋季结束时点亮时间(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?!)
Run Code Online (Sandbox Code Playgroud)

这是一个错误吗?我知道Java Calendar有一些奇怪的约定,但我看不出这是如何正确的.

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

apa*_*gin 11

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

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

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

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

cal.add(Calendar.MINUTE, -cal.get(Calendar.MINUTE));
Run Code Online (Sandbox Code Playgroud)


Rau*_*uiu 5

apangin的回复中的链接说明了问题并提出了解决方案。我确实尝试用困难的方式查看代码。

如果我们在设置之前和之后检查DST_OFFSET:

System.out.println(cal.get(Calendar.DST_OFFSET));
cal.set(Calendar.MINUTE, 0);
System.out.println(cal.get(Calendar.DST_OFFSET));
Run Code Online (Sandbox Code Playgroud)

它打印:

3600000
0
Run Code Online (Sandbox Code Playgroud)

查看methodgetTimeInMillis方法,我们看到它们在重新计算以毫秒为单位的时间时使用标志(isTimeSet)进行检查:

public long getTimeInMillis() {
   if (!isTimeSet) {
       updateTime();
   }
   return time;
}
Run Code Online (Sandbox Code Playgroud)

调用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

因此设置重置DST偏移量。这行获取偏移量:

zoneOffset = ((ZoneInfo)tz).getOffsets(time, zoneOffsets);
Run Code Online (Sandbox Code Playgroud)

点点下来的值被置:

internalSet(DST_OFFSET, zoneOffsets[1]); 
Run Code Online (Sandbox Code Playgroud)

下次调用getTimeInMillis时,以毫秒为单位的时间将更改。

正如apangin建议的那样,我们不应该在初始化日期之后使用set,否则我们将强制执行某种日期重置。这就是OpenJDK错误跟踪程序中的建议。

cal.add(Calendar.MINUTE, -cal.get(Calendar.MINUTE));
Run Code Online (Sandbox Code Playgroud)

更好的选择是使用Joda Time。该库的作者一直在研究新的Java 8的日期时间api,我希望不会出现此类问题。