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)
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 ...
In short, the Calendar class is not thread-safe, and GregorianCalendar isn't either because it inherits the non-thread-safe fields and methods.
Calendar , too) are not officially deprecated, just declared as de facto legacy.
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 :
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.
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
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With