JDK-4254545 : Calendar.roll() and Calendar.add() are broken
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util:i18n
  • Affected Version: 1.2.0,1.2.2,1.3.0
  • Priority: P4
  • Status: Closed
  • Resolution: Not an Issue
  • OS: generic,solaris_7,windows_nt
  • CPU: generic,x86
  • Submitted: 1999-07-15
  • Updated: 1999-10-16
  • Resolved: 1999-10-16
Related Reports
Relates :  
Description
This is a continuation to bug 4173516 which is closed now.

Calendar.roll() is broken on field 4 for any day d, 20 <= d <= 31.
Also, Calendar.add(0) and Calendar.roll() are broken on field 2 for day d=30,31.
This is using jdk1.2.2_W.

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

public class y2k_34 {
    static int ierr = 0;

    public static void main(String[] args) {
        int y = 2000, m = Calendar.MARCH, d = 25; // <<-- d=25
        int h = 10, M = 45, s = 15, S = 900;
        int limit = 40;
        int field, i;
	GregorianCalendar cal = new GregorianCalendar();

        System.out.println("Testing GregorianCalendar add...");
        for (field=0; field < Calendar.FIELD_COUNT; ++field) {
            if (field != Calendar.ZONE_OFFSET &&
                field != Calendar.DST_OFFSET) {
                cal.clear();
                cal.set(y, m, d, h, M, s);
                cal.set(Calendar.MILLISECOND, S);
                for (i = 0; i < limit; i++) {
                    cal.add(field, 1);
                }
                for (i = 0; i < limit; i++) {
                    cal.add(field, -1);
                }
                checkCalendar(cal, field, y, m, d, h, M, s, S);
            }
        }

        System.out.println("Testing GregorianCalendar roll...");
        for (field=0; field < Calendar.FIELD_COUNT; ++field) {
            if (field != Calendar.ZONE_OFFSET &&
                field != Calendar.DST_OFFSET) {
                cal.clear();
                cal.set(y, m, d, h, M, s);
                cal.set(Calendar.MILLISECOND, S);
                for (i = 0; i < limit; i++) {
                    cal.roll(field, 1);
                }
                for (i = 0; i < limit; i++) {
                    cal.roll(field, -1);
		}
                checkCalendar(cal, field, y, m, d, h, M, s, S);
            }
        }
	System.exit(ierr);
    }

    static void checkCalendar(Calendar c, int field,
                              int y, int m, int d, int h, int M, int s, int S) {
        if (c.get(Calendar.YEAR) != y ||
            c.get(Calendar.MONTH) != m ||
            c.get(Calendar.DATE) != d ||
            c.get(Calendar.HOUR_OF_DAY) != h ||
            c.get(Calendar.MINUTE) != M ||
            c.get(Calendar.SECOND) != s ||
            c.get(Calendar.MILLISECOND) != S) {
            System.err.println("Field " + field +
                               " FAIL, expected " +
                               y + "/" + (m + 1) + "/" + d +
                               " " + h + ":" + M + ":" + s + "." + S +
                               ", got " + c.get(Calendar.YEAR) +
                               "/" + (c.get(Calendar.MONTH) + 1) +
                               "/" + c.get(Calendar.DATE) +
                               " " + c.get(Calendar.HOUR_OF_DAY) +
                               ":" + c.get(Calendar.MINUTE) +
                               ":" + c.get(Calendar.SECOND) +
                               "." + c.get(Calendar.MILLISECOND));
	    ierr = 1;
        }
        else System.out.println("Field " + field + " ok");
    }
}

The output log:
---------------
tomboy% /net/ultraowl.eng/export/ultraowl2/jdk12x/sparc/jdk1.2.2_W/bin/java y2k_34
Testing GregorianCalendar add...
Field 0 ok
Field 1 ok
Field 2 ok
Field 3 ok
Field 4 ok
Field 5 ok
Field 6 ok
Field 7 ok
Field 8 ok
Field 9 ok
Field 10 ok
Field 11 ok
Field 12 ok
Field 13 ok
Field 14 ok
Testing GregorianCalendar roll...
Field 0 ok
Field 1 ok
Field 2 ok
Field 3 ok
Field 4 FAIL, expected 2000/2/25 10:45:15.900, got 2000/2/22 10:45:15.900
Field 5 ok
Field 6 ok
Field 7 ok
Field 8 ok
Field 9 ok
Field 10 ok
Field 11 ok
Field 12 ok
Field 13 ok
Field 14 ok

-----------

In the other example for date 1/31/2000 the output is:
Field 0 ok
Field 1 ok
Field 2 FAIL, expected 1999/12/31 10:45:15.900, got 1999/12/28 10:45:15.900
Field 3 ok
Field 4 ok
Field 5 ok
Field 6 ok
Field 7 ok
Field 8 ok
Field 9 ok
Field 10 ok
Field 11 ok
Field 12 ok
Field 13 ok
Field 14 ok
Testing GregorianCalendar roll...
Field 0 ok
Field 1 ok
Field 2 FAIL, expected 1999/12/31 10:45:15.900, got 1999/12/28 10:45:15.900
Field 3 ok
Field 4 ok
Field 5 ok
Field 6 ok
Field 7 ok
Field 8 ok
Field 9 ok
Field 10 ok
Field 11 ok
Field 12 ok
Field 13 ok
Field 14 ok


anat.kremer@Eng 1999-07-15

Name: krT82822			Date: 09/04/99


(note: filed for 1.2.1, but changed to kestrel-beta, since behavior still present in kestrel-beta)

Compile and run the class to see wrong results.

import java.awt.*;
import java.util.*;

