diff --git a/libjava/ChangeLog b/libjava/ChangeLog index d21d0a33a4d..8176ba93e15 100644 --- a/libjava/ChangeLog +++ b/libjava/ChangeLog @@ -1,3 +1,25 @@ +2000-11-27 Warren Levy + + * 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. + 2000-11-27 Bryce McKinlay * java/util/Vector.java (ensureCapacity): Don't increment modCount. diff --git a/libjava/Makefile.am b/libjava/Makefile.am index 69131e30a17..5cd62031e6b 100644 --- a/libjava/Makefile.am +++ b/libjava/Makefile.am @@ -1229,6 +1229,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 diff --git a/libjava/Makefile.in b/libjava/Makefile.in index 279ec6ec800..da16e041f45 100644 --- a/libjava/Makefile.in +++ b/libjava/Makefile.in @@ -991,6 +991,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 @@ -1142,8 +1143,8 @@ java/lang/reflect/natArray.lo java/lang/reflect/natConstructor.lo \ 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 \ @@ -1684,8 +1685,9 @@ DEP_FILES = .deps/$(srcdir)/$(CONVERT_DIR)/gen-from-JIS.P \ .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 \ diff --git a/libjava/gnu/gcj/text/LocaleData_en.java b/libjava/gnu/gcj/text/LocaleData_en.java index cd13db0405d..c24c1121335 100644 --- a/libjava/gnu/gcj/text/LocaleData_en.java +++ b/libjava/gnu/gcj/text/LocaleData_en.java @@ -68,6 +68,16 @@ public final class LocaleData_en extends ListResourceBundle { "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, diff --git a/libjava/java/text/DateFormatSymbols.java b/libjava/java/text/DateFormatSymbols.java index b63bf369209..c8250c9344a 100644 --- a/libjava/java/text/DateFormatSymbols.java +++ b/libjava/java/text/DateFormatSymbols.java @@ -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 Object /**/ "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 Object 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 Object shortWeekdays = old.shortWeekdays; weekdays = old.weekdays; zoneStrings = old.zoneStrings; + dateFormats = old.dateFormats; + timeFormats = old.timeFormats; } public String[] getAmPmStrings() diff --git a/libjava/java/text/SimpleDateFormat.java b/libjava/java/text/SimpleDateFormat.java index feb64f0d02d..e2f70cddcc6 100644 --- a/libjava/java/text/SimpleDateFormat.java +++ b/libjava/java/text/SimpleDateFormat.java @@ -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 libgcj. +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 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 - * @date October 25, 1998. + * SimpleDateFormat provides convenient methods for parsing and formatting + * dates using Gregorian calendars (see java.util.GregorianCalendar). */ -/* 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. - */ - -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; - // Serialization method. + // 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"; + 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; } + + // Set up items normally taken care of by the constructor. + tokens = new Vector(); + compileFormat(pattern); } - public SimpleDateFormat () + private void compileFormat(String pattern) { - this("dd/MM/yy HH:mm", Locale.getDefault()); - } + // Any alphabetical characters are treated as pattern characters + // unless enclosed in single quotes. - public SimpleDateFormat (String pattern) + char thisChar; + int pos; + int field; + FieldSizePair current = null; + + for (int i=0; iDate 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 Date representing the start of the century for + * two digit years. + */ + public void set2DigitYearStart(Date date) { - String localChars = formatData.getLocalPatternChars(); - String standardChars = DateFormatSymbols.localPatternCharsDefault; - return applyLocalizedPattern (pattern, standardChars, localChars); + defaultCenturyStart = date; } - private final void append (StringBuffer buf, int value, int numDigits) + /** + * This method returns the format symbol information used for parsing + * and formatting dates. + * + * @return The date format symbols. + */ + public DateFormatSymbols getDateFormatSymbols() { - numberFormat.setMinimumIntegerDigits(numDigits); - numberFormat.format(value, buf, null); + return formatData; } - public StringBuffer format (Date date, StringBuffer buf, FieldPosition pos) + /** + * 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: + *

