This is the mail archive of the java-patches@gcc.gnu.org mailing list for the Java project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

SimpleDateFormat Y2K fix etc


This makes the date parser Y2K compliant (!!), and fixes a few minor
Calendar bugs that I noticed while testing that fix. It also makes
SimpleDateFormat a bit more efficient. I'm checking this in to the
trunk.

regards

Bryce.


2001-07-26  Bryce McKinlay  <bryce@waitaki.otago.ac.nz>

	* java/util/Calendar.java (set): Never recompute fields here. They 
	will already be set if someone set time explicitly, and it can cause
	problems to do so. Don't invalidate AM_PM setting if HOUR is set.
	* java/util/GregorianCalendar.java (computeTime): Don't ignore an
	HOUR setting if AM_PM is set. Don't try to ensure the HOUR value is 
	sane.
	* java/text/SimpleDateFormat.java (defaultCentury): New field.
	(readObject): Call set2DigitYearStart if appropriate so that 
	defaultCentury is calculated.
	(SimpleDateFormat): Don't bother clearing calendar here. Call
	computeCenturyStart().
	(set2DigitYearStart): Calculate and set defaultCentury.
	(format): Don't clone the calendar. Use "calendar" not "theCalendar"
	everywhere.
	(parse): Likewise. If the pattern is "y" or "yy" and it found exactly
	2 numeric digits, use the 80-20 heuristic to parse the value into a
	default century based on defaultCenturyStart.
	(computeCenturyStart): Rewritten. Call set2DigitYearStart().

Index: text/SimpleDateFormat.java
===================================================================
RCS file: /cvs/gcc/gcc/libjava/java/text/SimpleDateFormat.java,v
retrieving revision 1.12
diff -u -r1.12 SimpleDateFormat.java
--- SimpleDateFormat.java	2001/05/16 15:34:46	1.12
+++ SimpleDateFormat.java	2001/07/26 10:58:05
@@ -62,7 +62,8 @@
 
   private transient Vector tokens;
   private DateFormatSymbols formatData;  // formatData
-  private Date defaultCenturyStart = computeCenturyStart ();
+  private Date defaultCenturyStart;
+  private transient int defaultCentury;
   private String pattern;
   private int serialVersionOnStream = 1; // 0 indicates JDK1.1.3 or earlier
   private static final long serialVersionUID = 4774881970558875024L;
@@ -78,9 +79,12 @@
     stream.defaultReadObject();
     if (serialVersionOnStream < 1)
       {
-        defaultCenturyStart = computeCenturyStart ();
+        computeCenturyStart ();
 	serialVersionOnStream = 1;
       }
+    else
+      // Ensure that defaultCentury gets set.
+      set2DigitYearStart(defaultCenturyStart);
 
     // Set up items normally taken care of by the constructor.
     tokens = new Vector();
@@ -159,7 +163,7 @@
     super();
     Locale locale = Locale.getDefault();
     calendar = new GregorianCalendar(locale);
