new js api
[org.ibex.core.git] / src / org / ibex / js / JSDate.java
1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  *
3  * The contents of this file are subject to the Netscape Public
4  * License Version 1.1 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of
6  * the License at http://www.mozilla.org/NPL/
7  *
8  * Software distributed under the License is distributed on an "AS
9  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
10  * implied. See the License for the specific language governing
11  * rights and limitations under the License.
12  *
13  * The Original Code is Rhino code, released
14  * May 6, 1999.
15  *
16  * The Initial Developer of the Original Code is Netscape
17  * Communications Corporation.  Portions created by Netscape are
18  * Copyright (C) 1997-1999 Netscape Communications Corporation. All
19  * Rights Reserved.
20  *
21  * Contributor(s):
22  * Norris Boyd
23  * Mike McCabe
24  *
25  * Alternatively, the contents of this file may be used under the
26  * terms of the GNU Public License (the "GPL"), in which case the
27  * provisions of the GPL are applicable instead of those above.
28  * If you wish to allow use of your version of this file only
29  * under the terms of the GPL and not to allow others to use your
30  * version of this file under the NPL, indicate your decision by
31  * deleting the provisions above and replace them with the notice
32  * and other provisions required by the GPL.  If you do not delete
33  * the provisions above, a recipient may use your version of this
34  * file under either the NPL or the GPL.
35  */
36
37 package org.ibex.js;
38
39 import java.text.DateFormat;
40
41 /**
42  * This class implements the Date native object.
43  * See ECMA 15.9.
44  * @author Mike McCabe
45  * @author Adam Megacz (many modifications
46  */
47 public class JSDate extends JS {
48
49     public JSDate() {
50         if (thisTimeZone == null) {
51             // j.u.TimeZone is synchronized, so setting class statics from it
52             // should be OK.
53             thisTimeZone = java.util.TimeZone.getDefault();
54             LocalTZA = thisTimeZone.getRawOffset();
55         }
56     }
57
58     String coerceToString() { return date_format(date, FORMATSPEC_FULL); }
59
60     public JS callMethod(JS method_, JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
61         if(!JS.isString(method_)) return super.callMethod(method_, a0, a1, a2, rest, nargs);
62         String method = JS.toString(method_);
63         switch(nargs) {
64             case 0: {
65                 //#switch(method)
66                 case "toString": return JS.S(date_format(date, FORMATSPEC_FULL));
67                 case "toTimeString": return JS.S(date_format(date, FORMATSPEC_TIME));
68                 case "toDateString": return JS.S(date_format(date, FORMATSPEC_DATE));
69                 case "toLocaleString": return JS.S(toLocaleString(date));
70                 case "toLocaleTimeString": return JS.S(toLocaleTimeString(date));
71                 case "toLocaleDateString": return JS.S(toLocaleDateString(date));
72                 case "toUTCString": return JS.S(toUTCString(date));
73                 case "valueOf": return N(this.date);
74                 case "getTime": return N(this.date);
75                 case "getYear": return N(getYear(date));
76                 case "getFullYear": return N(YearFromTime(LocalTime(date)));
77                 case "getUTCFullYear": return N(YearFromTime(date));
78                 case "getMonth": return N(MonthFromTime(LocalTime(date)));
79                 case "getUTCMonth": return N(MonthFromTime(date));
80                 case "getDate": return N(DateFromTime(LocalTime(date)));
81                 case "getUTCDate": return N(DateFromTime(date));
82                 case "getDay": return N(WeekDay(LocalTime(date)));
83                 case "getUTCDay": return N(WeekDay(date));
84                 case "getHours": return N(HourFromTime(LocalTime(date)));
85                 case "getUTCHours": return N(HourFromTime(date));
86                 case "getMinutes": return N(MinFromTime(LocalTime(date)));
87                 case "getUTCMinutes": return N(MinFromTime(date));
88                 case "getSeconds": return N(SecFromTime(LocalTime(date)));
89                 case "getUTCSeconds": return N(SecFromTime(date));
90                 case "getMilliseconds": return N(msFromTime(LocalTime(date)));
91                 case "getUTCMilliseconds": return N(msFromTime(date));
92                 case "getTimezoneOffset": return N(getTimezoneOffset(date));
93                 //#end
94                 return super.callMethod(method_, a0, a1, a2, rest, nargs);
95             }
96             case 1: {
97                 //#switch(method)
98                 case "setTime": return N(this.setTime(toDouble(a0)));
99                 case "setYear": return N(this.setYear(toDouble(a0)));
100                 //#end
101                 // fall through
102             }
103             default: {
104                 JS[] args = new JS[nargs];
105                 for(int i=0; i<nargs; i++) args[i] = i==0 ? a0 : i==1 ? a1 : i==2 ? a2 : rest[i-3];
106                 //#switch(method)
107                 case "setMilliseconds": return N(this.makeTime(args, 1, true));
108                 case "setUTCMilliseconds": return N(this.makeTime(args, 1, false));
109                 case "setSeconds": return N(this.makeTime(args, 2, true));
110                 case "setUTCSeconds": return N(this.makeTime(args, 2, false));
111                 case "setMinutes": return N(this.makeTime(args, 3, true));
112                 case "setUTCMinutes": return N(this.makeTime(args, 3, false));
113                 case "setHours": return N(this.makeTime(args, 4, true));
114                 case "setUTCHours": return N(this.makeTime(args, 4, false));
115                 case "setDate": return N(this.makeDate(args, 1, true));
116                 case "setUTCDate": return N(this.makeDate(args, 1, false));
117                 case "setMonth": return N(this.makeDate(args, 2, true));
118                 case "setUTCMonth": return N(this.makeDate(args, 2, false));
119                 case "setFullYear": return N(this.makeDate(args, 3, true));
120                 case "setUTCFullYear": return N(this.makeDate(args, 3, false));
121                 //#end
122             }
123         }
124         return super.callMethod(method_, a0, a1, a2, rest, nargs);
125     }
126
127     public JS get(JS key_) throws JSExn {
128         if(!JS.isString(key_)) return super.get(key_);
129         String key = JS.toString(key_);
130         //#switch(key)
131         case "toString": return METHOD;
132         case "toTimeString": return METHOD;
133         case "toDateString": return METHOD;
134         case "toLocaleString": return METHOD;
135         case "toLocaleTimeString": return METHOD;
136         case "toLocaleDateString": return METHOD;
137         case "toUTCString": return METHOD;
138         case "valueOf": return METHOD;
139         case "getTime": return METHOD;
140         case "getYear": return METHOD;
141         case "getFullYear": return METHOD;
142         case "getUTCFullYear": return METHOD;
143         case "getMonth": return METHOD;
144         case "getUTCMonth": return METHOD;
145         case "getDate": return METHOD;
146         case "getUTCDate": return METHOD;
147         case "getDay": return METHOD;
148         case "getUTCDay": return METHOD;
149         case "getHours": return METHOD;
150         case "getUTCHours": return METHOD;
151         case "getMinutes": return METHOD;
152         case "getUTCMinutes": return METHOD;
153         case "getSeconds": return METHOD;
154         case "getUTCSeconds": return METHOD;
155         case "getMilliseconds": return METHOD;
156         case "getUTCMilliseconds": return METHOD;
157         case "getTimezoneOffset": return METHOD;
158         case "setTime": return METHOD;
159         case "setYear": return METHOD;
160         case "setMilliseconds": return METHOD;
161         case "setUTCMilliseconds": return METHOD;
162         case "setSeconds": return METHOD;
163         case "setUTCSeconds": return METHOD;
164         case "setMinutes": return METHOD;
165         case "setUTCMinutes": return METHOD;
166         case "setHours": return METHOD;
167         case "setUTCHours": return METHOD;
168         case "setDate": return METHOD;
169         case "setUTCDate": return METHOD;
170         case "setMonth": return METHOD;
171         case "setUTCMonth": return METHOD;
172         case "setFullYear": return METHOD;
173         case "setUTCFullYear": return METHOD;
174         //#end
175         return super.get(key_);
176     }
177
178     /* ECMA helper functions */
179
180     private static final double HalfTimeDomain = 8.64e15;
181     private static final double HoursPerDay    = 24.0;
182     private static final double MinutesPerHour = 60.0;
183     private static final double SecondsPerMinute = 60.0;
184     private static final double msPerSecond    = 1000.0;
185     private static final double MinutesPerDay  = (HoursPerDay * MinutesPerHour);
186     private static final double SecondsPerDay  = (MinutesPerDay * SecondsPerMinute);
187     private static final double SecondsPerHour = (MinutesPerHour * SecondsPerMinute);
188     private static final double msPerDay       = (SecondsPerDay * msPerSecond);
189     private static final double msPerHour      = (SecondsPerHour * msPerSecond);
190     private static final double msPerMinute    = (SecondsPerMinute * msPerSecond);
191
192     private static double Day(double t) {
193         return java.lang.Math.floor(t / msPerDay);
194     }
195
196     private static double TimeWithinDay(double t) {
197         double result;
198         result = t % msPerDay;
199         if (result < 0)
200             result += msPerDay;
201         return result;
202     }
203
204     private static int DaysInYear(int y) {
205         if (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0))
206             return 366;
207         else
208             return 365;
209     }
210
211
212     /* math here has to be f.p, because we need
213      *  floor((1968 - 1969) / 4) == -1
214      */
215     private static double DayFromYear(double y) {
216         return ((365 * ((y)-1970) + java.lang.Math.floor(((y)-1969)/4.0)
217                  - java.lang.Math.floor(((y)-1901)/100.0) + java.lang.Math.floor(((y)-1601)/400.0)));
218     }
219
220     private static double TimeFromYear(double y) {
221         return DayFromYear(y) * msPerDay;
222     }
223
224     private static int YearFromTime(double t) {
225         int lo = (int) java.lang.Math.floor((t / msPerDay) / 366) + 1970;
226         int hi = (int) java.lang.Math.floor((t / msPerDay) / 365) + 1970;
227         int mid;
228
229         /* above doesn't work for negative dates... */
230         if (hi < lo) {
231             int temp = lo;
232             lo = hi;
233             hi = temp;
234         }
235
236         /* Use a simple binary search algorithm to find the right
237            year.  This seems like brute force... but the computation
238            of hi and lo years above lands within one year of the
239            correct answer for years within a thousand years of
240            1970; the loop below only requires six iterations
241            for year 270000. */
242         while (hi > lo) {
243             mid = (hi + lo) / 2;
244             if (TimeFromYear(mid) > t) {
245                 hi = mid - 1;
246             } else {
247                 if (TimeFromYear(mid) <= t) {
248                     int temp = mid + 1;
249                     if (TimeFromYear(temp) > t) {
250                         return mid;
251                     }
252                     lo = mid + 1;
253                 }
254             }
255         }
256         return lo;
257     }
258
259     private static boolean InLeapYear(double t) {
260         return DaysInYear(YearFromTime(t)) == 366;
261     }
262
263     private static int DayWithinYear(double t) {
264         int year = YearFromTime(t);
265         return (int) (Day(t) - DayFromYear(year));
266     }
267     /*
268      * The following array contains the day of year for the first day of
269      * each month, where index 0 is January, and day 0 is January 1.
270      */
271
272     private static double DayFromMonth(int m, boolean leap) {
273         int day = m * 30;
274
275         if (m >= 7) { day += m / 2 - 1; }
276         else if (m >= 2) { day += (m - 1) / 2 - 1; }
277         else { day += m; }
278
279         if (leap && m >= 2) { ++day; }
280
281         return day;
282     }
283
284     private static int MonthFromTime(double t) {
285         int d, step;
286
287         d = DayWithinYear(t);
288
289         if (d < (step = 31))
290             return 0;
291
292         // Originally coded as step += (InLeapYear(t) ? 29 : 28);
293         // but some jits always returned 28!
294         if (InLeapYear(t))
295             step += 29;
296         else
297             step += 28;
298
299         if (d < step)
300             return 1;
301         if (d < (step += 31))
302             return 2;
303         if (d < (step += 30))
304             return 3;
305         if (d < (step += 31))
306             return 4;
307         if (d < (step += 30))
308             return 5;
309         if (d < (step += 31))
310             return 6;
311         if (d < (step += 31))
312             return 7;
313         if (d < (step += 30))
314             return 8;
315         if (d < (step += 31))
316             return 9;
317         if (d < (step += 30))
318             return 10;
319         return 11;
320     }
321
322     private static int DateFromTime(double t) {
323         int d, step, next;
324
325         d = DayWithinYear(t);
326         if (d <= (next = 30))
327             return d + 1;
328         step = next;
329
330         // Originally coded as next += (InLeapYear(t) ? 29 : 28);
331         // but some jits always returned 28!
332         if (InLeapYear(t))
333             next += 29;
334         else
335             next += 28;
336
337         if (d <= next)
338             return d - step;
339         step = next;
340         if (d <= (next += 31))
341             return d - step;
342         step = next;
343         if (d <= (next += 30))
344             return d - step;
345         step = next;
346         if (d <= (next += 31))
347             return d - step;
348         step = next;
349         if (d <= (next += 30))
350             return d - step;
351         step = next;
352         if (d <= (next += 31))
353             return d - step;
354         step = next;
355         if (d <= (next += 31))
356             return d - step;
357         step = next;
358         if (d <= (next += 30))
359             return d - step;
360         step = next;
361         if (d <= (next += 31))
362             return d - step;
363         step = next;
364         if (d <= (next += 30))
365             return d - step;
366         step = next;
367
368         return d - step;
369     }
370
371     private static int WeekDay(double t) {
372         double result;
373         result = Day(t) + 4;
374         result = result % 7;
375         if (result < 0)
376             result += 7;
377         return (int) result;
378     }
379
380     private static double Now() {
381         return (double) System.currentTimeMillis();
382     }
383
384     /* Should be possible to determine the need for this dynamically
385      * if we go with the workaround... I'm not using it now, because I
386      * can't think of any clean way to make toLocaleString() and the
387      * time zone (comment) in toString match the generated string
388      * values.  Currently it's wrong-but-consistent in all but the
389      * most recent betas of the JRE - seems to work in 1.1.7.
390      */
391     private final static boolean TZO_WORKAROUND = false;
392     private static double DaylightSavingTA(double t) {
393         if (!TZO_WORKAROUND) {
394             java.util.Date date = new java.util.Date((long) t);
395             if (thisTimeZone.inDaylightTime(date))
396                 return msPerHour;
397             else
398                 return 0;
399         } else {
400             /* Use getOffset if inDaylightTime() is broken, because it
401              * seems to work acceptably.  We don't switch over to it
402              * entirely, because it requires (expensive) exploded date arguments,
403              * and the api makes it impossible to handle dst
404              * changeovers cleanly.
405              */
406
407             // Hardcode the assumption that the changeover always
408             // happens at 2:00 AM:
409             t += LocalTZA + (HourFromTime(t) <= 2 ? msPerHour : 0);
410
411             int year = YearFromTime(t);
412             double offset = thisTimeZone.getOffset(year > 0 ? 1 : 0,
413                                                    year,
414                                                    MonthFromTime(t),
415                                                    DateFromTime(t),
416                                                    WeekDay(t),
417                                                    (int)TimeWithinDay(t));
418
419             if ((offset - LocalTZA) != 0)
420                 return msPerHour;
421             else
422                 return 0;
423             //         return offset - LocalTZA;
424         }
425     }
426
427     private static double LocalTime(double t) {
428         return t + LocalTZA + DaylightSavingTA(t);
429     }
430
431     public static double internalUTC(double t) {
432         return t - LocalTZA - DaylightSavingTA(t - LocalTZA);
433     }
434
435     private static int HourFromTime(double t) {
436         double result;
437         result = java.lang.Math.floor(t / msPerHour) % HoursPerDay;
438         if (result < 0)
439             result += HoursPerDay;
440         return (int) result;
441     }
442
443     private static int MinFromTime(double t) {
444         double result;
445         result = java.lang.Math.floor(t / msPerMinute) % MinutesPerHour;
446         if (result < 0)
447             result += MinutesPerHour;
448         return (int) result;
449     }
450
451     private static int SecFromTime(double t) {
452         double result;
453         result = java.lang.Math.floor(t / msPerSecond) % SecondsPerMinute;
454         if (result < 0)
455             result += SecondsPerMinute;
456         return (int) result;
457     }
458
459     private static int msFromTime(double t) {
460         double result;
461         result =  t % msPerSecond;
462         if (result < 0)
463             result += msPerSecond;
464         return (int) result;
465     }
466
467     private static double MakeTime(double hour, double min,
468                                    double sec, double ms)
469     {
470         return ((hour * MinutesPerHour + min) * SecondsPerMinute + sec)
471             * msPerSecond + ms;
472     }
473
474     private static double MakeDay(double year, double month, double date) {
475         double result;
476         boolean leap;
477         double yearday;
478         double monthday;
479
480         year += java.lang.Math.floor(month / 12);
481
482         month = month % 12;
483         if (month < 0)
484             month += 12;
485
486         leap = (DaysInYear((int) year) == 366);
487
488         yearday = java.lang.Math.floor(TimeFromYear(year) / msPerDay);
489         monthday = DayFromMonth((int) month, leap);
490
491         result = yearday
492             + monthday
493             + date - 1;
494         return result;
495     }
496
497     private static double MakeDate(double day, double time) {
498         return day * msPerDay + time;
499     }
500
501     private static double TimeClip(double d) {
502         if (d != d ||
503             d == Double.POSITIVE_INFINITY ||
504             d == Double.NEGATIVE_INFINITY ||
505             java.lang.Math.abs(d) > HalfTimeDomain)
506         {
507             return Double.NaN;
508         }
509         if (d > 0.0)
510             return java.lang.Math.floor(d + 0.);
511         else
512             return java.lang.Math.ceil(d + 0.);
513     }
514
515     /* end of ECMA helper functions */
516
517     /* find UTC time from given date... no 1900 correction! */
518     public static double date_msecFromDate(double year, double mon,
519                                             double mday, double hour,
520                                             double min, double sec,
521                                             double msec)
522     {
523         double day;
524         double time;
525         double result;
526
527         day = MakeDay(year, mon, mday);
528         time = MakeTime(hour, min, sec, msec);
529         result = MakeDate(day, time);
530         return result;
531     }
532
533
534     private static final int MAXARGS = 7;
535     private static double jsStaticJSFunction_UTC(JS[] args) throws JSExn {
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(JS o) throws JSExn { return JS.toDouble(o); }
896     private static double _toNumber(JS[] o, int index) throws JSExn { return JS.toDouble(o[index]); }
897     private static double toDouble(double d) { return d; }
898
899     public JSDate(JS a0, JS a1, JS a2, JS[] rest, int nargs) throws JSExn {
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(isString(a0))
910                     date = date_parseString(JS.toString(a0));
911                 else
912                     date = _toNumber(a0);
913                 obj.date = TimeClip(date);
914                 return;
915             }
916             default: {
917                 // multiple arguments; year, month, day etc.
918                 double array[] = new double[MAXARGS];
919                 array[0] = toDouble(a0);
920                 array[1] = toDouble(a1);
921                 if (nargs >= 2) array[2] = toDouble(a2);
922                 for(int i=0; i<nargs; i++) {
923                     double d = _toNumber(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
924                     if (d != d || Double.isInfinite(d)) {
925                         obj.date = Double.NaN;
926                         return;
927                     }
928                     array[i] = d;
929                 }
930                 
931                 /* adjust 2-digit years into the 20th century */
932                 if (array[0] >= 0 && array[0] <= 99)
933                     array[0] += 1900;
934                 
935                 /* if we got a 0 for 'date' (which is out of range)
936                  * pretend it's a 1 */
937                 if (array[2] < 1)
938                     array[2] = 1;
939                 
940                 double day = MakeDay(array[0], array[1], array[2]);
941                 double time = MakeTime(array[3], array[4], array[5], array[6]);
942                 time = MakeDate(day, time);
943                 time = internalUTC(time);
944                 obj.date = TimeClip(time);
945                 
946                 return;
947             }
948         }
949     }
950
951     /* constants for toString, toUTCString */
952     private static String NaN_date_str = "Invalid Date";
953
954     private static String[] days = {
955         "Sun","Mon","Tue","Wed","Thu","Fri","Sat"
956     };
957
958     private static String[] months = {
959         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
960         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
961     };
962
963     private static String toLocale_helper(double t,
964                                           java.text.DateFormat formatter)
965     {
966         if (t != t)
967             return NaN_date_str;
968
969         java.util.Date tempdate = new java.util.Date((long) t);
970         return formatter.format(tempdate);
971     }
972
973     private static String toLocaleString(double date) {
974         if (localeDateTimeFormatter == null)
975             localeDateTimeFormatter =
976                 DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
977
978         return toLocale_helper(date, localeDateTimeFormatter);
979     }
980
981     private static String toLocaleTimeString(double date) {
982         if (localeTimeFormatter == null)
983             localeTimeFormatter = DateFormat.getTimeInstance(DateFormat.LONG);
984
985         return toLocale_helper(date, localeTimeFormatter);
986     }
987
988     private static String toLocaleDateString(double date) {
989         if (localeDateFormatter == null)
990             localeDateFormatter = DateFormat.getDateInstance(DateFormat.LONG);
991
992         return toLocale_helper(date, localeDateFormatter);
993     }
994
995     private static String toUTCString(double date) {
996         StringBuffer result = new StringBuffer(60);
997
998         String dateStr = Integer.toString(DateFromTime(date));
999         String hourStr = Integer.toString(HourFromTime(date));
1000         String minStr = Integer.toString(MinFromTime(date));
1001         String secStr = Integer.toString(SecFromTime(date));
1002         int year = YearFromTime(date);
1003         String yearStr = Integer.toString(year > 0 ? year : -year);
1004
1005         result.append(days[WeekDay(date)]);
1006         result.append(", ");
1007         if (dateStr.length() == 1)
1008             result.append('0');
1009         result.append(dateStr);
1010         result.append(' ');
1011         result.append(months[MonthFromTime(date)]);
1012         if (year < 0)
1013             result.append(" -");
1014         else
1015             result.append(' ');
1016         int i;
1017         for (i = yearStr.length(); i < 4; i++)
1018             result.append('0');
1019         result.append(yearStr);
1020
1021         if (hourStr.length() == 1)
1022             result.append(" 0");
1023         else
1024             result.append(' ');
1025         result.append(hourStr);
1026         if (minStr.length() == 1)
1027             result.append(":0");
1028         else
1029             result.append(':');
1030         result.append(minStr);
1031         if (secStr.length() == 1)
1032             result.append(":0");
1033         else
1034             result.append(':');
1035         result.append(secStr);
1036
1037         result.append(" GMT");
1038         return result.toString();
1039     }
1040
1041     private static double getYear(double date) {
1042         int result = YearFromTime(LocalTime(date));
1043         result -= 1900;
1044         return result;
1045     }
1046
1047     private static double getTimezoneOffset(double date) {
1048         return (date - LocalTime(date)) / msPerMinute;
1049     }
1050
1051     public double setTime(double time) {
1052         this.date = TimeClip(time);
1053         return this.date;
1054     }
1055
1056     private double makeTime(JS[] args, int maxargs, boolean local) throws JSExn {
1057         int i;
1058         double conv[] = new double[4];
1059         double hour, min, sec, msec;
1060         double lorutime; /* Local or UTC version of date */
1061
1062         double time;
1063         double result;
1064
1065         double date = this.date;
1066
1067         /* just return NaN if the date is already NaN */
1068         if (date != date)
1069             return date;
1070
1071         /* Satisfy the ECMA rule that if a function is called with
1072          * fewer arguments than the specified formal arguments, the
1073          * remaining arguments are set to undefined.  Seems like all
1074          * the Date.setWhatever functions in ECMA are only varargs
1075          * beyond the first argument; this should be set to undefined
1076          * if it's not given.  This means that "d = new Date();
1077          * d.setMilliseconds()" returns NaN.  Blech.
1078          */
1079         if (args.length == 0)
1080             args = new JS[] { null };
1081
1082         for (i = 0; i < args.length && i < maxargs; i++) {
1083             conv[i] = _toNumber(args[i]);
1084
1085             // limit checks that happen in MakeTime in ECMA.
1086             if (conv[i] != conv[i] || Double.isInfinite(conv[i])) {
1087                 this.date = Double.NaN;
1088                 return this.date;
1089             }
1090             conv[i] = toDouble(conv[i]);
1091         }
1092
1093         if (local)
1094             lorutime = LocalTime(date);
1095         else
1096             lorutime = date;
1097
1098         i = 0;
1099         int stop = args.length;
1100
1101         if (maxargs >= 4 && i < stop)
1102             hour = conv[i++];
1103         else
1104             hour = HourFromTime(lorutime);
1105
1106         if (maxargs >= 3 && i < stop)
1107             min = conv[i++];
1108         else
1109             min = MinFromTime(lorutime);
1110
1111         if (maxargs >= 2 && i < stop)
1112             sec = conv[i++];
1113         else
1114             sec = SecFromTime(lorutime);
1115
1116         if (maxargs >= 1 && i < stop)
1117             msec = conv[i++];
1118         else
1119             msec = msFromTime(lorutime);
1120
1121         time = MakeTime(hour, min, sec, msec);
1122         result = MakeDate(Day(lorutime), time);
1123
1124         if (local)
1125             result = internalUTC(result);
1126         date = TimeClip(result);
1127
1128         this.date = date;
1129         return date;
1130     }
1131
1132     private double setHours(JS[] args) throws JSExn {
1133         return makeTime(args, 4, true);
1134     }
1135
1136     private double setUTCHours(JS[] args) throws JSExn {
1137         return makeTime(args, 4, false);
1138     }
1139
1140     private double makeDate(JS[] args, int maxargs, boolean local) throws JSExn {
1141         int i;
1142         double conv[] = new double[3];
1143         double year, month, day;
1144         double lorutime; /* local or UTC version of date */
1145         double result;
1146
1147         double date = this.date;
1148
1149         /* See arg padding comment in makeTime.*/
1150         if (args.length == 0)
1151             args = new JS[] { null };
1152
1153         for (i = 0; i < args.length && i < maxargs; i++) {
1154             conv[i] = _toNumber(args[i]);
1155
1156             // limit checks that happen in MakeDate in ECMA.
1157             if (conv[i] != conv[i] || Double.isInfinite(conv[i])) {
1158                 this.date = Double.NaN;
1159                 return this.date;
1160             }
1161             conv[i] = toDouble(conv[i]);
1162         }
1163
1164         /* return NaN if date is NaN and we're not setting the year,
1165          * If we are, use 0 as the time. */
1166         if (date != date) {
1167             if (args.length < 3) {
1168                 return Double.NaN;
1169             } else {
1170                 lorutime = 0;
1171             }
1172         } else {
1173             if (local)
1174                 lorutime = LocalTime(date);
1175             else
1176                 lorutime = date;
1177         }
1178
1179         i = 0;
1180         int stop = args.length;
1181
1182         if (maxargs >= 3 && i < stop)
1183             year = conv[i++];
1184         else
1185             year = YearFromTime(lorutime);
1186
1187         if (maxargs >= 2 && i < stop)
1188             month = conv[i++];
1189         else
1190             month = MonthFromTime(lorutime);
1191
1192         if (maxargs >= 1 && i < stop)
1193             day = conv[i++];
1194         else
1195             day = DateFromTime(lorutime);
1196
1197         day = MakeDay(year, month, day); /* day within year */
1198         result = MakeDate(day, TimeWithinDay(lorutime));
1199
1200         if (local)
1201             result = internalUTC(result);
1202
1203         date = TimeClip(result);
1204
1205         this.date = date;
1206         return date;
1207     }
1208
1209     private double setYear(double year) {
1210         double day, result;
1211         if (year != year || Double.isInfinite(year)) {
1212             this.date = Double.NaN;
1213             return this.date;
1214         }
1215
1216         if (this.date != this.date) {
1217             this.date = 0;
1218         } else {
1219             this.date = LocalTime(this.date);
1220         }
1221
1222         if (year >= 0 && year <= 99)
1223             year += 1900;
1224
1225         day = MakeDay(year, MonthFromTime(this.date), DateFromTime(this.date));
1226         result = MakeDate(day, TimeWithinDay(this.date));
1227         result = internalUTC(result);
1228
1229         this.date = TimeClip(result);
1230         return this.date;
1231     }
1232
1233
1234     //    private static final int
1235     //        Id_toGMTString  =  Id_toUTCString; // Alias, see Ecma B.2.6
1236 // #/string_id_map#
1237
1238     /* cached values */
1239     private static java.util.TimeZone thisTimeZone;
1240     private static double LocalTZA;
1241     private static java.text.DateFormat timeZoneFormatter;
1242     private static java.text.DateFormat localeDateTimeFormatter;
1243     private static java.text.DateFormat localeDateFormatter;
1244     private static java.text.DateFormat localeTimeFormatter;
1245
1246     private double date;
1247
1248     public long getRawTime() { return (long)this.date; }
1249 }
1250
1251