901ee9df667247b1a106dc7d4d6b73fc4f67d1b5
[org.ibex.core.git] / src / org / xwt / js / JSDate.java
1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
3  * The contents of this file are subject to the Netscape Public
4  * License Version 1.1 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of
6  * the License at http://www.mozilla.org/NPL/
7  *
8  * Software distributed under the License is distributed on an "AS
9  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
10  * implied. See the License for the specific language governing
11  * rights and limitations under the License.
12  *
13  * The Original Code is Rhino code, released
14  * May 6, 1999.
15  *
16  * The Initial Developer of the Original Code is Netscape
17  * Communications Corporation.  Portions created by Netscape are
18  * Copyright (C) 1997-1999 Netscape Communications Corporation. All
19  * Rights Reserved.
20  *
21  * Contributor(s):
22  * Norris Boyd
23  * Mike McCabe
24  *
25  * Alternatively, the contents of this file may be used under the
26  * terms of the GNU Public License (the "GPL"), in which case the
27  * provisions of the GPL are applicable instead of those above.
28  * If you wish to allow use of your version of this file only
29  * under the terms of the GPL and not to allow others to use your
30  * version of this file under the NPL, indicate your decision by
31  * deleting the provisions above and replace them with the notice
32  * and other provisions required by the GPL.  If you do not delete
33  * the provisions above, a recipient may use your version of this
34  * file under either the NPL or the GPL.
35  */
36
37 package org.xwt.js;
38
39 import java.util.TimeZone;
40 import java.util.Locale;
41 import java.text.NumberFormat;
42 import java.text.DateFormat;
43 import java.text.SimpleDateFormat;
44
45 /**
46  * This class implements the Date native object.
47  * See ECMA 15.9.
48  * @author Mike McCabe
49  * @author Adam Megacz (many modifications
50  */
51 public class JSDate extends JSCallable {
52
53     public JSDate() {
54         if (thisTimeZone == null) {
55             // j.u.TimeZone is synchronized, so setting class statics from it
56             // should be OK.
57             thisTimeZone = java.util.TimeZone.getDefault();
58             LocalTZA = thisTimeZone.getRawOffset();
59         }
60     }
61
62     public String coerceToString() { return date_format(date, FORMATSPEC_FULL); }
63
64     public Object callMethod(Object method, Object a0, Object a1, Object a2, Object[] rest, int nargs) {
65         switch(nargs) {
66             case 0: {
67                 //#switch(method)
68                 case "toString": return date_format(date, FORMATSPEC_FULL);
69                 case "toTimeString": return date_format(date, FORMATSPEC_TIME);
70                 case "toDateString": return date_format(date, FORMATSPEC_DATE);
71                 case "toLocaleString": return toLocaleString(date);
72                 case "toLocaleTimeString": return toLocaleTimeString(date);
73                 case "toLocaleDateString": return toLocaleDateString(date);
74                 case "toUTCString": return toUTCString(date);
75                 case "valueOf": return N(this.date);
76                 case "getTime": return N(this.date);
77                 case "getYear": return N(getYear(date));
78                 case "getFullYear": return N(YearFromTime(LocalTime(date)));
79                 case "getUTCFullYear": return N(YearFromTime(date));
80                 case "getMonth": return N(MonthFromTime(LocalTime(date)));
81                 case "getUTCMonth": return N(MonthFromTime(date));
82                 case "getDate": return N(DateFromTime(LocalTime(date)));
83                 case "getUTCDate": return N(DateFromTime(date));
84                 case "getDay": return N(WeekDay(LocalTime(date)));
85                 case "getUTCDay": return N(WeekDay(date));
86                 case "getHours": return N(HourFromTime(LocalTime(date)));
87                 case "getUTCHours": return N(HourFromTime(date));
88                 case "getMinutes": return N(MinFromTime(LocalTime(date)));
89                 case "getUTCMinutes": return N(MinFromTime(date));
90                 case "getSeconds": return N(SecFromTime(LocalTime(date)));
91                 case "getUTCSeconds": return N(SecFromTime(date));
92                 case "getMilliseconds": return N(msFromTime(LocalTime(date)));
93                 case "getUTCMilliseconds": return N(msFromTime(date));
94                 case "getTimezoneOffset": return N(getTimezoneOffset(date));
95                 //#end
96                 return super.callMethod(method, a0, a1, a2, rest, nargs);
97             }
98             case 1: {
99                 //#switch(method)
100                 case "setTime": return N(this.setTime(toDouble(a0)));
101                 case "setYear": return N(this.setYear(toDouble(a0)));
102                 //#end
103                 // fall through
104             }
105             default: {
106                 Object[] args = new Object[nargs];
107                 for(int i=0; i<nargs; i++) args[i] = i==0 ? a0 : i==1 ? a1 : i==2 ? a2 : rest[i-3];
108                 //#switch(method)
109                 case "setMilliseconds": return N(this.makeTime(args, 1, true));
110                 case "setUTCMilliseconds": return N(this.makeTime(args, 1, false));
111                 case "setSeconds": return N(this.makeTime(args, 2, true));
112                 case "setUTCSeconds": return N(this.makeTime(args, 2, false));
113                 case "setMinutes": return N(this.makeTime(args, 3, true));
114                 case "setUTCMinutes": return N(this.makeTime(args, 3, false));
115                 case "setHours": return N(this.makeTime(args, 4, true));
116                 case "setUTCHours": return N(this.makeTime(args, 4, false));
117                 case "setDate": return N(this.makeDate(args, 1, true));
118                 case "setUTCDate": return N(this.makeDate(args, 1, false));
119                 case "setMonth": return N(this.makeDate(args, 2, true));
120                 case "setUTCMonth": return N(this.makeDate(args, 2, false));
121                 case "setFullYear": return N(this.makeDate(args, 3, true));
122                 case "setUTCFullYear": return N(this.makeDate(args, 3, false));
123                 //#end
124             }
125         }
126         return super.callMethod(method, a0, a1, a2, rest, nargs);
127     }
128
129     public Object get(Object key) {
130         //#switch(key)
131         case "toString": return METHOD;
132         case "toTimeString": return METHOD;
133         case "toDateString": return METHOD;
134         case "toLocaleString": return METHOD;
135         case "toLocaleTimeString": return METHOD;
136         case "toLocaleDateString": return METHOD;
137         case "toUTCString": return METHOD;
138         case "valueOf": return METHOD;
139         case "getTime": return METHOD;
140         case "getYear": return METHOD;
141         case "getFullYear": return METHOD;
142         case "getUTCFullYear": return METHOD;
143         case "getMonth": return METHOD;
144         case "getUTCMonth": return METHOD;
145         case "getDate": return METHOD;
146         case "getUTCDate": return METHOD;
147         case "getDay": return METHOD;
148         case "getUTCDay": return METHOD;
149         case "getHours": return METHOD;
150         case "getUTCHours": return METHOD;
151         case "getMinutes": return METHOD;
152         case "getUTCMinutes": return METHOD;
153         case "getSeconds": return METHOD;
154         case "getUTCSeconds": return METHOD;
155         case "getMilliseconds": return METHOD;
156         case "getUTCMilliseconds": return METHOD;
157         case "getTimezoneOffset": return METHOD;
158         case "setTime": return METHOD;
159         case "setYear": return METHOD;
160         case "setMilliseconds": return METHOD;
161         case "setUTCMilliseconds": return METHOD;
162         case "setSeconds": return METHOD;
163         case "setUTCSeconds": return METHOD;
164         case "setMinutes": return METHOD;
165         case "setUTCMinutes": return METHOD;
166         case "setHours": return METHOD;
167         case "setUTCHours": return METHOD;
168         case "setDate": return METHOD;
169         case "setUTCDate": return METHOD;
170         case "setMonth": return METHOD;
171         case "setUTCMonth": return METHOD;
172         case "setFullYear": return METHOD;
173         case "setUTCFullYear": return METHOD;
174         //#end
175         return super.get(key);
176     }
177
178     /* ECMA helper functions */
179
180     private static final double HalfTimeDomain = 8.64e15;
181     private static final double HoursPerDay    = 24.0;
182     private static final double MinutesPerHour = 60.0;
183     private static final double SecondsPerMinute = 60.0;
184     private static final double msPerSecond    = 1000.0;
185     private static final double MinutesPerDay  = (HoursPerDay * MinutesPerHour);
186     private static final double SecondsPerDay  = (MinutesPerDay * SecondsPerMinute);
187     private static final double SecondsPerHour = (MinutesPerHour * SecondsPerMinute);
188     private static final double msPerDay       = (SecondsPerDay * msPerSecond);
189     private static final double msPerHour      = (SecondsPerHour * msPerSecond);
190     private static final double msPerMinute    = (SecondsPerMinute * msPerSecond);
191
192     private static double Day(double t) {
193         return java.lang.Math.floor(t / msPerDay);
194     }
195
196     private static double TimeWithinDay(double t) {
197         double result;
198         result = t % msPerDay;
199         if (result < 0)
200             result += msPerDay;
201         return result;
202     }
203
204     private static int DaysInYear(int y) {
205         if (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0))
206             return 366;
207         else
208             return 365;
209     }
210
211
212     /* math here has to be f.p, because we need
213      *  floor((1968 - 1969) / 4) == -1
214      */
215     private static double DayFromYear(double y) {
216         return ((365 * ((y)-1970) + java.lang.Math.floor(((y)-1969)/4.0)
217                  - java.lang.Math.floor(((y)-1901)/100.0) + java.lang.Math.floor(((y)-1601)/400.0)));
218     }
219
220     private static double TimeFromYear(double y) {
221         return DayFromYear(y) * msPerDay;
222     }
223
224     private static int YearFromTime(double t) {
225         int lo = (int) java.lang.Math.floor((t / msPerDay) / 366) + 1970;
226         int hi = (int) java.lang.Math.floor((t / msPerDay) / 365) + 1970;
227         int mid;
228
229         /* above doesn't work for negative dates... */
230         if (hi < lo) {
231             int temp = lo;
232             lo = hi;
233             hi = temp;
234         }
235
236         /* Use a simple binary search algorithm to find the right
237            year.  This seems like brute force... but the computation
238            of hi and lo years above lands within one year of the
239            correct answer for years within a thousand years of
240            1970; the loop below only requires six iterations
241            for year 270000. */
242         while (hi > lo) {
243             mid = (hi + lo) / 2;
244             if (TimeFromYear(mid) > t) {
245                 hi = mid - 1;
246             } else {
247                 if (TimeFromYear(mid) <= t) {
248                     int temp = mid + 1;
249                     if (TimeFromYear(temp) > t) {
250                         return mid;
251                     }
252                     lo = mid + 1;
253                 }
254             }
255         }
256         return lo;
257     }
258
259     private static boolean InLeapYear(double t) {
260         return DaysInYear(YearFromTime(t)) == 366;
261     }
262
263     private static int DayWithinYear(double t) {
264         int year = YearFromTime(t);
265         return (int) (Day(t) - DayFromYear(year));
266     }
267     /*
268      * The following array contains the day of year for the first day of
269      * each month, where index 0 is January, and day 0 is January 1.
270      */
271
272     private static double DayFromMonth(int m, boolean leap) {
273         int day = m * 30;
274
275         if (m >= 7) { day += m / 2 - 1; }
276         else if (m >= 2) { day += (m - 1) / 2 - 1; }
277         else { day += m; }
278
279         if (leap && m >= 2) { ++day; }
280
281         return day;
282     }
283
284     private static int MonthFromTime(double t) {
285         int d, step;
286
287         d = DayWithinYear(t);
288
289         if (d < (step = 31))
290             return 0;
291
292         // Originally coded as step += (InLeapYear(t) ? 29 : 28);
293         // but some jits always returned 28!
294         if (InLeapYear(t))
295             step += 29;
296         else
297             step += 28;
298
299         if (d < step)
300             return 1;
301         if (d < (step += 31))
302             return 2;
303         if (d < (step += 30))
304             return 3;
305         if (d < (step += 31))
306             return 4;
307         if (d < (step += 30))
308             return 5;
309         if (d < (step += 31))
310             return 6;
311         if (d < (step += 31))
312             return 7;
313         if (d < (step += 30))
314             return 8;
315         if (d < (step += 31))
316             return 9;
317         if (d < (step += 30))
318             return 10;
319         return 11;
320     }
321
322     private static int DateFromTime(double t) {
323         int d, step, next;
324
325         d = DayWithinYear(t);
326         if (d <= (next = 30))
327             return d + 1;
328         step = next;
329
330         // Originally coded as next += (InLeapYear(t) ? 29 : 28);
331         // but some jits always returned 28!
332         if (InLeapYear(t))
333             next += 29;
334         else
335             next += 28;
336
337         if (d <= next)
338             return d - step;
339         step = next;
340         if (d <= (next += 31))
341             return d - step;
342         step = next;
343         if (d <= (next += 30))
344             return d - step;
345         step = next;
346         if (d <= (next += 31))
347             return d - step;
348         step = next;
349         if (d <= (next += 30))
350             return d - step;
351         step = next;
352         if (d <= (next += 31))
353             return d - step;
354         step = next;
355         if (d <= (next += 31))
356             return d - step;
357         step = next;
358         if (d <= (next += 30))
359             return d - step;
360         step = next;
361         if (d <= (next += 31))
362             return d - step;
363         step = next;
364         if (d <= (next += 30))
365             return d - step;
366         step = next;
367
368         return d - step;
369     }
370
371     private static int WeekDay(double t) {
372         double result;
373         result = Day(t) + 4;
374         result = result % 7;
375         if (result < 0)
376             result += 7;
377         return (int) result;
378     }
379
380     private static double Now() {
381         return (double) System.currentTimeMillis();
382     }
383
384     /* Should be possible to determine the need for this dynamically
385      * if we go with the workaround... I'm not using it now, because I
386      * can't think of any clean way to make toLocaleString() and the
387      * time zone (comment) in toString match the generated string
388      * values.  Currently it's wrong-but-consistent in all but the
389      * most recent betas of the JRE - seems to work in 1.1.7.
390      */
391     private final static boolean TZO_WORKAROUND = false;
392     private static double DaylightSavingTA(double t) {
393         if (!TZO_WORKAROUND) {
394             java.util.Date date = new java.util.Date((long) t);
395             if (thisTimeZone.inDaylightTime(date))
396                 return msPerHour;
397             else
398                 return 0;
399         } else {
400             /* Use getOffset if inDaylightTime() is broken, because it
401              * seems to work acceptably.  We don't switch over to it
402              * entirely, because it requires (expensive) exploded date arguments,
403              * and the api makes it impossible to handle dst
404              * changeovers cleanly.
405              */
406
407             // Hardcode the assumption that the changeover always
408             // happens at 2:00 AM:
409             t += LocalTZA + (HourFromTime(t) <= 2 ? msPerHour : 0);
410
411             int year = YearFromTime(t);
412             double offset = thisTimeZone.getOffset(year > 0 ? 1 : 0,
413                                                    year,
414                                                    MonthFromTime(t),
415                                                    DateFromTime(t),
416                                                    WeekDay(t),
417                                                    (int)TimeWithinDay(t));
418
419             if ((offset - LocalTZA) != 0)
420                 return msPerHour;
421             else
422                 return 0;
423             //         return offset - LocalTZA;
424         }
425     }
426
427     private static double LocalTime(double t) {
428         return t + LocalTZA + DaylightSavingTA(t);
429     }
430
431     public static double internalUTC(double t) {
432         return t - LocalTZA - DaylightSavingTA(t - LocalTZA);
433     }
434
435     private static int HourFromTime(double t) {
436         double result;
437         result = java.lang.Math.floor(t / msPerHour) % HoursPerDay;
438         if (result < 0)
439             result += HoursPerDay;
440         return (int) result;
441     }
442
443     private static int MinFromTime(double t) {
444         double result;
445         result = java.lang.Math.floor(t / msPerMinute) % MinutesPerHour;
446         if (result < 0)
447             result += MinutesPerHour;
448         return (int) result;
449     }
450
451     private static int SecFromTime(double t) {
452         double result;
453         result = java.lang.Math.floor(t / msPerSecond) % SecondsPerMinute;
454         if (result < 0)
455             result += SecondsPerMinute;
456         return (int) result;
457     }
458
459     private static int msFromTime(double t) {
460         double result;
461         result =  t % msPerSecond;
462         if (result < 0)
463             result += msPerSecond;
464         return (int) result;
465     }
466
467     private static double MakeTime(double hour, double min,
468                                    double sec, double ms)
469     {
470         return ((hour * MinutesPerHour + min) * SecondsPerMinute + sec)
471             * msPerSecond + ms;
472     }
473
474     private static double MakeDay(double year, double month, double date) {
475         double result;
476         boolean leap;
477         double yearday;
478         double monthday;
479
480         year += java.lang.Math.floor(month / 12);
481
482         month = month % 12;
483         if (month < 0)
484             month += 12;
485
486         leap = (DaysInYear((int) year) == 366);
487
488         yearday = java.lang.Math.floor(TimeFromYear(year) / msPerDay);
489         monthday = DayFromMonth((int) month, leap);
490
491         result = yearday
492             + monthday
493             + date - 1;
494         return result;
495     }
496
497     private static double MakeDate(double day, double time) {
498         return day * msPerDay + time;
499     }
500
501     private static double TimeClip(double d) {
502         if (d != d ||
503             d == Double.POSITIVE_INFINITY ||
504             d == Double.NEGATIVE_INFINITY ||
505             java.lang.Math.abs(d) > HalfTimeDomain)
506         {
507             return Double.NaN;
508         }
509         if (d > 0.0)
510             return java.lang.Math.floor(d + 0.);
511         else
512             return java.lang.Math.ceil(d + 0.);
513     }
514
515     /* end of ECMA helper functions */
516
517     /* find UTC time from given date... no 1900 correction! */
518     public static double date_msecFromDate(double year, double mon,
519                                             double mday, double hour,
520                                             double min, double sec,
521                                             double msec)
522     {
523         double day;
524         double time;
525         double result;
526
527         day = MakeDay(year, mon, mday);
528         time = MakeTime(hour, min, sec, msec);
529         result = MakeDate(day, time);
530         return result;
531     }
532
533
534     private static final int MAXARGS = 7;
535     private static double jsStaticJSFunction_UTC(Object[] args) {
536         double array[] = new double[MAXARGS];
537         int loop;
538         double d;
539
540         for (loop = 0; loop < MAXARGS; loop++) {
541             if (loop < args.length) {
542                 d = _toNumber(args[loop]);
543                 if (d != d || Double.isInfinite(d)) {
544                     return Double.NaN;
545                 }
546                 array[loop] = toDouble(args[loop]);
547             } else {
548                 array[loop] = 0;
549             }
550         }
551
552         /* adjust 2-digit years into the 20th century */
553         if (array[0] >= 0 && array[0] <= 99)
554             array[0] += 1900;
555
556             /* if we got a 0 for 'date' (which is out of range)
557              * pretend it's a 1.  (So Date.UTC(1972, 5) works) */
558         if (array[2] < 1)
559             array[2] = 1;
560
561         d = date_msecFromDate(array[0], array[1], array[2],
562                               array[3], array[4], array[5], array[6]);
563         d = TimeClip(d);
564         return d;
565         //        return N(d);
566     }
567
568     /*
569      * Use ported code from jsdate.c rather than the locale-specific
570      * date-parsing code from Java, to keep js and rhino consistent.
571      * Is this the right strategy?
572      */
573
574     /* for use by date_parse */
575
576     /* replace this with byte arrays?  Cheaper? */
577     private static String wtb[] = {
578         "am", "pm",
579         "monday", "tuesday", "wednesday", "thursday", "friday",
580         "saturday", "sunday",
581         "january", "february", "march", "april", "may", "june",
582         "july", "august", "september", "october", "november", "december",
583         "gmt", "ut", "utc", "est", "edt", "cst", "cdt",
584         "mst", "mdt", "pst", "pdt"
585         /* time zone table needs to be expanded */
586     };
587
588     private static int ttb[] = {
589         -1, -2, 0, 0, 0, 0, 0, 0, 0,     /* AM/PM */
590         2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
591         10000 + 0, 10000 + 0, 10000 + 0, /* UT/UTC */
592         10000 + 5 * 60, 10000 + 4 * 60,  /* EDT */
593         10000 + 6 * 60, 10000 + 5 * 60,
594         10000 + 7 * 60, 10000 + 6 * 60,
595         10000 + 8 * 60, 10000 + 7 * 60
596     };
597
598     /* helper for date_parse */
599     private static boolean date_regionMatches(String s1, int s1off,
600                                               String s2, int s2off,
601                                               int count)
602     {
603         boolean result = false;
604         /* return true if matches, otherwise, false */
605         int s1len = s1.length();
606         int s2len = s2.length();
607
608         while (count > 0 && s1off < s1len && s2off < s2len) {
609             if (Character.toLowerCase(s1.charAt(s1off)) !=
610                 Character.toLowerCase(s2.charAt(s2off)))
611                 break;
612             s1off++;
613             s2off++;
614             count--;
615         }
616
617         if (count == 0) {
618             result = true;
619         }
620         return result;
621     }
622
623     private static double date_parseString(String s) {
624         double msec;
625
626         int year = -1;
627         int mon = -1;
628         int mday = -1;
629         int hour = -1;
630         int min = -1;
631         int sec = -1;
632         char c = 0;
633         char si = 0;
634         int i = 0;
635         int n = -1;
636         double tzoffset = -1;
637         char prevc = 0;
638         int limit = 0;
639         boolean seenplusminus = false;
640
641         if (s == null)  // ??? Will s be null?
642             return Double.NaN;
643         limit = s.length();
644         while (i < limit) {
645             c = s.charAt(i);
646             i++;
647             if (c <= ' ' || c == ',' || c == '-') {
648                 if (i < limit) {
649                     si = s.charAt(i);
650                     if (c == '-' && '0' <= si && si <= '9') {
651                         prevc = c;
652                     }
653                 }
654                 continue;
655             }
656             if (c == '(') { /* comments) */
657                 int depth = 1;
658                 while (i < limit) {
659                     c = s.charAt(i);
660                     i++;
661                     if (c == '(')
662                         depth++;
663                     else if (c == ')')
664                         if (--depth <= 0)
665                             break;
666                 }
667                 continue;
668             }
669             if ('0' <= c && c <= '9') {
670                 n = c - '0';
671                 while (i < limit && '0' <= (c = s.charAt(i)) && c <= '9') {
672                     n = n * 10 + c - '0';
673                     i++;
674                 }
675
676                 /* allow TZA before the year, so
677                  * 'Wed Nov 05 21:49:11 GMT-0800 1997'
678                  * works */
679
680                 /* uses of seenplusminus allow : in TZA, so Java
681                  * no-timezone style of GMT+4:30 works
682                  */
683                 if ((prevc == '+' || prevc == '-')/*  && year>=0 */) {
684                     /* make ':' case below change tzoffset */
685                     seenplusminus = true;
686
687                     /* offset */
688                     if (n < 24)
689                         n = n * 60; /* EG. "GMT-3" */
690                     else
691                         n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */
692                     if (prevc == '+')       /* plus means east of GMT */
693                         n = -n;
694                     if (tzoffset != 0 && tzoffset != -1)
695                         return Double.NaN;
696                     tzoffset = n;
697                 } else if (n >= 70  ||
698                            (prevc == '/' && mon >= 0 && mday >= 0 && year < 0)) {
699                     if (year >= 0)
700                         return Double.NaN;
701                     else if (c <= ' ' || c == ',' || c == '/' || i >= limit)
702                         year = n < 100 ? n + 1900 : n;
703                     else
704                         return Double.NaN;
705                 } else if (c == ':') {
706                     if (hour < 0)
707                         hour = /*byte*/ n;
708                     else if (min < 0)
709                         min = /*byte*/ n;
710                     else
711                         return Double.NaN;
712                 } else if (c == '/') {
713                     if (mon < 0)
714                         mon = /*byte*/ n-1;
715                     else if (mday < 0)
716                         mday = /*byte*/ n;
717                     else
718                         return Double.NaN;
719                 } else if (i < limit && c != ',' && c > ' ' && c != '-') {
720                     return Double.NaN;
721                 } else if (seenplusminus && n < 60) {  /* handle GMT-3:30 */
722                     if (tzoffset < 0)
723                         tzoffset -= n;
724                     else
725                         tzoffset += n;
726                 } else if (hour >= 0 && min < 0) {
727                     min = /*byte*/ n;
728                 } else if (min >= 0 && sec < 0) {
729                     sec = /*byte*/ n;
730                 } else if (mday < 0) {
731                     mday = /*byte*/ n;
732                 } else {
733                     return Double.NaN;
734                 }
735                 prevc = 0;
736             } else if (c == '/' || c == ':' || c == '+' || c == '-') {
737                 prevc = c;
738             } else {
739                 int st = i - 1;
740                 int k;
741                 while (i < limit) {
742                     c = s.charAt(i);
743                     if (!(('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')))
744                         break;
745                     i++;
746                 }
747                 if (i <= st + 1)
748                     return Double.NaN;
749                 for (k = wtb.length; --k >= 0;)
750                     if (date_regionMatches(wtb[k], 0, s, st, i-st)) {
751                         int action = ttb[k];
752                         if (action != 0) {
753                             if (action < 0) {
754                                 /*
755                                  * AM/PM. Count 12:30 AM as 00:30, 12:30 PM as
756                                  * 12:30, instead of blindly adding 12 if PM.
757                                  */
758                                 if (hour > 12 || hour < 0) {
759                                     return Double.NaN;
760                                 } else {
761                                     if (action == -1 && hour == 12) { // am
762                                         hour = 0;
763                                     } else if (action == -2 && hour != 12) {// pm
764                                         hour += 12;
765                                     }
766                                 }
767                             } else if (action <= 13) { /* month! */
768                                 if (mon < 0) {
769                                     mon = /*byte*/ (action - 2);
770                                 } else {
771                                     return Double.NaN;
772                                 }
773                             } else {
774                                 tzoffset = action - 10000;
775                             }
776                         }
777                         break;
778                     }
779                 if (k < 0)
780                     return Double.NaN;
781                 prevc = 0;
782             }
783         }
784         if (year < 0 || mon < 0 || mday < 0)
785             return Double.NaN;
786         if (sec < 0)
787             sec = 0;
788         if (min < 0)
789             min = 0;
790         if (hour < 0)
791             hour = 0;
792         if (tzoffset == -1) { /* no time zone specified, have to use local */
793             double time;
794             time = date_msecFromDate(year, mon, mday, hour, min, sec, 0);
795             return internalUTC(time);
796         }
797
798         msec = date_msecFromDate(year, mon, mday, hour, min, sec, 0);
799         msec += tzoffset * msPerMinute;
800         return msec;
801     }
802
803     private static double jsStaticJSFunction_parse(String s) {
804         return date_parseString(s);
805     }
806
807     private static final int FORMATSPEC_FULL = 0;
808     private static final int FORMATSPEC_DATE = 1;
809     private static final int FORMATSPEC_TIME = 2;
810
811     private static String date_format(double t, int format) {
812         if (t != t)
813             return NaN_date_str;
814
815         StringBuffer result = new StringBuffer(60);
816         double local = LocalTime(t);
817
818         /* offset from GMT in minutes.  The offset includes daylight savings,
819            if it applies. */
820         int minutes = (int) java.lang.Math.floor((LocalTZA + DaylightSavingTA(t))
821                                        / msPerMinute);
822         /* map 510 minutes to 0830 hours */
823         int offset = (minutes / 60) * 100 + minutes % 60;
824
825         String dateStr = Integer.toString(DateFromTime(local));
826         String hourStr = Integer.toString(HourFromTime(local));
827         String minStr = Integer.toString(MinFromTime(local));
828         String secStr = Integer.toString(SecFromTime(local));
829         String offsetStr = Integer.toString(offset > 0 ? offset : -offset);
830         int year = YearFromTime(local);
831         String yearStr = Integer.toString(year > 0 ? year : -year);
832
833         /* Tue Oct 31 09:41:40 GMT-0800 (PST) 2000 */
834         /* Tue Oct 31 2000 */
835         /* 09:41:40 GMT-0800 (PST) */
836
837         if (format != FORMATSPEC_TIME) {
838             result.append(days[WeekDay(local)]);
839             result.append(' ');
840             result.append(months[MonthFromTime(local)]);
841             if (dateStr.length() == 1)
842                 result.append(" 0");
843             else
844                 result.append(' ');
845             result.append(dateStr);
846             result.append(' ');
847         }
848
849         if (format != FORMATSPEC_DATE) {
850             if (hourStr.length() == 1)
851                 result.append('0');
852             result.append(hourStr);
853             if (minStr.length() == 1)
854                 result.append(":0");
855             else
856                 result.append(':');
857             result.append(minStr);
858             if (secStr.length() == 1)
859                 result.append(":0");
860             else
861                 result.append(':');
862             result.append(secStr);
863             if (offset > 0)
864                 result.append(" GMT+");
865             else
866                 result.append(" GMT-");
867             for (int i = offsetStr.length(); i < 4; i++)
868                 result.append('0');
869             result.append(offsetStr);
870
871             if (timeZoneFormatter == null)
872                 timeZoneFormatter = new java.text.SimpleDateFormat("zzz");
873
874             if (timeZoneFormatter != null) {
875                 result.append(" (");
876                 java.util.Date date = new java.util.Date((long) t);
877                 result.append(timeZoneFormatter.format(date));
878                 result.append(')');
879             }
880             if (format != FORMATSPEC_TIME)
881                 result.append(' ');
882         }
883
884         if (format != FORMATSPEC_TIME) {
885             if (year < 0)
886                 result.append('-');
887             for (int i = yearStr.length(); i < 4; i++)
888                 result.append('0');
889             result.append(yearStr);
890         }
891
892         return result.toString();
893     }
894
895     private static double _toNumber(Object o) { return JS.toDouble(o); }
896     private static double _toNumber(Object[] o, int index) { return JS.toDouble(o[index]); }
897     private static double toDouble(double d) { return d; }
898
899     public JSDate(JSArray args_) {
900         Object[] args = new Object[args_.length()];
901         for(int i=0; i<args.length; i++) args[i] = args_.elementAt(i);
902         JSDate obj = this;
903
904         // if called as a constructor with no args,
905         // return a new Date with the current time.
906         if (args.length == 0) {
907             obj.date = Now();
908             return;
909         }
910
911         // if called with just one arg -
912         if (args.length == 1) {
913             double date;
914             if (args[0] instanceof JS)
915                 args[0] = ((JS) args[0]).toString();
916             if (!(args[0] instanceof String)) {
917                 // if it's not a string, use it as a millisecond date
918                 date = _toNumber(args[0]);
919             } else {
920                 // it's a string; parse it.
921                 String str = (String) args[0];
922                 date = date_parseString(str);
923             }
924             obj.date = TimeClip(date);
925             return;
926         }
927
928         // multiple arguments; year, month, day etc.
929         double array[] = new double[MAXARGS];
930         int loop;
931         double d;
932
933         for (loop = 0; loop < MAXARGS; loop++) {
934             if (loop < args.length) {
935                 d = _toNumber(args[loop]);
936
937                 if (d != d || Double.isInfinite(d)) {
938                     obj.date = Double.NaN;
939                     return;
940                 }
941                 array[loop] = toDouble(args[loop]);
942             } else {
943                 array[loop] = 0;
944             }
945         }
946
947         /* adjust 2-digit years into the 20th century */
948         if (array[0] >= 0 && array[0] <= 99)
949             array[0] += 1900;
950
951         /* if we got a 0 for 'date' (which is out of range)
952          * pretend it's a 1 */
953         if (array[2] < 1)
954             array[2] = 1;
955
956         double day = MakeDay(array[0], array[1], array[2]);
957         double time = MakeTime(array[3], array[4], array[5], array[6]);
958         time = MakeDate(day, time);
959         time = internalUTC(time);
960         obj.date = TimeClip(time);
961
962         return;
963     }
964
965     /* constants for toString, toUTCString */
966     private static String NaN_date_str = "Invalid Date";
967
968     private static String[] days = {
969         "Sun","Mon","Tue","Wed","Thu","Fri","Sat"
970     };
971
972     private static String[] months = {
973         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
974         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
975     };
976
977     private static String toLocale_helper(double t,
978                                           java.text.DateFormat formatter)
979     {
980         if (t != t)
981             return NaN_date_str;
982
983         java.util.Date tempdate = new java.util.Date((long) t);
984         return formatter.format(tempdate);
985     }
986
987     private static String toLocaleString(double date) {
988         if (localeDateTimeFormatter == null)
989             localeDateTimeFormatter =
990                 DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
991
992         return toLocale_helper(date, localeDateTimeFormatter);
993     }
994
995     private static String toLocaleTimeString(double date) {
996         if (localeTimeFormatter == null)
997             localeTimeFormatter = DateFormat.getTimeInstance(DateFormat.LONG);
998
999         return toLocale_helper(date, localeTimeFormatter);
1000     }
1001
1002     private static String toLocaleDateString(double date) {
1003         if (localeDateFormatter == null)
1004             localeDateFormatter = DateFormat.getDateInstance(DateFormat.LONG);
1005
1006         return toLocale_helper(date, localeDateFormatter);
1007     }
1008
1009     private static String toUTCString(double date) {
1010         StringBuffer result = new StringBuffer(60);
1011
1012         String dateStr = Integer.toString(DateFromTime(date));
1013         String hourStr = Integer.toString(HourFromTime(date));
1014         String minStr = Integer.toString(MinFromTime(date));
1015         String secStr = Integer.toString(SecFromTime(date));
1016         int year = YearFromTime(date);
1017         String yearStr = Integer.toString(year > 0 ? year : -year);
1018
1019         result.append(days[WeekDay(date)]);
1020         result.append(", ");
1021         if (dateStr.length() == 1)
1022             result.append('0');
1023         result.append(dateStr);
1024         result.append(' ');
1025         result.append(months[MonthFromTime(date)]);
1026         if (year < 0)
1027             result.append(" -");
1028         else
1029             result.append(' ');
1030         int i;
1031         for (i = yearStr.length(); i < 4; i++)
1032             result.append('0');
1033         result.append(yearStr);
1034
1035         if (hourStr.length() == 1)
1036             result.append(" 0");
1037         else
1038             result.append(' ');
1039         result.append(hourStr);
1040         if (minStr.length() == 1)
1041             result.append(":0");
1042         else
1043             result.append(':');
1044         result.append(minStr);
1045         if (secStr.length() == 1)
1046             result.append(":0");
1047         else
1048             result.append(':');
1049         result.append(secStr);
1050
1051         result.append(" GMT");
1052         return result.toString();
1053     }
1054
1055     private static double getYear(double date) {
1056         int result = YearFromTime(LocalTime(date));
1057         result -= 1900;
1058         return result;
1059     }
1060
1061     private static double getTimezoneOffset(double date) {
1062         return (date - LocalTime(date)) / msPerMinute;
1063     }
1064
1065     public double setTime(double time) {
1066         this.date = TimeClip(time);
1067         return this.date;
1068     }
1069
1070     private double makeTime(Object[] args, int maxargs, boolean local) {
1071         int i;
1072         double conv[] = new double[4];
1073         double hour, min, sec, msec;
1074         double lorutime; /* Local or UTC version of date */
1075
1076         double time;
1077         double result;
1078
1079         double date = this.date;
1080
1081         /* just return NaN if the date is already NaN */
1082         if (date != date)
1083             return date;
1084
1085         /* Satisfy the ECMA rule that if a function is called with
1086          * fewer arguments than the specified formal arguments, the
1087          * remaining arguments are set to undefined.  Seems like all
1088          * the Date.setWhatever functions in ECMA are only varargs
1089          * beyond the first argument; this should be set to undefined
1090          * if it's not given.  This means that "d = new Date();
1091          * d.setMilliseconds()" returns NaN.  Blech.
1092          */
1093         if (args.length == 0)
1094             args = new Object[] { null };
1095
1096         for (i = 0; i < args.length && i < maxargs; i++) {
1097             conv[i] = _toNumber(args[i]);
1098
1099             // limit checks that happen in MakeTime in ECMA.
1100             if (conv[i] != conv[i] || Double.isInfinite(conv[i])) {
1101                 this.date = Double.NaN;
1102                 return this.date;
1103             }
1104             conv[i] = toDouble(conv[i]);
1105         }
1106
1107         if (local)
1108             lorutime = LocalTime(date);
1109         else
1110             lorutime = date;
1111
1112         i = 0;
1113         int stop = args.length;
1114
1115         if (maxargs >= 4 && i < stop)
1116             hour = conv[i++];
1117         else
1118             hour = HourFromTime(lorutime);
1119
1120         if (maxargs >= 3 && i < stop)
1121             min = conv[i++];
1122         else
1123             min = MinFromTime(lorutime);
1124
1125         if (maxargs >= 2 && i < stop)
1126             sec = conv[i++];
1127         else
1128             sec = SecFromTime(lorutime);
1129
1130         if (maxargs >= 1 && i < stop)
1131             msec = conv[i++];
1132         else
1133             msec = msFromTime(lorutime);
1134
1135         time = MakeTime(hour, min, sec, msec);
1136         result = MakeDate(Day(lorutime), time);
1137
1138         if (local)
1139             result = internalUTC(result);
1140         date = TimeClip(result);
1141
1142         this.date = date;
1143         return date;
1144     }
1145
1146     private double setHours(Object[] args) {
1147         return makeTime(args, 4, true);
1148     }
1149
1150     private double setUTCHours(Object[] args) {
1151         return makeTime(args, 4, false);
1152     }
1153
1154     private double makeDate(Object[] args, int maxargs, boolean local) {
1155         int i;
1156         double conv[] = new double[3];
1157         double year, month, day;
1158         double lorutime; /* local or UTC version of date */
1159         double result;
1160
1161         double date = this.date;
1162
1163         /* See arg padding comment in makeTime.*/
1164         if (args.length == 0)
1165             args = new Object[] { null };
1166
1167         for (i = 0; i < args.length && i < maxargs; i++) {
1168             conv[i] = _toNumber(args[i]);
1169
1170             // limit checks that happen in MakeDate in ECMA.
1171             if (conv[i] != conv[i] || Double.isInfinite(conv[i])) {
1172                 this.date = Double.NaN;
1173                 return this.date;
1174             }
1175             conv[i] = toDouble(conv[i]);
1176         }
1177
1178         /* return NaN if date is NaN and we're not setting the year,
1179          * If we are, use 0 as the time. */
1180         if (date != date) {
1181             if (args.length < 3) {
1182                 return Double.NaN;
1183             } else {
1184                 lorutime = 0;
1185             }
1186         } else {
1187             if (local)
1188                 lorutime = LocalTime(date);
1189             else
1190                 lorutime = date;
1191         }
1192
1193         i = 0;
1194         int stop = args.length;
1195
1196         if (maxargs >= 3 && i < stop)
1197             year = conv[i++];
1198         else
1199             year = YearFromTime(lorutime);
1200
1201         if (maxargs >= 2 && i < stop)
1202             month = conv[i++];
1203         else
1204             month = MonthFromTime(lorutime);
1205
1206         if (maxargs >= 1 && i < stop)
1207             day = conv[i++];
1208         else
1209             day = DateFromTime(lorutime);
1210
1211         day = MakeDay(year, month, day); /* day within year */
1212         result = MakeDate(day, TimeWithinDay(lorutime));
1213
1214         if (local)
1215             result = internalUTC(result);
1216
1217         date = TimeClip(result);
1218
1219         this.date = date;
1220         return date;
1221     }
1222
1223     private double setYear(double year) {
1224         double day, result;
1225         if (year != year || Double.isInfinite(year)) {
1226             this.date = Double.NaN;
1227             return this.date;
1228         }
1229
1230         if (this.date != this.date) {
1231             this.date = 0;
1232         } else {
1233             this.date = LocalTime(this.date);
1234         }
1235
1236         if (year >= 0 && year <= 99)
1237             year += 1900;
1238
1239         day = MakeDay(year, MonthFromTime(this.date), DateFromTime(this.date));
1240         result = MakeDate(day, TimeWithinDay(this.date));
1241         result = internalUTC(result);
1242
1243         this.date = TimeClip(result);
1244         return this.date;
1245     }
1246
1247
1248     //    private static final int
1249     //        Id_toGMTString  =  Id_toUTCString; // Alias, see Ecma B.2.6
1250 // #/string_id_map#
1251
1252     /* cached values */
1253     private static java.util.TimeZone thisTimeZone;
1254     private static double LocalTZA;
1255     private static java.text.DateFormat timeZoneFormatter;
1256     private static java.text.DateFormat localeDateTimeFormatter;
1257     private static java.text.DateFormat localeDateFormatter;
1258     private static java.text.DateFormat localeTimeFormatter;
1259
1260     private double date;
1261
1262     public long getRawTime() { return (long)this.date; }
1263 }
1264
1265