JDK-5014535 : (cal) GregorianCalendar.roll(int field, int amount) -incorrect rolling from leap-years
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util:i18n
  • Affected Version: 1.4.2
  • Priority: P4
  • Status: Closed
  • Resolution: Won't Fix
  • OS: linux
  • CPU: x86
  • Submitted: 2004-03-16
  • Updated: 2008-02-20
  • Resolved: 2008-02-20
Description

Name: gm110360			Date: 03/16/2004


FULL PRODUCT VERSION :
java version "1.4.2_04"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_04-b05)
Java HotSpot(TM) Client VM (build 1.4.2_04-b05, mixed mode)

FULL OS VERSION :
Linux standa 2.4.18 #15 Thu Mar 13 16:23:20 CET 2003 i686 Intel(R) Celeron(R) CPU 1.70GHz GenuineIntel GNU/Linux

EXTRA RELEVANT SYSTEM CONFIGURATION :
This bug is on all the platforms

A DESCRIPTION OF THE PROBLEM :
In GregorianCalendar's instance having its date set to the 29th of February 2000 (or any other leap-year) and rolling year to a non-leap year by call to roll(Calendar.YEAR, -1), the resulted date is 1st of March 1999 instead of 28th of February 1999.
This means that rolling of the year in this case affects other fields, which doesn't fit the way it should be.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Save included source code to a file named BugTest.java,
compile and run. Check system output for the results.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Tue Feb 29 20:34:45 CET 2000
Sun Feb 28 20:34:45 CET 1999
ACTUAL -
Tue Feb 29 20:34:45 CET 2000
Mon Mar 01 20:34:45 CET 1999

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.util.*;

public class BugTest {
    
    public BugTest() {
    }
    
    public static void main(String[] args) {
        GregorianCalendar cal = new GregorianCalendar();
        cal.set(Calendar.YEAR, 2000);
        cal.set(Calendar.MONTH, Calendar.FEBRUARY);
        cal.set(Calendar.DAY_OF_MONTH, 29);
        System.out.println(cal.getTime());
        cal.roll(Calendar.YEAR, -1);
        System.out.println(cal.getTime());
    }
    
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
In GregorianCalendar.java, about line 773, replace the code

switch (field) {
        case ERA:
        case YEAR:
        case AM_PM:
        case MINUTE:
        case SECOND:
        case MILLISECOND:
            // These fields are handled simply, since they have fixed minima
            // and maxima.  The field DAY_OF_MONTH is almost as simple.  Other
            // fields are complicated, since the range within they must roll
            // varies depending on the date.
            break;

by code

switch (field) {
        case ERA:
        case AM_PM:
        case MINUTE:
        case SECOND:
        case MILLISECOND:
            // These fields are handled simply, since they have fixed minima
            // and maxima.  The field DAY_OF_MONTH is almost as simple.  Other
            // fields are complicated, since the range within they must roll
            // varies depending on the date.
            break;
        case YEAR:
            /* if calendar set to 29th of February handle here the situation and                  return, else break */
(Incident Review ID: 186562) 
======================================================================

Comments
EVALUATION Closing this bug as "will not fix" due to the compatibility concerns, while it's desirable to have consistent behavior with add().
20-02-2008

EVALUATION Staying in February for the case makes sense. In fact, add() behaves differently. However, roll() is working that way since JDK1.1. The behavior change might break existing applications. ###@###.### 2004-03-17
17-03-2004