Initial revision
[gcc.git] / libjava / java / util / Date.java
1 /* Copyright (C) 1998, 1999 Cygnus Solutions
2
3 This file is part of libgcj.
4
5 This software is copyrighted work licensed under the terms of the
6 Libgcj License. Please consult the file "LIBGCJ_LICENSE" for
7 details. */
8
9 package java.util;
10 import java.text.*;
11
12 /**
13 * @author Per Bothner <bothner@cygnus.com>
14 * @date October 24, 1998.
15 */
16
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.
23 */
24
25 public class Date implements java.io.Serializable, Cloneable
26 {
27 private long millis;
28
29 public Date() { millis = System.currentTimeMillis(); }
30
31 public Date(long millis) { this.millis = millis; }
32
33 public Date(int year, int month, int date, int hours,
34 int minutes, int seconds)
35 {
36 setTime(year, month, date, hours, minutes, seconds);
37 }
38
39 public Date(int year, int month, int date, int hours, int minutes)
40 {
41 setTime(year, month, date, hours, minutes, 0);
42 }
43
44 public Date(int year, int month, int date)
45 {
46 setTime(year, month, date, 0, 0, 0);
47 }
48
49 public Date (String s) { this(parse(s)); }
50
51 private static int skipParens(String string, int offset)
52 {
53 int len = string.length();
54 int p = 0;
55 int i;
56
57 for (i = offset; i < len; ++i)
58 {
59 if (string.charAt(i) == '(')
60 ++p;
61 else if (string.charAt(i) == ')')
62 {
63 --p;
64 if (p == 0)
65 return i + 1;
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.
70 if (p < 0)
71 return i;
72 }
73 }
74
75 // Not sure what to do if `p != 0' here.
76 return i;
77 }
78
79 private static int parseTz(String tok, char sign)
80 throws IllegalArgumentException
81 {
82 int num;
83
84 try
85 {
86 // parseInt doesn't handle '+' so strip off sign.
87 num = Integer.parseInt(tok.substring(1));
88 }
89 catch (NumberFormatException ex)
90 {
91 throw new IllegalArgumentException(tok);
92 }
93
94 // Convert hours to minutes.
95 if (num < 24)
96 num *= 60;
97 else
98 num = (num / 100) * 60 + num % 100;
99
100 return sign == '-' ? -num : num;
101 }
102
103 private static int parseMonth(String tok)
104 {
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" };
111
112 int i;
113 for (i = 0; i < 12; i++)
114 if (months[i].startsWith(tok))
115 return i;
116
117 // Return -1 if not found.
118 return -1;
119 }
120
121 private static boolean parseDayOfWeek(String tok)
122 {
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" };
128
129 int i;
130 for (i = 0; i < 7; i++)
131 if (daysOfWeek[i].startsWith(tok))
132 return true;
133
134 return false;
135 }
136
137 public static long parse(String string)
138 {
139 // Initialize date/time fields before parsing begins.
140 int year = -1;
141 int month = -1;
142 int day = -1;
143 int hour = -1;
144 int minute = -1;
145 int second = -1;
146 int timezone = 0;
147 boolean localTimezone = true;
148
149 // Trim out any nested stuff in parentheses now to make parsing easier.
150 StringBuffer buf = new StringBuffer();
151 int off = 0;
152 int openParenOffset, tmpMonth;
153 while ((openParenOffset = string.indexOf('(', off)) >= 0)
154 {
155 // Copy part of string leading up to open paren.
156 buf.append(string.substring(off, openParenOffset));
157 off = skipParens(string, openParenOffset);
158 }
159 buf.append(string.substring(off));
160
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,");
165
166 while (strtok.hasMoreTokens())
167 {
168 String tok = strtok.nextToken();
169 char firstch = tok.charAt(0);
170 if ((firstch == '+' || firstch == '-') && year >= 0)
171 {
172 timezone = parseTz(tok, firstch);
173 localTimezone = false;
174 }
175 else if (firstch >= '0' && firstch <= '9')
176 {
177 while (tok != null && tok.length() > 0)
178 {
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();
187 if (colon >= 0)
188 punctOffset = Math.min(punctOffset, colon);
189 if (slash >= 0)
190 punctOffset = Math.min(punctOffset, slash);
191 if (hyphen >= 0)
192 punctOffset = Math.min(punctOffset, hyphen);
193 // Following code relies on -1 being the exceptional
194 // case.
195 if (punctOffset == tok.length())
196 punctOffset = -1;
197
198 int num;
199 try
200 {
201 num = Integer.parseInt(punctOffset < 0 ? tok :
202 tok.substring(0, punctOffset));
203 }
204 catch (NumberFormatException ex)
205 {
206 throw new IllegalArgumentException(tok);
207 }
208
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)
222 {
223 if (hour < 0)
224 hour = num;
225 else
226 minute = num;
227 }
228 else if (punctOffset > 0 && punctOffset == slash)
229 {
230 if (month < 0)
231 month = num - 1;
232 else
233 day = num;
234 }
235 else if (hour >= 0 && minute < 0)
236 minute = num;
237 else if (minute >= 0 && second < 0)
238 second = num;
239 else if (day < 0)
240 day = num;
241 else
242 throw new IllegalArgumentException(tok);
243
244 // Advance string if there's more to process in this token.
245 if (punctOffset < 0 || punctOffset + 1 >= tok.length())
246 tok = null;
247 else
248 tok = tok.substring(punctOffset + 1);
249 }
250 }
251 else if (firstch >= 'A' && firstch <= 'Z')
252 {
253 if (tok.equals("AM"))
254 {
255 if (hour < 1 || hour > 12)
256 throw new IllegalArgumentException(tok);
257 if (hour == 12)
258 hour = 0;
259 }
260 else if (tok.equals("PM"))
261 {
262 if (hour < 1 || hour > 12)
263 throw new IllegalArgumentException(tok);
264 if (hour < 12)
265 hour += 12;
266 }
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"))
272 {
273 int signOffset = 3;
274 if (tok.charAt(1) == 'T' && tok.charAt(2) != 'C')
275 signOffset = 2;
276
277 char sign = tok.charAt(signOffset);
278 if (sign != '+' && sign != '-')
279 throw new IllegalArgumentException(tok);
280
281 timezone = parseTz(tok.substring(signOffset), sign);
282 localTimezone = false;
283 }
284 else if ((tmpMonth = parseMonth(tok)) >= 0)
285 month = tmpMonth;
286 else if (tok.length() == 3 && tok.charAt(2) == 'T')
287 {
288 // Convert timezone offset from hours to minutes.
289 char ch = tok.charAt(0);
290 if (ch == 'E')
291 timezone = -5 * 60;
292 else if (ch == 'C')
293 timezone = -6 * 60;
294 else if (ch == 'M')
295 timezone = -7 * 60;
296 else if (ch == 'P')
297 timezone = -8 * 60;
298 else
299 throw new IllegalArgumentException(tok);
300
301 // Shift 60 minutes for Daylight Savings Time.
302 if (tok.charAt(1) == 'D')
303 timezone += 60;
304 else if (tok.charAt(1) != 'S')
305 throw new IllegalArgumentException(tok);
306
307 localTimezone = false;
308 }
309 else
310 throw new IllegalArgumentException(tok);
311 }
312 else
313 throw new IllegalArgumentException(tok);
314 }
315
316 // Unspecified minutes and seconds should default to 0.
317 if (minute < 0)
318 minute = 0;
319 if (second < 0)
320 second = 0;
321
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");
325
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);
332 }
333
334 public boolean after (Date when) { return this.millis > when.millis; }
335 public boolean before (Date when) { return this.millis < when.millis; }
336
337 public boolean equals(Object obj)
338 {
339 return (obj != null && obj instanceof Date
340 && ((Date)obj).millis == this.millis);
341 }
342
343 public long getTime() { return millis; }
344
345 public int hashCode()
346 {
347 return (int)(millis^(millis>>>32));
348 }
349
350 private void setTime(int year, int month, int date,
351 int hours, int minutes, int seconds)
352 {
353 Calendar cal = new GregorianCalendar(year+1900, month, date,
354 hours, minutes, seconds);
355 millis = cal.getTimeInMillis();
356 }
357
358 public void setTime(long millis) { this.millis = millis; }
359
360 private int getField (int fld)
361 {
362 Calendar cal = new GregorianCalendar();
363 cal.setTime(this);
364 return cal.get(fld);
365 }
366
367 public int getYear ()
368 {
369 return getField(Calendar.YEAR) - 1900;
370 }
371
372 public int getMonth ()
373 {
374 return getField(Calendar.MONTH);
375 }
376
377 public int getDate ()
378 {
379 return getField(Calendar.DATE);
380 }
381
382 public int getDay ()
383 {
384 return getField(Calendar.DAY_OF_WEEK) - 1;
385 }
386
387 public int getHours ()
388 {
389 return getField(Calendar.HOUR_OF_DAY);
390 }
391
392 public int getMinutes ()
393 {
394 return getField(Calendar.MINUTE);
395 }
396
397 public int getSeconds ()
398 {
399 return getField(Calendar.SECOND);
400 }
401
402 private void setField (int fld, int value)
403 {
404 Calendar cal = new GregorianCalendar();
405 cal.setTime(this);
406 cal.set(fld, value);
407 millis = cal.getTimeInMillis();
408 }
409
410 public void setYear (int year)
411 {
412 setField(Calendar.YEAR, 1900 + year);
413 }
414
415 public void setMonth (int month)
416 {
417 setField(Calendar.MONTH, month);
418 }
419
420 public void setDate (int date)
421 {
422 setField(Calendar.DATE, date);
423 }
424
425 public void setHours (int hours)
426 {
427 setField(Calendar.HOUR_OF_DAY, hours);
428 }
429
430 public void setMinutes (int minutes)
431 {
432 setField(Calendar.MINUTE, minutes);
433 }
434
435 public void setSeconds (int seconds)
436 {
437 setField(Calendar.SECOND, seconds);
438 }
439
440 public int getTimezoneOffset ()
441 {
442 Calendar cal = new GregorianCalendar();
443 cal.setTime(this);
444 return - (cal.get(Calendar.ZONE_OFFSET)
445 + cal.get(Calendar.DST_OFFSET)/(60*1000));
446 }
447
448 public native String toString ();
449
450 // TODO: toLocaleString
451 // TODO: toGMTString
452
453 public static long UTC (int year, int month, int date,
454 int hours, int minutes, int seconds)
455 {
456 GregorianCalendar cal = new GregorianCalendar (TimeZone.zoneGMT);
457 cal.set(year+1900, month, date, hours, minutes, seconds);
458 return cal.getTimeInMillis();
459 }
460 }