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

时间:2014-08-11 08:57:13

标签: 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 ?)
}

我预计在25.10.2014 23:00 GMT上设置小时= 0会给我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上也是如此。

2 个答案:

答案 0 :(得分: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.

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.

同样日历类具有以下副作用: -

        In lenient mode, all of the Calendar fields are normalized.

这意味着当您致电setTimeZone()& set(Calendar.HOUR_OF_DAY, 0),它设置内部成员变量以指示日历字段已设置。但那时日历的时间不会重新计算。只有在调用get()getTime()getTimeInMillis()add()roll()后,才会重新计算日历的时间。

这是日历类中的错误 JDK-4827490 : (cal) Doc: Calendar.setTimeZone behavior undocumented

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

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());
    }
}

输出

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

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

答案 1 :(得分:1)

您直接更改java.util.Date.setTimeZone(TimeZone.getTimeZone("UTC"));

您的GregorianCalendar's时区为CETdate's时区为UTC。然后你把它打印出来。使用GregorianCalendar时区更改您的UTC个实例。

//date.setTimeZone(TimeZone.getTimeZone("UTC")); <- remove it.
 date.set(Calendar.HOUR_OF_DAY, 0);

GregorianCalendar utcCAL = (GregorianCalendar) GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"));
utcCAL.setTimeInMillis(date.getTimeInMillis());
System.out.println(utcCAL.getTime().toGMTString() + " " + utcCAL.getTimeInMillis());

输出

25 Oct 2014 22:00:00 GMT 1414274400517

<强>更新

您也可以使用java.util.Date.UTC()功能。

//date.setTimeZone(TimeZone.getTimeZone("UTC"));
date.set(Calendar.HOUR_OF_DAY, 0);
Date utcDate = date.getTime();
utcDate.UTC(2014, 9, 1, 26, 1, 0);
System.out.println(utcDate.toGMTString() + " " + date.getTimeInMillis());