Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Did I find a java.util.Calendar bug?

I have the following test:

import static org.junit.Assert.assertEquals;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;

import org.junit.Test;
public class CalendarBug {
    private static final TimeZone UTC_ZONE = TimeZone.getTimeZone("UTC");// +0 hours
    private static final TimeZone IST_ZONE = TimeZone.getTimeZone("IST");// +5 hours 30 minutes

    @Test
    public void calendarBug() {
        Calendar utcCalendar = Calendar.getInstance(UTC_ZONE);
        utcCalendar.set(Calendar.YEAR, 2015);
        utcCalendar.set(Calendar.MONTH, 3);
        utcCalendar.set(Calendar.DAY_OF_MONTH, 12);
        utcCalendar.set(Calendar.HOUR_OF_DAY, 10);
        utcCalendar.set(Calendar.MINUTE, 0);
        utcCalendar.set(Calendar.SECOND, 0);
        SimpleDateFormat utcFormatter = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss Z" );
        utcFormatter.setTimeZone(UTC_ZONE);
        System.out.println( "If I have this line commented out, the test fails: " + utcFormatter.format(utcCalendar.getTime()));
        Calendar istCalendar = (Calendar) utcCalendar.clone();
        assertEquals(UTC_ZONE, istCalendar.getTimeZone());
        istCalendar.setTimeZone(IST_ZONE);
        assertEquals(istCalendar.getTimeInMillis(), utcCalendar.getTimeInMillis());
    }
}

If you run the test, it works. However if you comment out the System.out.println line with the formatter inside, it fails:

java.lang.AssertionError: expected:<1428813000979> but was:<1428832800979>
    at org.junit.Assert.fail(Assert.java:93)
    at org.junit.Assert.failNotEquals(Assert.java:647)
    at org.junit.Assert.assertEquals(Assert.java:128)
    at org.junit.Assert.assertEquals(Assert.java:472)
    at org.junit.Assert.assertEquals(Assert.java:456)
    at xxx.yyy.CalendarBug.calendarBug(CalendarBug.java:29)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

It seems that the formatter changes the internal state of the utcCalendar and it causes the test to pass.

Did I misuse anything here? Is there an open bug in JDK for this?

Java version:

liptak@XXXXXX:~$ java -version
java version "1.7.0_60"
Java(TM) SE Runtime Environment (build 1.7.0_60-b19)
Java HotSpot(TM) 64-Bit Server VM (build 24.60-b09, mixed mode)
like image 701
Gábor Lipták Avatar asked Jul 01 '15 07:07

Gábor Lipták


People also ask

What is Java Util calendar?

The java.util.calendar class is an abstract class that provides methods for converting between a specific instant in time and a set of calendar fields such as YEAR, MONTH, DAY_OF_MONTH, HOUR, and so on, and for manipulating the calendar fields, such as getting the date of the next week.Following are the important ...

Is Java Util calendar thread safe?

In short, the Calendar class is not thread-safe, and GregorianCalendar isn't either because it inherits the non-thread-safe fields and methods.

Is calendar deprecated Java?

Calendar , too) are not officially deprecated, just declared as de facto legacy.


1 Answers

EDIT : I posted an incorrect answer in haste, due to reading only part of your code.

Here's a completely new answer:

First of all, the Javadoc of Calendar states when the time of the Calendar is actually calculated :

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.

The behavior you encountered is not a bug.

You create a Calendar instance and then create a clone of that instance. Then you change the timezone of the cloned instance.

However, there are two scenarios :

  1. The time of the original Calendar object is calculated prior to creating the clone, as a result of calling utcCalendar.getTime() in your print statement. The cloned Calendar contains this time, so changing the timezone doesn't change that time (since getTimeInMillis() returns UTC milliseconds from the epoch, so it doesn't depend on which timezone is used). The time is not recalculated when you call assertEquals(istCalendar.getTimeInMillis(), utcCalendar.getTimeInMillis()), so the test passes.

  2. The time of the original Calendar object is not calculated prior to creating the clone. Then you change the time zone of the cloned instance. Now, when you call assertEquals(istCalendar.getTimeInMillis(), utcCalendar.getTimeInMillis()), the times in both Calendar instances are calculated, and they are different, since 10AM IST and 10AM UTC are not the same time (they are 5.5 hours apart, which is exactly the difference between 1428813000979 and 1428832800979).

like image 85
Eran Avatar answered Sep 20 '22 22:09

Eran