Why GregorianCalendar changes day when setting HOUR_OF_DAY to 0 in UTC?
Asked Answered
W

2

14

I observed a strange behaviour of java.util.GregorianCalendar, and I wonder why it behaves so.

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. So first I set the actual CET date, than changed the timezone to UTC, and finally set the HOUR_OF_DAY to 0.

Example:

  • 26.10.2014 01:00 CET is the same as 25.10.2014 23:00 UTC
  • midnight(25.10.2014 23:00 UTC) should be 25.10.2014 00:00 UTC

see junit code below:

@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.

However, if I uncomment line 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.

Wrongly answered 11/8, 2014 at 8:57 Comment(5)
The most perplexing is that the line date.get(Calendar.YEAR); influences the final result. A bug in JDKs GregorianCalendar?Wrongly
Check Calendar JavaDoc. This class behaviour is pretty complex. Calling some methods will force calendar to perform timestamp / field calculation, which changes internal state of the calendar and might have some unexpected side effects.Jolee
Yeah, the GregorianCalendar is not great...Rogelioroger
The call of method date.get(...) calls indirectly the method complete() wich compute fields, wich are not set at this time. So the object will change.Udale
This might be the case. If the DAY_OF_MONTH is not recalculated properly after setTimeZone() maybe the next set(HOUR_OF_DAY,0) is working on incorrect internal state and the change of the day is missed.Wrongly
C
9

As GregorianCalender extends Calendar class, it inherits all the features of it. From the 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 :

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 :-

        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. 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.

This is a bug in Calendar class JDK-4827490 : (cal) Doc: Calendar.setTimeZone behavior undocumented

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.

Culture answered 12/8, 2014 at 7:34 Comment(2)
Great, that link to JDK bug was exactly what I was looking for! This only confirms that we should use Joda even for simplest time/date operations.Wrongly
Good to know, you get what you exactly want.Culture
S
1

You directly change java.util.Date.setTimeZone(TimeZone.getTimeZone("UTC"));?

Your GregorianCalendar's timezone is CET and date's timezone is UTC. And you print it out. Change your GregorianCalendar instance with UTC timezone.

//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.

//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());
Snowfield answered 11/8, 2014 at 10:22 Comment(3)
If I set HOUR_OF_DAY before changing the timezone, I will actually get midnight in CET, not in UTC.Wrongly
Did u test my program? As my answer, Your GregorianCalendar's timezone is CET and date's timezone is UTC.Snowfield
I actually don't understand. I tested you program, and it gives me first midnight in CET, then transforms this to UTC. This is actually a bit different than what I wanted. I edited the question, and added example, I hope it is more clear now.Wrongly

© 2022 - 2024 — McMap. All rights reserved.