]>
Commit | Line | Data |
---|---|---|
2ba5f774 | 1 | /* Copyright (C) 1998, 1999 Free Software Foundation |
ee9dd372 TT |
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 | ||
98e7ae29 TT |
448 | public String toString () |
449 | { | |
450 | // This is slow, but does it matter? There is no particularly | |
451 | // fast way to do it, because we need the timezone offset, which | |
452 | // we don't store. Unix ctime() doesn't provide this information. | |
453 | SimpleDateFormat fmt = new SimpleDateFormat ("E MMM dd HH:mm:ss z yyyy", | |
454 | Locale.US); | |
455 | fmt.setTimeZone(TimeZone.getDefault()); | |
456 | return fmt.format(this); | |
457 | } | |
ee9dd372 | 458 | |
98e7ae29 TT |
459 | public String toGMTString () |
460 | { | |
461 | // This method is deprecated. We don't care if it is very slow. | |
462 | SimpleDateFormat fmt = new SimpleDateFormat ("d MMM yyyy HH:mm:ss 'GMT'", | |
463 | Locale.US); | |
464 | fmt.setTimeZone(TimeZone.zoneGMT); | |
465 | return fmt.format(this); | |
466 | } | |
467 | ||
468 | public String toLocaleString () | |
469 | { | |
470 | // This method is deprecated. We don't care if it is very slow. | |
471 | DateFormat fmt = DateFormat.getDateTimeInstance(); | |
472 | fmt.setTimeZone(TimeZone.getDefault()); | |
473 | return fmt.format(this); | |
474 | } | |
ee9dd372 TT |
475 | |
476 | public static long UTC (int year, int month, int date, | |
477 | int hours, int minutes, int seconds) | |
478 | { | |
479 | GregorianCalendar cal = new GregorianCalendar (TimeZone.zoneGMT); | |
480 | cal.set(year+1900, month, date, hours, minutes, seconds); | |
481 | return cal.getTimeInMillis(); | |
482 | } | |
483 | } |