简体   繁体   English

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

[英]Why GregorianCalendar changes day when setting HOUR_OF_DAY to 0 in UTC?

I observed a strange behaviour of java.util.GregorianCalendar, and I wonder why it behaves so. 我观察到java.util.GregorianCalendar的一个奇怪的行为,我想知道它为什么会这样。

I wanted to get the time in UTC, which is the same instant as 26.10.2014 01:00 CET and then get UTC midnight for the same day. 我想以UTC 26.10.2014 01:00 CET获得时间,这与26.10.2014 01:00 CET相同,然后在同一天获得UTC午夜。 So first I set the actual CET date, than changed the timezone to UTC, and finally set the HOUR_OF_DAY to 0. 所以首先我设置实际的CET日期,而不是将时区更改为UTC,最后将HOUR_OF_DAY设置为0。

Example: 例:

  • 26.10.2014 01:00 CET is the same as 25.10.2014 23:00 UTC 26.10.2014 01:00 CET与25.10.2014 23:00 UTC相同
  • midnight(25.10.2014 23:00 UTC) should be 25.10.2014 00:00 UTC 午夜(25.10.2014 23:00 UTC)应该是25.10.2014 00:00 UTC

see junit code below: 请参阅下面的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 ?)
}

I expected that setting hour=0 on 25.10.2014 23:00 GMT will give me 25.10.2014 00:00 GMT , but it changed to 26.10.2014 00:00 GMT . 我预计25.10.2014 23:00 GMT设置小时= 0将给我25.10.2014 00:00 GMT ,但它改为26.10.2014 00:00 GMT

However, if I uncomment line date.get(Calendar.YEAR); 但是,如果我取消注释行date.get(Calendar.YEAR); , the date seems to be calculated correctly. ,日期似乎正确计算。

The same happens on jdk.1.7.0_10 and jrockit-jdk1.6.0_37. jdk.1.7.0_10和jrockit-jdk1.6.0_37也是如此。

As GregorianCalender extends Calendar class, it inherits all the features of it. 由于GregorianCalender扩展了Calendar类,它继承了它的所有功能。 From the Java Doc 来自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 Example : 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.

Also Calendar class has the following side effect :- Calendar类也有以下副作用 : -

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

It means when you call setTimeZone( ) & set(Calendar.HOUR_OF_DAY, 0) , it sets internal member variable to indicate that Calendar fields are set. 这意味着当您调用setTimeZone( )& set(Calendar.HOUR_OF_DAY, 0) ,它会设置内部成员变量以指示已设置Calendar字段。 But Calendar's time is not recomputed at that time. 但是日历的时间当时没有重新计算 The Calendar's time is recomputed only after a call to get() , getTime() , getTimeInMillis() , add() , or roll() is made. 只有在调用get()getTime()getTimeInMillis()add()roll()之后才重新计算Calendar的时间。

This is a bug in Calendar class JDK-4827490 : (cal) Doc: Calendar.setTimeZone behavior undocumented 这是Calendar类 JDK-4827490中错误 :(cal)Doc:Calendar.setTimeZone行为未记录

Now, your example is modified to get it work as below :- 现在, 您的示例已修改为使其工作如下: -

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

Output : 输出:

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

I hope now you will get a clear idea about unpredictable behavior of Calendar class. 我希望您现在可以清楚地了解Calendar类的不可预测的行为。

You directly change java.util.Date.setTimeZone(TimeZone.getTimeZone("UTC")); 你直接改变java.util.Date.setTimeZone(TimeZone.getTimeZone("UTC")); ?

Your GregorianCalendar's timezone is CET and date's timezone is UTC . 您的GregorianCalendar's时区是CETdate's时区是UTC And you print it out. 然后你把它打印出来。 Change your GregorianCalendar instance with UTC timezone. 使用UTC时区更改GregorianCalendar实例。

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

Output 产量

25 Oct 2014 22:00:00 GMT 1414274400517

Update 更新

You can also use java.util.Date.UTC() function. 您还可以使用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());

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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