-    calendar.clear ();
+    computeCenturyStart();
     tokens = new Vector();
     formatData = new DateFormatSymbols(locale);
     pattern = (formatData.dateFormats[DEFAULT] + ' '
@@ -186,7 +190,7 @@
   {
     super();
     calendar = new GregorianCalendar(locale);
-    calendar.clear ();
+    computeCenturyStart();
     tokens = new Vector();
     formatData = new DateFormatSymbols(locale);
     compileFormat(pattern);
@@ -203,10 +207,6 @@
   {
     super();
     calendar = new GregorianCalendar();
-    calendar.clear ();
-    // FIXME: XXX: Is it really necessary to set the timezone?
-    // The Calendar constructor is supposed to take care of this.
-    calendar.setTimeZone(TimeZone.getDefault());
     tokens = new Vector();
     this.formatData = formatData;
     compileFormat(pattern);
@@ -309,6 +309,10 @@
   public void set2DigitYearStart(Date date)
   {
     defaultCenturyStart = date;
+    calendar.clear();
+    calendar.setTime(date);
+    int year = calendar.get(Calendar.YEAR);
+    defaultCentury = year - (year % 100);
   }
 
   /**
@@ -386,8 +390,7 @@
   public StringBuffer format(Date date, StringBuffer buffer, FieldPosition pos)
   {
     String temp;
-    Calendar theCalendar = (Calendar) calendar.clone();
-    theCalendar.setTime(date);
+    calendar.setTime(date);
     
     // go through vector, filling in fields where applicable, else toString
     Enumeration e = tokens.elements();
@@ -398,10 +401,10 @@
 	int beginIndex = buffer.length();
 	switch (p.field) {
 	case ERA_FIELD:
-	  buffer.append(formatData.eras[theCalendar.get(Calendar.ERA)]);
+	  buffer.append(formatData.eras[calendar.get(Calendar.ERA)]);
 	  break;
 	case YEAR_FIELD:
-	  temp = String.valueOf(theCalendar.get(Calendar.YEAR));
+	  temp = String.valueOf(calendar.get(Calendar.YEAR));
 	  if (p.size < 4)
 	    buffer.append(temp.substring(temp.length()-2));
 	  else
@@ -409,60 +412,60 @@
 	  break;
 	case MONTH_FIELD:
 	  if (p.size < 3)
-	    withLeadingZeros(theCalendar.get(Calendar.MONTH)+1,p.size,buffer);
+	    withLeadingZeros(calendar.get(Calendar.MONTH)+1,p.size,buffer);
 	  else if (p.size < 4)
-	    buffer.append(formatData.shortMonths[theCalendar.get(Calendar.MONTH)]);
+	    buffer.append(formatData.shortMonths[calendar.get(Calendar.MONTH)]);
 	  else
-	    buffer.append(formatData.months[theCalendar.get(Calendar.MONTH)]);
+	    buffer.append(formatData.months[calendar.get(Calendar.MONTH)]);
 	  break;
 	case DATE_FIELD:
-	  withLeadingZeros(theCalendar.get(Calendar.DATE),p.size,buffer);
+	  withLeadingZeros(calendar.get(Calendar.DATE),p.size,buffer);
 	  break;
 	case HOUR_OF_DAY1_FIELD: // 1-24
-	  withLeadingZeros(((theCalendar.get(Calendar.HOUR_OF_DAY)+23)%24)+1,p.size,buffer);
+	  withLeadingZeros(((calendar.get(Calendar.HOUR_OF_DAY)+23)%24)+1,p.size,buffer);
 	  break;
 	case HOUR_OF_DAY0_FIELD: // 0-23
-	  withLeadingZeros(theCalendar.get(Calendar.HOUR_OF_DAY),p.size,buffer);
+	  withLeadingZeros(calendar.get(Calendar.HOUR_OF_DAY),p.size,buffer);
 	  break;
 	case MINUTE_FIELD:
-	  withLeadingZeros(theCalendar.get(Calendar.MINUTE),p.size,buffer);
+	  withLeadingZeros(calendar.get(Calendar.MINUTE),p.size,buffer);
 	  break;
 	case SECOND_FIELD:
-	  withLeadingZeros(theCalendar.get(Calendar.SECOND),p.size,buffer);
+	  withLeadingZeros(calendar.get(Calendar.SECOND),p.size,buffer);
 	  break;
 	case MILLISECOND_FIELD:
-	  withLeadingZeros(theCalendar.get(Calendar.MILLISECOND),p.size,buffer);
+	  withLeadingZeros(calendar.get(Calendar.MILLISECOND),p.size,buffer);
 	  break;
 	case DAY_OF_WEEK_FIELD:
 	  if (p.size < 4)
-	    buffer.append(formatData.shortWeekdays[theCalendar.get(Calendar.DAY_OF_WEEK)]);
+	    buffer.append(formatData.shortWeekdays[calendar.get(Calendar.DAY_OF_WEEK)]);
 	  else
-	    buffer.append(formatData.weekdays[theCalendar.get(Calendar.DAY_OF_WEEK)]);
+	    buffer.append(formatData.weekdays[calendar.get(Calendar.DAY_OF_WEEK)]);
 	  break;
 	case DAY_OF_YEAR_FIELD:
-	  withLeadingZeros(theCalendar.get(Calendar.DAY_OF_YEAR),p.size,buffer);
+	  withLeadingZeros(calendar.get(Calendar.DAY_OF_YEAR),p.size,buffer);
 	  break;
 	case DAY_OF_WEEK_IN_MONTH_FIELD:
-	  withLeadingZeros(theCalendar.get(Calendar.DAY_OF_WEEK_IN_MONTH),p.size,buffer);
+	  withLeadingZeros(calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH),p.size,buffer);
 	  break;
 	case WEEK_OF_YEAR_FIELD:
-	  withLeadingZeros(theCalendar.get(Calendar.WEEK_OF_YEAR),p.size,buffer);
+	  withLeadingZeros(calendar.get(Calendar.WEEK_OF_YEAR),p.size,buffer);
 	  break;
 	case WEEK_OF_MONTH_FIELD:
-	  withLeadingZeros(theCalendar.get(Calendar.WEEK_OF_MONTH),p.size,buffer);
+	  withLeadingZeros(calendar.get(Calendar.WEEK_OF_MONTH),p.size,buffer);
 	  break;
 	case AM_PM_FIELD:
-	  buffer.append(formatData.ampms[theCalendar.get(Calendar.AM_PM)]);
+	  buffer.append(formatData.ampms[calendar.get(Calendar.AM_PM)]);
 	  break;
 	case HOUR1_FIELD: // 1-12
-	  withLeadingZeros(((theCalendar.get(Calendar.HOUR)+11)%12)+1,p.size,buffer);
+	  withLeadingZeros(((calendar.get(Calendar.HOUR)+11)%12)+1,p.size,buffer);
 	  break;
 	case HOUR0_FIELD: // 0-11
-	  withLeadingZeros(theCalendar.get(Calendar.HOUR),p.size,buffer);
+	  withLeadingZeros(calendar.get(Calendar.HOUR),p.size,buffer);
 	  break;
 	case TIMEZONE_FIELD:
-	  TimeZone zone = theCalendar.getTimeZone();
-	  boolean isDST = theCalendar.get(Calendar.DST_OFFSET) != 0;
+	  TimeZone zone = calendar.getTimeZone();
+	  boolean isDST = calendar.get(Calendar.DST_OFFSET) != 0;
 	  // FIXME: XXX: This should be a localized time zone.
 	  String zoneID = zone.getDisplayName(isDST, p.size > 3 ? TimeZone.LONG : TimeZone.SHORT);
 	  buffer.append(zoneID);
@@ -482,7 +485,8 @@
     return buffer;
   }
 
-  private void withLeadingZeros(int value, int length, StringBuffer buffer) {
+  private void withLeadingZeros(int value, int length, StringBuffer buffer) 
+  {
     String valStr = String.valueOf(value);
     for (length -= valStr.length(); length > 0; length--)
       buffer.append('0');
@@ -514,12 +518,10 @@
     int fmt_index = 0;
     int fmt_max = pattern.length();
 
-    // We copy the Calendar because if we don't we will modify it and
-    // then this.equals() will no longer have the desired result.
-    Calendar theCalendar = (Calendar) calendar.clone ();
-    theCalendar.clear();
+    calendar.clear();
     boolean saw_timezone = false;
     int quote_start = -1;
+    boolean is2DigitYear = false;
     for (; fmt_index < fmt_max; ++fmt_index)
       {
 	char ch = pattern.charAt(fmt_index);
@@ -552,7 +554,7 @@
 	int first = fmt_index;
 	while (++fmt_index < fmt_max && pattern.charAt(fmt_index) == ch)
 	  ;
-	int count = fmt_index - first;
+	int fmt_count = fmt_index - first;
 	--fmt_index;
 
 	// We can handle most fields automatically: most either are
@@ -564,6 +566,7 @@
 	boolean is_numeric = true;
 	String[] match = null;
 	int offset = 0;
+	boolean maybe2DigitYear = false;
 	switch (ch)
 	  {
 	  case 'd':
@@ -579,7 +582,7 @@
 	    is_numeric = false;
 	    offset = 1;
 	    calendar_field = Calendar.DAY_OF_WEEK;
-	    match = (count <= 3
+	    match = (fmt_count <= 3
 		     ? formatData.getShortWeekdays()
 		     : formatData.getWeekdays());
 	    break;
@@ -591,20 +594,20 @@
 	    break;
 	  case 'M':
 	    calendar_field = Calendar.MONTH;
-	    if (count <= 2)
+	    if (fmt_count <= 2)
 	      offset = -1;
 	    else
 	      {
 		is_numeric = false;
-		match = (count <= 3
+		match = (fmt_count <= 3
 			 ? formatData.getShortMonths()
 			 : formatData.getMonths());
 	      }
 	    break;
 	  case 'y':
 	    calendar_field = Calendar.YEAR;
-	    if (count <= 2)
-	      offset = 1900;
+	    if (fmt_count <= 2)
+	      maybe2DigitYear = true;
 	    break;
 	  case 'K':
 	    calendar_field = Calendar.HOUR;
@@ -655,8 +658,8 @@
 		    found_zone = true;
 		    saw_timezone = true;
 		    TimeZone tz = TimeZone.getTimeZone (strings[0]);
-		    theCalendar.setTimeZone (tz);
-		    theCalendar.set (Calendar.ZONE_OFFSET, tz.getRawOffset ());
+		    calendar.setTimeZone (tz);
+		    calendar.set (Calendar.ZONE_OFFSET, tz.getRawOffset ());
 		    offset = 0;
 		    if (k > 2 && tz instanceof SimpleTimeZone)
 		      {
@@ -680,9 +683,12 @@
 
 	// Compute the value we should assign to the field.
 	int value;
+	int index = -1;
 	if (is_numeric)
 	  {
-	    numberFormat.setMinimumIntegerDigits(count);
+	    numberFormat.setMinimumIntegerDigits(fmt_count);
+	    if (maybe2DigitYear)
+	      index = pos.getIndex();
 	    Number n = numberFormat.parse(dateStr, pos);
 	    if (pos == null || ! (n instanceof Long))
 	      return null;
@@ -690,7 +696,7 @@
 	  }
 	else if (match != null)
 	  {
-	    int index = pos.getIndex();
+	    index = pos.getIndex();
 	    int i;
 	    for (i = offset; i < match.length; ++i)
 	      {
@@ -707,9 +713,28 @@
 	  }
 	else
 	  value = offset;
+	  
+	if (maybe2DigitYear)
+	  {
+	    // Parse into default century if the numeric year string has 
+	    // exactly 2 digits.
+	    int digit_count = pos.getIndex() - index;
+	    if (digit_count == 2)
+	      is2DigitYear = true;
+	  }
 
 	// Assign the value and move on.
-	theCalendar.set(calendar_field, value);
+	calendar.set(calendar_field, value);
+      }
+    
+    if (is2DigitYear)
+      {
+	// Apply the 80-20 heuristic to dermine the full year based on 
+	// defaultCenturyStart. 
+	int year = defaultCentury + calendar.get(Calendar.YEAR);
+	calendar.set(Calendar.YEAR, year);
+	if (calendar.getTime().compareTo(defaultCenturyStart) < 0)
+	  calendar.set(Calendar.YEAR, year + 100);      
       }
 
     try
@@ -718,10 +743,10 @@
 	  {
 	    // Use the real rules to determine whether or not this
 	    // particular time is in daylight savings.
-	    theCalendar.clear (Calendar.DST_OFFSET);
-	    theCalendar.clear (Calendar.ZONE_OFFSET);
+	    calendar.clear (Calendar.DST_OFFSET);
+	    calendar.clear (Calendar.ZONE_OFFSET);
 	  }
-        return theCalendar.getTime();
+        return calendar.getTime();
       }
     catch (IllegalArgumentException x)
       {
@@ -732,18 +757,10 @@
 
   // Compute the start of the current century as defined by
   // get2DigitYearStart.
-  private Date computeCenturyStart ()
+  private void computeCenturyStart()
   {
-    // Compute the current year.  We assume a year has 365 days.  Then
-    // compute 80 years ago, and finally reconstruct the number of
-    // milliseconds.  We do this computation in this strange way
-    // because it lets us easily truncate the milliseconds, seconds,
-    // etc, which don't matter and which confuse
-    // SimpleDateFormat.equals().
-    long now = System.currentTimeMillis ();
-    now /= 365L * 24L * 60L * 60L * 1000L;
-    now -= 80;
-    now *= 365L * 24L * 60L * 60L * 1000L;
-    return new Date (now);
+    int year = calendar.get(Calendar.YEAR);
+    calendar.set(Calendar.YEAR, year - 80);
+    set2DigitYearStart(calendar.getTime());
   }
 }
Index: util/Calendar.java
===================================================================
RCS file: /cvs/gcc/gcc/libjava/java/util/Calendar.java,v
retrieving revision 1.9
diff -u -r1.9 Calendar.java
--- Calendar.java	2001/05/10 18:13:16	1.9
+++ Calendar.java	2001/07/26 10:58:05
@@ -549,8 +549,6 @@
    */
   public final void set(int field, int value)
   {
-    if (!areFieldsSet)
-      computeFields();
     isTimeSet = false;
     fields[field] = value;
     isSet[field] = true;
@@ -573,7 +571,6 @@
 	isSet[HOUR] = false;
 	break;
       case HOUR:
-	isSet[AM_PM] = false;
 	isSet[HOUR_OF_DAY] = false;
 	break;
       }
@@ -587,8 +584,6 @@
    */
   public final void set(int year, int month, int date)
   {
-    if (!areFieldsSet)
-      computeFields();
     isTimeSet = false;
     fields[YEAR] = year;
     fields[MONTH] = month;
Index: util/GregorianCalendar.java
===================================================================
RCS file: /cvs/gcc/gcc/libjava/java/util/GregorianCalendar.java,v
retrieving revision 1.10
diff -u -r1.10 GregorianCalendar.java
--- GregorianCalendar.java	2001/05/15 21:44:24	1.10
+++ GregorianCalendar.java	2001/07/26 10:58:05
@@ -373,9 +373,17 @@
       year = 1 - year;
 
     int[] daysOfYear = getDayOfYear(year);
-    int hour = isSet[HOUR_OF_DAY] ? fields[HOUR_OF_DAY]
-      : (isSet[HOUR] && isSet[AM_PM]
-	 ? fields[AM_PM] * 12 + (fields[HOUR] % 12) : 0);
+
+    int hour = 0;
+    if (isSet[HOUR_OF_DAY])
+      hour = fields[HOUR_OF_DAY];
+    else if (isSet[HOUR])
+      {
+	hour = fields[HOUR];
+        if (isSet[AM_PM] && fields[AM_PM] == PM)
+	  hour += 12;
+      }
+
     int minute = isSet[MINUTE] ? fields[MINUTE] : 0;
     int second = isSet[SECOND] ? fields[SECOND] : 0;
     int millis = isSet[MILLISECOND] ? fields[MILLISECOND] : 0;

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]