fixed the new 1-arg JSDate constructor
[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(long l) throws JSExn {
894         this.date = (double)l;
895         thisTimeZone = java.util.TimeZone.getDefault();
896         LocalTZA = thisTimeZone.getRawOffset();
897     }
898
899     public JSDate(JS[] args) throws JSExn {
900
901         JSDate obj = this;
902         switch (args.length) {
903             case 0: {
904                 obj.date = Now();
905                 return;
906             }
907             case 1: {
908                 double date;
909                 if(JSU.isString(args[0]))
910                     date = date_parseString(JSU.toString(args[0]));
911                 else
912                     date = _toNumber(args[0]);
913                 obj.date = TimeClip(date);
914                 return;
915             }
916             default: {
917                 // multiple arguments; year, month, day etc.
918                 double array[] = new double[MAXARGS];
919                 array[0] = JSU.toDouble(args[0]);
920                 array[1] = JSU.toDouble(args[1]);
921                 if (args.length >= 2) array[2] = JSU.toDouble(args[2]);
922                 for (int i=0; i < args.length; i++) {
923                     double d = _toNumber(args[i]);
924                     if (d != d || Double.isInfinite(d)) {
925                         obj.date = Double.NaN;
926                         return;
927                     }
928                     array[i] = d;
929                 }
930                 
931                 /* adjust 2-digit years into the 20th century */
932                 if (array[0] >= 0 && array[0] <= 99)
933                     array[0] += 1900;
934                 
935                 /* if we got a 0 for 'date' (which is out of range)
936                  * pretend it's a 1 */
937                 if (array[2] < 1)
938                     array[2] = 1;
939                 
940                 double day = MakeDay(array[0], array[1], array[2]);
941                 double time = MakeTime(array[3], array[4], array[5], array[6]);
942                 time = MakeDate(day, time);
943                 time = internalUTC(time);
944                 obj.date = TimeClip(time);
945                 
946                 return;
947             }
948         }
949     }
950
951     /* constants for toString, toUTCString */
952     private static String NaN_date_str = "Invalid Date";
953
954     private static String[] days = {
955         "Sun","Mon","Tue","Wed","Thu","Fri","Sat"
956     };
957
958     private static String[] months = {
959         "Jan", "Feb", "Mar", "Apr", "May", "Jun",
960         "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
961     };
962
963     private static String toLocale_helper(double t,
964                                           java.text.DateFormat formatter)
965     {
966         if (t != t)
967             return NaN_date_str;
968
969         java.util.Date tempdate = new java.util.Date((long) t);
970         return formatter.format(tempdate);
971     }
972
973     private static String toLocaleString(double date) {
974         if (localeDateTimeFormatter == null)
975             localeDateTimeFormatter =
976                 DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
977
978         return toLocale_helper(date, localeDateTimeFormatter);
979     }
980
981     private static String toLocaleTimeString(double date) {
982         if (localeTimeFormatter == null)
983             localeTimeFormatter = DateFormat.getTimeInstance(DateFormat.LONG);
984
985         return toLocale_helper(date, localeTimeFormatter);
986     }
987
988     private static String toLocaleDateString(double date) {
989         if (localeDateFormatter == null)
990             localeDateFormatter = DateFormat.getDateInstance(DateFormat.LONG);
991
992         return toLocale_helper(date, localeDateFormatter);
993     }
994
995     private static String toUTCString(double date) {
996         StringBuffer result = new StringBuffer(60);
997
998         String dateStr = Integer.toString(DateFromTime(date));
999         String hourStr = Integer.toString(HourFromTime(date));
1000         String minStr = Integer.toString(MinFromTime(date));
1001         String secStr = Integer.toString(SecFromTime(date));
1002         int year = YearFromTime(date);
1003         String yearStr = Integer.toString(year > 0 ? year : -year);
1004
1005         result.append(days[WeekDay(date)]);
1006         result.append(", ");
1007         if (dateStr.length() == 1)
1008             result.append('0');
1009         result.append(dateStr);
1010         result.append(' ');
1011         result.append(months[MonthFromTime(date)]);
1012         if (year < 0)
1013             result.append(" -");
1014         else
1015             result.append(' ');
1016         int i;
1017         for (i = yearStr.length(); i < 4; i++)
1018             result.append('0');
1019         result.append(yearStr);
1020
1021         if (hourStr.length() == 1)
1022             result.append(" 0");
1023         else
1024             result.append(' ');
1025         result.append(hourStr);
1026         if (minStr.length() == 1)
1027             result.append(":0");
1028         else
1029             result.append(':');
1030         result.append(minStr);
1031         if (secStr.length() == 1)
1032             result.append(":0");
1033         else
1034             result.append(':');
1035         result.append(secStr);
1036
1037         result.append(" GMT");
1038         return result.toString();
1039     }
1040
1041     private static double getYear(double date) {
1042         int result = YearFromTime(LocalTime(date));
1043         result -= 1900;
1044         return result;
1045     }
1046
1047     private static double getTimezoneOffset(double date) {
1048         return (date - LocalTime(date)) / msPerMinute;
1049     }
1050
1051     public double setTime(double time) {
1052         this.date = TimeClip(time);
1053         return this.date;
1054     }
1055
1056     private double makeTime(JS[] args, int maxargs, boolean local) throws JSExn {
1057         int i;
1058         double conv[] = new double[4];
1059         double hour, min, sec, msec;
1060         double lorutime; /* Local or UTC version of date */
1061
1062         double time;
1063         double result;
1064
1065         double date = this.date;
1066
1067         /* just return NaN if the date is already NaN */
1068         if (date != date)
1069             return date;
1070
1071         /* Satisfy the ECMA rule that if a function is called with
1072          * fewer arguments than the specified formal arguments, the
1073          * remaining arguments are set to undefined.  Seems like all
1074          * the Date.setWhatever functions in ECMA are only varargs
1075          * beyond the first argument; this should be set to undefined
1076          * if it's not given.  This means that "d = new Date();
1077          * d.setMilliseconds()" returns NaN.  Blech.
1078          */
1079         if (args.length == 0)
1080             args = new JS[] { null };
1081
1082         for (i = 0; i < args.length && i < maxargs; i++) {
1083             conv[i] = _toNumber(args[i]);
1084
1085             // limit checks that happen in MakeTime in ECMA.
1086             if (conv[i] != conv[i] || Double.isInfinite(conv[i])) {
1087                 this.date = Double.NaN;
1088                 return this.date;
1089             }
1090         }
1091
1092         if (local)
1093             lorutime = LocalTime(date);
1094         else
1095             lorutime = date;
1096
1097         i = 0;
1098         int stop = args.length;
1099
1100         if (maxargs >= 4 && i < stop)
1101             hour = conv[i++];
1102         else
1103             hour = HourFromTime(lorutime);
1104
1105         if (maxargs >= 3 && i < stop)
1106             min = conv[i++];
1107         else
1108             min = MinFromTime(lorutime);
1109
1110         if (maxargs >= 2 && i < stop)
1111             sec = conv[i++];
1112         else
1113             sec = SecFromTime(lorutime);
1114
1115         if (maxargs >= 1 && i < stop)
1116             msec = conv[i++];
1117         else
1118             msec = msFromTime(lorutime);
1119
1120         time = MakeTime(hour, min, sec, msec);
1121         result = MakeDate(Day(lorutime), time);
1122
1123         if (local)
1124             result = internalUTC(result);
1125         date = TimeClip(result);
1126
1127         this.date = date;
1128         return date;
1129     }
1130
1131     private double setHours(JS[] args) throws JSExn {
1132         return makeTime(args, 4, true);
1133     }
1134
1135     private double setUTCHours(JS[] args) throws JSExn {
1136         return makeTime(args, 4, false);
1137     }
1138
1139     private double makeDate(JS[] args, int maxargs, boolean local) throws JSExn {
1140         int i;
1141         double conv[] = new double[3];
1142         double year, month, day;
1143         double lorutime; /* local or UTC version of date */
1144         double result;
1145
1146         double date = this.date;
1147
1148         /* See arg padding comment in makeTime.*/
1149         if (args.length == 0)
1150             args = new JS[] { null };
1151
1152         for (i = 0; i < args.length && i < maxargs; i++) {
1153             conv[i] = _toNumber(args[i]);
1154
1155             // limit checks that happen in MakeDate in ECMA.
1156             if (conv[i] != conv[i] || Double.isInfinite(conv[i])) {
1157                 this.date = Double.NaN;
1158                 return this.date;
1159             }
1160         }
1161
1162         /* return NaN if date is NaN and we're not setting the year,
1163          * If we are, use 0 as the time. */
1164         if (date != date) {
1165             if (args.length < 3) {
1166                 return Double.NaN;
1167             } else {
1168                 lorutime = 0;
1169             }
1170         } else {
1171             if (local)
1172                 lorutime = LocalTime(date);
1173             else
1174                 lorutime = date;
1175         }
1176
1177         i = 0;
1178         int stop = args.length;
1179
1180         if (maxargs >= 3 && i < stop)
1181             year = conv[i++];
1182         else
1183             year = YearFromTime(lorutime);
1184
1185         if (maxargs >= 2 && i < stop)
1186             month = conv[i++];
1187         else
1188             month = MonthFromTime(lorutime);
1189
1190         if (maxargs >= 1 && i < stop)
1191             day = conv[i++];
1192         else
1193             day = DateFromTime(lorutime);
1194
1195         day = MakeDay(year, month, day); /* day within year */
1196         result = MakeDate(day, TimeWithinDay(lorutime));
1197
1198         if (local)
1199             result = internalUTC(result);
1200
1201         date = TimeClip(result);
1202
1203         this.date = date;
1204         return date;
1205     }
1206
1207     private double setYear(double year) {
1208         double day, result;
1209         if (year != year || Double.isInfinite(year)) {
1210             this.date = Double.NaN;
1211             return this.date;
1212         }
1213
1214         if (this.date != this.date) {
1215             this.date = 0;
1216         } else {
1217             this.date = LocalTime(this.date);
1218         }
1219
1220         if (year >= 0 && year <= 99)
1221             year += 1900;
1222
1223         day = MakeDay(year, MonthFromTime(this.date), DateFromTime(this.date));
1224         result = MakeDate(day, TimeWithinDay(this.date));
1225         result = internalUTC(result);
1226
1227         this.date = TimeClip(result);
1228         return this.date;
1229     }
1230
1231
1232     //    private static final int
1233     //        Id_toGMTString  =  Id_toUTCString; // Alias, see Ecma B.2.6
1234 // #/string_id_map#
1235
1236     /* cached values */
1237     private static java.util.TimeZone thisTimeZone;
1238     private static double LocalTZA;
1239     private static java.text.DateFormat timeZoneFormatter;
1240     private static java.text.DateFormat localeDateTimeFormatter;
1241     private static java.text.DateFormat localeDateFormatter;
1242     private static java.text.DateFormat localeTimeFormatter;
1243
1244     private double date;
1245
1246     public long getRawTime() { return (long)this.date; }
1247 }
1248
1249