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