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