Why does GregorianCalendar.getTimeInMillis () change the value of the instance?

I found very strange behavior of GregorianCalendar.getTimeInMillis (), it looks like it changes the value of the instance content. In the following code, you can see that the two blocks of code differ by only one commented line where getTimeInMillis () is called. Why is the result different when I uncommented the line?

With a comment, the

2014-10-25T22:00:00Z -> 2014-10-26T22:00:00.000+01:00
2014-10-25T22:00:00Z -> 2014-10-27T00:00:00.000+01:00

      

but when I uncomment the getTimeInMillis () line, both results are the same:

2014-10-25T22:00:00Z -> 2014-10-27T00:00:00.000+01:00
2014-10-25T22:00:00Z -> 2014-10-27T00:00:00.000+01:00

      

code:

package com.test;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;

import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

public class Main {

public static void main(String[] args) {
    try {
        XMLGregorianCalendar date1 = DatatypeFactory.newInstance()
                .newXMLGregorianCalendar("2014-10-25T22:00:00Z");
        XMLGregorianCalendar date2 = DatatypeFactory.newInstance()
                .newXMLGregorianCalendar("2014-10-25T22:00:00Z");
        int days = 1;

        GregorianCalendar gregorianCalendar1 = date1.toGregorianCalendar();
        // gregorianCalendar1.getTimeInMillis();  //UNCOMMENT THIS LINE TO GET A DIFFERENT RESULT
        gregorianCalendar1.setTimeZone(TimeZone.getDefault());
        gregorianCalendar1.add(Calendar.DAY_OF_MONTH, days);
        XMLGregorianCalendar newXMLGregorianCalendar1 = DatatypeFactory
                .newInstance().newXMLGregorianCalendar(gregorianCalendar1);
        System.out.printf("%s -> %s\n", date1, newXMLGregorianCalendar1);

        GregorianCalendar gregorianCalendar2 = date2.toGregorianCalendar();
        gregorianCalendar2.getTimeInMillis();
        gregorianCalendar2.setTimeZone(TimeZone.getDefault());
        gregorianCalendar2.add(Calendar.DAY_OF_MONTH, days);
        XMLGregorianCalendar newXMLGregorianCalendar2 = DatatypeFactory
                .newInstance().newXMLGregorianCalendar(gregorianCalendar2);
        System.out.printf("%s -> %s\n", date2, newXMLGregorianCalendar2);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

      

+3


source to share


2 answers


This is a time zone change. Not December 31st in Shanghai, but manually, in your code.

In particular, you change the timezone after you have forced the calendar to calculate its fields (depending on the "old" timezone). This breaks the internal state of the calendar. Obviously it shouldn't be, but it's just one of the many strange behaviors that classes exhibit Calendar

, and most likely mostly caused by their mutability.

Some of the potential difficulties are also pointed out in the comments in the implementation Calendar#setTimeZone

:

* Consider the sequence of calls: 
* cal.setTimeZone(EST); cal.set(HOUR, 1); cal.setTimeZone(PST).
* Is cal set to 1 o'clock EST or 1 o'clock PST?  Answer: PST. 

      

You could get around this by looking at the source code GregorianCalendar

and trying to avoid critical call sequences. But as others have already pointed out, the whole old Date / Time API is badly broken. If you can, you should use the new Java 8 Date / Time API (or the Joda Time API , which is similar enough to Java 8 to make it easy to later change your existing Joda code to Java 8 code).




Here's an example to show the difference between setting the timezone before the call to getTimeMillis

and after the call getTimeMillis

:

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;

import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

public class GregorianCalendarTest {

    public static void main(String[] args) {
        String fromSettingTimeZoneBeforeCall = createString(true);
        String fromSettingTimeZoneAfterCall = createString(false);

        System.out.println("Before: "+fromSettingTimeZoneBeforeCall);
        System.out.println("After : "+fromSettingTimeZoneAfterCall);
    }

    private static String createString(boolean setTimeZoneBeforeCall)
    {
        try {
            XMLGregorianCalendar date = DatatypeFactory.newInstance()
                .newXMLGregorianCalendar("2014-10-25T22:00:00Z");
            int days = 1;

            GregorianCalendar gregorianCalendar = date.toGregorianCalendar();
            System.out.println("After creating: "+gregorianCalendar);

            if (!setTimeZoneBeforeCall)
            {
                gregorianCalendar.getTimeInMillis(); 
                System.out.println("After millis  : "+gregorianCalendar);
            }

            gregorianCalendar.setTimeZone(TimeZone.getDefault());
            System.out.println("After timezone: "+gregorianCalendar);

            if (setTimeZoneBeforeCall)
            {
                gregorianCalendar.getTimeInMillis(); 
                System.out.println("After millis  : "+gregorianCalendar);
            }

            gregorianCalendar.add(Calendar.DAY_OF_MONTH, days);
            System.out.println("After adding  : "+gregorianCalendar);

            XMLGregorianCalendar newXMLGregorianCalendar = DatatypeFactory
                .newInstance().newXMLGregorianCalendar(gregorianCalendar);
            System.out.println("After all     : "+gregorianCalendar);

            return newXMLGregorianCalendar.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

      


EDIT: This behavior is also documented in this bug report: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=5026826

+2


source


Pre-Java 8 Calendar implementations have been heavily criticized for "strange" behavior. I think it has something to do with the following documentation:

Getting and Setting Calendar Field Values The calendar field values can be set by calling the set methods. Any field values set in a Calendar will not be interpreted until it needs to calculate its time value (milliseconds from the Epoch) or values of the calendar fields. Calling the get, getTimeInMillis, getTime, add and roll involves such calculation.

Note that the toString () method is marked as debug:



Return a string representation of this calendar. This method is intended to be used only for debugging purposes, and the format of the returned string may vary between implementations. The returned string may be empty but may not be null.

While this probably won't fail (as long as you don't use toString () in real logic) it is better to use Joda-Time or the new Java-8 Date and Time

+2


source







All Articles