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