为什么GregorianCalendar.getTimeInMillis()会更改实例的值?

时间:2014-10-29 15:12:41

标签: java

我发现GregorianCalendar.getTimeInMillis()的一个非常奇怪的行为,似乎它改变了实例内容的值。在下面的代码中,您可以看到两个代码块只在一条注释行中有所不同,其中调用了getTimeInMillis()。当我取消注释该行时,为什么结果会有所不同?

使用注释调用输出

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

但是当我取消注释getTimeInMillis()行时,两个结果都是相同的:

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

代码:

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

}

2 个答案:

答案 0 :(得分:2)

Pre-Java 8日历的实施受到了很多批评,因为#34;奇怪的"行为。我认为这是由于以下文档:

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.

请注意,toString()方法标记为仅调试:

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.

虽然这可能最终不会出现错误(只要你不在实际逻辑中使用toString()),最好使用Joda-Time或新的Java-8 Date and Time

答案 1 :(得分:2)

这是时区变化。不是在12月31日在上海,而是手动,在您的代码中。

特别是,在强制日历计算其字段(基于“旧”时区)之后,您正在更改时区。这会弄乱日历的内部状态。当然,情况并非如此,但只是Calendar类暴露的许多奇怪行为中的一种 - 而且很可能主要是由于可变性引起的。

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. 

您可以通过研究GregorianCalendar的源代码并尝试避免关键的调用序列来解决这个问题。但正如其他人已经指出的那样:整个日期/时间API严重受损。如果有机会,您应该考虑使用new Date/Time API of Java 8(或Joda Time API,它与Java 8类似,以便以后将现有的基于Joda的代码更改为Java 8代码。


以下是一个示例,演示了在调用getTimeMillis 之前设置时间区调用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;
        }
    }
}

编辑:此错误报告中还描述了此行为:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=5026826