This is the mail archive of the java-patches@sources.redhat.com 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]

PATCH: TimeZone code; merges with Classpath


Folks,
Here is a patch of some TimeZone code that I've merged with Classpath.  It
still has a bit more to go, but I wanted to get this in now as a
placeholder.  I'm hoping to get the rest in later tonight or tomorrow.
--warrenl



2000-11-27  Warren Levy  <warrenl@cygnus.com>

	* Makefile.am: Added natTimeZone.cc.
	* Makefile.in: Rebuilt.
	* gnu/gcj/text/LocaleData_en.java: Added DateFormat entries.
	* java/text/DateFormatSymbols.java (ampms): Made package private.
	(eras): Made package private.
	(months): Made package private.
	(shortMonths): Made package private.
	(shortWeekdays): Made package private.
	(weekdays): Made package private.
	(formatPrefixes): New private field.
	(localPatternCharsDefault): Made private.
	(dateFormats): New package private field.
	(timeFormats): New package private field.
	(formatsForKey): New private method.
	(DateFormatSymbols(Locale)): Set dateFormats and timeFormats.
	(DateFormatSymbols(DateFormatSymbols)): Ditto.
	* java/text/SimpleDateFormat.java: Merged with Classpath.
	* java/util/TimeZone.java: Merged with Classpath.
	* java/util/natTimeZone.cc: New file.

Index: Makefile.am
===================================================================
RCS file: /cvs/java/libgcj/libjava/Makefile.am,v
retrieving revision 1.97
diff -u -p -r1.97 Makefile.am
--- Makefile.am	2000/10/27 10:33:46	1.97
+++ Makefile.am	2000/11/28 03:01:44
@@ -1219,6 +1219,7 @@ java/net/natPlainDatagramSocketImpl.cc \
 java/net/natPlainSocketImpl.cc \
 java/text/natCollator.cc \
 java/util/natGregorianCalendar.cc \
+java/util/natTimeZone.cc \
 java/util/zip/natDeflater.cc \
 java/util/zip/natInflater.cc
 
Index: Makefile.in
===================================================================
RCS file: /cvs/java/libgcj/libjava/Makefile.in,v
retrieving revision 1.105
diff -u -p -r1.105 Makefile.in
--- Makefile.in	2000/10/27 10:33:46	1.105
+++ Makefile.in	2000/11/28 03:01:45
@@ -963,6 +963,7 @@ java/net/natPlainDatagramSocketImpl.cc \
 java/net/natPlainSocketImpl.cc \
 java/text/natCollator.cc \
 java/util/natGregorianCalendar.cc \
+java/util/natTimeZone.cc \
 java/util/zip/natDeflater.cc \
 java/util/zip/natInflater.cc
 
@@ -1114,8 +1115,8 @@ java/lang/reflect/natArray.lo java/lang/
 java/lang/reflect/natField.lo java/lang/reflect/natMethod.lo \
 java/net/natInetAddress.lo java/net/natPlainDatagramSocketImpl.lo \
 java/net/natPlainSocketImpl.lo java/text/natCollator.lo \
-java/util/natGregorianCalendar.lo java/util/zip/natDeflater.lo \
-java/util/zip/natInflater.lo
+java/util/natGregorianCalendar.lo java/util/natTimeZone.lo \
+java/util/zip/natDeflater.lo java/util/zip/natInflater.lo
 libgcjx_la_OBJECTS =  gnu/gcj/xlib/natClip.lo \
 gnu/gcj/xlib/natColormap.lo gnu/gcj/xlib/natDisplay.lo \
 gnu/gcj/xlib/natDrawable.lo gnu/gcj/xlib/natFont.lo \
@@ -1649,8 +1650,9 @@ DEP_FILES =  .deps/$(srcdir)/$(CONVERT_D
 .deps/java/util/jar/JarException.P .deps/java/util/jar/JarFile.P \
 .deps/java/util/jar/JarInputStream.P \
 .deps/java/util/jar/JarOutputStream.P .deps/java/util/jar/Manifest.P \
-.deps/java/util/natGregorianCalendar.P .deps/java/util/zip/Adler32.P \
-.deps/java/util/zip/CRC32.P .deps/java/util/zip/CheckedInputStream.P \
+.deps/java/util/natGregorianCalendar.P .deps/java/util/natTimeZone.P \
+.deps/java/util/zip/Adler32.P .deps/java/util/zip/CRC32.P \
+.deps/java/util/zip/CheckedInputStream.P \
 .deps/java/util/zip/CheckedOutputStream.P \
 .deps/java/util/zip/Checksum.P \
 .deps/java/util/zip/DataFormatException.P \
Index: gnu/gcj/text/LocaleData_en.java
===================================================================
RCS file: /cvs/java/libgcj/libjava/gnu/gcj/text/LocaleData_en.java,v
retrieving revision 1.6
diff -u -p -r1.6 LocaleData_en.java
--- LocaleData_en.java	2000/10/10 23:09:07	1.6
+++ LocaleData_en.java	2000/11/28 03:01:45
@@ -68,6 +68,16 @@ public final class LocaleData_en extends
     { "shortWeekdays", shortWeekdaysDefault },
     { "weekdays", weekdaysDefault },
 
+    // These are for DateFormat.
+    { "shortDateFormat", "M/d/yy" },	      // Java's Y2K bug.
+    { "mediumDateFormat", "d-MMM-yy" },
+    { "longDateFormat", "MMMM d, yyyy" },
+    { "fullDateFormat", "EEEE MMMM d, yyyy G" },
+    { "shortTimeFormat", "h:mm a" },
+    { "mediumTimeFormat", "h:mm:ss a" },
+    { "longTimeFormat", "h:mm:ss a z" },
+    { "fullTimeFormat", "h:mm:ss;S 'o''clock' a z" },
+
     // For RuleBasedCollator.
     // FIXME: this is nowhere near complete.
     // In particular we must mark accents as ignorable,
Index: java/text/DateFormatSymbols.java
===================================================================
RCS file: /cvs/java/libgcj/libjava/java/text/DateFormatSymbols.java,v
retrieving revision 1.5
diff -u -p -r1.5 DateFormatSymbols.java
--- DateFormatSymbols.java	2000/10/10 23:09:08	1.5
+++ DateFormatSymbols.java	2000/11/28 03:01:46
@@ -24,21 +24,29 @@ import java.util.ResourceBundle;
 public class DateFormatSymbols extends Object
   implements java.io.Serializable, Cloneable
 {
-  private String[] ampms;
-  private String[] eras;
+  String[] ampms;
+  String[] eras;
   private String localPatternChars;
-  private String[] months;
-  private String[] shortMonths;
-  private String[] shortWeekdays;
-  private String[] weekdays;
+  String[] months;
+  String[] shortMonths;
+  String[] shortWeekdays;
+  String[] weekdays;
   private String[][] zoneStrings;
 
   private static final long serialVersionUID = -5987973545549424702L;
 
+  // The order of these prefixes must be the same as in DateFormat
+  // FIXME: XXX: Note that this differs from the Classpath implemention
+  // in that there is no "default" entry; that is due to differing
+  // implementations where DateFormat.DEFAULT is MEDIUM here but 4 in
+  // Classpath (the JCL says it should be MEDIUM).  That will need to be 
+  // resolved in the merge.
+  private static final String[] formatPrefixes = { "full", "long", "medium", "short" };
+
   private static final String[] ampmsDefault = {"AM", "PM" };
   private static final String[] erasDefault = {"BC", "AD" };
   // localPatternCharsDefault is used by SimpleDateFormat.
-  protected static final String localPatternCharsDefault
+  private static final String localPatternCharsDefault
     = "GyMdkHmsSEDFwWahKz";
   private static final String[] monthsDefault = {
     "January", "February", "March", "April", "May", "June",
@@ -77,6 +85,24 @@ public class DateFormatSymbols extends O
       /**/   "Alaska Daylight Time", "ADT", "Anchorage" }
   };
 
+  // These are each arrays with a value for SHORT, MEDIUM, LONG, FULL,
+  // and DEFAULT (constants defined in java.text.DateFormat).  While
+  // not part of the official spec, we need a way to get at locale-specific
+  // default formatting patterns.  They are declared package scope so
+  // as to be easily accessible where needed (DateFormat, SimpleDateFormat).
+  transient String[] dateFormats;
+  transient String[] timeFormats;
+
+  private String[] formatsForKey(ResourceBundle res, String key) 
+  {
+    String[] values = new String [formatPrefixes.length];
+    for (int i = 0; i < formatPrefixes.length; i++)
+      {
+        values[i] = res.getString(formatPrefixes[i]+key);
+      }
+    return values;
+  }
+
   private final Object safeGetResource (ResourceBundle res,
 					String key, Object def)
   {
@@ -116,6 +142,9 @@ public class DateFormatSymbols extends O
     weekdays = (String[]) safeGetResource (res, "weekdays", weekdaysDefault);
     zoneStrings = (String[][]) safeGetResource (res, "zoneStrings",
 						zoneStringsDefault);
+
+    dateFormats = formatsForKey(res, "DateFormat");
+    timeFormats = formatsForKey(res, "TimeFormat");
   }
 
   public DateFormatSymbols ()
@@ -134,6 +163,8 @@ public class DateFormatSymbols extends O
     shortWeekdays = old.shortWeekdays;
     weekdays = old.weekdays;
     zoneStrings = old.zoneStrings;
+    dateFormats = old.dateFormats;
+    timeFormats = old.timeFormats;
   }
 
   public String[] getAmPmStrings()
Index: java/text/SimpleDateFormat.java
===================================================================
RCS file: /cvs/java/libgcj/libjava/java/text/SimpleDateFormat.java,v
retrieving revision 1.5
diff -u -p -r1.5 SimpleDateFormat.java
--- SimpleDateFormat.java	2000/10/10 23:09:08	1.5
+++ SimpleDateFormat.java	2000/11/28 03:01:47
@@ -1,107 +1,268 @@
-/* Copyright (C) 1998, 1999, 2000  Free Software Foundation
+/* SimpleDateFormat.java -- A class for parsing/formating simple 
+   date constructs
+   Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+ 
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+As a special exception, if you link this library with other files to
+produce an executable, this library does not by itself cause the
+resulting executable to be covered by the GNU General Public License.
+This exception does not however invalidate any other reasons why the
+executable file might be covered by the GNU General Public License. */
 
-   This file is part of libgcj.
 
-This software is copyrighted work licensed under the terms of the
-Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
-details.  */
-
 package java.text;
 
-import java.util.*;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.SimpleTimeZone;
+import java.util.Vector;
 import java.io.ObjectInputStream;
 import java.io.IOException;
 
 /**
- * @author Per Bothner <bothner@cygnus.com>
- * @date October 25, 1998.
- */
-/* Written using "Java Class Libraries", 2nd edition, plus online
- * API docs for JDK 1.2 beta from http://www.javasoft.com.
- * Status:  Believed complete and correct to 1.2.
+ * SimpleDateFormat provides convenient methods for parsing and formatting
+ * dates using Gregorian calendars (see java.util.GregorianCalendar). 
  */
