JDK-4208960 : TimeZone.getOffset doesn't work with february 29th
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util:i18n
  • Affected Version: 1.1.2,1.2.0,1.2.1
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic,windows_nt
  • CPU: generic,x86
  • Submitted: 1999-02-04
  • Updated: 1999-11-23
  • Resolved: 1999-09-27
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availabitlity Release.

To download the current JDK release, click here.
Other
1.2.1_003 b03Fixed
Description

Name: gsC80088			Date: 02/04/99


With Java 2, NT4.0 and the system time zone set to Eastern time Us&Canada, and daylight saving on,
the getOffset function returns an IllegalArgumentException for no apparent reasons.
The same code works with the time zone set to GMT UK time.

import java.util.*;

public class TestTimeZone {

  public static void main(String[] args) {

    SimpleTimeZone tz = (SimpleTimeZone)Calendar.getInstance().getTimeZone();
    System.out.println("Offset for february 28, 1996: " + tz.getOffset(GregorianCalendar.AD, 1996, 1, 28, Calendar.WEDNESDAY, 0));
    System.out.println("Offset for february 29, 1996: " + tz.getOffset(GregorianCalendar.AD, 1996, 1, 29, Calendar.THURSDAY, 0));
  }

}
(Review ID: 49133)
======================================================================

Name: vi73552			Date: 05/20/99


The getOffset(...) method in SimpleTimeZone throws an InvalidArgumentException when it is passed a date of Feb. 29th during a leap year (tested both Feb 29th 1996 and Feb 29th 2000). The reason for this appears to be that the SimpleTimeZone class uses a simple validation routine that doesn't handle leap years.


Code to reproduce the problem:


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

public class DateTest 
{
  public static void main(String[] args)
    {
    try
      {
      SimpleDateFormat dateFormatter = new SimpleDateFormat("MM/dd/yyyy");
      Date d = dateFormatter.parse("02/29/1996");
      TimeZone tz = TimeZone.getTimeZone("PST");
      Calendar c = Calendar.getInstance();
      c.setTime(d);
      int timeOfDay = c.get(Calendar.HOUR_OF_DAY) * 60 * 60 *1000;
      timeOfDay += c.get(Calendar.MINUTE) * 60 * 1000;
      timeOfDay += c.get(Calendar.SECOND) * 1000;

      int offset = tz.getOffset(c.get(Calendar.ERA), c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH), c.get(Calendar.DAY_OF_WEEK), timeOfDay); 

      System.out.println("Got an offset of: " + offset);
      }
    catch (Exception e)
      {
      e.printStackTrace(System.out);
      }
    }
}


output of the program is:

java.lang.IllegalArgumentException
	at java.util.SimpleTimeZone.getOffset(Compiled Code)
	at java.util.SimpleTimeZone.getOffset(SimpleTimeZone.java:356)
	at DateTest.main(DateTest.java:19)


This is with java -version:
java version "1.2"
Classic VM (build JDK-1.2-V, native threads)

And with java -fullversion:
java full version "JDK-1.2-V"


Upon examination of the code in SimpleTimeZone.java, we discovered that it uses the following array to determine the lengths of the various months. If it finds a day_of_month outside this range, it throws an IllegalArgumentException.


======================================================================

Name: krT82822			Date: 09/19/99


SUB: Y2K Leap Year related error in MailApi class MimeMessage

JavaMailApiVersion: 1.1.x
OS: Windows NT 4.0 Service Pack 3 or greater
Platform: Intel Based

MimeMessage class setSentDate(new Date()) method causes the 
following Error if you set the system date to Year 2000, Feb 29.

// Code
   MimeMessage msg = new MimeMessage(outSession);
   // Set the Date: header causes the Error
   msg.setSentDate(new Date());

ERROR:
=====

