我观察到java.util.GregorianCalendar的一个奇怪的行为,我想知道它为什么会这样。
我希望得到UTC的时间,这与26.10.2014 01:00 CET
的时间相同,然后在同一天获得UTC午夜。所以首先我设置实际的CET日期,而不是将时区更改为UTC,最后将HOUR_OF_DAY设置为0.
示例:
请参阅下面的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上也是如此。
答案 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
时区为CET
,date'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());