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