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