aboutsummaryrefslogtreecommitdiff
path: root/libjava/java/util/Date.java
blob: 6a8a7654938e8163bc5e550d864b2d0decb4c915 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
/* Copyright (C) 1998, 1999  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 and toLocaleString.
 *   Serialization spec:  Specifies readObject/writeObject.
 */

public class Date implements java.io.Serializable, Cloneable
{
  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.zoneGMT);
    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.zoneGMT);
    cal.set(year+1900, month, date, hours, minutes, seconds);
    return cal.getTimeInMillis();
  }
}