在UTC中将HOUR_OF_DAY设置为0时,为什么GregorianCalendar会更改日期?

tpo*_*eba 12 java gregorian-calendar

我观察到java.util.GregorianCalendar的一个奇怪的行为,我想知道它为什么会这样.

我希望得到UTC的时间,这是同一时刻26.10.2014 01:00 CET,然后在同一天获得UTC午夜.所以首先我设置实际的CET日期,而不是将时区更改为UTC,最后将HOUR_OF_DAY设置为0.

例:

  • 26.10.2014 01:00 CET与25.10.2014 23:00 UTC相同
  • 午夜(25.10.2014 23:00 UTC)应该是25.10.2014 00:00 UTC

请参阅下面的junit代码:

@Test
public void testWeird() {
    GregorianCalendar date = (GregorianCalendar) GregorianCalendar.getInstance(TimeZone.getTimeZone("CET"));
    date.set(2014, 9, 26, 1, 0, 0); //26.10.2014
    System.out.println(date.getTime().toGMTString() + " " + date.getTimeInMillis()); // 25 Oct 2014 23:00:00 GMT 1414278000764 (OK)

    date.setTimeZone(TimeZone.getTimeZone("UTC"));
    //date.get(Calendar.YEAR); // uncomment this line to get different results

    System.out.println(date.getTime().toGMTString() + " " + date.getTimeInMillis()); // 25 Oct 2014 23:00:00 GMT 1414278000764 (OK)
    date.set(Calendar.HOUR_OF_DAY, 0);
    System.out.println(date.getTime().toGMTString() + " " + date.getTimeInMillis()); // 26 Oct 2014 00:00:00 GMT 1414281600764 (NOT OK! why not 25 Oct 2014 00:00:00 GMT 1414195200218 ?)
}
Run Code Online (Sandbox Code Playgroud)

我预计设置小时= 0 25.10.2014 23:00 GMT会给我25.10.2014 00:00 GMT,但它改为26.10.2014 00:00 GMT.

但是,如果我取消注释行date.get(Calendar.YEAR);,则日期似乎正确计算.

jdk.1.7.0_10和jrockit-jdk1.6.0_37也是如此.

OO7*_*OO7 8

由于GregorianCalender扩展了Calendar类,它继承了它的所有功能.来自Java Doc

set(f, value) changes calendar field f to value. In addition, it sets an internal 
member variable to indicate that calendar field f has been changed. Although 
calendar field f is changed immediately, the calendar's time value in 
milliseconds is not recomputed until the next call to get(), getTime(), 
getTimeInMillis(),add(), or roll() is made. Thus, multiple calls to set() do not 
trigger multiple, unnecessary computations. As a result of changing a calendar 
field using set(), other calendar fields may also change, depending on the calendar 
field, the calendar field value, and the calendar system. In addition, get(f) will 
not necessarily return value set by the call to the set method after the calendar 
fields have been recomputed.
Run Code Online (Sandbox Code Playgroud)

Java Doc示例:

Consider a GregorianCalendar originally set to August 31, 1999. Calling 
set(Calendar.MONTH, Calendar.SEPTEMBER) sets the date to September 31, 1999. This 
is a temporary internal representation that resolves to October 1, 1999 if 
getTime()is then called. However, a call to set(Calendar.DAY_OF_MONTH, 30) before 
the call to getTime() sets the date to September 30, 1999, since no recomputation 
occurs after set() itself.
Run Code Online (Sandbox Code Playgroud)

此外日历类具有以下副作用: -

        In lenient mode, all of the Calendar fields are normalized.
Run Code Online (Sandbox Code Playgroud)

这意味着当你调用setTimeZone(&时set(Calendar.HOUR_OF_DAY, 0),它设置内部成员变量以指示日历字段已设置.但是日历的时间当时没有重新计算.日历的时间仅一个电话后,重新计算到 get(),getTime(),getTimeInMillis(),add(),或roll()制成.

这是Calendar类 JDK-4827490中错误:(cal)Doc:Calendar.setTimeZone行为未记录

现在,您的示例已修改为使其工作如下: -

public class DateTimeTest {

    @SuppressWarnings("deprecation")
    public static void main(String[] args) {
        GregorianCalendar date = (GregorianCalendar) GregorianCalendar.getInstance(TimeZone
            .getTimeZone("CET"));

        // 26.10.2014 01:00:00
        date.set(2014, 9, 26, 1, 0, 0);

        // 25 Oct 2014 23:00:00 GMT 1414278000764
        System.out.println("CET to UTC    : " + date.getTime().toGMTString() + " "
            + date.getTimeInMillis());

        date.setTimeZone(TimeZone.getTimeZone("UTC"));

//      date.roll(Calendar.HOUR_OF_DAY, true);  //uncomment this line & comment below line & check the different behavior of Calender.
        date.get(Calendar.HOUR_OF_DAY);

        // 25 Oct 2014 23:00:00 GMT 1414278000764
        System.out.println("UTC          : " + date.getTime().toGMTString() + " "
            + date.getTimeInMillis());

        date.set(Calendar.HOUR_OF_DAY, 0);

        // 25 Oct 2014 00:00:00 GMT 1414195200218
        System.out.println("UTC Midnight : " + date.getTime().toGMTString() + " "
            + date.getTimeInMillis());
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

CET to UTC   : 25 Oct 2014 23:00:00 GMT 1414278000008
UTC          : 25 Oct 2014 23:00:00 GMT 1414278000008
UTC Midnight : 25 Oct 2014 00:00:00 GMT 1414195200008
Run Code Online (Sandbox Code Playgroud)

我希望您现在可以清楚地了解Calendar类的不可预测的行为.