JDK-4248500 : roll methods for GregorianCalendar not working properly for October 31
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util:i18n
  • Affected Version: 1.2.0,1.2.2
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: generic,windows_nt
  • CPU: generic,x86
  • Submitted: 1999-06-22
  • Updated: 2000-02-15
  • Resolved: 2000-02-15
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Relates :  
Description

Name: skT88420			Date: 06/22/99


The roll methods in the GregorianCalendar class do not always function properly when rolling October 31 forward.  This problem occurs when using the boolean version of the roll method, public void roll(int field, boolean up), and when using the alternate version, public void roll(int field, int amount) to roll Calendar.DAY_OF_YEAR.  However, the problem only seems to occur when rolling the day of year forward and not when rolling it backward.

The following code should demonstrate my problem:

import java.util.*;
import java.text.DateFormat;

public class DateTest
{
   public static void main(String args[])
    {
        GregorianCalendar current = new GregorianCalendar(1999,8,29);
        
        for (int i=0; i<75; i++)
        {
            current.roll(Calendar.DAY_OF_YEAR, true);
            System.out.println(DateFormat.getDateInstance(DateFormat.LONG).format(current.getTime()));
        }
        
    }
}


Notice that October 31, 1999 is printed twice.
(Review ID: 84655) 
======================================================================

Name: krT82822			Date: 01/30/2000


(see also 4191164, 4248500)

I have observed this bug in JDK 1.1.7, JDK 1.1.8, JDK 1.2.1, JDK 1.2.2.

The Calendar/GregorianCalendar code is very broken around DST switchovers.
I'll include a sample to illustrate what I mean. Basically, calling
Calendar.set(SECOND, 0) sets the underlying long from EDT to EST!!!

As documented by others there are no constructors that allow creation of times
around these dst switchovers, or explicit api calls to say
Calendar.setIsDST(true); Without these it is impossible to disambiguate 1:30 am
on Oct 31, 1999 from the EST or EDT versions.

Here's the code: To see the bug, compile and execute and see that after you set
the seconds field to 0, the calendar time switches from EDT to EST, a difference
of ONE hour!!!

--
import java.util.*;

public class DSTTest {
    public static void main(String args[]) {
	Date d = new Date();
	GregorianCalendar cal = new GregorianCalendar(1999, 9, 31, 1, 15);

	d = cal.getTime();
	System.out.println("Date = " + d);

	// subtract one hour
	long val = d.getTime() - (60*1000*60);
	
	d = new Date(val);
	System.out.println("Date after subtracting = " + d);
	
	cal.setTime(d);
	System.out.println("Calendar after setTime = " + cal.getTime() + " Long=
" + cal.getTime().getTime());
	
	cal.set(Calendar.SECOND, 0);
	System.out.println("Calendar after set(SECOND,0) = " + cal.getTime() + "
Long = " + cal.getTime().getTime());
    }
}
(Review ID: 100459)
======================================================================

Comments
WORK AROUND Name: skT88420 Date: 06/22/99 Handling October 31 as a "special case" makes my program functional, however, this is certainly not a desirable solution. ======================================================================
11-06-2004

PUBLIC COMMENTS GregorianCalendar.roll() is broken in some field at the boundary of Daylight switch.
10-06-2004

EVALUATION roll(DAY_OF_YEAR), maybe add() adds just ONE_DAY*delta in millisecs. 10/31 is on the switch of daylight saving. Thus, ONE_DAY*delta is not enough to be 11/1 (actually, should be ONE_DAY*delta + ONE_HOUR). Other mothods that does not care dayright saving and use aboslute millisecs might be broken, also. koushi.takahashi@japan 1999-07-30 add() methods do DST adjustment. Hence, the problem only applied to roll() method. koushi.takahashi@japan 1999-08-10 The suggested fix has been updated for code simplicity. koushi.takahashi@japan 1999-10-20 This doesn't seem to simply be a roll() problem. The second sample code demonstrates a problem of time/fields calculation. Closing this bug as a duplicate of 4312621. masayoshi.okutsu@Eng 2000-02-15
15-02-2000

SUGGESTED FIX *** /tmp/geta3064 Wed Oct 20 11:28:50 1999 --- GregorianCalendar.java Wed Oct 20 10:42:16 1999 *************** *** 874,883 **** // the start of the year, and get the length of the year. long delta = amount * ONE_DAY; // Scale up from days to millis long min2 = time - (internalGet(DAY_OF_YEAR) - 1) * ONE_DAY; ! int yearLength = yearLength(); ! time = (time + delta - min2) % (yearLength*ONE_DAY); ! if (time < 0) time += yearLength*ONE_DAY; ! setTimeInMillis(time + min2); return; } case DAY_OF_WEEK: --- 874,880 ---- // the start of the year, and get the length of the year. long delta = amount * ONE_DAY; // Scale up from days to millis long min2 = time - (internalGet(DAY_OF_YEAR) - 1) * ONE_DAY; ! rollInMillis(delta, min2, yearLength()*ONE_DAY); return; } case DAY_OF_WEEK: *************** *** 891,899 **** int leadDays = internalGet(DAY_OF_WEEK) - getFirstDayOfWeek(); if (leadDays < 0) leadDays += 7; long min2 = time - leadDays * ONE_DAY; ! time = (time + delta - min2) % ONE_WEEK; ! if (time < 0) time += ONE_WEEK; ! setTimeInMillis(time + min2); return; } case DAY_OF_WEEK_IN_MONTH: --- 888,894 ---- int leadDays = internalGet(DAY_OF_WEEK) - getFirstDayOfWeek(); if (leadDays < 0) leadDays += 7; long min2 = time - leadDays * ONE_DAY; ! rollInMillis(delta, min2, ONE_WEEK); return; } case DAY_OF_WEEK_IN_MONTH: *************** *** 912,921 **** // From these compute the min and gap millis for rolling. long min2 = time - preWeeks * ONE_WEEK; long gap2 = ONE_WEEK * (preWeeks + postWeeks + 1); // Must add 1! ! // Roll within this range ! time = (time + delta - min2) % gap2; ! if (time < 0) time += gap2; ! setTimeInMillis(time + min2); return; } case ZONE_OFFSET: --- 907,913 ---- // From these compute the min and gap millis for rolling. long min2 = time - preWeeks * ONE_WEEK; long gap2 = ONE_WEEK * (preWeeks + postWeeks + 1); // Must add 1! ! rollInMillis(delta, min2, gap2); return; } case ZONE_OFFSET: *************** *** 937,942 **** --- 929,956 ---- set(field, value); } + /** + * Private method to roll by the specified number of milliseconds. + * Handles crossing DST/Standard boundaries properly. + * @param millis the number of ms to add to time + * @param intervalStart the start of the interval we are rolling + * within, in ms + * @param intervalLength the length of the interval we are rolling + * within, in ms + */ + private void rollInMillis(long millis, long intervalStart, long intervalLength) { + this.time = (this.time + millis - intervalStart) % intervalLength; + if (this.time < 0) { + this.time += intervalLength; + } + long dstOffset = internalGet(DST_OFFSET); + setTimeInMillis(this.time + intervalStart); + dstOffset -= internalGet(DST_OFFSET); + if (dstOffset != 0) { + setTimeInMillis(this.time + dstOffset); + } + } + /** * Returns minimum value for the given field. * e.g. for Gregorian DAY_OF_MONTH, 1 koushi.takahashi@japan 1999-10-20
20-10-1999