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