java.lang.IllegalArgumentException
        at java.util.SimpleTimeZone.getOffset(SimpleTimeZone.java:395)
        at java.util.SimpleTimeZone.getOffset(SimpleTimeZone.java:356)
        at com.sun.mail.util.MailDateFormat.format(MailDateFormat.java:143)
        at java.text.DateFormat.format(DateFormat.java:312)
        at javax.mail.internet.MimeMessage.setSentDate(MimeMessage.java:587)
        at com.hp.ts.server.MailProcessor.OutMailMessage.deliverMessage(OutMailM
essage.java:440)
        at com.hp.ts.server.MailProcessor.OutMailMessage.sendMessage(OutMailMess
age.java:249)
        at com.hp.ts.server.MailProcessor.OutMailMessage.sendMessage(OutMailMess
age.java:213)
(Review ID: 94259)
======================================================================

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: kestrel FIXED IN: 1.2.1-003 1.2.2-001 1.2.2-004 kestrel INTEGRATED IN: 1.2.1-003 1.2.2-001 1.2.2-004 kestrel VERIFIED IN: dino
2004-06-14

WORK AROUND Name: gsC80088 Date: 02/04/99 None ====================================================================== Name: krT82822 Date: 09/19/99 msg.setHeader("Date", (new Date()).toString()); (Review ID: 94259) ======================================================================
2004-06-11

PUBLIC COMMENTS TimeZone.getOffset() thrown IllegalArgumentException when it has leap day as argument, ie. 2/29/96. The method did not aware leap year and alway though Feb has 28 days. Fix corrected.
2004-06-10

SUGGESTED FIX Below is source diff of fix that was backported to patch release for production win32/reference Solaris JDK1.2.2-001, JDK1.2.2-004, JDK1.2.1-003: *** /tmp/geta23842 Thu Nov 4 09:58:39 1999 --- /tmp/getb23842 Thu Nov 4 09:58:39 1999 *************** *** 353,360 **** || month > Calendar.DECEMBER) { throw new IllegalArgumentException("Illegal month " + month); } return getOffset(era, year, month, day, dayOfWeek, millis, ! staticMonthLength[month]); } /** --- 353,366 ---- || month > Calendar.DECEMBER) { throw new IllegalArgumentException("Illegal month " + month); } + int monthLength = 0; + if ((era == GregorianCalendar.AD) && isLeapYear(year)) { + monthLength = staticLeapMonthLength[month]; + } else { + monthLength = staticMonthLength[month]; + } return getOffset(era, year, month, day, dayOfWeek, millis, ! monthLength); } /** *************** *** 835,840 **** --- 841,852 ---- */ private final byte monthLength[] = staticMonthLength; private final static byte staticMonthLength[] = {31,28,31,30,31,30,31,31,30,31,30,31}; + private final static byte staticLeapMonthLength[] = {31,29,31,30,31,30,31,31,30,31,30,31}; + /** + * The year of the gregorianCutover, with 0 representing + * 1 BC, -1 representing 2 BC, etc. + */ + private transient int gregorianCutoverYear = 1582; /** * Variables specifying the mode of the start rule. Takes the following *************** *** 1175,1180 **** --- 1187,1204 ---- } } + /** + * Determines if the given year is a leap year. Returns true if the + * given year is a leap year. + * @param year the given year. + * @return true if the given year is a leap year; false otherwise. + */ + private boolean isLeapYear(int year) { + return year >= gregorianCutoverYear ? + ((year%4 == 0) && ((year%100 != 0) || (year%400 == 0))) : // Gregorian + (year%4 == 0); // Julian + } + /** * Pack the start and end rules into an array of bytes. Only pack * data which is not preserved by makeRulesCompatible. carolyn.lowenthal@Eng 1999-11-04
1999-11-04

EVALUATION The behavior described in this bug is a known limitation of the getOffset() method. It is documented already in the SimpleTimeZone javadoc. For this reason, the use of getOffset() has been explicitly discouraged; instead, call Calendar.get(ZONE_OFFSET) and Calendar.get(DST_OFFSET), as noted in the javadoc. Please close will not fix. alan.liu@eng 1999-03-19 getOffset() did not care about leap year. Means, it always treat Feb has 28 days. koushi.takahashi@japan 1999-09-21 Rose.Ng@eng 1999-11-08 bug fix incorporated in JDK1.2.1-003 JDK1.2.2-001 and JDK1.2.2-004 code reviewr: koushi
1999-09-21