JDK-6482399 : (cal) java.util.Calender add() and set() fail during DST to standard time transition
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util:i18n
  • Affected Version: 5.0u8
  • Priority: P2
  • Status: Closed
  • Resolution: Duplicate
  • OS: solaris_9
  • CPU: sparc
  • Submitted: 2006-10-16
  • Updated: 2010-07-29
  • Resolved: 2006-10-17
Related Reports
Duplicate :  
Description
The problem is that calling set() or roll() to zero for the seconds and minutes fields of the Calendar contains an apparently invalid Daylight Saving Time calculation.  The end result is that our program waits an hour instead of a minute during the DST-to-standard time transition.
Our objective is once a minute to determine what is the next "whole minute", e.g. in a scheduler.  We use java.util.Calendar to do this.  The algorithm is:

1. Return from wait().

2. Perform some tasks.

3. Get the current time in milliseconds in a Calendar instance.

4. Set the millisecond and second fields to zero in order to determine the current "whole" minute.

5. Add a minute to the Calendar.

6. Determine the UTC of the next "whole" minute from the Calendar.

7. Wait until that time, i.e. repeat.


I've also noticed that calling add() with a negative number of seconds and milliseconds is a reasonable workaround.  The bug is isolated to set() and roll().

I've attached a small program to illustrate this.  The same problem occurs regardless of JRE version.  Run the following with 1.5.0_06 (updated timezone files with the Australia transition on 4/2/2006 at 3am local time):

[sh] TZ=Australia/NSW java addminute "04/02/2006 01:58" 4

Output is as follows:

calendar initialized to 04/02/2006 01:58:00.010 (+1100)
after zeroing seconds and millis: 04/02/2006 01:58:00.000 (+1100)
after adding a minute: 04/02/2006 01:59:00.000 (+1100)
currentTime=1143903480010, nextWholeMinuteUTC=1143903540000, diff=59990

calendar initialized to 04/02/2006 01:59:00.010 (+1100)
after zeroing seconds and millis: 04/02/2006 01:59:00.000 (+1100)         << here, zeroing the seconds and millis works...timezone offset correct
after adding a minute: 04/02/2006 02:00:00.000 (+1100)
currentTime=1143903540010, nextWholeMinuteUTC=1143903600000, diff=59990

calendar initialized to 04/02/2006 02:00:00.010 (+1100)
after zeroing seconds and millis: 04/02/2006 02:00:00.000 (+1000)         << at 2am, zeroing the seconds and millis FAILS...timezone offset incorrect
after adding a minute: 04/02/2006 02:01:00.000 (+1000)
currentTime=1143903600010, nextWholeMinuteUTC=1143907260000, diff=3659990 << time difference after adding 1 minute is 1 hour!
    warning, time diff millis > 60000

calendar initialized to 04/02/2006 02:01:00.010 (+1100)
after zeroing seconds and millis: 04/02/2006 02:01:00.000 (+1000)
after adding a minute: 04/02/2006 02:02:00.000 (+1000)
currentTime=1143903660010, nextWholeMinuteUTC=1143907320000, diff=3659990
    warning, time diff millis > 60000

