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