public class TestRoll{
   public static void main( String[] args ){
      Calendar cal = Calendar.getInstance() ;

      //Setting the date as "01 Sep 1999"
      cal.set( 1999, 8, 1 ) ;

      //DEBUG
      System.out.println("Before rolling back : " + cal.getTime() ) ;

      //Going back one day
      cal.roll( Calendar.DATE, false ) ;

      //DEBUG(Expecting "31 Aug 1999")
      System.out.println("After rolling back : " + cal.getTime() ) ;
   }
}


java version "1.2.1"
Classic VM (build JDK-1.2.1-A, native threads)


Iam expecting the roll() method to rollback to 31 Aug 1999 but it gives 30 Sep 1999, which means it's looping within the same month.

------------

9/4/99 eval1127@eng -- looks like # 4254545, 4248500, etc.:

results with kestrel-beta (on Solaris -- got same results on NT with "build1.3beta-O"):

% java TestRoll
Before rolling back : Wed Sep 01 12:59:31 PDT 1999
After rolling back : Thu Sep 30 12:59:31 PDT 1999

% java -version
java version "1.3.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.0-E)
Java(TM) HotSpot Client VM (build 1.3beta-E-release, 1.3beta-E-release, interpreted mode)

(Review ID: 94862)
======================================================================

Name: skT88420			Date: 10/15/99


When using the roll() method of GregorianCalendar to move forward
or backward by days, the daytime fields are altered when passing
through a daylight savings boundary for the Locale.

That is, a date
Thu Apr 01 00:00:00 GMT+10:00 1999
rolled backwards on field Calendar.DAY_OF_YEAR becomes
Tue Mar 02 01:00:00 GMT+11:00 1999

It has passed through the change in DST for my locale.
(East Australia)
(Review ID: 96610)
======================================================================

Comments
WORK AROUND Name: skT88420 Date: 10/15/99 You must manually calculate the daylight savings offset using SimpleTimeZone.getDSTSavings() (Review ID: 96610) ======================================================================
11-06-2004

EVALUATION For the first example, roll(WEEK_OF_MONTH,1) and roll(WEEK_OF_MONTH,-1) won't get the same day. At the first or last week of month, when roll(WEEK_OF_MONTH is operated, on current implementation, the value is rounded up to first date or last date of the month. For instance, 3/25/2000, roll +1 week, is calclated as 3/32/2000 and rounded up to last day of the March, 3/31. Then roll -1 week will result in 3/24. For the second example, 1/31/2000 +1 month will be 2/29/2000 and -1 month will be 1/29/2000. This is true for other field. And stated in specification -- though not all the case is covered. Therefore, roundtriping like roll(+1) then roll(-1) won't be the same date or time. I think this is not a bug. koushi.takahashi@japan 1999-08-02 Probably a documentation problem as reported by 4254589. But need to look into all details of the implementation before closing this. masayoshi.okutsu@Eng 1999-08-11 Actually the description has several test case, let me examine one by one. (1) y2k_34.java: Mar,25th,2000AD roll(WEEK_OF_MONTH,1) 40 times, then roll(WEEK_OF_MONTH,-1) 40 times. The result on description is different from 1.2.2W (also Kestrel). The output was, $/usr/local/java/jdk1.3/solaris/bin/java y2k_34 Testing GregorianCalendar add... Field 0 ok Field 1 ok Field 2 ok Field 3 ok Field 4 ok Field 5 ok Field 6 ok Field 7 ok Field 8 ok Field 9 ok Field 10 ok Field 11 ok Field 12 ok Field 13 ok Field 14 ok Testing GregorianCalendar roll... Field 0 ok Field 1 ok Field 2 ok Field 3 ok Field 4 FAIL, expected 2000/3/25 10:45:15.900, got 2000/3/24 10:45:15.900 Field 5 ok Field 6 ok Field 7 ok Field 8 ok Field 9 ok Field 10 ok Field 11 ok Field 12 ok Field 13 ok Field 14 ok Anyway, Mar,25th + 1 week roll will be Mar,31st (roll/add roundup rule). Then after several times roll() will around Thursday of Mar,2000. Then ends up 2000/3/24. This is expected and not a bug. (2) y2K_34.java case of Jan. 31st, 2000. The result was, Testing GregorianCalendar add... Field 0 ok Field 1 ok Field 2 FAIL, expected 2000/1/31 10:45:15.900, got 2000/1/28 10:45:15.900 Field 3 ok Field 4 ok Field 5 ok Field 6 ok Field 7 ok Field 8 ok Field 9 ok Field 10 ok Field 11 ok Field 12 ok Field 13 ok Field 14 ok Testing GregorianCalendar roll... Field 0 ok Field 1 ok Field 2 FAIL, expected 2000/1/31 10:45:15.900, got 2000/1/29 10:45:15.900 Field 3 ok Field 4 ok Field 5 ok Field 6 ok Field 7 ok Field 8 ok Field 9 ok Field 10 ok Field 11 ok Field 12 ok Field 13 ok Field 14 ok First fail, Field 2 (MONTH). The data has been rounded up at 2001, Feb as 28 days (2000, Jan + add(1) 40 times goes 2003 May). Second fail is rounded up at 2000, Feb (roll won't change the year). Yes, not a bug. (3) TestRoll.java. This is the user misunderstanding of roll() method behaviour. 9/1/99 + roll(false). Rolls within September. 9/30/99 is expected day. not a bug. (4) roll() on DST boundary. 4/1/99 0:00 GMT+10 roll(DAY_OF_YEAR, -31) -- though the test program is not present, so it's my guess --. The behaviour of roll(DAY_OF_YEAR) is based on delta seconds. i.e. 24 * 60 * 60 * 31 sec. This is actually undocumented spec. I'll split another bugreport and close this one, since this last one is a bit off the topic. koushi.takahashi@japan 1999-10-15
15-10-1999