+ *

    + *
  • Is not null. + *
  • Is an instance of SimpleDateFormat. + *
  • Is equal to this object at the superclass (i.e., DateFormat) + * level. + *
  • Has the same formatting pattern. + *
  • Is using the same formatting symbols. + *
  • Is using the same century for two digit years. + *
+ * + * @param obj The object to compare for equality against. + * + * @return true if the specified object is equal to this object, + * false otherwise. + */ + public boolean equals(Object o) { - 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 - { - 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()); - } - } - } - return buf; - } - - 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; - } - - public Date parse (String source, ParsePosition pos) - { - int fmt_index = 0; - int fmt_max = pattern.length(); - - calendar.clear(); - int quote_start = -1; - for (; fmt_index < fmt_max; ++fmt_index) - { - 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; - } - - // 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: - 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); - } - // FIXME: what exception is thrown on an invalid - // non-lenient set? - catch (IllegalArgumentException x) - { - pos.setErrorIndex(pos.getIndex()); - return null; - } - } - - return calendar.getTime(); - } - - public boolean equals (Object obj) - { - if (! (obj instanceof SimpleDateFormat) || ! super.equals(obj) ) + if (o == null) return false; - SimpleDateFormat other = (SimpleDateFormat) obj; - return (DateFormatSymbols.equals(pattern, other.pattern) - && DateFormatSymbols.equals(formatData, other.formatData) - && DateFormatSymbols.equals(defaultCenturyStart, - other.defaultCenturyStart)); + + 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 Object clone () - { - // We know the superclass just call's Object's generic cloner. - return super.clone (); + + /** + * 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()) + { + pos.setBeginIndex(beginIndex); + pos.setEndIndex(buffer.length()); + } + } else { + buffer.append(o.toString()); + } + } + return buffer; } - public int hashCode () + 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) { - int hash = super.hashCode(); - if (pattern != null) - hash ^= pattern.hashCode(); - return hash; + 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)); + + int index = pos.getIndex(); + String buf = dateStr.substring(index, dateStr.length()); + + top: + for(;;) + { + + // 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; + } + } + } + + 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; + } + 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; + } + 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; + } + } + } + + 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); + } + } + + return theCalendar.getTime(); + } + + /** + * 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 null if the string cannot be + * parsed. + */ + public Date parse(String dateStr, ParsePosition pos) { + if (isLenient()) + return parseLenient(dateStr, pos); + else + return parseStrict(dateStr, pos); + } } + diff --git a/libjava/java/util/TimeZone.java b/libjava/java/util/TimeZone.java index 0145d72d11f..45c26f6497f 100644 --- a/libjava/java/util/TimeZone.java +++ b/libjava/java/util/TimeZone.java @@ -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. + +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 software is copyrighted work licensed under the terms of the -Libgcj License. Please consult the file "LIBGCJ_LICENSE" for -details. */ package java.util; +import java.text.DateFormatSymbols; /** - * @author Per Bothner - * @date October 24, 1998. + * This class represents a time zone offset and handles daylight savings. + * + * You can get the default time zone with getDefault. + * This represents the time zone where program is running. + * + * Another way to create a time zone is getTimeZone, where + * you can give an identifier as parameter. For instance, the identifier + * of the Central European Time zone is "CET". + * + * With the getAvailableIDs method, you can get all the + * supported time zone identifiers. + * + * @see Calendar + * @see SimpleTimeZone + * @author Jochen Hoenicke */ - -/* Written using "Java Class Libraries", 2nd edition, ISBN 0-201-31002-3. - * Status: getAvailableIDs, getDefault, getTimeZone only know about GMT. - */ - public abstract class TimeZone implements java.io.Serializable, Cloneable { + + /** + * Constant used to indicate that a short timezone abbreviation should + * be returned, such as "EST" + */ public static final int SHORT = 0; + + /** + * Constant used to indicate that a long timezone name should be + * returned, such as "Eastern Standard Time". + */ 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; + /** + * The time zone identifier, e.g. PST. + */ + private String ID; - static final TimeZone zoneGMT = new SimpleTimeZone(0, "GMT"); - - private static TimeZone zoneDefault; + /** + * 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"; + defaultZone = getTimeZone(tzid); + } + + /* This method returns us a time zone id string which is in the + form . + 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; + } + + /** + * 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; + } + + /** + * 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 (Local locale) { ... } FIXME - - 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 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 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 of the + * specified type in the current locale. + * + * @param dst Whether or not daylight savings time is in effect. + * @param style LONG for a long name, SHORT for + * a short abbreviation. + * + * @return The name of the time zone. + */ + public final String getDisplayName(boolean dst, int style) { - return ID; // FIXME + return (getDisplayName(dst, style, Locale.getDefault())); } - */ + + /** + * 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 LONG for a long name, SHORT 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) + { + DateFormatSymbols dfs; + try + { + dfs = new DateFormatSymbols(locale); + + // 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) + { + } + + return getDefaultDisplayName(dst); + } + + private String getDefaultDisplayName(boolean dst) + { + 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(); + } + + /** + * Returns true, if this time zone uses Daylight Savings Time. + */ public abstract boolean useDaylightTime(); - public abstract boolean inDaylightTime (Date date); + /** + * 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); - public static synchronized TimeZone getTimeZone (String ID) + /** + * 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) { - int i; - for (i = 0; i < tzIDs.length; ++i) + // First check timezones hash + TimeZone tz = (TimeZone) timezones.get(ID); + if (tz != null) { - if (ID.equals(tzIDs[i])) - break; - } - if (i == tzIDs.length) - return null; + if (tz.getID().equals(ID)) + return tz; - if (timeZones[i] == null) - { - if (ID.equals("GMT")) - timeZones[i] = zoneGMT; - else - timeZones[i] = new SimpleTimeZone (rawOffsets[i], tzIDs[i]); + // 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; } - return timeZones[i]; - } - - public static String[] getAvailableIDs() - { - return (String[]) tzIDs.clone(); - } - - public static String[] getAvailableIDs(int rawOffset) - { - int first, last; - - for (first = 0; first < rawOffsets.length; ++first) + // 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")) { - if (rawOffset == rawOffsets[first]) - break; - } - if (first == rawOffsets.length) - return new String[0]; - for (last = first + 1; last < rawOffsets.length; ++last) - { - if (rawOffset != rawOffsets[last]) - break; - } + int pos = 3; + int offset_direction = 1; - String[] r = new String[last - first]; - for (int i = first; i < last; ++i) - { - r[i - first] = tzIDs[i]; - } + if (ID.charAt(pos) == '-') + { + offset_direction = -1; + pos++; + } + else if (ID.charAt(pos) == '+') + { + pos++; + } - return r; - } - - 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"); } + /** + * 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 {"Phoenix", "Denver"}, 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) + { + 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; + } + ******************************************************************/ + + /** + * 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()]); + } + ******************************************************************/ + + /** + * 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 zoneDefault == null ? setDefault() : zoneDefault; + return defaultZone; } - public static void setDefault (TimeZone zone) { zoneDefault = zone; } - - public boolean hasSameRules (TimeZone other) + public static void setDefault(TimeZone zone) { - return this == other; + defaultZone = zone; } - public Object clone () + /** + * 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) { - // Just use Object's generic cloner. - return super.clone (); + return other.getRawOffset() == getRawOffset(); } - // 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 = + /** + * Returns a clone of this object. I can't imagine, why this is + * useful for a time zone. + */ + public Object clone() { - "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 = - { - -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"); } diff --git a/libjava/java/util/natTimeZone.cc b/libjava/java/util/natTimeZone.cc new file mode 100644 index 00000000000..61128c833b6 --- /dev/null +++ b/libjava/java/util/natTimeZone.cc @@ -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 + +#include +#include + +#include +#include + +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# 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(¤t_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; +} +