1
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
51 public class JSDate extends JS {
52
53 public JSDate() {
54 if (thisTimeZone == null) {
55
56
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
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