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