1 /* Copyright (C) 1998, 1999 Cygnus Solutions
3 This file is part of libgcj.
5 This software is copyrighted work licensed under the terms of the
6 Libgcj License. Please consult the file "LIBGCJ_LICENSE" for
13 * @author Per Bothner <bothner@cygnus.com>
14 * @date October 24, 1998.
17 /* Written using "Java Class Libraries", 2nd edition, ISBN 0-201-31002-3,
18 * "The Java Language Specification", ISBN 0-201-63451-1,
19 * and O'Reilly's "Java in a Nutshell".
20 * Status: Need to re-write toString().
21 * Missing: ToGMTString and toLocaleString.
22 * Serialization spec: Specifies readObject/writeObject.
25 public class Date
implements java
.io
.Serializable
, Cloneable
29 public Date() { millis
= System
.currentTimeMillis(); }
31 public Date(long millis
) { this.millis
= millis
; }
33 public Date(int year
, int month
, int date
, int hours
,
34 int minutes
, int seconds
)
36 setTime(year
, month
, date
, hours
, minutes
, seconds
);
39 public Date(int year
, int month
, int date
, int hours
, int minutes
)
41 setTime(year
, month
, date
, hours
, minutes
, 0);
44 public Date(int year
, int month
, int date
)
46 setTime(year
, month
, date
, 0, 0, 0);
49 public Date (String s
) { this(parse(s
)); }
51 private static int skipParens(String string
, int offset
)
53 int len
= string
.length();
57 for (i
= offset
; i
< len
; ++i
)
59 if (string
.charAt(i
) == '(')
61 else if (string
.charAt(i
) == ')')
66 // If we've encounted unbalanced parens, just return the
67 // leftover one as an ordinary character. It will be
68 // caught later in parsing and cause an
69 // IllegalArgumentException.
75 // Not sure what to do if `p != 0' here.
79 private static int parseTz(String tok
, char sign
)
80 throws IllegalArgumentException
86 // parseInt doesn't handle '+' so strip off sign.
87 num
= Integer
.parseInt(tok
.substring(1));
89 catch (NumberFormatException ex
)
91 throw new IllegalArgumentException(tok
);
94 // Convert hours to minutes.
98 num
= (num
/ 100) * 60 + num
% 100;
100 return sign
== '-' ?
-num
: num
;
103 private static int parseMonth(String tok
)
105 // Initialize strings for month names.
106 // We could possibly use the fields of DateFormatSymbols but that is
107 // localized and thus might not match the English words specified.
108 String months
[] = { "JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY",
109 "JUNE", "JULY", "AUGUST", "SEPTEMBER", "OCTOBER",
110 "NOVEMBER", "DECEMBER" };
113 for (i
= 0; i
< 12; i
++)
114 if (months
[i
].startsWith(tok
))
117 // Return -1 if not found.
121 private static boolean parseDayOfWeek(String tok
)
123 // Initialize strings for days of the week names.
124 // We could possibly use the fields of DateFormatSymbols but that is
125 // localized and thus might not match the English words specified.
126 String daysOfWeek
[] = { "SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY",
127 "THURSDAY", "FRIDAY", "SATURDAY" };
130 for (i
= 0; i
< 7; i
++)
131 if (daysOfWeek
[i
].startsWith(tok
))
137 public static long parse(String string
)
139 // Initialize date/time fields before parsing begins.
147 boolean localTimezone
= true;
149 // Trim out any nested stuff in parentheses now to make parsing easier.
150 StringBuffer buf
= new StringBuffer();
152 int openParenOffset
, tmpMonth
;
153 while ((openParenOffset
= string
.indexOf('(', off
)) >= 0)
155 // Copy part of string leading up to open paren.
156 buf
.append(string
.substring(off
, openParenOffset
));
157 off
= skipParens(string
, openParenOffset
);
159 buf
.append(string
.substring(off
));
161 // Make all chars upper case to simplify comparisons later.
162 // Also ignore commas; treat them as delimiters.
163 StringTokenizer strtok
=
164 new StringTokenizer(buf
.toString().toUpperCase(), " \t\n\r,");
166 while (strtok
.hasMoreTokens())
168 String tok
= strtok
.nextToken();
169 char firstch
= tok
.charAt(0);
170 if ((firstch
== '+' || firstch
== '-') && year
>= 0)
172 timezone
= parseTz(tok
, firstch
);
173 localTimezone
= false;
175 else if (firstch
>= '0' && firstch
<= '9')
177 while (tok
!= null && tok
.length() > 0)
179 // A colon or slash may be valid in the number.
180 // Find the first of these before calling parseInt.
181 int colon
= tok
.indexOf(':');
182 int slash
= tok
.indexOf('/');
183 int hyphen
= tok
.indexOf('-');
184 // We choose tok.length initially because it makes
185 // processing simpler.
186 int punctOffset
= tok
.length();
188 punctOffset
= Math
.min(punctOffset
, colon
);
190 punctOffset
= Math
.min(punctOffset
, slash
);
192 punctOffset
= Math
.min(punctOffset
, hyphen
);
193 // Following code relies on -1 being the exceptional
195 if (punctOffset
== tok
.length())
201 num
= Integer
.parseInt(punctOffset
< 0 ? tok
:
202 tok
.substring(0, punctOffset
));
204 catch (NumberFormatException ex
)
206 throw new IllegalArgumentException(tok
);
209 // TBD: Spec says year can be followed by a slash. That might
210 // make sense if using YY/MM/DD formats, but it would fail in
211 // that format for years <= 70. Also, what about 1900? That
212 // is interpreted as the year 3800; seems that the comparison
213 // should be num >= 1900 rather than just > 1900.
214 // What about a year of 62 - 70? (61 or less could be a (leap)
215 // second). 70/MM/DD cause an exception but 71/MM/DD is ok
216 // even though there's no ambiguity in either case.
217 // For the parse method, the spec as written seems too loose.
218 // Until shown otherwise, we'll follow the spec as written.
219 if (num
> 70 && (punctOffset
< 0 || punctOffset
== slash
))
220 year
= num
> 1900 ? num
- 1900 : num
;
221 else if (punctOffset
> 0 && punctOffset
== colon
)
228 else if (punctOffset
> 0 && punctOffset
== slash
)
235 else if (hour
>= 0 && minute
< 0)
237 else if (minute
>= 0 && second
< 0)
242 throw new IllegalArgumentException(tok
);
244 // Advance string if there's more to process in this token.
245 if (punctOffset
< 0 || punctOffset
+ 1 >= tok
.length())
248 tok
= tok
.substring(punctOffset
+ 1);
251 else if (firstch
>= 'A' && firstch
<= 'Z')
253 if (tok
.equals("AM"))
255 if (hour
< 1 || hour
> 12)
256 throw new IllegalArgumentException(tok
);
260 else if (tok
.equals("PM"))
262 if (hour
< 1 || hour
> 12)
263 throw new IllegalArgumentException(tok
);
267 else if (parseDayOfWeek(tok
))
268 ; // Ignore it; throw the token away.
269 else if (tok
.equals("UT") || tok
.equals("UTC") || tok
.equals("GMT"))
270 localTimezone
= false;
271 else if (tok
.startsWith("UT") || tok
.startsWith("GMT"))
274 if (tok
.charAt(1) == 'T' && tok
.charAt(2) != 'C')
277 char sign
= tok
.charAt(signOffset
);
278 if (sign
!= '+' && sign
!= '-')
279 throw new IllegalArgumentException(tok
);
281 timezone
= parseTz(tok
.substring(signOffset
), sign
);
282 localTimezone
= false;
284 else if ((tmpMonth
= parseMonth(tok
)) >= 0)
286 else if (tok
.length() == 3 && tok
.charAt(2) == 'T')
288 // Convert timezone offset from hours to minutes.
289 char ch
= tok
.charAt(0);
299 throw new IllegalArgumentException(tok
);
301 // Shift 60 minutes for Daylight Savings Time.
302 if (tok
.charAt(1) == 'D')
304 else if (tok
.charAt(1) != 'S')
305 throw new IllegalArgumentException(tok
);
307 localTimezone
= false;
310 throw new IllegalArgumentException(tok
);
313 throw new IllegalArgumentException(tok
);
316 // Unspecified minutes and seconds should default to 0.
322 // Throw exception if any other fields have not been recognized and set.
323 if (year
< 0 || month
< 0 || day
< 0 || hour
< 0)
324 throw new IllegalArgumentException("Missing field");
326 // Return the time in either local time or relative to GMT as parsed.
327 // If no time-zone was specified, get the local one (in minutes) and
328 // convert to milliseconds before adding to the UTC.
329 return UTC(year
, month
, day
, hour
, minute
, second
) + (localTimezone ?
330 new Date(year
, month
, day
).getTimezoneOffset() * 60 * 1000:
331 -timezone
* 60 * 1000);
334 public boolean after (Date when
) { return this.millis
> when
.millis
; }
335 public boolean before (Date when
) { return this.millis
< when
.millis
; }
337 public boolean equals(Object obj
)
339 return (obj
!= null && obj
instanceof Date
340 && ((Date
)obj
).millis
== this.millis
);
343 public long getTime() { return millis
; }
345 public int hashCode()
347 return (int)(millis^
(millis
>>>32));
350 private void setTime(int year
, int month
, int date
,
351 int hours
, int minutes
, int seconds
)
353 Calendar cal
= new GregorianCalendar(year
+1900, month
, date
,
354 hours
, minutes
, seconds
);
355 millis
= cal
.getTimeInMillis();
358 public void setTime(long millis
) { this.millis
= millis
; }
360 private int getField (int fld
)
362 Calendar cal
= new GregorianCalendar();
367 public int getYear ()
369 return getField(Calendar
.YEAR
) - 1900;
372 public int getMonth ()
374 return getField(Calendar
.MONTH
);
377 public int getDate ()
379 return getField(Calendar
.DATE
);
384 return getField(Calendar
.DAY_OF_WEEK
) - 1;
387 public int getHours ()
389 return getField(Calendar
.HOUR_OF_DAY
);
392 public int getMinutes ()
394 return getField(Calendar
.MINUTE
);
397 public int getSeconds ()
399 return getField(Calendar
.SECOND
);
402 private void setField (int fld
, int value
)
404 Calendar cal
= new GregorianCalendar();
407 millis
= cal
.getTimeInMillis();
410 public void setYear (int year
)
412 setField(Calendar
.YEAR
, 1900 + year
);
415 public void setMonth (int month
)
417 setField(Calendar
.MONTH
, month
);
420 public void setDate (int date
)
422 setField(Calendar
.DATE
, date
);
425 public void setHours (int hours
)
427 setField(Calendar
.HOUR_OF_DAY
, hours
);
430 public void setMinutes (int minutes
)
432 setField(Calendar
.MINUTE
, minutes
);
435 public void setSeconds (int seconds
)
437 setField(Calendar
.SECOND
, seconds
);
440 public int getTimezoneOffset ()
442 Calendar cal
= new GregorianCalendar();
444 return - (cal
.get(Calendar
.ZONE_OFFSET
)
445 + cal
.get(Calendar
.DST_OFFSET
)/(60*1000));
448 public native String
toString ();
450 // TODO: toLocaleString
453 public static long UTC (int year
, int month
, int date
,
454 int hours
, int minutes
, int seconds
)
456 GregorianCalendar cal
= new GregorianCalendar (TimeZone
.zoneGMT
);
457 cal
.set(year
+1900, month
, date
, hours
, minutes
, seconds
);
458 return cal
.getTimeInMillis();