| /* 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. */ |
| |
| package java.util; |
| import java.text.*; |
| |
| /** |
| * @author Per Bothner <bothner@cygnus.com> |
| * @date October 24, 1998. |
| */ |
| |
| /* Written using "Java Class Libraries", 2nd edition, ISBN 0-201-31002-3, |
| * "The Java Language Specification", ISBN 0-201-63451-1, |
| * and O'Reilly's "Java in a Nutshell". |
| * Status: Need to re-write toString(). |
| * Missing: ToGMTString. |
| */ |
| public class Date implements java.io.Serializable, Cloneable |
| { |
| private static final long serialVersionUID = 7523967970034938905L; |
| |
| transient private long millis; |
| |
| public Date() { millis = System.currentTimeMillis(); } |
| |
| public Date(long millis) { this.millis = millis; } |
| |
| public Date(int year, int month, int date, int hours, |
| int minutes, int seconds) |
| { |
| setTime(year, month, date, hours, minutes, seconds); |
| } |
| |
| public Date(int year, int month, int date, int hours, int minutes) |
| { |
| setTime(year, month, date, hours, minutes, 0); |
| } |
| |
| public Date(int year, int month, int date) |
| { |
| setTime(year, month, date, 0, 0, 0); |
| } |
| |
| public Date (String s) { this(parse(s)); } |
| |
| private static int skipParens(String string, int offset) |
| { |
| int len = string.length(); |
| int p = 0; |
| int i; |
| |
| for (i = offset; i < len; ++i) |
| { |
| if (string.charAt(i) == '(') |
| ++p; |
| else if (string.charAt(i) == ')') |
| { |
| --p; |
| if (p == 0) |
| return i + 1; |
| // If we've encounted unbalanced parens, just return the |
| // leftover one as an ordinary character. It will be |
| // caught later in parsing and cause an |
| // IllegalArgumentException. |
| if (p < 0) |
| return i; |
| } |
| } |
| |
| // Not sure what to do if `p != 0' here. |
| return i; |
| } |
| |
| private static int parseTz(String tok, char sign) |
| throws IllegalArgumentException |
| { |
| int num; |
| |
| try |
| { |
| // parseInt doesn't handle '+' so strip off sign. |
| num = Integer.parseInt(tok.substring(1)); |
| } |
| catch (NumberFormatException ex) |
| { |
| throw new IllegalArgumentException(tok); |
| } |
| |
| // Convert hours to minutes. |
| if (num < 24) |
| num *= 60; |
| else |
| num = (num / 100) * 60 + num % 100; |
| |
| return sign == '-' ? -num : num; |
| } |
| |
| private static int parseMonth(String tok) |
| { |
| // Initialize strings for month names. |
| // We could possibly use the fields of DateFormatSymbols but that is |
| // localized and thus might not match the English words specified. |
| String months[] = { "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", |
| "JUNE", "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", |
| "NOVEMBER", "DECEMBER" }; |
| |
| int i; |
| for (i = 0; i < 12; i++) |
| if (months[i].startsWith(tok)) |
| return i; |
| |
| // Return -1 if not found. |
| return -1; |
| } |
| |
| private static boolean parseDayOfWeek(String tok) |
| { |
| // Initialize strings for days of the week names. |
| // We could possibly use the fields of DateFormatSymbols but that is |
| // localized and thus might not match the English words specified. |
| String daysOfWeek[] = { "SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY", |
| "THURSDAY", "FRIDAY", "SATURDAY" }; |
| |
| int i; |
| for (i = 0; i < 7; i++) |
| if (daysOfWeek[i].startsWith(tok)) |
| return true; |
| |
| return false; |
| } |
| |
| public static long parse(String string) |
| { |
| // Initialize date/time fields before parsing begins. |
| int year = -1; |
| int month = -1; |
| int day = -1; |
| int hour = -1; |
| int minute = -1; |
| int second = -1; |
| int timezone = 0; |
| boolean localTimezone = true; |
| |
| // Trim out any nested stuff in parentheses now to make parsing easier. |
| StringBuffer buf = new StringBuffer(); |
| int off = 0; |
| int openParenOffset, tmpMonth; |
| while ((openParenOffset = string.indexOf('(', off)) >= 0) |
| { |
| // Copy part of string leading up to open paren. |
| buf.append(string.substring(off, openParenOffset)); |
| off = skipParens(string, openParenOffset); |
| } |
| buf.append(string.substring(off)); |
| |
| // Make all chars upper case to simplify comparisons later. |
| // Also ignore commas; treat them as delimiters. |
| StringTokenizer strtok = |
| new StringTokenizer(buf.toString().toUpperCase(), " \t\n\r,"); |
| |
| while (strtok.hasMoreTokens()) |
| { |
| String tok = strtok.nextToken(); |
| char firstch = tok.charAt(0); |
| if ((firstch == '+' || firstch == '-') && year >= 0) |
| { |
| timezone = parseTz(tok, firstch); |
| localTimezone = false; |
| } |
| else if (firstch >= '0' && firstch <= '9') |
| { |
| while (tok != null && tok.length() > 0) |
| { |
| // A colon or slash may be valid in the number. |
| // Find the first of these before calling parseInt. |
| int colon = tok.indexOf(':'); |
| int slash = tok.indexOf('/'); |
| int hyphen = tok.indexOf('-'); |
| // We choose tok.length initially because it makes |
| // processing simpler. |
| int punctOffset = tok.length(); |
| if (colon >= 0) |
| punctOffset = Math.min(punctOffset, colon); |
| if (slash >= 0) |
| punctOffset = Math.min(punctOffset, slash); |
| if (hyphen >= 0) |
| punctOffset = Math.min(punctOffset, hyphen); |
| // Following code relies on -1 being the exceptional |
| // case. |
| if (punctOffset == tok.length()) |
| punctOffset = -1; |
| |
| int num; |
| try |
| { |
| num = Integer.parseInt(punctOffset < 0 ? tok : |
| tok.substring(0, punctOffset)); |
| } |
| catch (NumberFormatException ex) |
| { |
| throw new IllegalArgumentException(tok); |
| } |
| |
| // TBD: Spec says year can be followed by a slash. That might |
| // make sense if using YY/MM/DD formats, but it would fail in |
| // that format for years <= 70. Also, what about 1900? That |
| // is interpreted as the year 3800; seems that the comparison |
| // should be num >= 1900 rather than just > 1900. |
| // What about a year of 62 - 70? (61 or less could be a (leap) |
| // second). 70/MM/DD cause an exception but 71/MM/DD is ok |
| // even though there's no ambiguity in either case. |
| // For the parse method, the spec as written seems too loose. |
| // Until shown otherwise, we'll follow the spec as written. |
| if (num > 70 && (punctOffset < 0 || punctOffset == slash)) |
| year = num > 1900 ? num - 1900 : num; |
| else if (punctOffset > 0 && punctOffset == colon) |
| { |
| if (hour < 0) |
| hour = num; |
| else |
| minute = num; |
| } |
| else if (punctOffset > 0 && punctOffset == slash) |
| { |
| if (month < 0) |
| month = num - 1; |
| else |
| day = num; |
| } |
| else if (hour >= 0 && minute < 0) |
| minute = num; |
| else if (minute >= 0 && second < 0) |
| second = num; |
| else if (day < 0) |
| day = num; |
| else |
| throw new IllegalArgumentException(tok); |
| |
| // Advance string if there's more to process in this token. |
| if (punctOffset < 0 || punctOffset + 1 >= tok.length()) |
| tok = null; |
| else |
| tok = tok.substring(punctOffset + 1); |
| } |
| } |
| else if (firstch >= 'A' && firstch <= 'Z') |
| { |
| if (tok.equals("AM")) |
| { |
| if (hour < 1 || hour > 12) |
| throw new IllegalArgumentException(tok); |
| if (hour == 12) |
| hour = 0; |
| } |
| else if (tok.equals("PM")) |
| { |
| if (hour < 1 || hour > 12) |
| throw new IllegalArgumentException(tok); |
| if (hour < 12) |
| hour += 12; |
| } |
| else if (parseDayOfWeek(tok)) |
| ; // Ignore it; throw the token away. |
| else if (tok.equals("UT") || tok.equals("UTC") || tok.equals("GMT")) |
| localTimezone = false; |
| else if (tok.startsWith("UT") || tok.startsWith("GMT")) |
| { |
| int signOffset = 3; |
| if (tok.charAt(1) == 'T' && tok.charAt(2) != 'C') |
| signOffset = 2; |
| |
| char sign = tok.charAt(signOffset); |
| if (sign != '+' && sign != '-') |
| throw new IllegalArgumentException(tok); |
| |
| timezone = parseTz(tok.substring(signOffset), sign); |
| localTimezone = false; |
| } |
| else if ((tmpMonth = parseMonth(tok)) >= 0) |
| month = tmpMonth; |
| else if (tok.length() == 3 && tok.charAt(2) == 'T') |
| { |
| // Convert timezone offset from hours to minutes. |
| char ch = tok.charAt(0); |
| if (ch == 'E') |
| timezone = -5 * 60; |
| else if (ch == 'C') |
| timezone = -6 * 60; |
| else if (ch == 'M') |
| timezone = -7 * 60; |
| else if (ch == 'P') |
| timezone = -8 * 60; |
| else |
| throw new IllegalArgumentException(tok); |
| |
| // Shift 60 minutes for Daylight Savings Time. |
| if (tok.charAt(1) == 'D') |
| timezone += 60; |
| else if (tok.charAt(1) != 'S') |
| throw new IllegalArgumentException(tok); |
| |
| localTimezone = false; |
| } |
| else |
| throw new IllegalArgumentException(tok); |
| } |
| else |
| throw new IllegalArgumentException(tok); |
| } |
| |
| // Unspecified minutes and seconds should default to 0. |
| if (minute < 0) |
| minute = 0; |
| if (second < 0) |
| second = 0; |
| |
| // Throw exception if any other fields have not been recognized and set. |
| if (year < 0 || month < 0 || day < 0 || hour < 0) |
| throw new IllegalArgumentException("Missing field"); |
| |
| // Return the time in either local time or relative to GMT as parsed. |
| // If no time-zone was specified, get the local one (in minutes) and |
| // convert to milliseconds before adding to the UTC. |
| return UTC(year, month, day, hour, minute, second) + (localTimezone ? |
| new Date(year, month, day).getTimezoneOffset() * 60 * 1000: |
| -timezone * 60 * 1000); |
| } |
| |
| public boolean after (Date when) { return this.millis > when.millis; } |
| public boolean before (Date when) { return this.millis < when.millis; } |
| |
| public boolean equals(Object obj) |
| { |
| return (obj != null && obj instanceof Date |
| && ((Date)obj).millis == this.millis); |
| } |
| |
| public long getTime() { return millis; } |
| |
| public int hashCode() |
| { |
| return (int)(millis^(millis>>>32)); |
| } |
| |
| private void setTime(int year, int month, int date, |
| int hours, int minutes, int seconds) |
| { |
| Calendar cal = new GregorianCalendar(year+1900, month, date, |
| hours, minutes, seconds); |
| millis = cal.getTimeInMillis(); |
| } |
| |
| public void setTime(long millis) { this.millis = millis; } |
| |
| private int getField (int fld) |
| { |
| Calendar cal = new GregorianCalendar(); |
| cal.setTime(this); |
| return cal.get(fld); |
| } |
| |
| public int getYear () |
| { |
| return getField(Calendar.YEAR) - 1900; |
| } |
| |
| public int getMonth () |
| { |
| return getField(Calendar.MONTH); |
| } |
| |
| public int getDate () |
| { |
| return getField(Calendar.DATE); |
| } |
| |
| public int getDay () |
| { |
| return getField(Calendar.DAY_OF_WEEK) - 1; |
| } |
| |
| public int getHours () |
| { |
| return getField(Calendar.HOUR_OF_DAY); |
| } |
| |
| public int getMinutes () |
| { |
| return getField(Calendar.MINUTE); |
| } |
| |
| public int getSeconds () |
| { |
| return getField(Calendar.SECOND); |
| } |
| |
| private void setField (int fld, int value) |
| { |
| Calendar cal = new GregorianCalendar(); |
| cal.setTime(this); |
| cal.set(fld, value); |
| millis = cal.getTimeInMillis(); |
| } |
| |
| public void setYear (int year) |
| { |
| setField(Calendar.YEAR, 1900 + year); |
| } |
| |
| public void setMonth (int month) |
| { |
| setField(Calendar.MONTH, month); |
| } |
| |
| public void setDate (int date) |
| { |
| setField(Calendar.DATE, date); |
| } |
| |
| public void setHours (int hours) |
| { |
| setField(Calendar.HOUR_OF_DAY, hours); |
| } |
| |
| public void setMinutes (int minutes) |
| { |
| setField(Calendar.MINUTE, minutes); |
| } |
| |
| public void setSeconds (int seconds) |
| { |
| setField(Calendar.SECOND, seconds); |
| } |
| |
| public int getTimezoneOffset () |
| { |
| Calendar cal = new GregorianCalendar(); |
| cal.setTime(this); |
| return - (cal.get(Calendar.ZONE_OFFSET) |
| + cal.get(Calendar.DST_OFFSET)/(60*1000)); |
| } |
| |
| public String toString () |
| { |
| // This is slow, but does it matter? There is no particularly |
| // fast way to do it, because we need the timezone offset, which |
| // we don't store. Unix ctime() doesn't provide this information. |
| SimpleDateFormat fmt = new SimpleDateFormat ("E MMM dd HH:mm:ss z yyyy", |
| Locale.US); |
| fmt.setTimeZone(TimeZone.getDefault()); |
| return fmt.format(this); |
| } |
| |
| public String toGMTString () |
| { |
| // This method is deprecated. We don't care if it is very slow. |
| SimpleDateFormat fmt = new SimpleDateFormat ("d MMM yyyy HH:mm:ss 'GMT'", |
| Locale.US); |
| fmt.setTimeZone(TimeZone.getTimeZone("GMT")); |
| return fmt.format(this); |
| } |
| |
| public String toLocaleString () |
| { |
| // This method is deprecated. We don't care if it is very slow. |
| DateFormat fmt = DateFormat.getDateTimeInstance(); |
| fmt.setTimeZone(TimeZone.getDefault()); |
| return fmt.format(this); |
| } |
| |
| public static long UTC (int year, int month, int date, |
| int hours, int minutes, int seconds) |
| { |
| GregorianCalendar cal = new GregorianCalendar (TimeZone.getTimeZone("GMT")); |
| cal.set(year+1900, month, date, hours, minutes, seconds); |
| return cal.getTimeInMillis(); |
| } |
| |
| /** |
| * Reads an Object from the stream. |
| */ |
| private void readObject (java.io.ObjectInputStream input) |
| throws java.io.IOException, ClassNotFoundException |
| { |
| input.defaultReadObject (); |
| millis = input.readLong (); |
| } |
| |
| /** |
| * Writes an Object to the stream. |
| * @serialdata A long value representing the offset from the epoch |
| * in milliseconds. This is the same value that is returned by the |
| * method getTime(). |
| */ |
| private void writeObject (java.io.ObjectOutputStream output) |
| throws java.io.IOException |
| { |
| output.defaultWriteObject (); |
| output.writeLong (millis); |
| } |
| } |