I found java.util.Calendar error?

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 on the line System.out.println with formatting inside, this will not work:

 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 utcCalendar , and this causes the test to pass.

Am I abusing something here? Is there an open error in the 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) 
+7
java clone unit-testing calendar
source share
2 answers

EDIT: I hastened to keep up with the error, due to reading only part of your code.

Here is a completely new answer:

First of all, Javadoc of Calendar indicates when the calendar time is calculated:

Getting and setting calendar field values

You can set calendar field values ​​by calling dialing methods. Any field values ​​set in Calendar will not be interpreted until it is required (milliseconds from an era) or field calendar values. The call get, getTimeInMillis , getTime , add and execute roll includes this calculation .

The behavior you encounter is not a mistake.

You create an instance of Calendar , and then create a clone of that instance. Then you change the time zone of the cloned instance.

However, there are two scenarios:

  • The time of the original Calendar object is calculated before the clone is created by calling utcCalendar.getTime() in your print application. The cloned calendar contains this time, so changing the time zone does not change this time (since getTimeInMillis() returns UTC milliseconds from the epoch , so it does not depend on what time zone is used). Time is not recounted when you call assertEquals(istCalendar.getTimeInMillis(), utcCalendar.getTimeInMillis()) , so the test passes.

  • The creation time of the original calendar object is not calculated until the clone is created. 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 UTC and 10AM UTC are not the same time (they are 5, 5 hours, which is the difference between 1428813000979 and 1428832800979 ).

+1
source share

I think this is more expected behavior since you are cloning.

First, the time values ​​on these two calendars cannot be the same as the IST will have a set of 19800000 set, and this is reflected above the AssertionError . A correct statement would be:

 assertNotEquals(istCalendar.getTimeInMillis(), utcCalendar.getTimeInMillis()); 

When you call utcCalendar.getTime(); to print formatted output, you actually change the internal state of the calendar object to isTimeComputed to true . This is to avoid recalculating the time when getTime() or getTime Millis() called again. The same flag is true as the cloned istCalendar object istCalendar and therefore, the time is not istCalendar to istCalendar when getTimeInMillis called.

This actually causes both calendars to mistakenly print the same milliseconds.

This is not a mistake, I would say. This is rather a side effect of the clone.

0
source share

All Articles