From 962276267861b61093a222158d78a30871e5d38b Mon Sep 17 00:00:00 2001 From: Tom Tromey Date: Thu, 10 May 2001 18:13:17 +0000 Subject: [PATCH] [multiple changes] 2001-05-10 Tom Tromey * java/util/GregorianCalendar.java: Imported from Classpath. * gnu/java/locale/LocaleInformation_nl.java: New file from Classpath. * gnu/java/locale/LocaleInformation_en.java: Likewise. * gnu/java/locale/LocaleInformation_de.java: Likewise. * gnu/java/locale/LocaleInformation.java: Likewise. * natGregorianCalendar.cc: Removed. * Makefile.in: Rebuilt. * Makefile.am (nat_source_files): Removed natGregorianCalendar.cc. 2001-05-10 Tom Tromey * java/text/SimpleDateFormat.java (computeCenturyStart): New method. (defaultCenturyStart): Use it. (readObject): Likewise. (SimpleDateFormat): Clear the calendar. Set the grouping on the number format. (parse): Copy the calendar before modifying it. Correctly handle the time zone. * java/util/Calendar.java (clear): Set field value(s) to 0. 2001-05-10 Jeff Sturm * Calendar.java (get): Clear areFieldsSet if requested field is not set. (set): Unset fields that depend on new value. From-SVN: r41942 --- libjava/ChangeLog | 32 + libjava/Makefile.am | 1 - libjava/Makefile.in | 64 +- .../gnu/java/locale/LocaleInformation.java | 37 + .../gnu/java/locale/LocaleInformation_de.java | 220 +++ .../gnu/java/locale/LocaleInformation_en.java | 332 +++++ .../gnu/java/locale/LocaleInformation_nl.java | 338 +++++ libjava/java/text/SimpleDateFormat.java | 62 +- libjava/java/util/Calendar.java | 65 +- libjava/java/util/GregorianCalendar.java | 1174 ++++++++++++++--- libjava/java/util/natGregorianCalendar.cc | 159 --- 11 files changed, 2085 insertions(+), 399 deletions(-) create mode 100644 libjava/gnu/java/locale/LocaleInformation.java create mode 100644 libjava/gnu/java/locale/LocaleInformation_de.java create mode 100644 libjava/gnu/java/locale/LocaleInformation_en.java create mode 100644 libjava/gnu/java/locale/LocaleInformation_nl.java delete mode 100644 libjava/java/util/natGregorianCalendar.cc diff --git a/libjava/ChangeLog b/libjava/ChangeLog index 5a95e2fc84d..c803cd672b0 100644 --- a/libjava/ChangeLog +++ b/libjava/ChangeLog @@ -1,3 +1,35 @@ +2001-05-10 Tom Tromey + + * java/util/GregorianCalendar.java: Imported from Classpath. + * gnu/java/locale/LocaleInformation_nl.java: New file from + Classpath. + * gnu/java/locale/LocaleInformation_en.java: Likewise. + * gnu/java/locale/LocaleInformation_de.java: Likewise. + * gnu/java/locale/LocaleInformation.java: Likewise. + * natGregorianCalendar.cc: Removed. + * Makefile.in: Rebuilt. + * Makefile.am (nat_source_files): Removed + natGregorianCalendar.cc. + +2001-05-10 Tom Tromey + + * java/text/SimpleDateFormat.java (computeCenturyStart): New + method. + (defaultCenturyStart): Use it. + (readObject): Likewise. + (SimpleDateFormat): Clear the calendar. Set the grouping on the + number format. + (parse): Copy the calendar before modifying it. Correctly handle + the time zone. + + * java/util/Calendar.java (clear): Set field value(s) to 0. + +2001-05-10 Jeff Sturm + + * Calendar.java (get): Clear areFieldsSet if requested field + is not set. + (set): Unset fields that depend on new value. + 2001-05-06 Bryce McKinlay * java/lang/Class.h (_Jv_Self): New union type. diff --git a/libjava/Makefile.am b/libjava/Makefile.am index 0a61a4f998f..d5644a1590d 100644 --- a/libjava/Makefile.am +++ b/libjava/Makefile.am @@ -1335,7 +1335,6 @@ java/net/natInetAddress.cc \ java/net/natPlainDatagramSocketImpl.cc \ java/net/natPlainSocketImpl.cc \ java/text/natCollator.cc \ -java/util/natGregorianCalendar.cc \ java/util/zip/natDeflater.cc \ java/util/zip/natInflater.cc diff --git a/libjava/Makefile.in b/libjava/Makefile.in index b191f8602e5..d4435b6d8d3 100644 --- a/libjava/Makefile.in +++ b/libjava/Makefile.in @@ -73,6 +73,7 @@ CXXCPP = @CXXCPP@ DIRLTDL = @DIRLTDL@ DIVIDESPEC = @DIVIDESPEC@ DLLTOOL = @DLLTOOL@ +EXCEPTIONSPEC = @EXCEPTIONSPEC@ EXEEXT = @EXEEXT@ GCDEPS = @GCDEPS@ GCINCS = @GCINCS@ @@ -119,29 +120,43 @@ here = @here@ libgcj_basedir = @libgcj_basedir@ AUTOMAKE_OPTIONS = foreign -@TESTSUBDIR_TRUE@SUBDIRS = @TESTSUBDIR_TRUE@$(DIRLTDL) testsuite gcj include -@TESTSUBDIR_FALSE@SUBDIRS = @TESTSUBDIR_FALSE@$(DIRLTDL) gcj include -@USE_LIBDIR_TRUE@toolexeclibdir = @USE_LIBDIR_TRUE@$(libdir)$(MULTISUBDIR) -@USE_LIBDIR_FALSE@toolexeclibdir = @USE_LIBDIR_FALSE@$(toolexecdir)/lib$(MULTISUBDIR) -@USE_LIBDIR_FALSE@toolexecdir = @USE_LIBDIR_FALSE@$(exec_prefix)/$(target_alias) -@XLIB_AWT_TRUE@cond_x_ltlibrary = @XLIB_AWT_TRUE@libgcjx.la -@XLIB_AWT_FALSE@cond_x_ltlibrary = +@TESTSUBDIR_TRUE@SUBDIRS = \ +@TESTSUBDIR_TRUE@$(DIRLTDL) testsuite gcj include +@TESTSUBDIR_FALSE@SUBDIRS = \ +@TESTSUBDIR_FALSE@$(DIRLTDL) gcj include +@USE_LIBDIR_TRUE@toolexeclibdir = \ +@USE_LIBDIR_TRUE@$(libdir)$(MULTISUBDIR) +@USE_LIBDIR_FALSE@toolexeclibdir = \ +@USE_LIBDIR_FALSE@$(toolexecdir)/lib$(MULTISUBDIR) +@USE_LIBDIR_FALSE@toolexecdir = \ +@USE_LIBDIR_FALSE@$(exec_prefix)/$(target_alias) +@XLIB_AWT_TRUE@cond_x_ltlibrary = \ +@XLIB_AWT_TRUE@libgcjx.la +@XLIB_AWT_FALSE@cond_x_ltlibrary = \ toolexeclib_LTLIBRARIES = libgcj.la $(cond_x_ltlibrary) toolexeclib_DATA = libgcj.spec data_DATA = libgcj.jar -@NEEDS_DATA_START_TRUE@toolexeclib_LIBRARIES = @NEEDS_DATA_START_TRUE@libgcjdata.a -@NEEDS_DATA_START_TRUE@libgcjdata_a_SOURCES = @NEEDS_DATA_START_TRUE@libgcjdata.c +@NEEDS_DATA_START_TRUE@toolexeclib_LIBRARIES = \ +@NEEDS_DATA_START_TRUE@libgcjdata.a +@NEEDS_DATA_START_TRUE@libgcjdata_a_SOURCES = \ +@NEEDS_DATA_START_TRUE@libgcjdata.c -@NATIVE_TRUE@bin_PROGRAMS = @NATIVE_TRUE@jv-convert gij +@NATIVE_TRUE@bin_PROGRAMS = \ +@NATIVE_TRUE@jv-convert gij bin_SCRIPTS = addr2name.awk -@CANADIAN_TRUE@@NULL_TARGET_TRUE@ZIP = @CANADIAN_TRUE@@NULL_TARGET_TRUE@$(MULTIBUILDTOP)../$(COMPPATH)/fastjar/fastjar$(EXEEXT) -@CANADIAN_TRUE@@NULL_TARGET_FALSE@ZIP = @CANADIAN_TRUE@@NULL_TARGET_FALSE@fastjar -@CANADIAN_FALSE@ZIP = @CANADIAN_FALSE@$(MULTIBUILDTOP)../$(COMPPATH)/fastjar/fastjar$(EXEEXT) -@CANADIAN_TRUE@GCJH = @CANADIAN_TRUE@gcjh -@CANADIAN_FALSE@GCJH = @CANADIAN_FALSE@$(MULTIBUILDTOP)../$(COMPPATH)/gcc/gcjh$(EXEEXT) +@CANADIAN_TRUE@@NULL_TARGET_TRUE@ZIP = \ +@CANADIAN_TRUE@@NULL_TARGET_TRUE@$(MULTIBUILDTOP)../$(COMPPATH)/fastjar/fastjar$(EXEEXT) +@CANADIAN_TRUE@@NULL_TARGET_FALSE@ZIP = \ +@CANADIAN_TRUE@@NULL_TARGET_FALSE@fastjar +@CANADIAN_FALSE@ZIP = \ +@CANADIAN_FALSE@$(MULTIBUILDTOP)../$(COMPPATH)/fastjar/fastjar$(EXEEXT) +@CANADIAN_TRUE@GCJH = \ +@CANADIAN_TRUE@gcjh +@CANADIAN_FALSE@GCJH = \ +@CANADIAN_FALSE@$(MULTIBUILDTOP)../$(COMPPATH)/gcc/gcjh$(EXEEXT) GCJ_WITH_FLAGS = $(GCJ) --encoding=UTF-8 @@ -160,8 +175,10 @@ AM_CXXFLAGS = -fno-rtti -fvtable-thunks -fnon-call-exceptions \ -fdollars-in-identifiers \ @LIBGCJ_CXXFLAGS@ @X_CFLAGS@ $(WARNINGS) -D_GNU_SOURCE -@USING_GCC_TRUE@AM_CFLAGS = @USING_GCC_TRUE@@LIBGCJ_CFLAGS@ $(WARNINGS) -@USING_GCC_FALSE@AM_CFLAGS = @USING_GCC_FALSE@@LIBGCJ_CFLAGS@ +@USING_GCC_TRUE@AM_CFLAGS = \ +@USING_GCC_TRUE@@LIBGCJ_CFLAGS@ $(WARNINGS) +@USING_GCC_FALSE@AM_CFLAGS = \ +@USING_GCC_FALSE@@LIBGCJ_CFLAGS@ JCFLAGS = -g JC1FLAGS = @LIBGCJ_JAVAFLAGS@ $(GCJFLAGS) @@ -229,7 +246,8 @@ extra_headers = java/lang/Object.h java/lang/Class.h NM = nm -@NATIVE_TRUE@@MAINTAINER_MODE_TRUE@noinst_PROGRAMS = @NATIVE_TRUE@@MAINTAINER_MODE_TRUE@gen-from-JIS +@NATIVE_TRUE@@MAINTAINER_MODE_TRUE@noinst_PROGRAMS = \ +@NATIVE_TRUE@@MAINTAINER_MODE_TRUE@gen-from-JIS CONVERT_DIR = gnu/gcj/convert @@ -1060,7 +1078,6 @@ java/net/natInetAddress.cc \ java/net/natPlainDatagramSocketImpl.cc \ java/net/natPlainSocketImpl.cc \ java/text/natCollator.cc \ -java/util/natGregorianCalendar.cc \ java/util/zip/natDeflater.cc \ java/util/zip/natInflater.cc @@ -1212,8 +1229,7 @@ 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/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 \ @@ -1813,8 +1829,8 @@ DEP_FILES = .deps/$(srcdir)/$(CONVERT_DIR)/gen-from-JIS.P \ .deps/java/util/jar/JarEntry.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/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 \ @@ -2199,7 +2215,7 @@ distdir: $(DISTFILES) @for file in $(DISTFILES); do \ d=$(srcdir); \ if test -d $$d/$$file; then \ - cp -pr $$d/$$file $(distdir)/$$file; \ + cp -pr $$/$$file $(distdir)/$$file; \ else \ test -f $(distdir)/$$file \ || ln $$d/$$file $(distdir)/$$file 2> /dev/null \ diff --git a/libjava/gnu/java/locale/LocaleInformation.java b/libjava/gnu/java/locale/LocaleInformation.java new file mode 100644 index 00000000000..452820a18f6 --- /dev/null +++ b/libjava/gnu/java/locale/LocaleInformation.java @@ -0,0 +1,37 @@ +/* LocaleInformation.java -- Default locale information + Copyright (C) 1998 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. */ + + +package gnu.java.locale; + +/** + * This is the resource bundle for the default locale, which right now is + * hardcoded to US English. + */ +public class LocaleInformation extends LocaleInformation_en +{ +} + diff --git a/libjava/gnu/java/locale/LocaleInformation_de.java b/libjava/gnu/java/locale/LocaleInformation_de.java new file mode 100644 index 00000000000..5eea9057cf4 --- /dev/null +++ b/libjava/gnu/java/locale/LocaleInformation_de.java @@ -0,0 +1,220 @@ +/* LocaleInformation_de.java -- German locale data + Copyright (C) 1999 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. */ + + +package gnu.java.locale; + +import java.util.ListResourceBundle; +import java.util.Calendar; + +/** + * This class contains locale data for the German locale + * @author Jochen Hoenicke + */ +public class LocaleInformation_de extends ListResourceBundle +{ + +/* + * This area is used for defining object values + */ + +/** + * This is the set of collation rules used by java.text.RuleBasedCollator + * to sort strings properly. See the documentation of that class for the + * proper format. + */ +private static final String collation_rules = + "-<0,1<2<3<4<5<6<7<8<9 + * This is the same rule as used in the English locale. + */ +private static final String collation_rules = + "-<0,1<2<3<4<5<6<7<8<9A,a + * This is the same list as used in the English local + */ +private static final String[] word_breaks = { " ", "\t", "\r\n", "\n" }; + +/** + * This is the list of sentence break sequences used by + * java.text.BreakIterator + *

+ * This is the same list as used in the English local + */ +private static final String[] sentence_breaks = { ". " }; + +/** + * This is the list of potential line break locations. + *

+ * This is the same list as used in the English local + */ +private static final String[] line_breaks = { "\t", "-", "\r\n", + "\n", ". ", ". ", ".", "? ", "? ", "?", "! ", "! ", "!", ", ", " " }; + +/** + * This is the list of months, fully spelled out + */ +private static final String[] months = { "januari", "februari", "maart", + "april", "mei", "juni", "juli", "augustus", "september", "october", + "november", "december", null }; + +/** + * This is the list of abbreviated month names + */ +private static final String[] shortMonths = { "jan", "feb", "mrt", "apr", "mei", + "jun", "jul", "aug", "sep", "oct", "nov", "dec", null }; + +/** + * This is the list of weekdays, fully spelled out + */ +private static final String[] weekdays = { null, "zondag", "maandag", "dinsdag", + "woensdag", "donderdag", "vrijdag", "zaterdag" }; + +/** + * This is the list of abbreviated weekdays + */ +private static final String[] shortWeekdays = { null, "zo", "ma", "di", "wo", + "do", "vr", "za" }; + +/** + * This is the list of AM/PM strings + *

+ * Is there a real equivalent in Dutch? "Voormiddag"/"Namiddag"? + * Just using the Latin names for now. + */ +private static final String[] ampms = { "AM", "PM" }; + +/** + * This is the list of era identifiers + *

+ * Is there a real equivalent in Dutch? "voor Christus"/"na Christus"? + * Just use the Latin/English names for now. + */ +private static final String[] eras = { "BC", "AD" }; + +/** + * This is the list of timezone strings. The JDK appears to include a + * city name as the sixth element. + * XXX - TODO - FIXME - Which timezones should be included here and how are + * they called? + */ +private static final String[][] zoneStrings = +{ + // European time zones. The city names are a little bit random. + { "WET", "West Europese Tijd", "WET", "West Europese Zomertijd", "WEST", "London" }, + { "CET", "Centraal Europese Tijd", "CET", "Centraal Europese Zomertijd", "CEST", "Amsterdam" }, + { "EET", "Oost Europese Tijd", "EET", "Oost Europese Zomertijd", "EEST", "Istanbul" }, +}; + +/** + * This is the list of pattern characters for formatting dates + *

+ * This is the same as the English locale uses: era (G), year (y), month (M), + * month (d), hour from 1-12 (h), hour 0-23 (H), minute (m), second (s), + * millisecond (S), date of week (E), date of year (D), + * day of week in month (F), week in year (w), week in month (W), am/pm (a), + * hour from 1-24 (k), hour from 0-11 (K), time zone (z). + * Why would you use others? + */ +private static final String localPatternChars = "GyMdhHmsSEDFwWakKz"; // Not a mistake! + +/** + * This is the DateFormat.SHORT date format + */ +private static final String shortDateFormat = "dd-MM-yy"; + +/** + * This is the DateFormat.MEDIUM format + */ +private static final String mediumDateFormat = "dd-MMM-yy"; + +/** + * This is the DateFormat.LONG format + */ +private static final String longDateFormat = "dd MMMM yyyy"; + +/** + * This is the DateFormat.FULL format + */ +private static final String fullDateFormat = "EEEE dd MMMM yyyy"; + +/** + * This is the DateFormat.DEFAULT format + */ +private static final String defaultDateFormat = mediumDateFormat; + +/** + * This is the TimeFormat.SHORT format + */ +private static final String shortTimeFormat = "HH:mm"; + +/** + * This is the TimeFormat.MEDIUM format + */ +private static final String mediumTimeFormat = "HH:mm:ss"; + +/** + * This is the TimeFormat.LONG format + */ +private static final String longTimeFormat = "HH:mm:ss"; + +/** + * This is the TimeFormat.FULL format + */ +private static final String fullTimeFormat = "HH:mm:ss z"; + +/** + * This is the TimeFormat.DEFAULT format + */ +private static final String defaultTimeFormat = shortTimeFormat; + +/** + * This is the currency symbol + */ +private static final String currencySymbol = "fl"; + +/** + * This is the international currency symbol. + */ +private static final String intlCurrencySymbol = "NLG"; + +/** + * This is the decimal point. + */ +private static final String decimalSeparator = ","; + +/** + * This is the exponential symbol + */ +private static final String exponential = "E"; + +/** + * This is the char used for digits in format strings + */ +private static final String digit = "#"; + +/** + * This is the grouping separator symbols + */ +private static final String groupingSeparator = ","; + +/** + * This is the symbols for infinity + */ +private static final String infinity = "\u221e"; + +/** + * This is the symbol for the not a number value + */ +private static final String NaN = "\ufffd"; + +/** + * This is the minus sign symbol. + */ +private static final String minusSign = "-"; + +/** + * This is the decimal separator in monetary values. + */ +private static final String monetarySeparator = ","; + +/** + * This is the separator between positive and negative subpatterns. + */ +private static final String patternSeparator = ";"; + +/** + * This is the percent sign + */ +private static final String percent = "%"; + +/** + * This is the per mille sign + */ +private static final String perMill = "\u2030"; + +/** + * This is the character for zero. + */ +private static final String zeroDigit = "0"; + +/*************************************************************************/ + +/** + * This is the object array used to hold the keys and values + * for this bundle + */ + +private static final Object[][] contents = +{ + // For RuleBasedCollator + { "collation_rules", collation_rules }, + // For BreakIterator + { "word_breaks", word_breaks }, + { "sentence_breaks", sentence_breaks }, + { "line_breaks", line_breaks }, + // For SimpleDateFormat/DateFormatSymbols + { "months", months }, + { "shortMonths", shortMonths }, + { "weekdays", weekdays }, + { "shortWeekdays", shortWeekdays }, + { "ampms", ampms }, + { "eras", eras }, + { "zoneStrings", zoneStrings }, + { "localPatternChars", localPatternChars }, + { "shortDateFormat", shortDateFormat }, + { "mediumDateFormat", mediumDateFormat }, + { "longDateFormat", longDateFormat }, + { "fullDateFormat", fullDateFormat }, + { "defaultDateFormat", defaultDateFormat }, + { "shortTimeFormat", shortTimeFormat }, + { "mediumTimeFormat", mediumTimeFormat }, + { "longTimeFormat", longTimeFormat }, + { "fullTimeFormat", fullTimeFormat }, + { "defaultTimeFormat", defaultTimeFormat }, + // For DecimalFormat/DecimalFormatSymbols + { "currencySymbol", currencySymbol }, + { "intlCurrencySymbol", intlCurrencySymbol }, + { "decimalSeparator", decimalSeparator }, + { "digit", digit }, + { "exponential", exponential }, + { "groupingSeparator", groupingSeparator }, + { "infinity", infinity }, + { "NaN", NaN }, + { "minusSign", minusSign }, + { "monetarySeparator", monetarySeparator }, + { "patternSeparator", patternSeparator }, + { "percent", percent }, + { "perMill", perMill }, + { "zeroDigit", zeroDigit }, +}; + +/*************************************************************************/ + +/** + * This method returns the object array of key, value pairs containing + * the data for this bundle. + * + * @return The key, value information. + */ +public Object[][] +getContents() +{ + return(contents); +} + +} // class LocaleInformation_nl diff --git a/libjava/java/text/SimpleDateFormat.java b/libjava/java/text/SimpleDateFormat.java index 527fcc87ada..f0976054c63 100644 --- a/libjava/java/text/SimpleDateFormat.java +++ b/libjava/java/text/SimpleDateFormat.java @@ -62,8 +62,7 @@ public class SimpleDateFormat extends DateFormat private transient Vector tokens; private DateFormatSymbols formatData; // formatData - private Date defaultCenturyStart = - new Date(System.currentTimeMillis() - (80*365*24*60*60*1000)); + private Date defaultCenturyStart = computeCenturyStart (); private String pattern; private int serialVersionOnStream = 1; // 0 indicates JDK1.1.3 or earlier private static final long serialVersionUID = 4774881970558875024L; @@ -79,8 +78,7 @@ public class SimpleDateFormat extends DateFormat stream.defaultReadObject(); if (serialVersionOnStream < 1) { - defaultCenturyStart = - new Date(System.currentTimeMillis() - (80*365*24*60*60*1000)); + defaultCenturyStart = computeCenturyStart (); serialVersionOnStream = 1; } @@ -161,11 +159,14 @@ public class SimpleDateFormat extends DateFormat super(); Locale locale = Locale.getDefault(); calendar = new GregorianCalendar(locale); + calendar.clear (); tokens = new Vector(); formatData = new DateFormatSymbols(locale); - pattern = formatData.dateFormats[DEFAULT]+' '+formatData.timeFormats[DEFAULT]; + pattern = (formatData.dateFormats[DEFAULT] + ' ' + + formatData.timeFormats[DEFAULT]); compileFormat(pattern); numberFormat = NumberFormat.getInstance(locale); + numberFormat.setGroupingUsed (false); } /** @@ -185,20 +186,24 @@ public class SimpleDateFormat extends DateFormat { super(); calendar = new GregorianCalendar(locale); + calendar.clear (); tokens = new Vector(); formatData = new DateFormatSymbols(locale); compileFormat(pattern); this.pattern = pattern; numberFormat = NumberFormat.getInstance(locale); + numberFormat.setGroupingUsed (false); } /** * Creates a date formatter using the specified pattern. The * specified DateFormatSymbols will be used when formatting. */ - public SimpleDateFormat(String pattern, DateFormatSymbols formatData) { + public SimpleDateFormat(String pattern, DateFormatSymbols formatData) + { super(); calendar = new GregorianCalendar(); + calendar.clear (); // FIXME: XXX: Is it really necessary to set the timezone? // The Calendar constructor is supposed to take care of this. calendar.setTimeZone(TimeZone.getDefault()); @@ -207,6 +212,7 @@ public class SimpleDateFormat extends DateFormat compileFormat(pattern); this.pattern = pattern; numberFormat = NumberFormat.getInstance(); + numberFormat.setGroupingUsed (false); } // What is the difference between localized and unlocalized? The @@ -377,7 +383,8 @@ public class SimpleDateFormat extends DateFormat * appending to the specified StringBuffer. The input StringBuffer * is returned as output for convenience. */ - public StringBuffer format(Date date, StringBuffer buffer, FieldPosition pos) { + public StringBuffer format(Date date, StringBuffer buffer, FieldPosition pos) + { String temp; Calendar theCalendar = (Calendar) calendar.clone(); theCalendar.setTime(date); @@ -507,7 +514,10 @@ public class SimpleDateFormat extends DateFormat int fmt_index = 0; int fmt_max = pattern.length(); - calendar.clear(); + // We copy the Calendar because if we don't we will modify it and + // then this.equals() will no longer have the desired result. + Calendar theCalendar = (Calendar) calendar.clone (); + theCalendar.clear(); int quote_start = -1; for (; fmt_index < fmt_max; ++fmt_index) { @@ -553,7 +563,6 @@ public class SimpleDateFormat extends DateFormat boolean is_numeric = true; String[] match = null; int offset = 0; - int zone_number = 0; switch (ch) { case 'd': @@ -626,6 +635,7 @@ public class SimpleDateFormat extends DateFormat // We need a special case for the timezone, because it // uses a different data structure than the other cases. is_numeric = false; + // We don't actually use this; see below. calendar_field = Calendar.DST_OFFSET; String[][] zoneStrings = formatData.getZoneStrings(); int zoneCount = zoneStrings.length; @@ -642,11 +652,11 @@ public class SimpleDateFormat extends DateFormat } 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])); + found_zone = true; + TimeZone tz = TimeZone.getTimeZone (strings[0]); + theCalendar.setTimeZone (tz); + theCalendar.clear (Calendar.DST_OFFSET); + theCalendar.clear (Calendar.ZONE_OFFSET); pos.setIndex(index + strings[k].length()); break; } @@ -690,15 +700,16 @@ public class SimpleDateFormat extends DateFormat value = i; } else - value = zone_number; + value = 0; // Assign the value and move on. - calendar.set(calendar_field, value); + if (calendar_field != Calendar.DST_OFFSET) + theCalendar.set(calendar_field, value); } try { - return calendar.getTime(); + return theCalendar.getTime(); } catch (IllegalArgumentException x) { @@ -706,4 +717,21 @@ public class SimpleDateFormat extends DateFormat return null; } } + + // Compute the start of the current century as defined by + // get2DigitYearStart. + private Date computeCenturyStart () + { + // Compute the current year. We assume a year has 365 days. Then + // compute 80 years ago, and finally reconstruct the number of + // milliseconds. We do this computation in this strange way + // because it lets us easily truncate the milliseconds, seconds, + // etc, which don't matter and which confuse + // SimpleDateFormat.equals(). + long now = System.currentTimeMillis (); + now /= 365L * 24L * 60L * 60L * 1000L; + now -= 80; + now *= 365L * 24L * 60L * 60L * 1000L; + return new Date (now); + } } diff --git a/libjava/java/util/Calendar.java b/libjava/java/util/Calendar.java index 05c3f633eb1..17f4c7756e5 100644 --- a/libjava/java/util/Calendar.java +++ b/libjava/java/util/Calendar.java @@ -1,5 +1,5 @@ /* java.util.Calendar - Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc. + Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -54,7 +54,7 @@ import java.io.*; * * When computing the date from time fields, it may happen, that there * are either two few fields set, or some fields are inconsistent. This - * cases will handled in a calender specific way. Missing fields are + * cases will handled in a calendar specific way. Missing fields are * replaced by the fields of the epoch: 1970 January 1 00:00.
* * To understand, how the day of year is computed out of the fields @@ -356,7 +356,7 @@ public abstract class Calendar implements Serializable, Cloneable private static final String bundleName = "gnu.java.locale.Calendar"; /** - * Constructs a new Calender with the default time zone and the default + * Constructs a new Calendar with the default time zone and the default * locale. */ protected Calendar() @@ -365,7 +365,7 @@ public abstract class Calendar implements Serializable, Cloneable } /** - * Constructs a new Calender with the given time zone and the given + * Constructs a new Calendar with the given time zone and the given * locale. * @param zone a time zone. * @param locale a locale. @@ -483,7 +483,7 @@ public abstract class Calendar implements Serializable, Cloneable } /** - * Sets this Calender's time to the given Date. All time fields + * Sets this Calendar's time to the given Date. All time fields * are invalidated by this method. */ public final void setTime(Date date) @@ -503,7 +503,7 @@ public abstract class Calendar implements Serializable, Cloneable } /** - * Sets this Calender's time to the given Time. All time fields + * Sets this Calendar's time to the given Time. All time fields * are invalidated by this method. * @param time the time in milliseconds since the epoch */ @@ -522,6 +522,9 @@ public abstract class Calendar implements Serializable, Cloneable */ public final int get(int field) { + // If the requested field is invalid, force all fields to be recomputed. + if (!isSet[field]) + areFieldsSet = false; complete(); return fields[field]; } @@ -551,6 +554,29 @@ public abstract class Calendar implements Serializable, Cloneable isTimeSet = false; fields[field] = value; isSet[field] = true; + switch (field) + { + case YEAR: + case MONTH: + case DATE: + isSet[WEEK_OF_YEAR] = false; + isSet[DAY_OF_YEAR] = false; + isSet[WEEK_OF_MONTH] = false; + isSet[DAY_OF_WEEK] = false; + isSet[DAY_OF_WEEK_IN_MONTH] = false; + break; + case AM_PM: + isSet[HOUR_OF_DAY] = false; + break; + case HOUR_OF_DAY: + isSet[AM_PM] = false; + isSet[HOUR] = false; + break; + case HOUR: + isSet[AM_PM] = false; + isSet[HOUR_OF_DAY] = false; + break; + } } /** @@ -568,6 +594,11 @@ public abstract class Calendar implements Serializable, Cloneable fields[MONTH] = month; fields[DATE] = date; isSet[YEAR] = isSet[MONTH] = isSet[DATE] = true; + isSet[WEEK_OF_YEAR] = false; + isSet[DAY_OF_YEAR] = false; + isSet[WEEK_OF_MONTH] = false; + isSet[DAY_OF_WEEK] = false; + isSet[DAY_OF_WEEK_IN_MONTH] = false; } /** @@ -584,6 +615,8 @@ public abstract class Calendar implements Serializable, Cloneable fields[HOUR_OF_DAY] = hour; fields[MINUTE] = minute; isSet[HOUR_OF_DAY] = isSet[MINUTE] = true; + isSet[AM_PM] = false; + isSet[HOUR] = false; } /** @@ -611,7 +644,10 @@ public abstract class Calendar implements Serializable, Cloneable isTimeSet = false; areFieldsSet = false; for (int i = 0; i < FIELD_COUNT; i++) - isSet[i] = false; + { + isSet[i] = false; + fields[i] = 0; + } } /** @@ -623,6 +659,7 @@ public abstract class Calendar implements Serializable, Cloneable isTimeSet = false; areFieldsSet = false; isSet[field] = false; + fields[field] = 0; } /** @@ -647,7 +684,7 @@ public abstract class Calendar implements Serializable, Cloneable } /** - * Compares the given calender with this. + * Compares the given calendar with this. * @param o the object to that we should compare. * @return true, if the given object is a calendar, that represents * the same time (but doesn't neccessary have the same fields). @@ -670,10 +707,10 @@ public abstract class Calendar implements Serializable, Cloneable } /** - * Compares the given calender with this. + * Compares the given calendar with this. * @param o the object to that we should compare. * @return true, if the given object is a calendar, and this calendar - * represents a smaller time than the calender o. + * represents a smaller time than the calendar o. * @exception ClassCastException if o is not an calendar. * @since JDK1.2 you don't need to override this method */ @@ -683,10 +720,10 @@ public abstract class Calendar implements Serializable, Cloneable } /** - * Compares the given calender with this. + * Compares the given calendar with this. * @param o the object to that we should compare. * @return true, if the given object is a calendar, and this calendar - * represents a bigger time than the calender o. + * represents a bigger time than the calendar o. * @exception ClassCastException if o is not an calendar. * @since JDK1.2 you don't need to override this method */ @@ -866,7 +903,7 @@ public abstract class Calendar implements Serializable, Cloneable * @since jdk1.2 */ // FIXME: XXX: Not abstract in JDK 1.2. - // public abstract int getActualMinimum(int field); + public abstract int getActualMinimum(int field); /** * Gets the actual maximum value that is allowed for the specified field. @@ -876,7 +913,7 @@ public abstract class Calendar implements Serializable, Cloneable * @since jdk1.2 */ // FIXME: XXX: Not abstract in JDK 1.2. - // public abstract int getActualMaximum(int field); + public abstract int getActualMaximum(int field); /** * Return a clone of this object. diff --git a/libjava/java/util/GregorianCalendar.java b/libjava/java/util/GregorianCalendar.java index cc06c2919d2..912efdf7eb8 100644 --- a/libjava/java/util/GregorianCalendar.java +++ b/libjava/java/util/GregorianCalendar.java @@ -1,265 +1,1071 @@ -/* Copyright (C) 1998, 1999, 2000 Free Software Foundation +/* java.util.GregorianCalendar + Copyright (C) 1998, 1999, 2001 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; /** - * @author Per Bothner - * @date October 24, 1998. - */ - -/* Written using "Java Class Libraries", 2nd edition, ISBN 0-201-31002-3, - * and "The Java Language Specification", ISBN 0-201-63451-1. - * Status: "leniency" is not handled, and neither is roll-over in - * add and roll. This is partly because of unclear specification. - * hashCode has no spec. + * This class represents the Gregorian calendar, that is used in most + * countries all over the world. It does also handle the Julian calendar + * for dates smaller than the date of the change to the Gregorian calendar. + * This change date is different from country to country, you can set it with + * setGregorianChange + * + * The Gregorian calendar differs from the Julian calendar by a different + * leap year rule (no leap year every 100 years, except if year is divisible + * by 400). The non existing days that were omited when the change took + * place are interpreted as gregorian date + * + * There are to eras available for the Gregorian calendar, namely BC and AD. + * + * @see Calendar + * @see TimeZone */ - -public class GregorianCalendar extends Calendar { +public class GregorianCalendar extends Calendar +{ + /** + * Constant representing the era BC (before Christ). + */ public static final int BC = 0; + + /** + * Constant representing the era AD (Anno Domini). + */ public static final int AD = 1; - // The fields are as specified in Sun's "Serialized Form" - // in the JDK 1.2 beta 4 API specification. - // Value from a simple test program (getGregorianChange.getTime()). - long gregorianCutover = -12219292800000L; - - private final static int[] mins = { - 0 /* ERA */, - 1 /* YEAR */, - 0 /* MONTH */, - 0 /* WEEK_OF_YEAR */, - 0 /* WEEK_OF_MONTH */, - 1 /* DATE */, - 1 /* DAY_OF_YEAR */, - 1 /* DAY_OF_WEEK */, - -1 /* DAY_OF_WEEK_IN_MONTH */, - 0 /* AM_PM */, - 0 /* HOUR */, - 0 /* HOUR_OF_DAY */, - 0 /* MINUTE */, - 0 /* SECOND */, - 0 /* MILLISECOND */, - -43200000 /* ZONE_OFFSET */, - 0 /* DST_OFFSET */ - }; - - private final static int[] maxs = { - 1 /* ERA */, - 5000000 /* YEAR */, - 11 /* MONTH */, - 54 /* WEEK_OF_YEAR */, - 6 /* WEEK_OF_MONTH */, - 31 /* DATE */, - 366 /* DAY_OF_YEAR */, - 7 /* DAY_OF_WEEK */, - 6 /* DAY_OF_WEEK_IN_MONTH */, - 1 /* AM_PM */, - 12 /* HOUR */, - 23 /* HOUR_OF_DAY */, - 59 /* MINUTE */, - 59 /* SECOND */, - 999 /* MILLISECOND */, - 43200000 /* ZONE_OFFSET */, - 3600000 /* DST_OFFSET */ - }; - - private final static int[] leastMaximums = { - 1 /* ERA */, - 5000000 /* YEAR */, - 11 /* MONTH */, - 53 /* WEEK_OF_YEAR */, - 6 /* WEEK_OF_MONTH */, - 28 /* DATE */, - 365 /* DAY_OF_YEAR */, - 7 /* DAY_OF_WEEK */, - 4 /* DAY_OF_WEEK_IN_MONTH */, - 1 /* AM_PM */, - 11 /* HOUR */, - 23 /* HOUR_OF_DAY */, - 59 /* MINUTE */, - 59 /* SECOND */, - 999 /* MILLISECOND */, - 43200000 /* ZONE_OFFSET */, - 3600000 /* DST_OFFSET */ - }; - - private static final long serialVersionUID = -8125100834729963327L; - - public GregorianCalendar () + /** + * The point at which the Gregorian calendar rules were used. + * This is locale dependent; the default for most catholic + * countries is midnight (UTC) on October 5, 1582 (Julian), + * or October 15, 1582 (Gregorian). + */ + private long gregorianCutover; + + static final long serialVersionUID = -8125100834729963327L; + + /** + * The name of the resource bundle. + */ + private static final String bundleName = "gnu.java.locale.Calendar"; + + /** + * Constructs a new GregorianCalender representing the current + * time, using the default time zone and the default locale. + */ + public GregorianCalendar() + { + this(TimeZone.getDefault(), Locale.getDefault()); + } + + /** + * Constructs a new GregorianCalender representing the current + * time, using the specified time zone and the default locale. + * @param zone a time zone. + */ + public GregorianCalendar(TimeZone zone) + { + this(zone, Locale.getDefault()); + } + + /** + * Constructs a new GregorianCalender representing the current + * time, using the default time zone and the specified locale. + * @param locale a locale. + */ + public GregorianCalendar(Locale locale) + { + this(TimeZone.getDefault(), locale); + } + + /** + * Constructs a new GregorianCalender representing the current + * time with the given time zone and the given locale. + * @param zone a time zone. + * @param locale a locale. + */ + public GregorianCalendar(TimeZone zone, Locale locale) + { + super(zone, locale); + ResourceBundle rb = ResourceBundle.getBundle(bundleName, locale); + gregorianCutover = ((Date) rb.getObject("gregorianCutOver")).getTime(); + time = System.currentTimeMillis(); + isTimeSet = true; + areFieldsSet = false; + } + + /** + * Constructs a new GregorianCalendar representing midnight on the + * given date with the default time zone and locale. + * @param year corresponds to the YEAR time field. + * @param month corresponds to the MONTH time field. + * @param day corresponds to the DAY time field. + */ + public GregorianCalendar(int year, int month, int day) + { + super(); + set(year, month, day); + } + + /** + * Constructs a new GregorianCalendar representing midnight on the + * given date with the default time zone and locale. + * @param year corresponds to the YEAR time field. + * @param month corresponds to the MONTH time field. + * @param day corresponds to the DAY time field. + * @param hour corresponds to the HOUR_OF_DAY time field. + * @param minute corresponds to the MINUTE time field. + */ + public GregorianCalendar(int year, int month, int day, int hour, int minute) { - this(TimeZone.getDefault (), Locale.getDefault ()); + super(); + set(year, month, day, hour, minute); } - public GregorianCalendar (TimeZone zone) + /** + * Constructs a new GregorianCalendar representing midnight on the + * given date with the default time zone and locale. + * @param year corresponds to the YEAR time field. + * @param month corresponds to the MONTH time field. + * @param day corresponds to the DAY time field. + * @param hour corresponds to the HOUR_OF_DAY time field. + * @param minute corresponds to the MINUTE time field. + * @param second corresponds to the SECOND time field. + */ + public GregorianCalendar(int year, int month, int day, + int hour, int minute, int second) { - this (zone, Locale.getDefault ()); + super(); + set(year, month, day, hour, minute, second); } - public GregorianCalendar (Locale locale) + /** + * Sets the date of the switch from Julian dates to Gregorian dates. + * You can use new Date(Long.MAX_VALUE) to use a pure + * Julian calendar, or Long.MIN_VALUE for a pure Gregorian + * calendar. + * @param date the date of the change. + */ + public void setGregorianChange(Date date) { - this (TimeZone.getDefault (), locale); + gregorianCutover = date.getTime(); } - public GregorianCalendar (TimeZone zone, Locale locale) + /** + * Gets the date of the switch from Julian dates to Gregorian dates. + * @return the date of the change. + */ + public final Date getGregorianChange(Date date) { - super (zone, locale); - setDefaultTime (); + return new Date(gregorianCutover); } - public GregorianCalendar (int year, int month, int date) + /** + * Determines if the given year is a leap year. The result is + * undefined if the gregorian change took place in 1800, so that + * the end of february is skiped and you give that year + * (well...).
+ * + * The year should be positive and you can't give an ERA. But + * remember that before 4 BC there wasn't a consistent leap year + * rule, so who cares. + * + * @param year a year use nonnegative value for BC. + * @return true, if the given year is a leap year, false otherwise. */ + public boolean isLeapYear(int year) { - this(); - set (year, month, date, 0, 0, 0); + if ((year & 3) != 0) + // Only years divisible by 4 can be leap years + return false; + + // compute the linear day of the 29. February of that year. + // The 13 is the number of days, that were omitted in the Gregorian + // Calender until the epoch. + int julianDay = (((year-1) * (365*4+1)) >> 2) + (31+29 - + (((1970-1) * (365*4+1)) / 4 + 1 - 13)); + + // If that day is smaller than the gregorianChange the julian + // rule applies: This is a leap year since it is divisible by 4. + if (julianDay * (24 * 60 * 60 * 1000L) < gregorianCutover) + return true; + + return ((year % 100) != 0 || (year % 400) == 0); + } + + /** + * Get the linear time in milliseconds since the epoch. If you + * specify a nonpositive year it is interpreted as BC as + * following: 0 is 1 BC, -1 is 2 BC and so on. The date is + * interpreted as gregorian if the change occured before that date. + * + * @param year the year of the date. + * @param dayOfYear the day of year of the date; 1 based. + * @param millis the millisecond in that day. + * @return the days since the epoch, may be negative. */ + private long getLinearTime(int year, int dayOfYear, int millis) + { + // The 13 is the number of days, that were omitted in the Gregorian + // Calender until the epoch. + // We shift right by 2 instead of dividing by 4, to get correct + // results for negative years (and this is even more efficient). + int julianDay = ((year * (365 * 4 + 1)) >> 2) + dayOfYear - + ((1970 * (365 * 4 + 1)) / 4 + 1 - 13); + long time = julianDay * (24 * 60 * 60 * 1000L) + millis; + + if (time >= gregorianCutover) + { + // subtract the days that are missing in gregorian calendar + // with respect to julian calendar. + // + // Okay, here we rely on the fact that the gregorian + // calendar was introduced in the AD era. This doesn't work + // with negative years. + // + // The additional leap year factor accounts for the fact that + // a leap day is not seen on Jan 1 of the leap year. + int gregOffset = (year / 400) - (year / 100) + 2; + if (isLeapYear (year, true) && dayOfYear < 31 + 29) + --gregOffset; + time += gregOffset * (24 * 60 * 60 * 1000L); + } + return time; } - public GregorianCalendar (int year, int month, int date, - int hour, int minute) + private int getWeekDay(int year, int dayOfYear) { - this(); - set (year, month, date, hour, minute, 0); + int day = + (int) (getLinearTime(year, dayOfYear, 0) / (24 * 60 * 60 * 1000L)); + + // The epoch was a thursday. + int weekday = (day + THURSDAY) % 7; + if (weekday <= 0) + weekday += 7; + return weekday; } - public GregorianCalendar (int year, int month, int date, - int hour, int minute, int second) + /** + * Calculate the dayOfYear from the fields array. + * The relativeDays is used, to account for weeks that begin before + * the gregorian change and end after it.
+ * + * We return two values, the first is used to determine, if we + * should use Gregorian calendar or Julian calendar, in case of + * the change year, the second is a relative day after the given + * day. This is necessary for week calculation in the year in + * which gregorian change occurs.
+ * + * @param year the year, negative for BC. + * @return an array of two int values, the first containing a reference + * day of current year, the second a relative count since this reference + * day. */ + private int[] getDayOfYear(int year) { - this(); - set (year, month, date, hour, minute, second); + if (isSet[MONTH]) + { + int dayOfYear; + if (fields[MONTH] > FEBRUARY) + { + + // The months after February are regular: + // 9 is an offset found by try and error. + dayOfYear = (fields[MONTH] * (31 + 30 + 31 + 30 + 31) - 9) / 5; + if (isLeapYear(year)) + dayOfYear++; + } + else + dayOfYear = 31 * fields[MONTH]; + + if (isSet[DAY_OF_MONTH]) + { + return new int[] + { + dayOfYear + fields[DAY_OF_MONTH], 0}; + } + if (isSet[WEEK_OF_MONTH] && isSet[DAY_OF_WEEK]) + { + // the weekday of the first day in that month is: + int weekday = getWeekDay(year, ++dayOfYear); + + return new int[] + { + dayOfYear, + // the day of week in the first week + // (weeks starting on sunday) is: + fields[DAY_OF_WEEK] - weekday + + // Now jump to the right week and correct the possible + // error made by assuming sunday is the first week day. + 7 * (fields[WEEK_OF_MONTH] + + (fields[DAY_OF_WEEK] < getFirstDayOfWeek()? 0 : -1) + + (weekday < getFirstDayOfWeek()? -1 : 0))}; + } + if (isSet[DAY_OF_WEEK] && isSet[DAY_OF_WEEK_IN_MONTH]) + { + // the weekday of the first day in that month is: + int weekday = getWeekDay(year, ++dayOfYear); + return new int[] { + dayOfYear, + fields[DAY_OF_WEEK] - weekday + + 7 * (fields[DAY_OF_WEEK_IN_MONTH] + + (fields[DAY_OF_WEEK] < weekday ? 0 : -1))}; + } + } + + // MONTH + something did not succeed. + if (isSet[DAY_OF_YEAR]) + { + return new int[] {0, fields[DAY_OF_YEAR]}; + } + + if (isSet[DAY_OF_WEEK] && isSet[WEEK_OF_YEAR]) + { + int dayOfYear = getMinimalDaysInFirstWeek(); + // the weekday of the day, that begins the first week + // in that year is: + int weekday = getWeekDay(year, dayOfYear); + + return new int[] { + dayOfYear, + // the day of week in the first week + // (weeks starting on sunday) is: + fields[DAY_OF_WEEK] - weekday + // Now jump to the right week and correct the possible + // error made by assuming sunday is the first week day. + + 7 * (fields[WEEK_OF_YEAR] + + (fields[DAY_OF_WEEK] < getFirstDayOfWeek()? 0 : -1) + + (weekday < getFirstDayOfWeek()? -1 : 0))}; + } + + // As last resort return Jan, 1st. + return new int[] {1, 0}; } - private final void setDefaultTime () + /** + * Converts the time field values (fields) to + * milliseconds since the epoch UTC (time). + */ + protected synchronized void computeTime() { - setTimeInMillis (System.currentTimeMillis()); + int era = isSet[ERA] ? fields[ERA] : AD; + int year = isSet[YEAR] ? fields[YEAR] : 1970; + if (era == BC) + year = 1 - year; + + int[] daysOfYear = getDayOfYear(year); + int hour = isSet[HOUR_OF_DAY] ? fields[HOUR_OF_DAY] + : (isSet[HOUR] && isSet[AM_PM] + ? fields[AM_PM] * 12 + (fields[HOUR] % 12) : 0); + int minute = isSet[MINUTE] ? fields[MINUTE] : 0; + int second = isSet[SECOND] ? fields[SECOND] : 0; + int millis = isSet[MILLISECOND] ? fields[MILLISECOND] : 0; + int millisInDay; + + if (isLenient()) + { + // prevent overflow + long allMillis = (((hour * 60L) + minute) * 60L + second) * 1000L + + millis; + daysOfYear[1] += allMillis / (24 * 60 * 60 * 1000L); + millisInDay = (int) (allMillis % (24 * 60 * 60 * 1000L)); + } + else + { + if (hour < 0 || hour >= 24 || minute < 0 || minute > 59 + || second < 0 || second > 59 || millis < 0 || millis >= 1000) + throw new IllegalArgumentException(); + millisInDay = (((hour * 60) + minute) * 60 + second) * 1000 + millis; + } + time = getLinearTime(year, daysOfYear[0], millisInDay); + + // Add the relative days after calculating the linear time, to + // get right behaviour when jumping over the gregorianCutover. + time += daysOfYear[1] * (24 * 60 * 60 * 1000L); + + + TimeZone zone = getTimeZone(); + int rawOffset = isSet[ZONE_OFFSET] + ? fields[ZONE_OFFSET] : getTimeZone().getRawOffset(); + + int dayOfYear = daysOfYear[0] + daysOfYear[1]; + int month = (dayOfYear * 5 + 3) / (31 + 30 + 31 + 30 + 31); + int day = (6 + (dayOfYear * 5 + 3) % (31 + 30 + 31 + 30 + 31)) / 5; + int weekday = ((int) (time / (24 * 60 * 60 * 1000L)) + THURSDAY) % 7; + if (weekday <= 0) + weekday += 7; + int dstOffset = isSet[DST_OFFSET] + ? fields[DST_OFFSET] : (zone.getOffset((year < 0) ? BC : AD, + (year < 0) ? 1 - year : year, + month, day, weekday, millisInDay) + - zone.getRawOffset()); + time -= rawOffset + dstOffset; + isTimeSet = true; } - public int getMinimum(int calfield) { return mins[calfield]; } - public int getGreatestMinimum(int calfield) { return mins[calfield]; } - public int getMaximum(int calfield) { return maxs[calfield]; } - public int getLeastMaximum(int calfield) { return leastMaximums[calfield]; } + /** + * Determines if the given year is a leap year. + * + * The year should be positive and you can't give an ERA. But + * remember that before 4 BC there wasn't a consistent leap year + * rule, so who cares. + * + * @param year a year use nonnegative value for BC. + * @param gregorian if true, use gregorian leap year rule. + * @return true, if the given year is a leap year, false otherwise. */ + private boolean isLeapYear(int year, boolean gregorian) + { + if ((year & 3) != 0) + // Only years divisible by 4 can be leap years + return false; - protected native void computeFields(); + if (!gregorian) + return true; - protected native void computeTime(); + // We rely on AD area here. + return ((year % 100) != 0 || (year % 400) == 0); + } - public void add (int fld, int amount) + /** + * Get the linear day in days since the epoch, using the + * Julian or Gregorian calendar as specified. If you specify a + * nonpositive year it is interpreted as BC as following: 0 is 1 + * BC, -1 is 2 BC and so on. + * + * @param year the year of the date. + * @param dayOfYear the day of year of the date; 1 based. + * @param gregorian True, if we should use Gregorian rules. + * @return the days since the epoch, may be negative. */ + private int getLinearDay(int year, int dayOfYear, boolean gregorian) { - if (fld >= ZONE_OFFSET) - throw new IllegalArgumentException("bad field to add"); - fields[fld] += amount; - adjust(fld); + // The 13 is the number of days, that were omitted in the Gregorian + // Calender until the epoch. + // We shift right by 2 instead of dividing by 4, to get correct + // results for negative years (and this is even more efficient). + int julianDay = ((year * (365 * 4 + 1)) >> 2) + dayOfYear - + ((1970 * (365 * 4 + 1)) / 4 + 1 - 13); + + if (gregorian) + { + // subtract the days that are missing in gregorian calendar + // with respect to julian calendar. + // + // Okay, here we rely on the fact that the gregorian + // calendar was introduced in the AD era. This doesn't work + // with negative years. + // + // The additional leap year factor accounts for the fact that + // a leap day is not seen on Jan 1 of the leap year. + int gregOffset = (year / 400) - (year / 100) + 2; + if (isLeapYear (year, true) && dayOfYear < 31 + 29) + --gregOffset; + julianDay += gregOffset; + } + return julianDay; } - public void roll (int fld, boolean up) + /** + * Converts the given linear day into era, year, month, + * day_of_year, day_of_month, day_of_week, and writes the result + * into the fields array. + * @param day the linear day. + */ + private void calculateDay(int day, boolean gregorian) { - if (fld >= ZONE_OFFSET) - throw new IllegalArgumentException("bad field to roll"); + // the epoch is a Thursday. + int weekday = (day + THURSDAY) % 7; + if (weekday <= 0) + weekday += 7; + fields[DAY_OF_WEEK] = weekday; + + // get a first approximation of the year. This may be one + // year to big. + int year = 1970 + (gregorian + ? ((day - 100) * 400) / (365 * 400 + 100 - 4 + 1) + : ((day - 100) * 4) / (365 * 4 + 1)); + if (day >= 0) + year++; + + int firstDayOfYear = getLinearDay(year, 1, gregorian); + + // Now look in which year day really lies. + if (day < firstDayOfYear) + { + year--; + firstDayOfYear = getLinearDay(year, 1, gregorian); + } + + day -= firstDayOfYear - 1; // day of year, one based. - int old = fields[fld]; - if (up) + fields[DAY_OF_YEAR] = day; + if (year <= 0) { - fields[fld] = old == getMaximum(fld) ? getMinimum(fld) - : old + 1; + fields[ERA] = BC; + fields[YEAR] = 1 - year; } else { - fields[fld] = old == getMinimum(fld) ? getMaximum(fld) - : old - 1; + fields[ERA] = AD; + fields[YEAR] = year; + } + + int leapday = isLeapYear(year, gregorian) ? 1 : 0; + if (day <= 31 + 28 + leapday) + { + fields[MONTH] = day / 32; // 31->JANUARY, 32->FEBRUARY + fields[DAY_OF_MONTH] = day - 31 * fields[MONTH]; + } + else + { + // A few more magic formulas + int scaledDay = (day - leapday) * 5 + 8; + fields[MONTH] = scaledDay / (31 + 30 + 31 + 30 + 31); + fields[DAY_OF_MONTH] = (scaledDay % (31 + 30 + 31 + 30 + 31)) / 5 + 1; } } - private void adjust (int fld) + /** + * Converts the milliseconds since the epoch UTC + * (time) to time fields + * (fields). + */ + protected synchronized void computeFields() { - int value = fields[fld]; - int radix = maxs[fld] + 1; - switch (fld) + boolean gregorian = (time >= gregorianCutover); + + TimeZone zone = getTimeZone(); + fields[ZONE_OFFSET] = zone.getRawOffset(); + long localTime = time + fields[ZONE_OFFSET]; + + int day = (int) (localTime / (24 * 60 * 60 * 1000L)); + int millisInDay = (int) (localTime % (24 * 60 * 60 * 1000L)); + if (millisInDay < 0) { + millisInDay += (24 * 60 * 60 * 1000); + day--; + } + + calculateDay(day, gregorian); + fields[DST_OFFSET] = + zone.getOffset(fields[ERA], fields[YEAR], fields[MONTH], + fields[DAY_OF_MONTH], fields[DAY_OF_WEEK], + millisInDay) - fields[ZONE_OFFSET]; + + millisInDay += fields[DST_OFFSET]; + if (millisInDay >= 24 * 60 * 60 * 1000) + { + millisInDay -= 24 * 60 * 60 * 1000; + calculateDay(++day, gregorian); + } + + fields[DAY_OF_WEEK_IN_MONTH] = (fields[DAY_OF_MONTH] + 6) / 7; + + // which day of the week are we (0..6), relative to getFirstDayOfWeek + int relativeWeekday = (7 + fields[DAY_OF_WEEK] - getFirstDayOfWeek()) % 7; + + fields[WEEK_OF_MONTH] = (fields[DAY_OF_MONTH] - relativeWeekday + 6) / 7; + + int weekOfYear = (fields[DAY_OF_YEAR] - relativeWeekday + 6) / 7; + + // Do the Correction: getMinimalDaysInFirstWeek() is always in the + // first week. + int minDays = getMinimalDaysInFirstWeek(); + int firstWeekday = + (7 + getWeekDay(fields[YEAR], minDays) - getFirstDayOfWeek()) % 7; + if (minDays - firstWeekday < 1) + weekOfYear++; + fields[WEEK_OF_YEAR] = weekOfYear; + + + int hourOfDay = millisInDay / (60 * 60 * 1000); + fields[AM_PM] = (hourOfDay < 12) ? AM : PM; + int hour = hourOfDay % 12; + fields[HOUR] = (hour == 0) ? 12 : hour; + fields[HOUR_OF_DAY] = hourOfDay; + millisInDay %= (60 * 60 * 1000); + fields[MINUTE] = millisInDay / (60 * 1000); + millisInDay %= (60 * 1000); + fields[SECOND] = millisInDay / (1000); + fields[MILLISECOND] = millisInDay % 1000; + + + areFieldsSet = isSet[ERA] = isSet[YEAR] = isSet[MONTH] = + isSet[WEEK_OF_YEAR] = isSet[WEEK_OF_MONTH] = + isSet[DAY_OF_MONTH] = isSet[DAY_OF_YEAR] = isSet[DAY_OF_WEEK] = + isSet[DAY_OF_WEEK_IN_MONTH] = isSet[AM_PM] = isSet[HOUR] = + isSet[HOUR_OF_DAY] = isSet[MINUTE] = isSet[SECOND] = + isSet[MILLISECOND] = isSet[ZONE_OFFSET] = isSet[DST_OFFSET] = true; + + } + + /** + * Compares the given calender with this. + * @param o the object to that we should compare. + * @return true, if the given object is a calendar, that represents + * the same time (but doesn't neccessary have the same fields). + * @XXX Should we check if time zones, locale, cutover etc. are equal? + */ + public boolean equals(Object o) + { + if (!(o instanceof GregorianCalendar)) + return false; + + GregorianCalendar cal = (GregorianCalendar) o; + return (cal.getTimeInMillis() == getTimeInMillis()); + } + +// /** +// * Compares the given calender with this. +// * @param o the object to that we should compare. +// * @return true, if the given object is a calendar, and this calendar +// * represents a smaller time than the calender o. +// */ +// public boolean before(Object o) { +// if (!(o instanceof GregorianCalendar)) +// return false; + +// GregorianCalendar cal = (GregorianCalendar) o; +// return (cal.getTimeInMillis() < getTimeInMillis()); +// } + +// /** +// * Compares the given calender with this. +// * @param o the object to that we should compare. +// * @return true, if the given object is a calendar, and this calendar +// * represents a bigger time than the calender o. +// */ +// public boolean after(Object o) { +// if (!(o instanceof GregorianCalendar)) +// return false; + +// GregorianCalendar cal = (GregorianCalendar) o; +// return (cal.getTimeInMillis() > getTimeInMillis()); +// } + + /** + * Adds the specified amount of time to the given time field. The + * amount may be negative to subtract the time. If the field overflows + * it does what you expect: Jan, 25 + 10 Days is Feb, 4. + * @param field the time field. One of the time field constants. + * @param amount the amount of time. + */ + public void add(int field, int amount) + { + switch (field) + { + case YEAR: + complete(); + fields[YEAR] += amount; + isTimeSet = false; + break; case MONTH: - case SECOND: - case MILLISECOND: - if (value >= radix) + complete(); + int months = fields[MONTH] + amount; + fields[YEAR] += months / 12; + fields[MONTH] = months % 12; + if (fields[MONTH] < 0) { - int next = value / radix; - fields[fld] = value - radix * next; - fields[fld - 1] += next; - adjust(fld - 1); + fields[MONTH] += 12; + fields[YEAR]--; } - else if (value < 0) // min[fld] + isTimeSet = false; + int maxDay = getActualMaximum(DAY_OF_MONTH); + if (fields[DAY_OF_MONTH] > maxDay) { - int next = (value - radix - 1) / radix; - fields[fld] = value - radix * next; - fields[fld - 1] += next; - adjust(fld - 1); + fields[DAY_OF_MONTH] = maxDay; + isTimeSet = false; } break; + case DAY_OF_MONTH: + case DAY_OF_YEAR: + case DAY_OF_WEEK: + if (!isTimeSet) + computeTime(); + time += amount * (24 * 60 * 60 * 1000L); + areFieldsSet = false; + break; + case WEEK_OF_YEAR: + case WEEK_OF_MONTH: + case DAY_OF_WEEK_IN_MONTH: + if (!isTimeSet) + computeTime(); + time += amount * (7 * 24 * 60 * 60 * 1000L); + areFieldsSet = false; + break; + case AM_PM: + if (!isTimeSet) + computeTime(); + time += amount * (12 * 60 * 60 * 1000L); + areFieldsSet = false; + break; + case HOUR: + case HOUR_OF_DAY: + if (!isTimeSet) + computeTime(); + time += amount * (60 * 60 * 1000L); + areFieldsSet = false; + break; + case MINUTE: + if (!isTimeSet) + computeTime(); + time += amount * (60 * 1000L); + areFieldsSet = false; + break; + case SECOND: + if (!isTimeSet) + computeTime(); + time += amount * (1000L); + areFieldsSet = false; + break; + case MILLISECOND: + if (!isTimeSet) + computeTime(); + time += amount; + areFieldsSet = false; + break; + case ZONE_OFFSET: + complete(); + fields[ZONE_OFFSET] += amount; + time -= amount; + break; + case DST_OFFSET: + complete(); + fields[DST_OFFSET] += amount; + isTimeSet = false; + break; + default: + throw new IllegalArgumentException + ("Unknown Calendar field: " + field); } } - public final Date getGregorianChange() { return new Date(gregorianCutover); } - public void setGregorianChange (Date date) - { gregorianCutover = date.getTime(); } - public boolean isLeapYear(int year) + /** + * Rolls the specified time field up or down. This means add one + * to the specified field, but don't change the other fields. If + * the maximum for this field is reached, start over with the + * minimum value. + * + * Note: There may be situation, where the other + * fields must be changed, e.g rolling the month on May, 31. + * The date June, 31 is automatically converted to July, 1. + * This requires lenient settings. + * + * @param field the time field. One of the time field constants. + * @param up the direction, true for up, false for down. + */ + public void roll(int field, boolean up) { - if ((year % 4) != 0) - return false; - if ((year % 100) != 0 || (year % 400) == 0) - return true; - // year divisible by 100 but not 400. - GregorianCalendar date = new GregorianCalendar(year, FEBRUARY, 28); - return gregorianCutover < date.getTimeInMillis(); + roll(field, up ? 1 : -1); } - public boolean after (Object cal) + private void cleanUpAfterRoll(int field, int delta) { - return cal instanceof Calendar - && getTimeInMillis() > ((Calendar) cal).getTimeInMillis(); + switch (field) + { + case ERA: + case YEAR: + case MONTH: + // check that day of month is still in correct range + if (fields[DAY_OF_MONTH] > getActualMaximum(DAY_OF_MONTH)) + fields[DAY_OF_MONTH] = getActualMaximum(DAY_OF_MONTH); + isTimeSet = false; + isSet[WEEK_OF_MONTH] = false; + isSet[DAY_OF_WEEK] = false; + isSet[DAY_OF_WEEK_IN_MONTH] = false; + isSet[DAY_OF_YEAR] = false; + isSet[WEEK_OF_YEAR] = false; + break; + + case DAY_OF_MONTH: + isSet[WEEK_OF_MONTH] = false; + isSet[DAY_OF_WEEK] = false; + isSet[DAY_OF_WEEK_IN_MONTH] = false; + isSet[DAY_OF_YEAR] = false; + isSet[WEEK_OF_YEAR] = false; + time += delta * (24 * 60 * 60 * 1000L); + break; + + case WEEK_OF_MONTH: + isSet[DAY_OF_MONTH] = false; + isSet[DAY_OF_WEEK_IN_MONTH] = false; + isSet[DAY_OF_YEAR] = false; + isSet[WEEK_OF_YEAR] = false; + time += delta * (7 * 24 * 60 * 60 * 1000L); + break; + case DAY_OF_WEEK_IN_MONTH: + isSet[DAY_OF_MONTH] = false; + isSet[WEEK_OF_MONTH] = false; + isSet[DAY_OF_YEAR] = false; + isSet[WEEK_OF_YEAR] = false; + time += delta * (7 * 24 * 60 * 60 * 1000L); + break; + case DAY_OF_YEAR: + isSet[MONTH] = false; + isSet[DAY_OF_MONTH] = false; + isSet[WEEK_OF_MONTH] = false; + isSet[DAY_OF_WEEK_IN_MONTH] = false; + isSet[DAY_OF_WEEK] = false; + isSet[WEEK_OF_YEAR] = false; + time += delta * (24 * 60 * 60 * 1000L); + break; + case WEEK_OF_YEAR: + isSet[MONTH] = false; + isSet[DAY_OF_MONTH] = false; + isSet[WEEK_OF_MONTH] = false; + isSet[DAY_OF_WEEK_IN_MONTH] = false; + isSet[DAY_OF_YEAR] = false; + time += delta * (7 * 24 * 60 * 60 * 1000L); + break; + + case AM_PM: + isSet[HOUR_OF_DAY] = false; + time += delta * (12 * 60 * 60 * 1000L); + break; + case HOUR: + isSet[HOUR_OF_DAY] = false; + time += delta * (60 * 60 * 1000L); + break; + case HOUR_OF_DAY: + isSet[HOUR] = false; + isSet[AM_PM] = false; + time += delta * (60 * 60 * 1000L); + break; + + case MINUTE: + time += delta * (60 * 1000L); + break; + case SECOND: + time += delta * (1000L); + break; + case MILLISECOND: + time += delta; + break; + } } - public boolean before (Object cal) + /** + * Rolls the specified time field by the given amount. This means + * add amount to the specified field, but don't change the other + * fields. If the maximum for this field is reached, start over + * with the minimum value and vice versa for negative amounts. + * + * Note: There may be situation, where the other + * fields must be changed, e.g rolling the month on May, 31. + * The date June, 31 is automatically corrected to June, 30. + * + * @param field the time field. One of the time field constants. + * @param amount the amount by which we should roll. + */ + public void roll(int field, int amount) { - return cal instanceof Calendar - && getTimeInMillis() < ((Calendar) cal).getTimeInMillis(); + switch (field) + { + case DAY_OF_WEEK: + // day of week is special: it rolls automatically + add(field, amount); + return; + case ZONE_OFFSET: + case DST_OFFSET: + throw new IllegalArgumentException("Can't roll time zone"); + } + complete(); + int min = getActualMinimum(field); + int range = getActualMaximum(field) - min + 1; + int oldval = fields[field]; + int newval = (oldval - min + range + amount) % range + min; + if (newval < min) + newval += range; + fields[field] = newval; + cleanUpAfterRoll(field, newval - oldval); } - public boolean equals (Object obj) + private static final int[] minimums = + { BC, 1, 1, 0, 1, 1, 1, SUNDAY, 1, + AM, 1, 0, 1, 1, 1, -(12*60*60*1000), 0 }; + + private static final int[] maximums = + { AD, 5000000, 12, 53, 5, 31, 366, SATURDAY, 5, + PM, 12, 23, 59, 59, 999, +(12*60*60*1000), (12*60*60*1000) }; + + /** + * Gets the smallest value that is allowed for the specified field. + * @param field the time field. One of the time field constants. + * @return the smallest value. + */ + public int getMinimum(int field) { - if (obj == null || ! (obj instanceof GregorianCalendar)) - return false; - GregorianCalendar other = (GregorianCalendar) obj; + return minimums[field]; + } - for (int i = FIELD_COUNT; --i >= 0; ) + /** + * Gets the biggest value that is allowed for the specified field. + * @param field the time field. One of the time field constants. + * @return the biggest value. + */ + public int getMaximum(int field) + { + return maximums[field]; + } + + + /** + * Gets the greatest minimum value that is allowed for the specified field. + * @param field the time field. One of the time field constants. + * @return the greatest minimum value. + */ + public int getGreatestMinimum(int field) + { + if (field == WEEK_OF_YEAR) + return 1; + return minimums[field]; + } + + /** + * Gets the smallest maximum value that is allowed for the + * specified field. For example this is 28 for DAY_OF_MONTH. + * @param field the time field. One of the time field constants. + * @return the least maximum value. + * @since jdk1.2 + */ + public int getLeastMaximum(int field) + { + switch (field) { - boolean set = isSet[i]; - if (set != other.isSet[i] - || (set && fields[i] != other.fields[i])) - return false; + case WEEK_OF_YEAR: + return 52; + case DAY_OF_MONTH: + return 28; + case DAY_OF_YEAR: + return 365; + case DAY_OF_WEEK_IN_MONTH: + case WEEK_OF_MONTH: + return 4; + default: + return maximums[field]; } - if (areFieldsSet != other.areFieldsSet - || isTimeSet != other.isTimeSet - || (isTimeSet && time != other.time) - || getFirstDayOfWeek() != other.getFirstDayOfWeek() - || getMinimalDaysInFirstWeek() != other.getMinimalDaysInFirstWeek() - || isLenient() != other.isLenient() - || ! getTimeZone().equals(other.getTimeZone())) - return false; - return true; } - public int hashCode () + /** + * Gets the actual minimum value that is allowed for the specified field. + * This value is dependant on the values of the other fields. Note that + * this calls complete() if not enough fields are set. This + * can have ugly side effects. + * @param field the time field. One of the time field constants. + * @return the actual minimum value. + * @since jdk1.2 + */ + public int getActualMinimum(int field) + { + if (field == WEEK_OF_YEAR) + { + int min = getMinimalDaysInFirstWeek(); + if (min == 0) + return 1; + if (!areFieldsSet || !isSet[ERA] || !isSet[YEAR]) + complete(); + + int year = fields[ERA] == AD ? fields[YEAR] : 1 - fields[YEAR]; + int weekday = getWeekDay(year, min); + if ((7 + weekday - getFirstDayOfWeek()) % 7 >= min - 1) + return 1; + return 0; + } + return minimums[field]; + } + + /** + * Gets the actual maximum value that is allowed for the specified field. + * This value is dependant on the values of the other fields. Note that + * this calls complete() if not enough fields are set. This + * can have ugly side effects. + * @param field the time field. One of the time field constants. + * @return the actual maximum value. + */ + public int getActualMaximum(int field) { - int hashcode = 0; - for (int i = FIELD_COUNT; --i >= 0; ) + switch (field) { - if (isSet[i]) - hashcode += 37 * fields[i]; + case WEEK_OF_YEAR: + { + if (!areFieldsSet || !isSet[ERA] || !isSet[YEAR]) + complete(); + // This is wrong for the year that contains the gregorian change. + // I.e it gives the weeks in the julian year or in the gregorian + // year in that case. + int year = fields[ERA] == AD ? fields[YEAR] : 1 - fields[YEAR]; + int lastDay = isLeapYear(year) ? 366 : 365; + int weekday = getWeekDay(year, lastDay); + int week = (lastDay + 6 + - (7 + weekday - getFirstDayOfWeek()) % 7) / 7; + + int minimalDays = getMinimalDaysInFirstWeek(); + int firstWeekday = getWeekDay(year, minimalDays); + if (minimalDays - (7 + firstWeekday - getFirstDayOfWeek()) % 7 < 1) + return week + 1; + } + case DAY_OF_MONTH: + { + if (!areFieldsSet || !isSet[MONTH]) + complete(); + int month = fields[MONTH]; + // If you change this, you should also change + // SimpleTimeZone.getDaysInMonth(); + if (month == FEBRUARY) + { + if (!isSet[YEAR] || !isSet[ERA]) + complete(); + int year = fields[ERA] == AD ? fields[YEAR] : 1 - fields[YEAR]; + return isLeapYear(year) ? 29 : 28; + } + else if (month < AUGUST) + return 31 - (month & 1); + else + return 30 + (month & 1); + } + case DAY_OF_YEAR: + { + if (!areFieldsSet || !isSet[ERA] || !isSet[YEAR]) + complete(); + int year = fields[ERA] == AD ? fields[YEAR] : 1 - fields[YEAR]; + return isLeapYear(year) ? 366 : 365; + } + case DAY_OF_WEEK_IN_MONTH: + { + // This is wrong for the month that contains the gregorian change. + int daysInMonth = getActualMaximum(DAY_OF_MONTH); + // That's black magic, I know + return (daysInMonth - (fields[DAY_OF_MONTH] - 1) % 7 + 6) / 7; + } + case WEEK_OF_MONTH: + { + int daysInMonth = getActualMaximum(DAY_OF_MONTH); + int weekday = (daysInMonth - fields[DAY_OF_MONTH] + + fields[DAY_OF_WEEK] - SUNDAY) % 7 + SUNDAY; + return (daysInMonth + 6 + - (7 + weekday - getFirstDayOfWeek()) % 7) / 7; + } + default: + return maximums[field]; } - if (isTimeSet) - hashcode += 89 * time; - return hashcode; } } diff --git a/libjava/java/util/natGregorianCalendar.cc b/libjava/java/util/natGregorianCalendar.cc deleted file mode 100644 index 34b49969b45..00000000000 --- a/libjava/java/util/natGregorianCalendar.cc +++ /dev/null @@ -1,159 +0,0 @@ -/* Copyright (C) 1998, 1999, 2000, 2001 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 - -#ifdef ECOS -#include -#endif - -#include -#include -#include -#include -#include - -void -java::util::GregorianCalendar::computeTime () -{ - struct tm tim; - tim.tm_sec = elements(fields)[SECOND]; - tim.tm_min = elements(fields)[MINUTE]; - tim.tm_hour = elements(fields)[HOUR_OF_DAY]; - tim.tm_mday = elements(fields)[DATE]; - tim.tm_mon = elements(fields)[MONTH]; - tim.tm_year = elements(fields)[YEAR] - 1900; - tim.tm_isdst = 0; -#ifndef ECOS - // FIXME: None of the standard C library access to the ECOS calendar - // is yet available. - time_t t = mktime (&tim); - - if (!isLenient ()) - { - // mktime will correct for any time leniencies (e.g. 31-Apr becomes - // 1-May). - // Daylight savings time is a special case since times in hour 23 - // will compute to hour 0 of the next day. - if (tim.tm_isdst == 0 || elements(fields)[HOUR_OF_DAY] != 23) - { - if (tim.tm_sec != elements(fields)[SECOND] || - tim.tm_min != elements(fields)[MINUTE] || - tim.tm_hour != elements(fields)[HOUR_OF_DAY] + - (tim.tm_isdst > 0 ? 1 : 0) || - tim.tm_mday != elements(fields)[DATE] || - tim.tm_mon != elements(fields)[MONTH] || - tim.tm_year != elements(fields)[YEAR] - 1900) - throw new java::lang::IllegalArgumentException (); - } - else - { - // The easiest thing to do is to temporarily shift the clock - // back from the 23th hour so mktime doesn't cause the extra - // hour for DST to roll the date to the next day. - struct tm tmp_tim; - tmp_tim.tm_sec = elements(fields)[SECOND]; - tmp_tim.tm_min = elements(fields)[MINUTE]; - tmp_tim.tm_hour = elements(fields)[HOUR_OF_DAY] - 1; - tmp_tim.tm_mday = elements(fields)[DATE]; - tmp_tim.tm_mon = elements(fields)[MONTH]; - tmp_tim.tm_year = elements(fields)[YEAR] - 1900; - tmp_tim.tm_isdst = 0; - mktime (&tmp_tim); - if (tmp_tim.tm_sec != elements(fields)[SECOND] || - tmp_tim.tm_min != elements(fields)[MINUTE] || - tmp_tim.tm_hour != elements(fields)[HOUR_OF_DAY] || - tmp_tim.tm_mday != elements(fields)[DATE] || - tmp_tim.tm_mon != elements(fields)[MONTH] || - tmp_tim.tm_year != elements(fields)[YEAR] - 1900) - throw new java::lang::IllegalArgumentException (); - } - } -#else - time_t t = 0; -#endif - - // Adjust for local timezone (introduced by mktime) and our - // timezone. -#if defined (STRUCT_TM_HAS_GMTOFF) - t -= tim.tm_gmtoff; -#elif defined (HAVE_TIMEZONE) - t += timezone; -#endif - // Adjust for milliseconds. - time = t * (jlong) 1000 + elements(fields)[MILLISECOND]; - - // Now adjust for the real timezone, i.e. our timezone, which is in millis. - java::util::TimeZone *zone = getTimeZone (); - time += zone->getRawOffset(); - - isTimeSet = true; -} - -void -java::util::GregorianCalendar::computeFields () -{ - time_t t = time / 1000; - int millis = time % 1000; - if (t < 0 && millis != 0) - { - t--; - millis = t - 1000 * t; - } - elements(fields)[MILLISECOND] = millis; - struct tm tim; - java::util::TimeZone *zone = getTimeZone (); - - // FIXME: None of the standard C library access to the ECOS calendar - // is yet available. -#ifdef ECOS - memset (&tim, 0, sizeof tim); -#else - if (zone->getRawOffset() == 0 || ! zone->useDaylightTime()) - { -#if defined(__JV_POSIX_THREADS__) && defined(HAVE_GMTIME_R) - gmtime_r (&t, &tim); -#else - // Get global lock (because gmtime uses a global buffer). FIXME - tim = *(struct tm*) gmtime (&t); - // Release global lock. FIXME -#endif - } - else - { -#if defined(__JV_POSIX_THREADS__) && defined(HAVE_LOCALTIME_R) - localtime_r (&t, &tim); -#else - // Get global lock (because localtime uses a global buffer). FIXME - tim = *(struct tm*) localtime (&t); - // Release global lock. FIXME -#endif - } -#endif /* ECOS */ - elements(fields)[SECOND] = tim.tm_sec; - elements(fields)[MINUTE] = tim.tm_min; - elements(fields)[HOUR_OF_DAY] = tim.tm_hour; - elements(fields)[AM_PM] = tim.tm_hour < 12 ? AM : PM; - elements(fields)[HOUR] = tim.tm_hour % 12; - elements(fields)[DATE] = tim.tm_mday; - elements(fields)[MONTH] = tim.tm_mon; - elements(fields)[YEAR] = 1900 + tim.tm_year; - elements(fields)[DAY_OF_WEEK] = tim.tm_wday + 1; - elements(fields)[DAY_OF_WEEK_IN_MONTH] = ((tim.tm_mday - 1) / 7) + 1; - elements(fields)[DAY_OF_YEAR] = tim.tm_yday + 1; - elements(fields)[WEEK_OF_MONTH] - = (tim.tm_mday + 6 + (5 - tim.tm_wday + getFirstDayOfWeek()) % 7) / 7; - elements(fields)[WEEK_OF_YEAR] - = (tim.tm_yday + 7 + (5 - tim.tm_wday + getFirstDayOfWeek()) % 7) / 7; - elements(fields)[ERA] = AD; - elements(fields)[DST_OFFSET] = tim.tm_isdst <= 0 ? 0 : 60*60*1000; - elements(fields)[ZONE_OFFSET] = getTimeZone()->getRawOffset(); - areFieldsSet = true; - for (int i = 0; i < FIELD_COUNT; i++) - elements(isSet__)[i] = true; -} -- 2.30.2