-
-public class SimpleDateFormat extends DateFormat
+public class SimpleDateFormat extends DateFormat 
 {
-  // Serialization fields.
-  private Date defaultCenturyStart = new Date();
-  private DateFormatSymbols formatData;
+  /** A pair class used by SimpleDateFormat as a compiled representation
+   *  of a format string.
+   */
+  private class FieldSizePair 
+  {
+    public int field;
+    public int size;
+
+    /** Constructs a pair with the given field and size values */
+    public FieldSizePair(int f, int s) {
+      field = f;
+      size = s;
+    }
+  }
+
+  private transient Vector tokens;
+  private DateFormatSymbols formatData;  // formatData
+  private Date defaultCenturyStart = 
+    new Date(System.currentTimeMillis() - (80*365*24*60*60*1000));
   private String pattern;
-  private int serialVersionOnStream = 1;
+  private int serialVersionOnStream = 1; // 0 indicates JDK1.1.3 or earlier
   private static final long serialVersionUID = 4774881970558875024L;
+
+  // This string is specified in the JCL.  We set it here rather than
+  // do a DateFormatSymbols(Locale.US).getLocalPatternChars() since
+  // someone could theoretically change those values (though unlikely).
+  private static final String standardChars = "GyMdkHmsSEDFwWahKz";
 
-  // Serialization method.
   private void readObject(ObjectInputStream stream)
     throws IOException, ClassNotFoundException
   {
     stream.defaultReadObject();
     if (serialVersionOnStream < 1)
       {
-        defaultCenturyStart = new Date();
+        defaultCenturyStart =
+	  new Date(System.currentTimeMillis() - (80*365*24*60*60*1000));
 	serialVersionOnStream = 1;
       }
-  }
 
-  public SimpleDateFormat ()
-  {
-    this("dd/MM/yy HH:mm", Locale.getDefault());
+    // Set up items normally taken care of by the constructor.
+    tokens = new Vector();
+    compileFormat(pattern);
+  }
+
+  private void compileFormat(String pattern) 
+  {
+    // Any alphabetical characters are treated as pattern characters
+    // unless enclosed in single quotes.
+
+    char thisChar;
+    int pos;
+    int field;
+    FieldSizePair current = null;
+
+    for (int i=0; i<pattern.length(); i++) {
+      thisChar = pattern.charAt(i);
+      field = formatData.getLocalPatternChars().indexOf(thisChar);
+      if (field == -1) {
+	current = null;
+	if (Character.isLetter(thisChar)) {
+	  // Not a valid letter
+	  tokens.addElement(new FieldSizePair(-1,0));
+	} else if (thisChar == '\'') {
+	  // Quoted text section; skip to next single quote
+	  pos = pattern.indexOf('\'',i+1);
+	  if (pos == -1) {
+	    // This ought to be an exception, but spec does not
+	    // let us throw one.
+	    tokens.addElement(new FieldSizePair(-1,0));
+	  }
+	  if ((pos+1 < pattern.length()) && (pattern.charAt(pos+1) == '\'')) {
+	    tokens.addElement(pattern.substring(i+1,pos+1));
+	  } else {
+	    tokens.addElement(pattern.substring(i+1,pos));
+	  }
+	  i = pos;
+	} else {
+	  // A special character
+	  tokens.addElement(new Character(thisChar));
+	}
+      } else {
+	// A valid field
+	if ((current != null) && (field == current.field)) {
+	  current.size++;
+	} else {
+	  current = new FieldSizePair(field,1);
+	  tokens.addElement(current);
+	}
+      }
+    }
   }
-
-  public SimpleDateFormat (String pattern)
+    
+  public String toString() 
+  {
+    StringBuffer output = new StringBuffer();
+    Enumeration e = tokens.elements();
+    while (e.hasMoreElements()) {
+      output.append(e.nextElement().toString());
+    }
+    return output.toString();
+  }
+      
+  /**
+   * Constructs a SimpleDateFormat using the default pattern for
+   * the default locale.
+   */
+  public SimpleDateFormat() 
+  {
+    /*
+     * There does not appear to be a standard API for determining 
+     * what the default pattern for a locale is, so use package-scope
+     * variables in DateFormatSymbols to encapsulate this.
+     */
+    super();
+    Locale locale = Locale.getDefault();
+    calendar = new GregorianCalendar(locale);
+    tokens = new Vector();
+    formatData = new DateFormatSymbols(locale);
+    pattern = formatData.dateFormats[DEFAULT]+' '+formatData.timeFormats[DEFAULT];
+    compileFormat(pattern);
+    numberFormat = NumberFormat.getInstance(locale);
+  }
+  
+  /**
+   * Creates a date formatter using the specified pattern, with the default
+   * DateFormatSymbols for the default locale.
+   */
+  public SimpleDateFormat(String pattern) 
   {
     this(pattern, Locale.getDefault());
   }
 
-  public SimpleDateFormat (String pattern, Locale locale)
-  {
+  /**
+   * Creates a date formatter using the specified pattern, with the default
+   * DateFormatSymbols for the given locale.
+   */
+  public SimpleDateFormat(String pattern, Locale locale) 
+  {
+    super();
+    calendar = new GregorianCalendar(locale);
+    tokens = new Vector();
+    formatData = new DateFormatSymbols(locale);
+    compileFormat(pattern);
     this.pattern = pattern;
-    this.calendar = Calendar.getInstance(locale);
-    this.numberFormat = NumberFormat.getInstance(locale);
-    numberFormat.setGroupingUsed(false);
-    this.formatData = new DateFormatSymbols (locale);
+    numberFormat = NumberFormat.getInstance(locale);
   }
 
-  public SimpleDateFormat (String pattern, DateFormatSymbols formatData)
-  {
-    this.pattern = pattern;
+  /**
+   * Creates a date formatter using the specified pattern. The
+   * specified DateFormatSymbols will be used when formatting.
+   */
+  public SimpleDateFormat(String pattern, DateFormatSymbols formatData) {
+    super();
+    calendar = new GregorianCalendar();
+    // 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;
-    this.calendar = Calendar.getInstance();
-    this.numberFormat = NumberFormat.getInstance();
-    numberFormat.setGroupingUsed(false);
+    compileFormat(pattern);
+    this.pattern = pattern;
+    numberFormat = NumberFormat.getInstance();
   }
 
-  public Date get2DigitYearStart()
-  {
-    return defaultCenturyStart;
-  }
+  // What is the difference between localized and unlocalized?  The
+  // docs don't say.
 
-  public void set2DigitYearStart(Date startDate)
+  /**
+   * This method returns a string with the formatting pattern being used
+   * by this object.  This string is unlocalized.
+   *
+   * @return The format string.
+   */
+  public String toPattern()
   {
-    defaultCenturyStart = startDate;
-  }
-
-  public DateFormatSymbols getDateFormatSymbols ()
-  {
-    return formatData;
+    return pattern;
   }
 
-  public void setDateFormatSymbols (DateFormatSymbols value)
+  /**
+   * This method returns a string with the formatting pattern being used
+   * by this object.  This string is localized.
+   *
+   * @return The format string.
+   */
+  public String toLocalizedPattern()
   {
-    formatData = value;
+    String localChars = formatData.getLocalPatternChars();
+    return applyLocalizedPattern (pattern, standardChars, localChars);
   }
 
-  public String toPattern ()
+  /**
+   * This method sets the formatting pattern that should be used by this
+   * object.  This string is not localized.
+   *
+   * @param pattern The new format pattern.
+   */
+  public void applyPattern(String pattern)
   {
-    return pattern;
+    tokens = new Vector();
+    compileFormat(pattern);
+    this.pattern = pattern;
   }
 
-  public void applyPattern (String pattern)
+  /**
+   * This method sets the formatting pattern that should be used by this
+   * object.  This string is localized.
+   *
+   * @param pattern The new format pattern.
+   */
+  public void applyLocalizedPattern(String pattern)
   {
-    this.pattern = pattern;
+    String localChars = formatData.getLocalPatternChars();
+    pattern = applyLocalizedPattern (pattern, localChars, standardChars);
+    applyPattern(pattern);
   }
 
-  private String applyLocalizedPattern (String pattern,
-					String oldChars, String newChars)
+  private String applyLocalizedPattern(String pattern,
+				       String oldChars, String newChars)
   {
     int len = pattern.length();
     StringBuffer buf = new StringBuffer(len);
@@ -122,424 +283,874 @@ public class SimpleDateFormat extends Da
     return buf.toString();
   }
 
-  public void applyLocalizedPattern (String pattern)
+  /** 
+   * Returns the start of the century used for two digit years.
+   *
+   * @return A <code>Date</code> representing the start of the century
+   * for two digit years.
+   */
+  public Date get2DigitYearStart()
   {
-    String localChars = formatData.getLocalPatternChars();
-    String standardChars = DateFormatSymbols.localPatternCharsDefault;
-    pattern = applyLocalizedPattern (pattern, localChars, standardChars);
-    applyPattern(pattern);
+    return defaultCenturyStart;
   }
 
-  public String toLocalizedPattern ()
+  /**
+   * Sets the start of the century used for two digit years.
+   *
+   * @param date A <code>Date</code> representing the start of the century for
+   * two digit years.
+   */
+  public void set2DigitYearStart(Date date)
+  {
+    defaultCenturyStart = date;
+  }
+
+  /**
+   * This method returns the format symbol information used for parsing
+   * and formatting dates.
+   *
+   * @return The date format symbols.
+   */
+  public DateFormatSymbols getDateFormatSymbols()
   {
-    String localChars = formatData.getLocalPatternChars();
-    String standardChars = DateFormatSymbols.localPatternCharsDefault;
-    return applyLocalizedPattern (pattern, standardChars, localChars);
+    return formatData;
   }
 
-  private final void append (StringBuffer buf, int value, int numDigits)
+  /**
+   * This method sets the format symbols information used for parsing
+   * and formatting dates.
+   *
+   * @param formatData The date format symbols.
+   */
+   public void setDateFormatSymbols(DateFormatSymbols formatData)
+   {
+     this.formatData = formatData;
+   }
+
+  /**
+   * This methods tests whether the specified object is equal to this
+   * object.  This will be true if and only if the specified object:
+   * <p>
+   * <ul>
+   * <li>Is not <code>null</code>.
+   * <li>Is an instance of <code>SimpleDateFormat</code>.
+   * <li>Is equal to this object at the superclass (i.e., <code>DateFormat</code>)
+   *     level.
+   * <li>Has the same formatting pattern.
+   * <li>Is using the same formatting symbols.
+   * <li>Is using the same century for two digit years.
+   * </ul>
+   *
+   * @param obj The object to compare for equality against.
+   *
+   * @return <code>true</code> if the specified object is equal to this object,
+   * <code>false</code> otherwise.
+   */
+  public boolean equals(Object o)
   {
-    numberFormat.setMinimumIntegerDigits(numDigits);
-    numberFormat.format(value, buf, null);
+    if (o == null)
+      return false;
+
+    if (!super.equals(o))
+      return false;
+
+    if (!(o instanceof SimpleDateFormat))
+      return false;
+
+    SimpleDateFormat sdf = (SimpleDateFormat)o;
+
+    if (!toPattern().equals(sdf.toPattern()))
+      return false;
+
+    if (!get2DigitYearStart().equals(sdf.get2DigitYearStart()))
+      return false;
+
+    if (!getDateFormatSymbols().equals(sdf.getDateFormatSymbols()))
+      return false;
+
+    return true;
   }
 
-  public StringBuffer format (Date date, StringBuffer buf, FieldPosition pos)
-  {
-    Calendar calendar = (Calendar) this.calendar.clone();
-    calendar.setTime(date);
-    int len = pattern.length();
-    int quoteStart = -1;
-    for (int i = 0;  i < len;  i++)
-      {
-	char ch = pattern.charAt(i);
-	if (ch == '\'')
-	  {
-	    // We must do a little lookahead to see if we have two
-	    // single quotes embedded in quoted text.
-	    if (i < len - 1 && pattern.charAt(i + 1) == '\'')
-	      {
-		++i;
-		buf.append(ch);
-	      }
-	    else
-	      quoteStart = quoteStart < 0 ? i : -1;
-	  }
-	// From JCL: any characters in the pattern that are not in
-	// the ranges of [a..z] and [A..Z] are treated as quoted
-	// text.
-	else if (quoteStart != -1
-	    || ((ch < 'a' || ch > 'z')
-		&& (ch < 'A' || ch > 'Z')))
-	  buf.append(ch);
-	else
+
+  /**
+   * Formats the date input according to the format string in use,
+   * appending to the specified StringBuffer.  The input StringBuffer
+   * is returned as output for convenience.
+   */
+  public StringBuffer format(Date date, StringBuffer buffer, FieldPosition pos) {
+    String temp;
+    Calendar theCalendar = (Calendar) calendar.clone();
+    theCalendar.setTime(date);
+    
+    // go through vector, filling in fields where applicable, else toString
+    Enumeration e = tokens.elements();
+    while (e.hasMoreElements()) {
+      Object o = e.nextElement();
+      if (o instanceof FieldSizePair) {
+	FieldSizePair p = (FieldSizePair) o;
+	int beginIndex = buffer.length();
+	switch (p.field) {
+	case ERA_FIELD:
+	  buffer.append(formatData.eras[theCalendar.get(Calendar.ERA)]);
+	  break;
+	case YEAR_FIELD:
+	  temp = String.valueOf(theCalendar.get(Calendar.YEAR));
+	  if (p.size < 4)
+	    buffer.append(temp.substring(temp.length()-2));
+	  else
+	    buffer.append(temp);
+	  break;
+	case MONTH_FIELD:
+	  if (p.size < 3)
+	    withLeadingZeros(theCalendar.get(Calendar.MONTH)+1,p.size,buffer);
+	  else if (p.size < 4)
+	    buffer.append(formatData.shortMonths[theCalendar.get(Calendar.MONTH)]);
+	  else
+	    buffer.append(formatData.months[theCalendar.get(Calendar.MONTH)]);
+	  break;
+	case DATE_FIELD:
+	  withLeadingZeros(theCalendar.get(Calendar.DATE),p.size,buffer);
+	  break;
+	case HOUR_OF_DAY1_FIELD:  // 1-12
+	  withLeadingZeros(theCalendar.get(Calendar.HOUR),p.size,buffer);
+	  break;
+	case HOUR_OF_DAY0_FIELD: // 0-23
+	  withLeadingZeros(theCalendar.get(Calendar.HOUR_OF_DAY),p.size,buffer);
+	  break;
+	case MINUTE_FIELD:
+	  withLeadingZeros(theCalendar.get(Calendar.MINUTE),p.size,buffer);
+	  break;
+	case SECOND_FIELD:
+	  withLeadingZeros(theCalendar.get(Calendar.SECOND),p.size,buffer);
+	  break;
+	case MILLISECOND_FIELD:
+	  withLeadingZeros(theCalendar.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)]);
+	  else
+	    buffer.append(formatData.weekdays[theCalendar.get(Calendar.DAY_OF_WEEK)]);
+	  break;
+	case DAY_OF_YEAR_FIELD:
+	  withLeadingZeros(theCalendar.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);
+	  break;
+	case WEEK_OF_YEAR_FIELD:
+	  withLeadingZeros(theCalendar.get(Calendar.WEEK_OF_YEAR),p.size,buffer);
+	  break;
+	case WEEK_OF_MONTH_FIELD:
+	  withLeadingZeros(theCalendar.get(Calendar.WEEK_OF_MONTH),p.size,buffer);
+	  break;
+	case AM_PM_FIELD:
+	  buffer.append(formatData.ampms[theCalendar.get(Calendar.AM_PM)]);
+	  break;
+	case HOUR1_FIELD: // 1-24
+	  withLeadingZeros(theCalendar.get(Calendar.HOUR_OF_DAY)+1,p.size,buffer);
+	  break;
+	case HOUR0_FIELD: // 0-11
+	  withLeadingZeros(theCalendar.get(Calendar.HOUR)-1,p.size,buffer);
+	  break;
+	case TIMEZONE_FIELD:
+	  // TODO
+	  break;
+	default:
+	  throw new IllegalArgumentException("Illegal pattern character");
+	}
+	if (pos != null && p.field == pos.getField())
 	  {
-	    int first = i;
-	    int value;
-	    while (++i < len && pattern.charAt(i) == ch) ;
-	    int count = i - first; // Number of repetions of ch in pattern.
-	    int beginIndex = buf.length();
-	    int field;
-	    i--;  // Skip all but last instance of ch in pattern.
-	    switch (ch)
-	      {
-	      case 'd':
-		append(buf, calendar.get(Calendar.DATE), count);
-		field = DateFormat.DATE_FIELD;
-		break;
-	      case 'D':
-		append(buf, calendar.get(Calendar.DAY_OF_YEAR), count);
-		field = DateFormat.DAY_OF_YEAR_FIELD;
-		break;
-	      case 'F':
-		append(buf, calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH),count);
-		field = DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD;
-		break;
-	      case 'E':
-		value = calendar.get(calendar.DAY_OF_WEEK);
-		buf.append(count <= 3 ? formatData.getShortWeekdays()[value]
-			   : formatData.getWeekdays()[value]);
-		field = DateFormat.DAY_OF_WEEK_FIELD;
-		break;
-	      case 'w':
-		append(buf, calendar.get(Calendar.WEEK_OF_YEAR), count); 
-		field = DateFormat.WEEK_OF_YEAR_FIELD;
-                break;
-	      case 'W':
-		append(buf, calendar.get(Calendar.WEEK_OF_MONTH), count); 
-		field = DateFormat.WEEK_OF_MONTH_FIELD;
-                break;
-	      case 'M':
-		value = calendar.get(Calendar.MONTH);
-		if (count <= 2)
-		  append(buf, value + 1, count);
-		else
-		  buf.append(count <= 3 ? formatData.getShortMonths()[value]
-			   : formatData.getMonths()[value]);
-		field = DateFormat.MONTH_FIELD;
-		break;
-	      case 'y':
-		value = calendar.get(Calendar.YEAR);
-		append(buf, count <= 2 ? value % 100 : value, count);
-		field = DateFormat.YEAR_FIELD;
-		break;
-	      case 'K':
-		append(buf, calendar.get(Calendar.HOUR), count);
-		field = DateFormat.HOUR0_FIELD;
-		break;
-	      case 'h':
-		value = ((calendar.get(Calendar.HOUR) + 11) % 12) + 1;
-		append(buf, value, count);
-		field = DateFormat.HOUR1_FIELD;
-		break;
-	      case 'H':
-		append(buf, calendar.get(Calendar.HOUR_OF_DAY), count);
-		field = DateFormat.HOUR_OF_DAY0_FIELD;
-		break;
-	      case 'k':
-		value = ((calendar.get(Calendar.HOUR_OF_DAY) + 23) % 24) + 1;
-		append(buf, value, count);
-		field = DateFormat.HOUR_OF_DAY1_FIELD;
-		break;
-	      case 'm':
-		append(buf, calendar.get(Calendar.MINUTE), count);
-		field = DateFormat.MINUTE_FIELD;
-		break;
-	      case 's':
-		append(buf, calendar.get(Calendar.SECOND), count);
-		field = DateFormat.SECOND_FIELD;
-		break;
-	      case 'S':
-		append(buf, calendar.get(Calendar.MILLISECOND), count);
-		field = DateFormat.MILLISECOND_FIELD;
-		break;
-	      case 'a':
-		value = calendar.get(calendar.AM_PM);
-		buf.append(formatData.getAmPmStrings()[value]);
-		field = DateFormat.AM_PM_FIELD;
-		break;
-	      case 'z':
-		String zoneID = calendar.getTimeZone().getID();
-		String[][] zoneStrings = formatData.getZoneStrings();
-		int zoneCount = zoneStrings.length;
-		for (int j = 0;  j < zoneCount;  j++)
-		  {
-		    String[] strings = zoneStrings[j];
-		    if (zoneID.equals(strings[0]))
-		      {
-			j = count > 3 ? 2 : 1;
-			if (calendar.get(Calendar.DST_OFFSET) != 0)
-			  j+=2;
-			zoneID = strings[j];
-			break;
-		      }
-		  }
-		buf.append(zoneID);
-		field = DateFormat.TIMEZONE_FIELD;
-		break;
-	      default:
-		// Note that the JCL is actually somewhat
-		// contradictory here.  It defines the pattern letters
-		// to be a particular list, but also says that a
-		// pattern containing an invalid pattern letter must
-		// throw an exception.  It doesn't describe what an
-		// invalid pattern letter might be, so we just assume
-		// it is any letter in [a-zA-Z] not explicitly covered
-		// above.
-		throw new RuntimeException("bad format string");
-	      }
-	    if (pos != null && field == pos.getField())
-	      {
-		pos.setBeginIndex(beginIndex);
-		pos.setEndIndex(buf.length());
-	      }
+	    pos.setBeginIndex(beginIndex);
+	    pos.setEndIndex(buffer.length());
 	  }
+      } else {
+	buffer.append(o.toString());
       }
-    return buf;
+    }
+    return buffer;
   }
 
-  private final boolean expect (String source, ParsePosition pos,
-				char ch)
-  {
-    int x = pos.getIndex();
-    boolean r = x < source.length() && source.charAt(x) == ch;
-    if (r)
-      pos.setIndex(x + 1);
-    else
-      pos.setErrorIndex(x);
-    return r;
-  }
+  private void withLeadingZeros(int value, int length, StringBuffer buffer) {
+    String valStr = String.valueOf(value);
+    for (length -= valStr.length(); length > 0; length--)
+      buffer.append('0');
+    buffer.append(valStr);
+  }
+
+  private int indexInArray(String dateStr, int index, String[] values) {
+    int l1 = dateStr.length()-index;
+    int l2;
+
+    for (int i=0; i < values.length; i++) {
+      if (values[i] == null)
+        continue;
+
+      l2 = values[i].length();
+      //System.err.println(values[i] + " " + dateStr.substring(index,index+l2));
+      if ((l1 >= l2) && (dateStr.substring(index,index+l2).equals(values[i])))
+	return i;
+    }
+    return -1;
+  }
+
+  /*
+   * Get the actual year value, converting two digit years if necessary.
+   */
+  private int processYear(int val)
+  {
+    if (val > 100)
+      return val;
+
+    Date d = get2DigitYearStart();
+    Calendar c = Calendar.getInstance();
+    c.setTime(d);
+    int y = c.get(YEAR_FIELD);
+
+    return ((y / 100) * 100) + val;
+  }
+
+  /*
+   * Ok, we ignore the format string and just try to parse what we can
+   * out of the string.  We need, month, day, year at a minimum. The real
+   * killer is stuff like XX/XX/XX.  How do we interpret that?  Is is the
+   * US style MM/DD/YY or the European style DD/MM/YY. Or is it YYYY/MM/DD?
+   * I'm an American, so I guess you know which one I'm choosing....
+   */
+  private Date parseLenient(String dateStr, ParsePosition pos)
+  {
+    int month = -1;
+    int day = -1;
+    int year = -1;
+    int era = -1;
+    int hour = -1;
+    int hour24 = -1;
+    int minute = -1;
+    int second = -1;
+    int millis = -1;
+    int ampm = -1;
+    int last = -1;
+    TimeZone tz = null;
+    char lastsep = ' ';
+    char nextchar = ' ';
+
+    Calendar cal = (Calendar)calendar.clone();
+    cal.clear();
+    cal.setTime(new Date(0));
 
-  public Date parse (String source, ParsePosition pos)
-  {
-    int fmt_index = 0;
-    int fmt_max = pattern.length();
+    int index = pos.getIndex();
+    String buf = dateStr.substring(index, dateStr.length());
 
-    calendar.clear();
-    int quote_start = -1;
-    for (; fmt_index < fmt_max; ++fmt_index)
+    top:
+    for(;;)
       {
-	char ch = pattern.charAt(fmt_index);
-	if (ch == '\'')
-	  {
-	    int index = pos.getIndex();
-	    if (fmt_index < fmt_max - 1
-		&& pattern.charAt(fmt_index + 1) == '\'')
-	      {
-		if (! expect (source, pos, ch))
-		  return null;
-		++fmt_index;
-	      }
-	    else
-	      quote_start = quote_start < 0 ? fmt_index : -1;
-	    continue;
-	  }
 
-	if (quote_start != -1
-	    || ((ch < 'a' || ch > 'z')
-		&& (ch < 'A' || ch > 'Z')))
-	  {
-	    if (! expect (source, pos, ch))
-	      return null;
-	    continue;
-	  }
+        // Are we at the end of the string?  If so, make sure we have
+        // enough data and return. // FIXME: Also detect sufficient data
+        // and return by setting buf to "" on an unparsible string.
+        if (buf.equals(""))
+          {
+            pos.setIndex(index);
+
+            // This is the minimum we need
+            if ((month == -1) || (day == -1) || (year == -1))
+              {
+                pos.setErrorIndex(index);
+                return null;
+              }
+
+             if (tz != null)
+               cal.setTimeZone(tz);
+
+             cal.set(Calendar.YEAR, year);
+             cal.set(Calendar.MONTH, month - 1);
+             cal.set(Calendar.DATE, day);           
+
+             if (ampm == 0)
+               cal.set(Calendar.AM_PM, Calendar.AM);
+             else if (ampm == 1)
+               cal.set(Calendar.AM_PM, Calendar.PM);
+
+             // If am/pm not set, we assume 24 hour day
+             if (hour != -1)
+               {
+                 if (ampm == -1)
+                   cal.set(Calendar.HOUR_OF_DAY, hour);
+                 else
+                   {
+                     if (ampm == 0)
+                       {
+                         if (hour == 12)
+                           hour = 0;
+                       }
+                     else
+                       {
+                         if (hour != 12)
+                           hour += 12;
+                       }
+    
+                     cal.set(Calendar.HOUR_OF_DAY, hour);
+                   }
+               }
+
+             if (minute != -1)
+               cal.set(Calendar.MINUTE, minute);
+
+             if (second != -1)
+               cal.set(Calendar.SECOND, second);
+
+             if (millis != -1)
+               cal.set(Calendar.MILLISECOND, millis);
+
+             if (era == 0)
+               cal.set(Calendar.ERA, GregorianCalendar.BC);
+             else if (era == 1)
+               cal.set(Calendar.ERA, GregorianCalendar.AD);
+
+             return cal.getTime();
+          }
+
+        // Skip over whitespace and expected punctuation
+        char c = buf.charAt(0);
+        boolean comma_found = false;
+        while(Character.isWhitespace(c) || (c == ':') || 
+              (c == ',') || (c == '.') || (c == '/'))
+          {
+            lastsep = c;
+            if (c == ',') // This is a total and utter crock
+              comma_found = true;
+            buf = buf.substring(1);
+            if (buf.equals(""))
+              continue;
+            c = buf.charAt(0);
+          }
+
+        if (comma_found == true)
+          lastsep = ',';
+
+        // Is it a month name?
+        for (int i = 0; i < formatData.months.length; i++)
+          if ((formatData.months[i] != null) 
+              && buf.startsWith(formatData.months[i]))
+            {
+              month = i + 1;
+              buf = buf.substring(formatData.months[i].length());
+              index += formatData.months[i].length();
+              last = MONTH_FIELD;
+              continue top;
+            }
+
+        // Is it a short month name?
+        for (int i = 0; i < formatData.shortMonths.length; i++)
+          if ((formatData.shortMonths[i] != null) 
+              && buf.startsWith(formatData.shortMonths[i]))
+            {
+              month = i + 1;
+              buf = buf.substring(formatData.shortMonths[i].length());
+              index += formatData.shortMonths[i].length();
+              last = MONTH_FIELD;
+              continue top;
+            }
+
+        // Is it a weekday name?
+        for (int i = 0; i < formatData.weekdays.length; i++)
+          if ((formatData.weekdays[i] != null)
+              && buf.startsWith(formatData.weekdays[i]))
+            {
+              buf = buf.substring(formatData.weekdays[i].length());
+              index += formatData.weekdays[i].length();
+              last = DAY_OF_WEEK_FIELD;
+              continue top;
+            }
+
+        // Is it a short weekday name?
+        for (int i = 0; i < formatData.shortWeekdays.length; i++)
+          if ((formatData.shortWeekdays[i] != null)
+              && buf.startsWith(formatData.shortWeekdays[i]))
+            {
+              buf = buf.substring(formatData.shortWeekdays[i].length());
+              index += formatData.shortWeekdays[i].length();
+              last = DAY_OF_WEEK_FIELD;
+              continue top;
+            }
+
+        // Is this an am/pm string?
+        for (int i = 0; i < formatData.ampms.length; i++) {
+          if ((formatData.ampms[i] != null)
+              && buf.toLowerCase().startsWith(formatData.ampms[i].toLowerCase()))
+            {
+              ampm = i;
+              buf = buf.substring(formatData.ampms[i].length());
+              index += formatData.ampms[i].length();
+              last = AM_PM_FIELD;
+              continue top;
+            }
+        }
+
+        // See if we have a number
+        c = buf.charAt(0);
+        String nbrstr = "";
+        while (Character.isDigit(c))
+          {
+            nbrstr = nbrstr + c;
+            buf = buf.substring(1);
+            if (buf.equals(""))
+              break;
+            c = buf.charAt(0);
+          }
+
+        // If we didn't get a number, try for a timezone, otherwise set buf
+        // to "" and loop to see if we are done.
+        if (nbrstr.equals(""))
+          {
+            // Ok, try for a timezone name
+            while(!Character.isWhitespace(c) && (c != ',') && (c != '.') &&
+                  (c != ':') && (c != '/'))
+              {
+                nbrstr = nbrstr + c;
+                buf = buf.substring(1);
+                if (buf.equals(""))
+                  break;
+                c = buf.charAt(0);
+              }
+            TimeZone tmptz = TimeZone.getTimeZone(nbrstr);
+             
+            // We get GMT on failure, so be sure we asked for it.
+            if (tmptz.getID().equals("GMT"))
+              {
+                if (!nbrstr.equals("GMT"))
+                  {
+                    buf = "";
+                    continue top;
+                  }
+              }
+
+            tz = tmptz;
+            last = TIMEZONE_FIELD;
+            index += nbrstr.length();
+            continue top;
+          }
+
+        // Convert to integer
+        int val = 0;
+        try
+          {
+            val = Integer.parseInt(nbrstr);
+          }
+        catch(Exception e)
+          {
+            return null; // Shouldn't happen
+          }
+
+        if (!buf.equals(""))
+          nextchar = buf.charAt(0);
+        else
+          nextchar = ' ';
+
+        // Figure out which value to assign to
+        // I make bad US assumptions about MM/DD/YYYY
+        if (last == DAY_OF_WEEK_FIELD)
+          {
+            day = val;
+            last = DATE_FIELD;
+          }
+        else if ((last == MONTH_FIELD) && (day != -1))
+          {
+            year = processYear(val);
+            last = YEAR_FIELD;
+          }
+        else if (last == MONTH_FIELD)
+          {
+            day = val;
+            last = DATE_FIELD;
+          }
+        else if (last == -1)
+          {
+            // Assume month
+            if ((val < 13) && (val > 0))
+              {
+                month = val;
+                last = MONTH_FIELD;
+              }
+            // Assume year. This only works for two digit years that aren't
+            // between 01 and 12
+            else
+              {
+                year = processYear(val);
+                last = YEAR_FIELD;
+              } 
+          }
+        else if ((last == YEAR_FIELD) && ((nextchar == '/') ||
+                 (nextchar == '.')))
+          {
+            month = val;
+            last = MONTH_FIELD;
+          }
+        else if (last == YEAR_FIELD)
+          {
+            hour = val;
+            last = HOUR0_FIELD;
+          }
+        else if ((last == DATE_FIELD) && ((nextchar == '/') ||
+                 (nextchar == '.') || buf.equals("")))
+          {
+            year = processYear(val);
+            last = YEAR_FIELD;
+          }
+        else if ((last == DATE_FIELD) && ((lastsep == '/') ||
+                 (lastsep == '.') || (lastsep == ',')))
+          {
+            year = processYear(val);
+            last = YEAR_FIELD;
+          }
+        else if (last == DATE_FIELD)
+          {
+            hour = val;
+            last = HOUR0_FIELD;
+          }
+        else if (last == HOUR0_FIELD)
+          {
+            minute = val;
+            last = MINUTE_FIELD;
+          }
+        else if (last == MINUTE_FIELD)
+          {
+            second = val;
+            last = SECOND_FIELD;
+          }
+        else if (lastsep == '.') 
+          {
+            ; // This is milliseconds or something.  Ignore it
+            last = WEEK_OF_YEAR_FIELD; // Just a random value
+          }
+        else // It is year. I have spoken!
+          {
+            year = processYear(val);
+            last = YEAR_FIELD;
+          }
+      }
+  }
 
-	// We've arrived at a potential pattern character in the
-	// pattern.
-	int first = fmt_index;
-	while (++fmt_index < fmt_max && pattern.charAt(fmt_index) == ch)
-	  ;
-	int count = fmt_index - first;
-	--fmt_index;
-
-	// We can handle most fields automatically: most either are
-	// numeric or are looked up in a string vector.  In some cases
-	// we need an offset.  When numeric, `offset' is added to the
-	// resulting value.  When doing a string lookup, offset is the
-	// initial index into the string array.
-	int calendar_field;
-	boolean is_numeric = true;
-	String[] match = null;
-	int offset = 0;
-	int zone_number = 0;
-	switch (ch)
-	  {
-	  case 'd':
-	    calendar_field = Calendar.DATE;
-	    break;
-	  case 'D':
-	    calendar_field = Calendar.DAY_OF_YEAR;
-	    break;
-	  case 'F':
-	    calendar_field = Calendar.DAY_OF_WEEK_IN_MONTH;
-	    break;
-	  case 'E':
-	    is_numeric = false;
-	    offset = 1;
-	    calendar_field = Calendar.DAY_OF_WEEK;
-	    match = (count <= 3
-		     ? formatData.getShortWeekdays()
-		     : formatData.getWeekdays());
-	    break;
-	  case 'w':
-	    calendar_field = Calendar.WEEK_OF_YEAR;
-	    break;
-	  case 'W':
-	    calendar_field = Calendar.WEEK_OF_MONTH;
-	    break;
-	  case 'M':
-	    calendar_field = Calendar.MONTH;
-	    if (count <= 2)
-	      ;
-	    else
-	      {
-		is_numeric = false;
-		match = (count <= 3
-			 ? formatData.getShortMonths()
-			 : formatData.getMonths());
-	      }
-	    break;
-	  case 'y':
-	    calendar_field = Calendar.YEAR;
-	    if (count <= 2)
-	      offset = 1900;
-	    break;
-	  case 'K':
-	    calendar_field = Calendar.HOUR;
-	    break;
-	  case 'h':
-	    calendar_field = Calendar.HOUR;
-	    offset = -1;
-	    break;
-	  case 'H':
-	    calendar_field = Calendar.HOUR_OF_DAY;
-	    break;
-	  case 'k':
-	    calendar_field = Calendar.HOUR_OF_DAY;
-	    offset = -1;
-	    break;
-	  case 'm':
-	    calendar_field = Calendar.MINUTE;
-	    break;
-	  case 's':
-	    calendar_field = Calendar.SECOND;
-	    break;
-	  case 'S':
-	    calendar_field = Calendar.MILLISECOND;
-	    break;
-	  case 'a':
-	    is_numeric = false;
-	    calendar_field = Calendar.AM_PM;
-	    match = formatData.getAmPmStrings();
-	    break;
-	  case 'z':
-	    // We need a special case for the timezone, because it
-	    // uses a different data structure than the other cases.
-	    is_numeric = false;
-	    calendar_field = Calendar.DST_OFFSET;
-	    String[][] zoneStrings = formatData.getZoneStrings();
-	    int zoneCount = zoneStrings.length;
-	    int index = pos.getIndex();
-	    boolean found_zone = false;
-	    for (int j = 0;  j < zoneCount;  j++)
-	      {
-		String[] strings = zoneStrings[j];
-		int k;
-		for (k = 1; k < strings.length; ++k)
-		  {
-		    if (source.startsWith(strings[k], index))
-		      break;
-		  }
-		if (k != strings.length)
-		  {
-		    if (k > 2)
-		      ;		// FIXME: dst.
-		    zone_number = 0; // FIXME: dst.
-		    // FIXME: raw offset to SimpleTimeZone const.
-		    calendar.setTimeZone(new SimpleTimeZone (1, strings[0]));
-		    pos.setIndex(index + strings[k].length());
-		    break;
-		  }
-	      }
-	    if (! found_zone)
-	      {
-		pos.setErrorIndex(pos.getIndex());
-		return null;
-	      }
-	    break;
-	  default:
+  private int parseLeadingZeros(String dateStr, ParsePosition pos,
+                                FieldSizePair p)
+  {
+    int value;
+    int index = pos.getIndex();
+    String buf = null;
+
+    if (p.size == 1)
+      {
+        char c = dateStr.charAt(index+1);
+        if ((dateStr.charAt(index) == '1') && 
+             Character.isDigit(dateStr.charAt(index+1)))
+          buf = dateStr.substring(index, index+2);
+        else
+          buf = dateStr.substring(index, index+1);
+        pos.setIndex(index + buf.length());
+      }
+    else if (p.size == 2)
+      {
+        buf = dateStr.substring(index, index+2);
+        pos.setIndex(index+2);
+      }
+    else if (p.size == 3)
+      {
+        buf = dateStr.substring(index, index+3);
+        pos.setIndex(index+3);
+      }
+    else
+      {
+        buf = dateStr.substring(index, index+4);
+        pos.setIndex(index+4);
+      }
+    try
+      {
+        value = Integer.parseInt(buf);
+      }
+    catch(NumberFormatException nfe)
+      {
+        pos.setIndex(index);
+        pos.setErrorIndex(index);
+        return -1;
+      } 
+
+    return value;
+  }
+
+  /*
+   * Note that this method doesn't properly protect against
+   * StringIndexOutOfBoundsException.  FIXME
+   */
+  private Date parseStrict(String dateStr, ParsePosition pos)
+  {
+    // start looking at position pos.index
+    Enumeration e = tokens.elements();
+    Calendar theCalendar = (Calendar) calendar.clone();
+    theCalendar.clear();
+    theCalendar.setTime(new Date(0));
+
+    int value, index, hour = -1;
+    String buf;
+    while (pos.getIndex() < dateStr.length()) {
+      Object o = e.nextElement();
+      if (o instanceof FieldSizePair) {
+	FieldSizePair p = (FieldSizePair) o;
+	switch (p.field) {
+
+	case ERA_FIELD:
+	  value = indexInArray(dateStr,pos.getIndex(),formatData.eras);
+	  if (value == -1) {
 	    pos.setErrorIndex(pos.getIndex());
 	    return null;
 	  }
-
-	// Compute the value we should assign to the field.
-	int value;
-	if (is_numeric)
-	  {
-	    numberFormat.setMinimumIntegerDigits(count);
-	    Number n = numberFormat.parse(source, pos);
-	    if (pos == null || ! (n instanceof Long))
-	      return null;
-	    value = n.intValue() + offset;
-	  }
-	else if (match != null)
-	  {
-	    int index = pos.getIndex();
-	    int i;
-	    for (i = offset; i < match.length; ++i)
-	      {
-		if (source.startsWith(match[i], index))
-		  break;
-	      }
-	    if (i == match.length)
-	      {
-		pos.setErrorIndex(index);
-		return null;
-	      }
-	    pos.setIndex(index + match[i].length());
-	    value = i;
-	  }
-	else
-	  value = zone_number;
-
-	// Assign the value and move on.
-	try
-	  {
-	    calendar.set(calendar_field, value);
+	  pos.setIndex(pos.getIndex() + formatData.eras[value].length());
+	  theCalendar.set(Calendar.ERA,value);
+	  break;
+
+	case YEAR_FIELD:
+          String y;
+	  if (p.size < 4)
+            y = dateStr.substring(pos.getIndex(), pos.getIndex() + 2);
+          else
+            y = dateStr.substring(pos.getIndex(), pos.getIndex() + 4);
+            
+          int year;
+          try
+            {
+              year = Integer.parseInt(y);
+            }
+          catch(NumberFormatException nfe)
+            {
+              pos.setErrorIndex(pos.getIndex());
+              return null;
+            }
+
+	  if (p.size < 4)
+            year += get2DigitYearStart().getYear();
+
+          theCalendar.set(Calendar.YEAR, year);
+	  if (p.size < 4)
+            pos.setIndex(pos.getIndex()+2);
+          else
+            pos.setIndex(pos.getIndex()+4);
+	  break;
+
+	case MONTH_FIELD:
+          if (p.size > 2)
+            {
+              index = pos.getIndex();
+
+	      value = indexInArray(dateStr,pos.getIndex(),
+                 (p.size == 3) ? formatData.shortMonths : formatData.months);
+	      if (value == -1) 
+                {
+	          pos.setErrorIndex(pos.getIndex());
+	          return null;
+	        }
+              if (p.size == 3)
+                pos.setIndex(index + formatData.shortMonths[value].length());
+              else
+                pos.setIndex(index + formatData.months[value].length());
+              theCalendar.set(Calendar.MONTH, value);
+              break;
+            }
+
+          value = parseLeadingZeros(dateStr, pos, p);
+          if (value == -1)
+            return null;
+
+          theCalendar.set(Calendar.MONTH, value);
+          break;
+
+	case DATE_FIELD:
+          value = parseLeadingZeros(dateStr, pos, p);
+          if (value == -1)
+            return null;
+
+          theCalendar.set(Calendar.DATE, value);
+	  break;
+
+	case HOUR_OF_DAY1_FIELD:
+	case HOUR_OF_DAY0_FIELD:
+          index = pos.getIndex();
+          buf = dateStr.substring(index, index+2);
+          try
+            {
+              value = Integer.parseInt(buf);
+            }
+          catch(NumberFormatException nfe)
+            {
+              return null;
+            }
+          if (p.field == HOUR_OF_DAY0_FIELD)
+           // theCalendar.set(Calendar.HOUR_OF_DAY, value);
+            hour = value + 1;
+          else
+           // theCalendar.set(Calendar.HOUR_OF_DAY, value-1);
+            hour = value;
+          pos.setIndex(index+2);
+
+	  break;
+
+	case MINUTE_FIELD:
+          value = parseLeadingZeros(dateStr, pos, p);
+          if (value == -1)
+            return null;
+
+          theCalendar.set(Calendar.MINUTE, value);
+	  break;
+
+	case SECOND_FIELD:
+          value = parseLeadingZeros(dateStr, pos, p);
+          if (value == -1)
+            return null;
+
+          theCalendar.set(Calendar.SECOND, value);
+	  break;
+
+	case MILLISECOND_FIELD:
+          value = parseLeadingZeros(dateStr, pos, p);
+          if (value == -1)
+            return null;
+         
+          theCalendar.set(Calendar.MILLISECOND, value);
+	  break;
+
+	case DAY_OF_WEEK_FIELD:
+	  value = indexInArray(dateStr,pos.getIndex(),(p.size < 4) ? formatData.shortWeekdays : formatData.weekdays);
+	  if (value == -1) {
+	    pos.setErrorIndex(pos.getIndex());
+	    return null;
 	  }
-	// FIXME: what exception is thrown on an invalid
-	// non-lenient set?
-	catch (IllegalArgumentException x)
-	  {
+	  pos.setIndex(pos.getIndex() + ((p.size < 4) ? formatData.shortWeekdays[value].length()
+	    : formatData.weekdays[value].length()));
+	  // Note: Calendar.set(Calendar.DAY_OF_WEEK,value) does not work
+	  // as implemented in jdk1.1.5 (possibly DAY_OF_WEEK is meant to
+	  // be read-only). Instead, calculate number of days offset.
+	  theCalendar.add(Calendar.DATE,value 
+			  - theCalendar.get(Calendar.DAY_OF_WEEK));
+	  // in JDK, this seems to clear the hours, so we'll do the same.
+	  theCalendar.set(Calendar.HOUR_OF_DAY,0);
+	  break;
+
+	case DAY_OF_YEAR_FIELD:
+          value = parseLeadingZeros(dateStr, pos, p);
+          if (value == -1)
+            return null;
+         
+	  theCalendar.set(Calendar.DAY_OF_YEAR, value);
+	  break;
+
+        // Just parse and ignore
+	case DAY_OF_WEEK_IN_MONTH_FIELD:
+          value = parseLeadingZeros(dateStr, pos, p);
+          if (value == -1)
+            return null;
+         
+	  break;
+
+        // Just parse and ignore
+	case WEEK_OF_YEAR_FIELD:
+          value = parseLeadingZeros(dateStr, pos, p);
+          if (value == -1)
+            return null;
+         
+	  break;
+
+        // Just parse and ignore
+	case WEEK_OF_MONTH_FIELD:
+          value = parseLeadingZeros(dateStr, pos, p);
+          if (value == -1)
+            return null;
+
+	  break;
+
+	case AM_PM_FIELD:
+	  value = indexInArray(dateStr,pos.getIndex(),formatData.ampms);
+	  if (value == -1) {
 	    pos.setErrorIndex(pos.getIndex());
 	    return null;
 	  }
+	  pos.setIndex(pos.getIndex() + formatData.ampms[value].length());
+	  theCalendar.set(Calendar.AM_PM,value);
+	  break;
+
+	case HOUR1_FIELD:
+	case HOUR0_FIELD:
+          value = parseLeadingZeros(dateStr, pos, p);
+          if (value == -1)
+            return null;
+          if (p.field == HOUR1_FIELD)
+            theCalendar.set(Calendar.HOUR, value);
+          if (p.field == HOUR0_FIELD)
+            theCalendar.set(Calendar.HOUR, value+1);
+	  break;
+
+	  /*
+	case TIMEZONE_FIELD:
+	  // TODO
+	  break;
+	  */
+
+	default:
+	  throw new IllegalArgumentException("Illegal pattern character: " +
+             p.field);
+	} // end switch
+      } else if (o instanceof String) {
+	String ostr = (String) o;
+	if (dateStr.substring(pos.getIndex(),pos.getIndex()+ostr.length()).equals(ostr)) {
+	  pos.setIndex(pos.getIndex() + ostr.length());
+	} else {
+	  pos.setErrorIndex(pos.getIndex());
+	  return null;
+	}
+      } else if (o instanceof Character) {
+	Character ochar = (Character) o;
+	if (dateStr.charAt(pos.getIndex()) == ochar.charValue()) {
+	  pos.setIndex(pos.getIndex() + 1);
+	} else {
+	  pos.setErrorIndex(pos.getIndex());
+	  return null;
+	}
       }
+    }
 
-    return calendar.getTime();
-  }
+    if (hour != -1)
+      {
+        if (theCalendar.get(Calendar.AM_PM) == Calendar.PM)
+          {
+            if (hour == 12)
+              theCalendar.set(Calendar.HOUR_OF_DAY, 12);
+            else
+              theCalendar.set(Calendar.HOUR_OF_DAY, hour + 12);
+          }
+        else
+          {
+            if (hour == 12)
+              theCalendar.set(Calendar.HOUR_OF_DAY, 0);
+            else
+              theCalendar.set(Calendar.HOUR_OF_DAY, hour);
+          }
+      }
 
-  public boolean equals (Object obj)
-  {
-    if (! (obj instanceof SimpleDateFormat) || ! super.equals(obj) )
-      return false;
-    SimpleDateFormat other = (SimpleDateFormat) obj;
-    return (DateFormatSymbols.equals(pattern, other.pattern)
-	    && DateFormatSymbols.equals(formatData, other.formatData)
-	    && DateFormatSymbols.equals(defaultCenturyStart,
-					other.defaultCenturyStart));
+    return theCalendar.getTime();
   }
 
-  public Object clone ()
-  {
-    // We know the superclass just call's Object's generic cloner.
-    return super.clone ();
-  }
+  /**
+   * This method parses the specified string into a date.
+   * 
+   * @param dateStr The date string to parse.
+   * @param pos The input and output parse position
+   *
+   * @return The parsed date, or <code>null</code> if the string cannot be
+   * parsed.
+   */
+  public Date parse(String dateStr, ParsePosition pos) {
+    if (isLenient())
+       return parseLenient(dateStr, pos);
+    else
+       return parseStrict(dateStr, pos);
 
-  public int hashCode ()
-  {
-    int hash = super.hashCode();
-    if (pattern != null)
-      hash ^= pattern.hashCode();
-    return hash;
   }
 }
+
Index: java/util/TimeZone.java
===================================================================
RCS file: /cvs/java/libgcj/libjava/java/util/TimeZone.java,v
retrieving revision 1.6
diff -u -p -r1.6 TimeZone.java
--- TimeZone.java	2000/09/08 19:37:09	1.6
+++ TimeZone.java	2000/11/28 03:01:47
@@ -1,189 +1,1101 @@
-/* Copyright (C) 1998, 1999, 2000  Free Software Foundation
+/* java.util.TimeZone
+   Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc.
 
-   This file is part of libgcj.
+This file is part of GNU Classpath.
 
-This software is copyrighted work licensed under the terms of the
-Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
-details.  */
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+02111-1307 USA.
+
+As a special exception, if you link this library with other files to
+produce an executable, this library does not by itself cause the
+resulting executable to be covered by the GNU General Public License.
+This exception does not however invalidate any other reasons why the
+executable file might be covered by the GNU General Public License. */
 
+
 package java.util;
+import java.text.DateFormatSymbols;
 
 /**
- * @author Per Bothner <bothner@cygnus.com>
- * @date October 24, 1998.
- */
-
-/* Written using "Java Class Libraries", 2nd edition, ISBN 0-201-31002-3.
- * Status:  getAvailableIDs, getDefault, getTimeZone only know about GMT.
+ * This class represents a time zone offset and handles daylight savings.
+ * 
+ * You can get the default time zone with <code>getDefault</code>.
+ * This represents the time zone where program is running.
+ *
+ * Another way to create a time zone is <code>getTimeZone</code>, where
+ * you can give an identifier as parameter.  For instance, the identifier
+ * of the Central European Time zone is "CET".
+ *
+ * With the <code>getAvailableIDs</code> method, you can get all the
+ * supported time zone identifiers.
+ *
+ * @see Calendar
+ * @see SimpleTimeZone
+ * @author Jochen Hoenicke
  */
-
 public abstract class TimeZone implements java.io.Serializable, Cloneable
 {
-  public static final int SHORT = 0;
-  public static final int LONG = 1;
 
-  // The fields are as specified in Sun's "Serialized Form"
-  // in the JDK 1.2 beta 4 API specification.
-  String ID;
+  /**
+   * Constant used to indicate that a short timezone abbreviation should
+   * be returned, such as "EST"
+   */
+  public static final int SHORT = 0;
 
-  static final TimeZone zoneGMT = new SimpleTimeZone(0, "GMT");
+  /**
+   * Constant used to indicate that a long timezone name should be
+   * returned, such as "Eastern Standard Time".
+   */
+  public static final int LONG = 1;
 
-  private static TimeZone zoneDefault;
+  /**
+   * The time zone identifier, e.g. PST.
+   */
+  private String ID;
+
+  /**
+   * The default time zone, as returned by getDefault.
+   */
+  private static TimeZone defaultZone;
 
   private static final long serialVersionUID = 3581463369166924961L;
 
-  public TimeZone ()
+  /**
+   * Hashtable for timezones by ID.  
+   */
+  private static final Hashtable timezones = new Hashtable();
+
+  static
   {
+    TimeZone tz;
+    // Automatically generated by scripts/timezones.pl
+    // XXX - Should we read this data from a file?
+    tz = new SimpleTimeZone(-11000 * 3600, "Pacific/Niue");
+    timezones.put("Pacific/Niue", tz);
+    timezones.put("Pacific/Apia", tz);
+    timezones.put("Pacific/Midway", tz);
+    timezones.put("Pacific/Pago_Pago", tz);
+    tz = new SimpleTimeZone
+      (-10000 * 3600, "America/Adak",
+       Calendar.APRIL, 1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("America/Adak", tz);
+    tz = new SimpleTimeZone(-10000 * 3600, "HST");
+    timezones.put("HST", tz);
+    timezones.put("Pacific/Fakaofo", tz);
+    timezones.put("Pacific/Honolulu", tz);
+    timezones.put("Pacific/Johnston", tz);
+    timezones.put("Pacific/Rarotonga", tz);
+    timezones.put("Pacific/Tahiti", tz);
+    tz = new SimpleTimeZone
+      (-9000 * 3600, "America/Juneau",
+       Calendar.APRIL, 1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("America/Juneau", tz);
+    timezones.put("America/Anchorage", tz);
+    timezones.put("America/Nome", tz);
+    timezones.put("America/Yakutat", tz);
+    tz = new SimpleTimeZone(-9000 * 3600, "Pacific/Gambier");
+    timezones.put("Pacific/Gambier", tz);
+    tz = new SimpleTimeZone(-8500 * 3600, "Pacific/Marquesas");
+    timezones.put("Pacific/Marquesas", tz);
+    tz = new SimpleTimeZone
+      (-8000 * 3600, "PST8PDT",
+       Calendar.APRIL, 1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("PST8PDT", tz);
+    timezones.put("America/Dawson", tz);
+    timezones.put("America/Los_Angeles", tz);
+    timezones.put("America/Tijuana", tz);
+    timezones.put("America/Vancouver", tz);
+    timezones.put("America/Whitehorse", tz);
+    timezones.put("US/Pacific-New", tz);
+    tz = new SimpleTimeZone(-8000 * 3600, "Pacific/Pitcairn");
+    timezones.put("Pacific/Pitcairn", tz);
+    tz = new SimpleTimeZone(-7000 * 3600, "MST");
+    timezones.put("MST", tz);
+    timezones.put("America/Dawson_Creek", tz);
+    timezones.put("America/Phoenix", tz);
+    tz = new SimpleTimeZone
+      (-7000 * 3600, "MST7MDT",
+       Calendar.APRIL, 1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("MST7MDT", tz);
+    timezones.put("America/Boise", tz);
+    timezones.put("America/Chihuahua", tz);
+    timezones.put("America/Denver", tz);
+    timezones.put("America/Edmonton", tz);
+    timezones.put("America/Inuvik", tz);
+    timezones.put("America/Mazatlan", tz);
+    timezones.put("America/Shiprock", tz);
+    timezones.put("America/Yellowknife", tz);
+    tz = new SimpleTimeZone(-6000 * 3600, "America/Regina");
+    timezones.put("America/Regina", tz);
+    timezones.put("America/Belize", tz);
+    timezones.put("America/Costa_Rica", tz);
+    timezones.put("America/El_Salvador", tz);
+    timezones.put("America/Guatemala", tz);
+    timezones.put("America/Managua", tz);
+    timezones.put("America/Swift_Current", tz);
+    timezones.put("America/Tegucigalpa", tz);
+    timezones.put("Pacific/Galapagos", tz);
+    tz = new SimpleTimeZone
+      (-6000 * 3600, "CST6CDT",
+       Calendar.APRIL, 1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("CST6CDT", tz);
+    timezones.put("America/Cambridge_Bay", tz);
+    timezones.put("America/Cancun", tz);
+    timezones.put("America/Chicago", tz);
+    timezones.put("America/Iqaluit", tz);
+    timezones.put("America/Menominee", tz);
+    timezones.put("America/Mexico_City", tz);
+    timezones.put("America/Pangnirtung", tz);
+    timezones.put("America/Rainy_River", tz);
+    timezones.put("America/Rankin_Inlet", tz);
+    timezones.put("America/Winnipeg", tz);
+    tz = new SimpleTimeZone
+      (-6000 * 3600, "Pacific/Easter",
+       Calendar.OCTOBER, 9, -Calendar.SUNDAY, 0 * 3600,
+       Calendar.MARCH, 9, -Calendar.SUNDAY, 0 * 3600);
+    timezones.put("Pacific/Easter", tz);
+    tz = new SimpleTimeZone
+      (-5000 * 3600, "America/Grand_Turk",
+       Calendar.APRIL, 1, Calendar.SUNDAY, 0 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 0 * 3600);
+    timezones.put("America/Grand_Turk", tz);
+    tz = new SimpleTimeZone
+      (-5000 * 3600, "America/Havana",
+       Calendar.APRIL, 1, 0, 0 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 0 * 3600);
+    timezones.put("America/Havana", tz);
+    tz = new SimpleTimeZone(-5000 * 3600, "EST");
+    timezones.put("EST", tz);
+    timezones.put("America/Bogota", tz);
+    timezones.put("America/Cayman", tz);
+    timezones.put("America/Guayaquil", tz);
+    timezones.put("America/Indiana/Indianapolis", tz);
+    timezones.put("America/Indiana/Knox", tz);
+    timezones.put("America/Indiana/Marengo", tz);
+    timezones.put("America/Indiana/Vevay", tz);
+    timezones.put("America/Indianapolis", tz);
+    timezones.put("America/Jamaica", tz);
+    timezones.put("America/Lima", tz);
+    timezones.put("America/Panama", tz);
+    timezones.put("America/Port-au-Prince", tz);
+    timezones.put("America/Porto_Acre", tz);
+    tz = new SimpleTimeZone
+      (-5000 * 3600, "EST5EDT",
+       Calendar.APRIL, 1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("EST5EDT", tz);
+    timezones.put("America/Detroit", tz);
+    timezones.put("America/Louisville", tz);
+    timezones.put("America/Montreal", tz);
+    timezones.put("America/Nassau", tz);
+    timezones.put("America/New_York", tz);
+    timezones.put("America/Nipigon", tz);
+    timezones.put("America/Thunder_Bay", tz);
+    tz = new SimpleTimeZone(-4000 * 3600, "America/Anguilla");
+    timezones.put("America/Anguilla", tz);
+    timezones.put("America/Antigua", tz);
+    timezones.put("America/Aruba", tz);
+    timezones.put("America/Barbados", tz);
+    timezones.put("America/Caracas", tz);
+    timezones.put("America/Curacao", tz);
+    timezones.put("America/Dominica", tz);
+    timezones.put("America/Grenada", tz);
+    timezones.put("America/Guadeloupe", tz);
+    timezones.put("America/Guyana", tz);
+    timezones.put("America/La_Paz", tz);
+    timezones.put("America/Manaus", tz);
+    timezones.put("America/Martinique", tz);
+    timezones.put("America/Montserrat", tz);
+    timezones.put("America/Port_of_Spain", tz);
+    timezones.put("America/Porto_Velho", tz);
+    timezones.put("America/Puerto_Rico", tz);
+    timezones.put("America/Santo_Domingo", tz);
+    timezones.put("America/St_Kitts", tz);
+    timezones.put("America/St_Lucia", tz);
+    timezones.put("America/St_Thomas", tz);
+    timezones.put("America/St_Vincent", tz);
+    timezones.put("America/Tortola", tz);
+    tz = new SimpleTimeZone
+      (-4000 * 3600, "America/Cuiaba",
+       Calendar.OCTOBER, 1, Calendar.SUNDAY, 0 * 3600,
+       Calendar.FEBRUARY, -1, Calendar.SUNDAY, 0 * 3600);
+    timezones.put("America/Cuiaba", tz);
+    timezones.put("America/Asuncion", tz);
+    timezones.put("America/Boa_Vista", tz);
+    tz = new SimpleTimeZone
+      (-4000 * 3600, "America/Thule",
+       Calendar.APRIL, 1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("America/Thule", tz);
+    timezones.put("America/Glace_Bay", tz);
+    timezones.put("America/Goose_Bay", tz);
+    timezones.put("America/Halifax", tz);
+    timezones.put("Atlantic/Bermuda", tz);
+    tz = new SimpleTimeZone
+      (-4000 * 3600, "Antarctica/Palmer",
+       Calendar.OCTOBER, 9, -Calendar.SUNDAY, 0 * 3600,
+       Calendar.MARCH, 9, -Calendar.SUNDAY, 0 * 3600);
+    timezones.put("Antarctica/Palmer", tz);
+    timezones.put("America/Santiago", tz);
+    tz = new SimpleTimeZone
+      (-4000 * 3600, "Atlantic/Stanley",
+       Calendar.SEPTEMBER, 2, Calendar.SUNDAY, 0 * 3600,
+       Calendar.APRIL, 16, -Calendar.SUNDAY, 0 * 3600);
+    timezones.put("Atlantic/Stanley", tz);
+    tz = new SimpleTimeZone(-3000 * 3600, "America/Buenos_Aires");
+    timezones.put("America/Buenos_Aires", tz);
+    timezones.put("America/Belem", tz);
+    timezones.put("America/Catamarca", tz);
+    timezones.put("America/Cayenne", tz);
+    timezones.put("America/Cordoba", tz);
+    timezones.put("America/Jujuy", tz);
+    timezones.put("America/Mendoza", tz);
+    timezones.put("America/Montevideo", tz);
+    timezones.put("America/Paramaribo", tz);
+    timezones.put("America/Rosario", tz);
+    tz = new SimpleTimeZone
+      (-3000 * 3600, "America/Fortaleza",
+       Calendar.OCTOBER, 1, Calendar.SUNDAY, 0 * 3600,
+       Calendar.FEBRUARY, -1, Calendar.SUNDAY, 0 * 3600);
+    timezones.put("America/Fortaleza", tz);
+    timezones.put("America/Araguaina", tz);
+    timezones.put("America/Maceio", tz);
+    timezones.put("America/Sao_Paulo", tz);
+    tz = new SimpleTimeZone
+      (-3000 * 3600, "America/Godthab",
+       Calendar.MARCH, 30, -Calendar.SATURDAY, 22000 * 3600,
+       Calendar.OCTOBER, 30, -Calendar.SATURDAY, 22000 * 3600);
+    timezones.put("America/Godthab", tz);
+    tz = new SimpleTimeZone
+      (-3000 * 3600, "America/Miquelon",
+       Calendar.APRIL, 1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("America/Miquelon", tz);
+    tz = new SimpleTimeZone
+      (-2500 * 3600, "America/St_Johns",
+       Calendar.APRIL, 1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("America/St_Johns", tz);
+    tz = new SimpleTimeZone(-2000 * 3600, "America/Noronha");
+    timezones.put("America/Noronha", tz);
+    timezones.put("Atlantic/South_Georgia", tz);
+    tz = new SimpleTimeZone
+      (-1000 * 3600, "America/Scoresbysund",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 0 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 0 * 3600);
+    timezones.put("America/Scoresbysund", tz);
+    timezones.put("Atlantic/Azores", tz);
+    tz = new SimpleTimeZone(-1000 * 3600, "Atlantic/Cape_Verde");
+    timezones.put("Atlantic/Cape_Verde", tz);
+    timezones.put("Atlantic/Jan_Mayen", tz);
+    tz = new SimpleTimeZone(0 * 3600, "GMT");
+    timezones.put("GMT", tz);
+    timezones.put("Africa/Abidjan", tz);
+    timezones.put("Africa/Accra", tz);
+    timezones.put("Africa/Bamako", tz);
+    timezones.put("Africa/Banjul", tz);
+    timezones.put("Africa/Bissau", tz);
+    timezones.put("Africa/Casablanca", tz);
+    timezones.put("Africa/Conakry", tz);
+    timezones.put("Africa/Dakar", tz);
+    timezones.put("Africa/El_Aaiun", tz);
+    timezones.put("Africa/Freetown", tz);
+    timezones.put("Africa/Lome", tz);
+    timezones.put("Africa/Monrovia", tz);
+    timezones.put("Africa/Nouakchott", tz);
+    timezones.put("Africa/Ouagadougou", tz);
+    timezones.put("Africa/Sao_Tome", tz);
+    timezones.put("Africa/Timbuktu", tz);
+    timezones.put("Atlantic/Reykjavik", tz);
+    timezones.put("Atlantic/St_Helena", tz);
+    timezones.put("Europe/Belfast", tz);
+    timezones.put("Europe/Dublin", tz);
+    timezones.put("Europe/London", tz);
+    timezones.put("UTC", tz);
+    tz = new SimpleTimeZone
+      (0 * 3600, "WET",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 1000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 1000 * 3600);
+    timezones.put("WET", tz);
+    timezones.put("Atlantic/Canary", tz);
+    timezones.put("Atlantic/Faeroe", tz);
+    timezones.put("Atlantic/Madeira", tz);
+    timezones.put("Europe/Lisbon", tz);
+    tz = new SimpleTimeZone(1000 * 3600, "Africa/Algiers");
+    timezones.put("Africa/Algiers", tz);
+    timezones.put("Africa/Bangui", tz);
+    timezones.put("Africa/Brazzaville", tz);
+    timezones.put("Africa/Douala", tz);
+    timezones.put("Africa/Kinshasa", tz);
+    timezones.put("Africa/Lagos", tz);
+    timezones.put("Africa/Libreville", tz);
+    timezones.put("Africa/Luanda", tz);
+    timezones.put("Africa/Malabo", tz);
+    timezones.put("Africa/Ndjamena", tz);
+    timezones.put("Africa/Niamey", tz);
+    timezones.put("Africa/Porto-Novo", tz);
+    timezones.put("Africa/Tunis", tz);
+    tz = new SimpleTimeZone
+      (1000 * 3600, "Africa/Windhoek",
+       Calendar.SEPTEMBER, 1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.APRIL, 1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("Africa/Windhoek", tz);
+    tz = new SimpleTimeZone
+      (1000 * 3600, "CET",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("CET", tz);
+    timezones.put("Africa/Ceuta", tz);
+    timezones.put("Arctic/Longyearbyen", tz);
+    timezones.put("Europe/Amsterdam", tz);
+    timezones.put("Europe/Andorra", tz);
+    timezones.put("Europe/Belgrade", tz);
+    timezones.put("Europe/Berlin", tz);
+    timezones.put("Europe/Bratislava", tz);
+    timezones.put("Europe/Brussels", tz);
+    timezones.put("Europe/Budapest", tz);
+    timezones.put("Europe/Copenhagen", tz);
+    timezones.put("Europe/Gibraltar", tz);
+    timezones.put("Europe/Ljubljana", tz);
+    timezones.put("Europe/Luxembourg", tz);
+    timezones.put("Europe/Madrid", tz);
+    timezones.put("Europe/Malta", tz);
+    timezones.put("Europe/Monaco", tz);
+    timezones.put("Europe/Oslo", tz);
+    timezones.put("Europe/Paris", tz);
+    timezones.put("Europe/Prague", tz);
+    timezones.put("Europe/Rome", tz);
+    timezones.put("Europe/San_Marino", tz);
+    timezones.put("Europe/Sarajevo", tz);
+    timezones.put("Europe/Skopje", tz);
+    timezones.put("Europe/Stockholm", tz);
+    timezones.put("Europe/Tirane", tz);
+    timezones.put("Europe/Vaduz", tz);
+    timezones.put("Europe/Vatican", tz);
+    timezones.put("Europe/Vienna", tz);
+    timezones.put("Europe/Warsaw", tz);
+    timezones.put("Europe/Zagreb", tz);
+    timezones.put("Europe/Zurich", tz);
+    timezones.put("MET", tz);
+    tz = new SimpleTimeZone
+      (2000 * 3600, "Africa/Cairo",
+       Calendar.APRIL, -1, Calendar.FRIDAY, 0 * 3600,
+       Calendar.SEPTEMBER, -1, Calendar.THURSDAY, 23000 * 3600);
+    timezones.put("Africa/Cairo", tz);
+    tz = new SimpleTimeZone(2000 * 3600, "Africa/Gaborone");
+    timezones.put("Africa/Gaborone", tz);
+    timezones.put("Africa/Blantyre", tz);
+    timezones.put("Africa/Bujumbura", tz);
+    timezones.put("Africa/Harare", tz);
+    timezones.put("Africa/Johannesburg", tz);
+    timezones.put("Africa/Khartoum", tz);
+    timezones.put("Africa/Kigali", tz);
+    timezones.put("Africa/Lubumbashi", tz);
+    timezones.put("Africa/Lusaka", tz);
+    timezones.put("Africa/Maputo", tz);
+    timezones.put("Africa/Maseru", tz);
+    timezones.put("Africa/Mbabane", tz);
+    timezones.put("Africa/Tripoli", tz);
+    timezones.put("Europe/Tallinn", tz);
+    tz = new SimpleTimeZone
+      (2000 * 3600, "Asia/Amman",
+       Calendar.APRIL, 1, 0, 0 * 3600, Calendar.OCTOBER, 1, 0, 0 * 3600);
+    timezones.put("Asia/Amman", tz);
+    timezones.put("Asia/Damascus", tz);
+    tz = new SimpleTimeZone
+      (2000 * 3600, "Asia/Beirut",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 0 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 0 * 3600);
+    timezones.put("Asia/Beirut", tz);
+    tz = new SimpleTimeZone
+      (2000 * 3600, "Asia/Gaza",
+       Calendar.APRIL, 3, Calendar.FRIDAY, 0 * 3600,
+       Calendar.OCTOBER, 3, Calendar.FRIDAY, 0 * 3600);
+    timezones.put("Asia/Gaza", tz);
+    tz = new SimpleTimeZone
+      (2000 * 3600, "Asia/Jerusalem",
+       Calendar.APRIL, 1, Calendar.FRIDAY, 2000 * 3600,
+       Calendar.SEPTEMBER, 1, Calendar.FRIDAY, 2000 * 3600);
+    timezones.put("Asia/Jerusalem", tz);
+    tz = new SimpleTimeZone
+      (2000 * 3600, "EET",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 3000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 3000 * 3600);
+    timezones.put("EET", tz);
+    timezones.put("Asia/Istanbul", tz);
+    timezones.put("Asia/Nicosia", tz);
+    timezones.put("Europe/Athens", tz);
+    timezones.put("Europe/Bucharest", tz);
+    timezones.put("Europe/Chisinau", tz);
+    timezones.put("Europe/Helsinki", tz);
+    timezones.put("Europe/Istanbul", tz);
+    timezones.put("Europe/Kiev", tz);
+    timezones.put("Europe/Riga", tz);
+    timezones.put("Europe/Simferopol", tz);
+    timezones.put("Europe/Sofia", tz);
+    timezones.put("Europe/Uzhgorod", tz);
+    timezones.put("Europe/Vilnius", tz);
+    timezones.put("Europe/Zaporozhye", tz);
+    tz = new SimpleTimeZone
+      (2000 * 3600, "Europe/Minsk",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("Europe/Minsk", tz);
+    timezones.put("Europe/Kaliningrad", tz);
+    tz = new SimpleTimeZone
+      (3000 * 3600, "Asia/Baghdad",
+       Calendar.APRIL, 1, 0, 3000 * 3600,
+       Calendar.OCTOBER, 1, 0, 3000 * 3600);
+    timezones.put("Asia/Baghdad", tz);
+    tz = new SimpleTimeZone
+      (3000 * 3600, "Europe/Tiraspol",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("Europe/Tiraspol", tz);
+    timezones.put("Europe/Moscow", tz);
+    tz = new SimpleTimeZone(3000 * 3600, "Indian/Comoro");
+    timezones.put("Indian/Comoro", tz);
+    timezones.put("Africa/Addis_Ababa", tz);
+    timezones.put("Africa/Asmera", tz);
+    timezones.put("Africa/Dar_es_Salaam", tz);
+    timezones.put("Africa/Djibouti", tz);
+    timezones.put("Africa/Kampala", tz);
+    timezones.put("Africa/Mogadishu", tz);
+    timezones.put("Africa/Nairobi", tz);
+    timezones.put("Antarctica/Syowa", tz);
+    timezones.put("Asia/Aden", tz);
+    timezones.put("Asia/Bahrain", tz);
+    timezones.put("Asia/Kuwait", tz);
+    timezones.put("Asia/Qatar", tz);
+    timezones.put("Asia/Riyadh", tz);
+    timezones.put("Indian/Antananarivo", tz);
+    timezones.put("Indian/Mayotte", tz);
+    tz = new SimpleTimeZone(3500 * 3600, "Asia/Tehran");
+    timezones.put("Asia/Tehran", tz);
+    tz = new SimpleTimeZone
+      (4000 * 3600, "Asia/Baku",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 1000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 1000 * 3600);
+    timezones.put("Asia/Baku", tz);
+    tz = new SimpleTimeZone
+      (4000 * 3600, "Asia/Tbilisi",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 0 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 0 * 3600);
+    timezones.put("Asia/Tbilisi", tz);
+    timezones.put("Asia/Aqtau", tz);
+    tz = new SimpleTimeZone
+      (4000 * 3600, "Asia/Yerevan",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("Asia/Yerevan", tz);
+    timezones.put("Europe/Samara", tz);
+    tz = new SimpleTimeZone(4000 * 3600, "Indian/Mauritius");
+    timezones.put("Indian/Mauritius", tz);
+    timezones.put("Asia/Dubai", tz);
+    timezones.put("Asia/Muscat", tz);
+    timezones.put("Indian/Mahe", tz);
+    timezones.put("Indian/Reunion", tz);
+    tz = new SimpleTimeZone(4500 * 3600, "Asia/Kabul");
+    timezones.put("Asia/Kabul", tz);
+    tz = new SimpleTimeZone
+      (5000 * 3600, "Asia/Aqtobe",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 0 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 0 * 3600);
+    timezones.put("Asia/Aqtobe", tz);
+    tz = new SimpleTimeZone
+      (5000 * 3600, "Asia/Bishkek",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 2500 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2500 * 3600);
+    timezones.put("Asia/Bishkek", tz);
+    tz = new SimpleTimeZone
+      (5000 * 3600, "Asia/Yekaterinburg",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("Asia/Yekaterinburg", tz);
+    tz = new SimpleTimeZone(5000 * 3600, "Indian/Kerguelen");
+    timezones.put("Indian/Kerguelen", tz);
+    timezones.put("Asia/Ashkhabad", tz);
+    timezones.put("Asia/Dushanbe", tz);
+    timezones.put("Asia/Karachi", tz);
+    timezones.put("Asia/Samarkand", tz);
+    timezones.put("Asia/Tashkent", tz);
+    timezones.put("Indian/Chagos", tz);
+    timezones.put("Indian/Maldives", tz);
+    tz = new SimpleTimeZone(5500 * 3600, "Asia/Calcutta");
+    timezones.put("Asia/Calcutta", tz);
+    tz = new SimpleTimeZone(5750 * 3600, "Asia/Katmandu");
+    timezones.put("Asia/Katmandu", tz);
+    tz = new SimpleTimeZone(6000 * 3600, "Antarctica/Mawson");
+    timezones.put("Antarctica/Mawson", tz);
+    timezones.put("Asia/Colombo", tz);
+    timezones.put("Asia/Dacca", tz);
+    timezones.put("Asia/Thimbu", tz);
+    tz = new SimpleTimeZone
+      (6000 * 3600, "Asia/Almaty",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 0 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 0 * 3600);
+    timezones.put("Asia/Almaty", tz);
+    tz = new SimpleTimeZone
+      (6000 * 3600, "Asia/Omsk",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("Asia/Omsk", tz);
+    timezones.put("Asia/Novosibirsk", tz);
+    tz = new SimpleTimeZone(6500 * 3600, "Asia/Rangoon");
+    timezones.put("Asia/Rangoon", tz);
+    timezones.put("Indian/Cocos", tz);
+    tz = new SimpleTimeZone(7000 * 3600, "Antarctica/Davis");
+    timezones.put("Antarctica/Davis", tz);
+    timezones.put("Asia/Bangkok", tz);
+    timezones.put("Asia/Jakarta", tz);
+    timezones.put("Asia/Phnom_Penh", tz);
+    timezones.put("Asia/Saigon", tz);
+    timezones.put("Asia/Vientiane", tz);
+    timezones.put("Indian/Christmas", tz);
+    tz = new SimpleTimeZone
+      (7000 * 3600, "Asia/Krasnoyarsk",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("Asia/Krasnoyarsk", tz);
+    tz = new SimpleTimeZone(8000 * 3600, "Antarctica/Casey");
+    timezones.put("Antarctica/Casey", tz);
+    timezones.put("Asia/Brunei", tz);
+    timezones.put("Asia/Chungking", tz);
+    timezones.put("Asia/Dili", tz);
+    timezones.put("Asia/Harbin", tz);
+    timezones.put("Asia/Hong_Kong", tz);
+    timezones.put("Asia/Kashgar", tz);
+    timezones.put("Asia/Kuala_Lumpur", tz);
+    timezones.put("Asia/Kuching", tz);
+    timezones.put("Asia/Macao", tz);
+    timezones.put("Asia/Manila", tz);
+    timezones.put("Asia/Shanghai", tz);
+    timezones.put("Asia/Singapore", tz);
+    timezones.put("Asia/Taipei", tz);
+    timezones.put("Asia/Ujung_Pandang", tz);
+    timezones.put("Asia/Urumqi", tz);
+    timezones.put("Australia/Perth", tz);
+    tz = new SimpleTimeZone
+      (8000 * 3600, "Asia/Irkutsk",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("Asia/Irkutsk", tz);
+    tz = new SimpleTimeZone
+      (8000 * 3600, "Asia/Ulan_Bator",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 0 * 3600,
+       Calendar.SEPTEMBER, -1, Calendar.SUNDAY, 0 * 3600);
+    timezones.put("Asia/Ulan_Bator", tz);
+    tz = new SimpleTimeZone(9000 * 3600, "Asia/Jayapura");
+    timezones.put("Asia/Jayapura", tz);
+    timezones.put("Asia/Pyongyang", tz);
+    timezones.put("Asia/Seoul", tz);
+    timezones.put("Asia/Tokyo", tz);
+    timezones.put("Pacific/Palau", tz);
+    tz = new SimpleTimeZone
+      (9000 * 3600, "Asia/Yakutsk",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("Asia/Yakutsk", tz);
+    tz = new SimpleTimeZone
+      (9500 * 3600, "Australia/Adelaide",
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.MARCH, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("Australia/Adelaide", tz);
+    timezones.put("Australia/Broken_Hill", tz);
+    tz = new SimpleTimeZone(9500 * 3600, "Australia/Darwin");
+    timezones.put("Australia/Darwin", tz);
+    tz = new SimpleTimeZone(10000 * 3600, "Antarctica/DumontDUrville");
+    timezones.put("Antarctica/DumontDUrville", tz);
+    timezones.put("Australia/Brisbane", tz);
+    timezones.put("Australia/Lindeman", tz);
+    timezones.put("Pacific/Guam", tz);
+    timezones.put("Pacific/Port_Moresby", tz);
+    timezones.put("Pacific/Saipan", tz);
+    timezones.put("Pacific/Truk", tz);
+    timezones.put("Pacific/Yap", tz);
+    tz = new SimpleTimeZone
+      (10000 * 3600, "Asia/Vladivostok",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("Asia/Vladivostok", tz);
+    tz = new SimpleTimeZone
+      (10000 * 3600, "Australia/Hobart",
+       Calendar.OCTOBER, 1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.MARCH, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("Australia/Hobart", tz);
+    tz = new SimpleTimeZone
+      (10000 * 3600, "Australia/Melbourne",
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.MARCH, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("Australia/Melbourne", tz);
+    timezones.put("Australia/Sydney", tz);
+/******************************************************************
+ * FIXME: XXX: Not yet available in libgcj.  Need new jdk 1.2
+ * SimpleTimeZone constructor.
+    tz = new SimpleTimeZone
+      (10500 * 3600, "Australia/Lord_Howe",
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.MARCH, -1, Calendar.SUNDAY, 2000 * 3600, 500 * 3600);
+    timezones.put("Australia/Lord_Howe", tz);
+ ******************************************************************/
+    tz = new SimpleTimeZone
+      (11000 * 3600, "Asia/Magadan",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("Asia/Magadan", tz);
+    tz = new SimpleTimeZone(11000 * 3600, "Pacific/Ponape");
+    timezones.put("Pacific/Ponape", tz);
+    timezones.put("Pacific/Efate", tz);
+    timezones.put("Pacific/Guadalcanal", tz);
+    timezones.put("Pacific/Kosrae", tz);
+    timezones.put("Pacific/Noumea", tz);
+    tz = new SimpleTimeZone(11500 * 3600, "Pacific/Norfolk");
+    timezones.put("Pacific/Norfolk", tz);
+    tz = new SimpleTimeZone
+      (12000 * 3600, "Antarctica/McMurdo",
+       Calendar.OCTOBER, 1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.MARCH, 3, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("Antarctica/McMurdo", tz);
+    timezones.put("Antarctica/South_Pole", tz);
+    timezones.put("Pacific/Auckland", tz);
+    tz = new SimpleTimeZone
+      (12000 * 3600, "Asia/Kamchatka",
+       Calendar.MARCH, -1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.OCTOBER, -1, Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("Asia/Kamchatka", tz);
+    timezones.put("Asia/Anadyr", tz);
+    tz = new SimpleTimeZone
+      (12000 * 3600, "Pacific/Fiji",
+       Calendar.NOVEMBER, 1, Calendar.SUNDAY, 2000 * 3600,
+       Calendar.FEBRUARY, -1, Calendar.SUNDAY, 3000 * 3600);
+    timezones.put("Pacific/Fiji", tz);
+    tz = new SimpleTimeZone(12000 * 3600, "Pacific/Tarawa");
+    timezones.put("Pacific/Tarawa", tz);
+    timezones.put("Pacific/Funafuti", tz);
+    timezones.put("Pacific/Kwajalein", tz);
+    timezones.put("Pacific/Majuro", tz);
+    timezones.put("Pacific/Nauru", tz);
+    timezones.put("Pacific/Wake", tz);
+    timezones.put("Pacific/Wallis", tz);
+    tz = new SimpleTimeZone
+      (12750 * 3600, "Pacific/Chatham",
+       Calendar.OCTOBER, 1, Calendar.SUNDAY, 2750 * 3600,
+       Calendar.MARCH, 3, Calendar.SUNDAY, 2750 * 3600);
+    timezones.put("Pacific/Chatham", tz);
+    tz = new SimpleTimeZone(13000 * 3600, "Pacific/Enderbury");
+    timezones.put("Pacific/Enderbury", tz);
+    tz = new SimpleTimeZone
+      (13000 * 3600, "Pacific/Tongatapu",
+       Calendar.OCTOBER, 1, Calendar.SATURDAY, 2000 * 3600,
+       Calendar.APRIL, 16, -Calendar.SUNDAY, 2000 * 3600);
+    timezones.put("Pacific/Tongatapu", tz);
+    tz = new SimpleTimeZone(14000 * 3600, "Pacific/Kiritimati");
+    timezones.put("Pacific/Kiritimati", tz);
   }
 
-  public abstract int getOffset (int era, int year, int month,
-				 int day, int dayOfWeek, int milliseconds);
 
-  public abstract void setRawOffset (int offsetMillis);
+  /* Look up default timezone */
+  static
+  {
+    // System.loadLibrary("javautil");
 
-  public abstract int getRawOffset ();
+    String tzid = System.getProperty("user.timezone");
 
-  public String getID () { return ID; }
+    if (tzid == null)
+      tzid = getDefaultTimeZoneId();
 
-  public void setID (String ID) { this.ID = ID; }
+    if (tzid == null)
+      tzid = "GMT";
 
-  public final String getDisplayName()
+    defaultZone = getTimeZone(tzid);
+  }
+
+  /* This method returns us a time zone id string which is in the
+     form <standard zone name><GMT offset><daylight time zone name>.
+     The GMT offset is in seconds, except where it is evenly divisible
+     by 3600, then it is in hours.  If the zone does not observe
+     daylight time, then the daylight zone name is omitted.  Examples:
+     in Chicago, the timezone would be CST6CDT.  In Indianapolis 
+     (which does not have Daylight Savings Time) the string would
+     be EST5
+   */
+  private static native String getDefaultTimeZoneId();
+
+  /**
+   * Gets the time zone offset, for current date, modified in case of 
+   * daylight savings.  This is the offset to add to UTC to get the local
+   * time.
+   * @param era the era of the given date
+   * @param year the year of the given date
+   * @param month the month of the given date, 0 for January.
+   * @param day the day of month
+   * @param dayOfWeek the day of week
+   * @param milliseconds the millis in the day (in local standard time)
+   * @return the time zone offset in milliseconds.
+   */
+  public abstract int getOffset(int era, int year, int month,
+				int day, int dayOfWeek, int milliseconds);
+
+  /**
+   * Gets the time zone offset, ignoring daylight savings.  This is
+   * the offset to add to UTC to get the local time.
+   * @return the time zone offset in milliseconds.  
+   */
+  public abstract int getRawOffset();
+
+  /**
+   * Sets the time zone offset, ignoring daylight savings.  This is
+   * the offset to add to UTC to get the local time.
+   * @param offsetMillis the time zone offset to GMT.
+   */
+  public abstract void setRawOffset(int offsetMillis);
+
+  /**
+   * Gets the identifier of this time zone. For instance, PST for
+   * Pacific Standard Time.
+   * @returns the ID of this time zone.  
+   */
+  public String getID()
   {
-    return ID;  // FIXME
+    return ID;
   }
 
-  // public final String getDisplayName (Local locale) { ... }  FIXME
+  /**
+   * Sets the identifier of this time zone. For instance, PST for
+   * Pacific Standard Time.
+   * @param id the new time zone ID.
+   */
+  public void setID(String id)
+  {
+    this.ID = id;
+  }
 
-  public final String getDisplayName (boolean daylight, int style)
+  /**
+   * This method returns a string name of the time zone suitable
+   * for displaying to the user.  The string returned will be the long
+   * description of the timezone in the current locale.  The name
+   * displayed will assume daylight savings time is not in effect.
+   *
+   * @return The name of the time zone.
+   */
+  public final String getDisplayName()
   {
-    return ID;  // FIXME
+    return (getDisplayName(false, LONG, Locale.getDefault()));
   }
 
-  /*
-  public final String getDisplayName (boolean daylight, int style, Locale locale)
+  /**
+   * This method returns a string name of the time zone suitable
+   * for displaying to the user.  The string returned will be the long
+   * description of the timezone in the specified locale. The name
+   * displayed will assume daylight savings time is not in effect.
+   *
+   * @param locale The locale for this timezone name.
+   *
+   * @return The name of the time zone.
+   */
+  public final String getDisplayName(Locale locale)
   {
-    return ID;  // FIXME
+    return (getDisplayName(false, LONG, locale));
   }
-  */
 
-  public abstract boolean useDaylightTime();
+  /**
+   * This method returns a string name of the time zone suitable
+   * for displaying to the user.  The string returned will be of the
+   * specified type in the current locale. 
+   *
+   * @param dst Whether or not daylight savings time is in effect.
+   * @param style <code>LONG</code> for a long name, <code>SHORT</code> for
+   * a short abbreviation.
+   *
+   * @return The name of the time zone.
+   */
+  public final String getDisplayName(boolean dst, int style)
+  {
+    return (getDisplayName(dst, style, Locale.getDefault()));
+  }
 
-  public abstract boolean inDaylightTime (Date date);
 
-  public static synchronized TimeZone getTimeZone (String ID)
+  /**
+   * This method returns a string name of the time zone suitable
+   * for displaying to the user.  The string returned will be of the
+   * specified type in the specified locale. 
+   *
+   * @param dst Whether or not daylight savings time is in effect.
+   * @param style <code>LONG</code> for a long name, <code>SHORT</code> for
+   * a short abbreviation.
+   * @param locale The locale for this timezone name.
+   *
+   * @return The name of the time zone.
+   */
+  public String getDisplayName(boolean dst, int style, Locale locale)
   {
-    int i;
-    for (i = 0; i < tzIDs.length; ++i)
+    DateFormatSymbols dfs;
+    try
       {
-	if (ID.equals(tzIDs[i]))
-	  break;
-      }
-    if (i == tzIDs.length)
-      return null;
+	dfs = new DateFormatSymbols(locale);
 
-    if (timeZones[i] == null)
+	// The format of the value returned is defined by us.
+	String[][]zoneinfo = dfs.getZoneStrings();
+	for (int i = 0; i < zoneinfo.length; i++)
+	  {
+	    if (zoneinfo[i][0].equals(getID()))
+	      {
+		if (!dst)
+		  {
+		    if (style == SHORT)
+		      return (zoneinfo[i][2]);
+		    else
+		      return (zoneinfo[i][1]);
+		  }
+		else
+		  {
+		    if (style == SHORT)
+		      return (zoneinfo[i][4]);
+		    else
+		      return (zoneinfo[i][3]);
+		  }
+	      }
+	  }
+      }
+    catch (MissingResourceException e)
       {
-	if (ID.equals("GMT"))
-	  timeZones[i] = zoneGMT;
-	else
-	  timeZones[i] = new SimpleTimeZone (rawOffsets[i], tzIDs[i]);
       }
 
-    return timeZones[i];
+    return getDefaultDisplayName(dst);
   }
 
-  public static String[] getAvailableIDs()
+  private String getDefaultDisplayName(boolean dst)
   {
-    return (String[]) tzIDs.clone();
+    int offset = getRawOffset();
+    if (dst && this instanceof SimpleTimeZone)
+      {
+	// ugly, but this is a design failure of the API:
+	// getDisplayName takes a dst parameter even though
+	// TimeZone knows nothing about daylight saving offsets.
+	offset += ((SimpleTimeZone) this).getDSTSavings();
+      }
+
+    StringBuffer sb = new StringBuffer(9);
+    sb.append("GMT");
+    sb.append(offset >= 0 ? '+' : '-');
+
+    offset = Math.abs(offset) / (1000 * 60);
+    int hours = offset / 60;
+    int minutes = offset % 60;
+
+    sb.append('0' + hours / 10).append('0' + hours % 10).append(':');
+    sb.append('0' + minutes / 10).append('0' + minutes % 10);
+    return sb.toString();
   }
 
-  public static String[] getAvailableIDs(int rawOffset)
-  {
-    int first, last;
+  /** 
+   * Returns true, if this time zone uses Daylight Savings Time.
+   */
+  public abstract boolean useDaylightTime();
 
-    for (first = 0; first < rawOffsets.length; ++first)
-      {
-	if (rawOffset == rawOffsets[first])
-	  break;
-      }
-    if (first == rawOffsets.length)
-      return new String[0];
-    for (last = first + 1; last < rawOffsets.length; ++last)
+  /**
+   * Returns true, if the given date is in Daylight Savings Time in this
+   * time zone.
+   * @param date the given Date.
+   */
+  public abstract boolean inDaylightTime(Date date);
+
+  /**
+   * Gets the TimeZone for the given ID.
+   * @param ID the time zone identifier.
+   * @return The time zone for the identifier or GMT, if no such time
+   * zone exists.
+   */
+  // FIXME: XXX: JCL indicates this and other methods are synchronized.
+  public static TimeZone getTimeZone(String ID)
+  {
+    // First check timezones hash
+    TimeZone tz = (TimeZone) timezones.get(ID);
+    if (tz != null)
       {
-	if (rawOffset != rawOffsets[last])
-	  break;
+	if (tz.getID().equals(ID))
+	  return tz;
+
+	// We always return a timezone with the requested ID.
+	// This is the same behaviour as with JDK1.2.
+	tz = (TimeZone) tz.clone();
+	tz.setID(ID);
+	// We also save the alias, so that we return the same
+	// object again if getTimeZone is called with the same
+	// alias.
+	timezones.put(ID, tz);
+	return tz;
       }
 
-    String[] r = new String[last - first];
-    for (int i = first; i < last; ++i)
+    // See if the ID is really a GMT offset form.
+    // Note that GMT is in the table so we know it is different.
+    if (ID.startsWith("GMT"))
       {
-	r[i - first] = tzIDs[i];
-      }
+	int pos = 3;
+	int offset_direction = 1;
 
-    return r;
-  }
+	if (ID.charAt(pos) == '-')
+	  {
+	    offset_direction = -1;
+	    pos++;
+	  }
+	else if (ID.charAt(pos) == '+')
+	  {
+	    pos++;
+	  }
 
-  private static synchronized TimeZone setDefault()
-  {
-    if (zoneDefault == null)
-      {
 	try
 	  {
-	    String id = System.getProperty("user.timezone");
-	    if (id != null && ! id.equals("GMT"))
-	      zoneDefault = getTimeZone(id);
+	    int hour, minute;
+
+	    String offset_str = ID.substring(pos);
+	    int idx = offset_str.indexOf(":");
+	    if (idx != -1)
+	      {
+		hour = Integer.parseInt(offset_str.substring(0, idx));
+		minute = Integer.parseInt(offset_str.substring(idx + 1));
+	      }
+	    else
+	      {
+		int offset_length = offset_str.length();
+		if (offset_length <= 2)
+		  {
+		    // Only hour
+		    hour = Integer.parseInt(offset_str);
+		    minute = 0;
+		  }
+		else
+		  {
+		    // hour and minute, not separated by colon
+		    hour = Integer.parseInt
+		      (offset_str.substring(0, offset_length - 2));
+		    minute = Integer.parseInt
+		      (offset_str.substring(offset_length - 2));
+		  }
+	      }
+
+	    return new SimpleTimeZone((hour * (60 * 60 * 1000) +
+				       minute * (60 * 1000))
+				      * offset_direction, ID);
 	  }
-	catch (Exception ex)
+	catch (NumberFormatException e)
 	  {
 	  }
-	if (zoneDefault == null)
-	  zoneDefault = zoneGMT;
       }
-    return zoneDefault;
+
+    // Finally, return GMT per spec
+    return getTimeZone("GMT");
   }
 
-  public static TimeZone getDefault()
+  /**
+   * Gets the available IDs according to the given time zone
+   * offset.  
+   * @param rawOffset the given time zone GMT offset.
+   * @return An array of IDs, where the time zone has the specified GMT
+   * offset. For example <code>{"Phoenix", "Denver"}</code>, since both have
+   * GMT-07:00, but differ in daylight savings behaviour.
+   */
+/******************************************************************
+ * FIXME: XXX: Not yet available in libgcj.  Need jdk 1.2 Iterator and Map.
+  public static String[] getAvailableIDs(int rawOffset)
   {
-    return zoneDefault == null ? setDefault() : zoneDefault;
+    int count = 0;
+    Iterator iter = timezones.entrySet().iterator();
+    while (iter.hasNext())
+      {
+	// Don't iterate the values, since we want to count 
+	// doubled values (aliases)
+	Map.Entry entry = (Map.Entry) iter.next();
+	if (((TimeZone) entry.getValue()).getRawOffset() == rawOffset)
+	  count++;
+      }
+
+    String[] ids = new String[count];
+    count = 0;
+    iter = timezones.entrySet().iterator();
+    while (iter.hasNext())
+      {
+	Map.Entry entry = (Map.Entry) iter.next();
+	if (((TimeZone) entry.getValue()).getRawOffset() == rawOffset)
+	  ids[count++] = (String) entry.getKey();
+      }
+    return ids;
   }
+ ******************************************************************/
 
-  public static void setDefault (TimeZone zone) { zoneDefault = zone; }
+  /**
+   * Gets all available IDs.
+   * @return An array of all supported IDs.
+   */
+/******************************************************************
+ * FIXME: XXX: Not yet available in libgcj.  Need jdk 1.2 java.util.Map.
+  public static String[] getAvailableIDs()
+  {
+    return (String[])
+      timezones.keySet().toArray(new String[timezones.size()]);
+  }
+ ******************************************************************/
 
-  public boolean hasSameRules (TimeZone other)
+  /**
+   * Returns the time zone under which the host is running.  This
+   * can be changed with setDefault.
+   * @return the time zone for this host.
+   * @see #setDefault
+   */
+  public static TimeZone getDefault()
   {
-    return this == other;
+    return defaultZone;
   }
 
-  public Object clone ()
+  public static void setDefault(TimeZone zone)
   {
-    // Just use Object's generic cloner.
-    return super.clone ();
+    defaultZone = zone;
   }
 
-  // Names of timezones.  This array is kept in parallel with
-  // rawOffsets.  This list comes from the JCL 1.1 book.
-  private static final String[] tzIDs =
+  /**
+   * Test if the other time zone uses the same rule and only
+   * possibly differs in ID.  This implementation for this particular
+   * class will return true if the raw offsets are identical.  Subclasses
+   * should override this method if they use daylight savings.
+   * @return true if this zone has the same raw offset
+   */
+  public boolean hasSameRules(TimeZone other)
   {
-    "MIT", "HST", "AST", "PST", "PNT",
-    "MST", "CST", "EST", "IET", "PRT",
-    "CNT", "AGT", "BET", "CAT", "GMT",
-    "ECT", "EET", "ART", "EAT", "MET",
-    "NET", "PLT", "IST", "BST", "VST",
-    "CTT", "JST", "ACT", "AET", "SST",
-    "NST"
-  };
-  // This holds raw offsets in milliseconds.
-  // 3600000 == 60 * 60 * 1000
-  private static final int[] rawOffsets =
+    return other.getRawOffset() == getRawOffset();
+  }
+
+  /**
+   * Returns a clone of this object.  I can't imagine, why this is
+   * useful for a time zone.
+   */
+  public Object clone()
   {
-    -11 * 3600000, -10 * 3600000, -9 * 3600000, -8 * 3600000, -7 * 3600000,
-    -7 * 3600000, -6 * 3600000, -5 * 3600000, -5 * 3600000, -4 * 3600000,
-    -35 * 360000, -3 * 3600000, -3 * 3600000, -1 * 3600000, 0,
-    1 * 3600000, 1 * 3600000, 2 * 3600000, 3 * 3600000, 35 * 360000,
-    4 * 3600000, 5 * 3600000, 55 * 360000, 6 * 3600000, 7 * 3600000,
-    8 * 3600000, 9 * 3600000, 95 * 360000, 10 * 3600000, 11 * 3600000,
-    12 * 3600000
-  };
-  // This caches all the corresponding zone objects.
-  private static TimeZone[] timeZones = new TimeZone[tzIDs.length];
+    try
+      {
+	return super.clone();
+      }
+    catch (CloneNotSupportedException ex)
+      {
+	return null;
+      }
+  }
+
+  static final TimeZone zoneGMT = new SimpleTimeZone(0, "GMT");
 }
Index: java/util/natTimeZone.cc
===================================================================
RCS file: natTimeZone.cc
diff -N natTimeZone.cc
--- /dev/null	Tue May  5 13:32:27 1998
+++ natTimeZone.cc	Mon Nov 27 19:01:47 2000
@@ -0,0 +1,72 @@
+/* Copyright (C) 2000  Free Software Foundation
+
+   This file is part of libgcj.
+
+This software is copyrighted work licensed under the terms of the
+Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
+details.  */
+
+#include <config.h>
+
+#include <gcj/cni.h>
+#include <java/util/TimeZone.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+
+/*
+ * This method returns a time zone string that is used by the static
+ * initializer in java.util.TimeZone to create the default timezone
+ * instance.  This is a key into the timezone table used by
+ * that class.
+ */
+jstring
+java::util::TimeZone::getDefaultTimeZoneId (void)
+{
+  time_t current_time;
+  char **tzinfo, *tzid;
+  long tzoffset;
+  jstring retval;
+
+  current_time = time(0);
+
+  mktime(localtime(&current_time));
+  tzinfo = tzname;
+  tzoffset = timezone;
+
+  if ((tzoffset % 3600) == 0)
+    tzoffset = tzoffset / 3600;
+
+  if (!strcmp(tzinfo[0], tzinfo[1]))  
+    {
+      tzid = (char*) _Jv_Malloc (strlen(tzinfo[0]) + 6);
+      if (!tzid)
+        return NULL;
+
+      sprintf(tzid, "%s%ld", tzinfo[0], tzoffset);
+    }
+  else
+    {
+      tzid = (char*) _Jv_Malloc (strlen(tzinfo[0]) + strlen(tzinfo[1]) + 6);
+      if (!tzid)
+        return NULL;
+
+      sprintf(tzid, "%s%ld%s", tzinfo[0], tzoffset, tzinfo[1]);
+    }
+
+  retval = JvNewStringUTF (tzid);
+  _Jv_Free (tzid);
+  return retval;
+}
+

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