Comments
WORK AROUND A time stamp for the next minute can be calculated by: (currentTime / ONE_MINUTE) * ONE_MINUTE + ONE_MINUTE where ONE_MINUTE is 60000. The test program can be written like this: ---- import java.text.*; import java.util.*; public class addminute { static final int ONE_MINUTE = 60000; static public void main( String[] args ) { if ( args.length != 2 ) { System.err.println( "usage: addminute 'MM/dd/yyyy HH:mm' iterations" ); System.exit( 1 ); } try { SimpleDateFormat df = new SimpleDateFormat( "MM/dd/yyyy HH:mm:ss.SSS (Z)" ); Date startDate = new SimpleDateFormat( "MM/dd/yyyy HH:mm" ).parse( args[ 0 ] ); long offsetToWholeMinute = 10; long currentTime = startDate.getTime() + offsetToWholeMinute; int iterations = Integer.parseInt( args[ 1 ] ); for ( int i = 0; i < iterations; i++ ) { System.out.println(); System.out.println( "calendar initialized to " + df.format(new Date(currentTime))); long nextWholeMinuteUTC = (currentTime / ONE_MINUTE) * ONE_MINUTE + ONE_MINUTE; System.out.println( "after adding a minute: " + df.format(new Date(nextWholeMinuteUTC))); long diff = nextWholeMinuteUTC - currentTime; System.out.println( "currentTime=" + currentTime + ", nextWholeMinuteUTC=" + nextWholeMinuteUTC + ", diff=" + diff ); if ( diff > ONE_MINUTE ) System.out.println( " warning, time diff millis > 60000" ); currentTime += ONE_MINUTE; } } catch( Exception ex ) { System.err.println( "error: " + ex ); } } } ---- $ java -showversion addminute '04/02/2006 01:58' 64 java version "1.5.0_06" Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_06-b05) Java HotSpot(TM) Client VM (build 1.5.0_06-b05, mixed mode) calendar initialized to 04/02/2006 01:58:00.010 (+1100) after adding a minute: 04/02/2006 01:59:00.000 (+1100) currentTime=1143903480010, nextWholeMinuteUTC=1143903540000, diff=59990 calendar initialized to 04/02/2006 01:59:00.010 (+1100) after adding a minute: 04/02/2006 02:00:00.000 (+1100) currentTime=1143903540010, nextWholeMinuteUTC=1143903600000, diff=59990 calendar initialized to 04/02/2006 02:00:00.010 (+1100) after adding a minute: 04/02/2006 02:01:00.000 (+1100) currentTime=1143903600010, nextWholeMinuteUTC=1143903660000, diff=59990 calendar initialized to 04/02/2006 02:01:00.010 (+1100) after adding a minute: 04/02/2006 02:02:00.000 (+1100) currentTime=1143903660010, nextWholeMinuteUTC=1143903720000, diff=59990 ... calendar initialized to 04/02/2006 02:57:00.010 (+1100) after adding a minute: 04/02/2006 02:58:00.000 (+1100) currentTime=1143907020010, nextWholeMinuteUTC=1143907080000, diff=59990 calendar initialized to 04/02/2006 02:58:00.010 (+1100) after adding a minute: 04/02/2006 02:59:00.000 (+1100) currentTime=1143907080010, nextWholeMinuteUTC=1143907140000, diff=59990 calendar initialized to 04/02/2006 02:59:00.010 (+1100) after adding a minute: 04/02/2006 02:00:00.000 (+1000) currentTime=1143907140010, nextWholeMinuteUTC=1143907200000, diff=59990 calendar initialized to 04/02/2006 02:00:00.010 (+1000) after adding a minute: 04/02/2006 02:01:00.000 (+1000) currentTime=1143907200010, nextWholeMinuteUTC=1143907260000, diff=59990 calendar initialized to 04/02/2006 02:01:00.010 (+1000) after adding a minute: 04/02/2006 02:02:00.000 (+1000) currentTime=1143907260010, nextWholeMinuteUTC=1143907320000, diff=59990 ---- Note that this doesn't handle leap seconds. (Currently, Calendar doesn't handle leap seconds, either.)
17-10-2006

EVALUATION Currently, there's no way to disambiguate a local time stamp given by the time of day fields (e.g., HOUR_OF_DAY, MINUTE, ...) in terms of standard and daylight time. Traditionally set() takes daylight time if it's ambiguous, while add() calculates the exact next minute. I'm closing this CR as a duplicate of 4312621. For the particular operation to calculate a time stamp for the next minute, there's a simple way. See Work Around.
17-10-2006

WORK AROUND Calling add() with a negative number of seconds and milliseconds is a reasonable workaround. The bug is isolated to set() and roll().
16-10-2006