Imported Upstream version 1.0.0
[platform/upstream/js.git] / js / src / jsdate.cpp
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=8 sw=4 et tw=78:
3  *
4  * ***** BEGIN LICENSE BLOCK *****
5  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6  *
7  * The contents of this file are subject to the Mozilla Public License Version
8  * 1.1 (the "License"); you may not use this file except in compliance with
9  * the License. You may obtain a copy of the License at
10  * http://www.mozilla.org/MPL/
11  *
12  * Software distributed under the License is distributed on an "AS IS" basis,
13  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14  * for the specific language governing rights and limitations under the
15  * License.
16  *
17  * The Original Code is Mozilla Communicator client code, released
18  * March 31, 1998.
19  *
20  * The Initial Developer of the Original Code is
21  * Netscape Communications Corporation.
22  * Portions created by the Initial Developer are Copyright (C) 1998
23  * the Initial Developer. All Rights Reserved.
24  *
25  * Contributor(s):
26  *
27  * Alternatively, the contents of this file may be used under the terms of
28  * either of the GNU General Public License Version 2 or later (the "GPL"),
29  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30  * in which case the provisions of the GPL or the LGPL are applicable instead
31  * of those above. If you wish to allow use of your version of this file only
32  * under the terms of either the GPL or the LGPL, and not to allow others to
33  * use your version of this file under the terms of the MPL, indicate your
34  * decision by deleting the provisions above and replace them with the notice
35  * and other provisions required by the GPL or the LGPL. If you do not delete
36  * the provisions above, a recipient may use your version of this file under
37  * the terms of any one of the MPL, the GPL or the LGPL.
38  *
39  * ***** END LICENSE BLOCK ***** */
40
41 /*
42  * JS date methods.
43  */
44
45 /*
46  * "For example, OS/360 devotes 26 bytes of the permanently
47  *  resident date-turnover routine to the proper handling of
48  *  December 31 on leap years (when it is Day 366).  That
49  *  might have been left to the operator."
50  *
51  * Frederick Brooks, 'The Second-System Effect'.
52  */
53
54 #include <ctype.h>
55 #include <locale.h>
56 #include <math.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include "jstypes.h"
60 #include "jsstdint.h"
61 #include "jsprf.h"
62 #include "prmjtime.h"
63 #include "jsutil.h"
64 #include "jsapi.h"
65 #include "jsversion.h"
66 #include "jsbuiltins.h"
67 #include "jscntxt.h"
68 #include "jsdate.h"
69 #include "jsinterp.h"
70 #include "jsnum.h"
71 #include "jsobj.h"
72 #include "jsstr.h"
73
74 #include "jsobjinlines.h"
75
76 using namespace js;
77
78 /*
79  * The JS 'Date' object is patterned after the Java 'Date' object.
80  * Here is an script:
81  *
82  *    today = new Date();
83  *
84  *    print(today.toLocaleString());
85  *
86  *    weekDay = today.getDay();
87  *
88  *
89  * These Java (and ECMA-262) methods are supported:
90  *
91  *     UTC
92  *     getDate (getUTCDate)
93  *     getDay (getUTCDay)
94  *     getHours (getUTCHours)
95  *     getMinutes (getUTCMinutes)
96  *     getMonth (getUTCMonth)
97  *     getSeconds (getUTCSeconds)
98  *     getMilliseconds (getUTCMilliseconds)
99  *     getTime
100  *     getTimezoneOffset
101  *     getYear
102  *     getFullYear (getUTCFullYear)
103  *     parse
104  *     setDate (setUTCDate)
105  *     setHours (setUTCHours)
106  *     setMinutes (setUTCMinutes)
107  *     setMonth (setUTCMonth)
108  *     setSeconds (setUTCSeconds)
109  *     setMilliseconds (setUTCMilliseconds)
110  *     setTime
111  *     setYear (setFullYear, setUTCFullYear)
112  *     toGMTString (toUTCString)
113  *     toLocaleString
114  *     toString
115  *
116  *
117  * These Java methods are not supported
118  *
119  *     setDay
120  *     before
121  *     after
122  *     equals
123  *     hashCode
124  */
125
126 /*
127  * 11/97 - jsdate.c has been rewritten to conform to the ECMA-262 language
128  * definition and reduce dependence on NSPR.  NSPR is used to get the current
129  * time in milliseconds, the time zone offset, and the daylight savings time
130  * offset for a given time.  NSPR is also used for Date.toLocaleString(), for
131  * locale-specific formatting, and to get a string representing the timezone.
132  * (Which turns out to be platform-dependent.)
133  *
134  * To do:
135  * (I did some performance tests by timing how long it took to run what
136  *  I had of the js ECMA conformance tests.)
137  *
138  * - look at saving results across multiple calls to supporting
139  * functions; the toString functions compute some of the same values
140  * multiple times.  Although - I took a quick stab at this, and I lost
141  * rather than gained.  (Fractionally.)  Hard to tell what compilers/processors
142  * are doing these days.
143  *
144  * - look at tweaking function return types to return double instead
145  * of int; this seems to make things run slightly faster sometimes.
146  * (though it could be architecture-dependent.)  It'd be good to see
147  * how this does on win32.  (Tried it on irix.)  Types could use a
148  * general going-over.
149  */
150
151 /*
152  * Supporting functions - ECMA 15.9.1.*
153  */
154
155 #define HoursPerDay     24.0
156 #define MinutesPerDay   (HoursPerDay * MinutesPerHour)
157 #define MinutesPerHour  60.0
158 #define SecondsPerDay   (MinutesPerDay * SecondsPerMinute)
159 #define SecondsPerHour  (MinutesPerHour * SecondsPerMinute)
160 #define SecondsPerMinute 60.0
161
162 #if defined(XP_WIN) || defined(XP_OS2)
163 /* Work around msvc double optimization bug by making these runtime values; if
164  * they're available at compile time, msvc optimizes division by them by
165  * computing the reciprocal and multiplying instead of dividing - this loses
166  * when the reciprocal isn't representable in a double.
167  */
168 static jsdouble msPerSecond = 1000.0;
169 static jsdouble msPerDay = SecondsPerDay * 1000.0;
170 static jsdouble msPerHour = SecondsPerHour * 1000.0;
171 static jsdouble msPerMinute = SecondsPerMinute * 1000.0;
172 #else
173 #define msPerDay        (SecondsPerDay * msPerSecond)
174 #define msPerHour       (SecondsPerHour * msPerSecond)
175 #define msPerMinute     (SecondsPerMinute * msPerSecond)
176 #define msPerSecond     1000.0
177 #endif
178
179 #define Day(t)          floor((t) / msPerDay)
180
181 static jsdouble
182 TimeWithinDay(jsdouble t)
183 {
184     jsdouble result;
185     result = fmod(t, msPerDay);
186     if (result < 0)
187         result += msPerDay;
188     return result;
189 }
190
191 static inline bool
192 IsLeapYear(jsint year)
193 {
194     return year % 4 == 0 && (year % 100 || (year % 400 == 0));
195 }
196
197 static inline jsint
198 DaysInYear(jsint year) 
199 {
200     return IsLeapYear(year) ? 366 : 365;
201 }
202
203 static inline jsint
204 DaysInFebruary(jsint year)
205 {
206     return IsLeapYear(year) ? 29 : 28;
207 }
208
209 /* math here has to be f.p, because we need
210  *  floor((1968 - 1969) / 4) == -1
211  */
212 #define DayFromYear(y)  (365 * ((y)-1970) + floor(((y)-1969)/4.0)            \
213                          - floor(((y)-1901)/100.0) + floor(((y)-1601)/400.0))
214 #define TimeFromYear(y) (DayFromYear(y) * msPerDay)
215
216 static jsint
217 YearFromTime(jsdouble t)
218 {
219     jsint y = (jsint) floor(t /(msPerDay*365.2425)) + 1970;
220     jsdouble t2 = (jsdouble) TimeFromYear(y);
221
222     /*
223      * Adjust the year if the approximation was wrong.  Since the year was
224      * computed using the average number of ms per year, it will usually
225      * be wrong for dates within several hours of a year transition.
226      */
227     if (t2 > t) {
228         y--;
229     } else {
230         if (t2 + msPerDay * DaysInYear(y) <= t)
231             y++;
232     }
233     return y;
234 }
235
236 #define DayWithinYear(t, year) ((intN) (Day(t) - DayFromYear(year)))
237
238 /*
239  * The following array contains the day of year for the first day of
240  * each month, where index 0 is January, and day 0 is January 1.
241  */
242 static jsdouble firstDayOfMonth[2][13] = {
243     {0.0, 31.0, 59.0, 90.0, 120.0, 151.0, 181.0, 212.0, 243.0, 273.0, 304.0, 334.0, 365.0},
244     {0.0, 31.0, 60.0, 91.0, 121.0, 152.0, 182.0, 213.0, 244.0, 274.0, 305.0, 335.0, 366.0}
245 };
246
247 #define DayFromMonth(m, leap) firstDayOfMonth[leap][(intN)m]
248
249 static intN
250 DaysInMonth(jsint year, jsint month)
251 {
252     JSBool leap = IsLeapYear(year);
253     intN result = intN(DayFromMonth(month, leap) - DayFromMonth(month-1, leap));
254     return result;
255 }
256
257 static intN
258 MonthFromTime(jsdouble t)
259 {
260     intN d, step;
261     jsint year = YearFromTime(t);
262     d = DayWithinYear(t, year);
263
264     if (d < (step = 31))
265         return 0;
266     if (d < (step += DaysInFebruary(year)))
267         return 1;
268     if (d < (step += 31))
269         return 2;
270     if (d < (step += 30))
271         return 3;
272     if (d < (step += 31))
273         return 4;
274     if (d < (step += 30))
275         return 5;
276     if (d < (step += 31))
277         return 6;
278     if (d < (step += 31))
279         return 7;
280     if (d < (step += 30))
281         return 8;
282     if (d < (step += 31))
283         return 9;
284     if (d < (step += 30))
285         return 10;
286     return 11;
287 }
288
289 static intN
290 DateFromTime(jsdouble t)
291 {
292     intN d, step, next;
293     jsint year = YearFromTime(t);
294     d = DayWithinYear(t, year);
295
296     if (d <= (next = 30))
297         return d + 1;
298     step = next;
299     if (d <= (next += DaysInFebruary(year)))
300         return d - step;
301     step = next;
302     if (d <= (next += 31))
303         return d - step;
304     step = next;
305     if (d <= (next += 30))
306         return d - step;
307     step = next;
308     if (d <= (next += 31))
309         return d - step;
310     step = next;
311     if (d <= (next += 30))
312         return d - step;
313     step = next;
314     if (d <= (next += 31))
315         return d - step;
316     step = next;
317     if (d <= (next += 31))
318         return d - step;
319     step = next;
320     if (d <= (next += 30))
321         return d - step;
322     step = next;
323     if (d <= (next += 31))
324         return d - step;
325     step = next;
326     if (d <= (next += 30))
327         return d - step;
328     step = next;
329     return d - step;
330 }
331
332 static intN
333 WeekDay(jsdouble t)
334 {
335     jsint result;
336     result = (jsint) Day(t) + 4;
337     result = result % 7;
338     if (result < 0)
339         result += 7;
340     return (intN) result;
341 }
342
343 #define MakeTime(hour, min, sec, ms) \
344 ((((hour) * MinutesPerHour + (min)) * SecondsPerMinute + (sec)) * msPerSecond + (ms))
345
346 static jsdouble
347 MakeDay(jsdouble year, jsdouble month, jsdouble date)
348 {
349     JSBool leap;
350     jsdouble yearday;
351     jsdouble monthday;
352
353     year += floor(month / 12);
354
355     month = fmod(month, 12.0);
356     if (month < 0)
357         month += 12;
358
359     leap = IsLeapYear((jsint) year);
360
361     yearday = floor(TimeFromYear(year) / msPerDay);
362     monthday = DayFromMonth(month, leap);
363
364     return yearday + monthday + date - 1;
365 }
366
367 #define MakeDate(day, time) ((day) * msPerDay + (time))
368
369 /*
370  * Years and leap years on which Jan 1 is a Sunday, Monday, etc.
371  *
372  * yearStartingWith[0][i] is an example non-leap year where
373  * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
374  *
375  * yearStartingWith[1][i] is an example leap year where
376  * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
377  */
378 static jsint yearStartingWith[2][7] = {
379     {1978, 1973, 1974, 1975, 1981, 1971, 1977},
380     {1984, 1996, 1980, 1992, 1976, 1988, 1972}
381 };
382
383 /*
384  * Find a year for which any given date will fall on the same weekday.
385  *
386  * This function should be used with caution when used other than
387  * for determining DST; it hasn't been proven not to produce an
388  * incorrect year for times near year boundaries.
389  */
390 static jsint
391 EquivalentYearForDST(jsint year)
392 {
393     jsint day;
394
395     day = (jsint) DayFromYear(year) + 4;
396     day = day % 7;
397     if (day < 0)
398         day += 7;
399
400     return yearStartingWith[IsLeapYear(year)][day];
401 }
402
403 /* LocalTZA gets set by js_InitDateClass() */
404 static jsdouble LocalTZA;
405
406 static jsdouble
407 DaylightSavingTA(jsdouble t, JSContext *cx)
408 {
409     /* abort if NaN */
410     if (JSDOUBLE_IS_NaN(t))
411         return t;
412
413     /*
414      * If earlier than 1970 or after 2038, potentially beyond the ken of
415      * many OSes, map it to an equivalent year before asking.
416      */
417     if (t < 0.0 || t > 2145916800000.0) {
418         jsint year = EquivalentYearForDST(YearFromTime(t));
419         jsdouble day = MakeDay(year, MonthFromTime(t), DateFromTime(t));
420         t = MakeDate(day, TimeWithinDay(t));
421     }
422
423     int64 timeMilliseconds = static_cast<int64>(t);
424     int64 offsetMilliseconds = cx->dstOffsetCache.getDSTOffsetMilliseconds(timeMilliseconds, cx);
425     return static_cast<jsdouble>(offsetMilliseconds);
426 }
427
428 static jsdouble
429 AdjustTime(jsdouble date, JSContext *cx)
430 {
431     jsdouble t = DaylightSavingTA(date, cx) + LocalTZA;
432     t = (LocalTZA >= 0) ? fmod(t, msPerDay) : -fmod(msPerDay - t, msPerDay);
433     return t;
434 }
435
436 static jsdouble
437 LocalTime(jsdouble t, JSContext *cx)
438 {
439     return t + AdjustTime(t, cx);
440 }
441
442 static jsdouble
443 UTC(jsdouble t, JSContext *cx)
444 {
445     return t - AdjustTime(t - LocalTZA, cx);
446 }
447
448 static intN
449 HourFromTime(jsdouble t)
450 {
451     intN result = (intN) fmod(floor(t/msPerHour), HoursPerDay);
452     if (result < 0)
453         result += (intN)HoursPerDay;
454     return result;
455 }
456
457 static intN
458 MinFromTime(jsdouble t)
459 {
460     intN result = (intN) fmod(floor(t / msPerMinute), MinutesPerHour);
461     if (result < 0)
462         result += (intN)MinutesPerHour;
463     return result;
464 }
465
466 static intN
467 SecFromTime(jsdouble t)
468 {
469     intN result = (intN) fmod(floor(t / msPerSecond), SecondsPerMinute);
470     if (result < 0)
471         result += (intN)SecondsPerMinute;
472     return result;
473 }
474
475 static intN
476 msFromTime(jsdouble t)
477 {
478     intN result = (intN) fmod(t, msPerSecond);
479     if (result < 0)
480         result += (intN)msPerSecond;
481     return result;
482 }
483
484 /**
485  * end of ECMA 'support' functions
486  */
487
488 /*
489  * Other Support routines and definitions
490  */
491
492 Class js_DateClass = {
493     js_Date_str,
494     JSCLASS_HAS_RESERVED_SLOTS(JSObject::DATE_CLASS_RESERVED_SLOTS) |
495     JSCLASS_HAS_CACHED_PROTO(JSProto_Date),
496     PropertyStub,         /* addProperty */
497     PropertyStub,         /* delProperty */
498     PropertyStub,         /* getProperty */
499     StrictPropertyStub,   /* setProperty */
500     EnumerateStub,
501     ResolveStub,
502     ConvertStub
503 };
504
505 /* for use by date_parse */
506
507 static const char* wtb[] = {
508     "am", "pm",
509     "monday", "tuesday", "wednesday", "thursday", "friday",
510     "saturday", "sunday",
511     "january", "february", "march", "april", "may", "june",
512     "july", "august", "september", "october", "november", "december",
513     "gmt", "ut", "utc",
514     "est", "edt",
515     "cst", "cdt",
516     "mst", "mdt",
517     "pst", "pdt"
518     /* time zone table needs to be expanded */
519 };
520
521 static int ttb[] = {
522     -1, -2, 0, 0, 0, 0, 0, 0, 0,       /* AM/PM */
523     2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
524     10000 + 0, 10000 + 0, 10000 + 0,   /* GMT/UT/UTC */
525     10000 + 5 * 60, 10000 + 4 * 60,    /* EST/EDT */
526     10000 + 6 * 60, 10000 + 5 * 60,    /* CST/CDT */
527     10000 + 7 * 60, 10000 + 6 * 60,    /* MST/MDT */
528     10000 + 8 * 60, 10000 + 7 * 60     /* PST/PDT */
529 };
530
531 /* helper for date_parse */
532 static JSBool
533 date_regionMatches(const char* s1, int s1off, const jschar* s2, int s2off,
534                    int count, int ignoreCase)
535 {
536     JSBool result = JS_FALSE;
537     /* return true if matches, otherwise, false */
538
539     while (count > 0 && s1[s1off] && s2[s2off]) {
540         if (ignoreCase) {
541             if (JS_TOLOWER((jschar)s1[s1off]) != JS_TOLOWER(s2[s2off])) {
542                 break;
543             }
544         } else {
545             if ((jschar)s1[s1off] != s2[s2off]) {
546                 break;
547             }
548         }
549         s1off++;
550         s2off++;
551         count--;
552     }
553
554     if (count == 0) {
555         result = JS_TRUE;
556     }
557
558     return result;
559 }
560
561 /* find UTC time from given date... no 1900 correction! */
562 static jsdouble
563 date_msecFromDate(jsdouble year, jsdouble mon, jsdouble mday, jsdouble hour,
564                   jsdouble min, jsdouble sec, jsdouble msec)
565 {
566     jsdouble day;
567     jsdouble msec_time;
568     jsdouble result;
569
570     day = MakeDay(year, mon, mday);
571     msec_time = MakeTime(hour, min, sec, msec);
572     result = MakeDate(day, msec_time);
573     return result;
574 }
575
576 /* compute the time in msec (unclipped) from the given args */
577 #define MAXARGS        7
578
579 static JSBool
580 date_msecFromArgs(JSContext *cx, uintN argc, Value *argv, jsdouble *rval)
581 {
582     uintN loop;
583     jsdouble array[MAXARGS];
584     jsdouble msec_time;
585
586     for (loop = 0; loop < MAXARGS; loop++) {
587         if (loop < argc) {
588             jsdouble d;
589             if (!ValueToNumber(cx, argv[loop], &d))
590                 return JS_FALSE;
591             /* return NaN if any arg is not finite */
592             if (!JSDOUBLE_IS_FINITE(d)) {
593                 *rval = js_NaN;
594                 return JS_TRUE;
595             }
596             array[loop] = js_DoubleToInteger(d);
597         } else {
598             if (loop == 2) {
599                 array[loop] = 1; /* Default the date argument to 1. */
600             } else {
601                 array[loop] = 0;
602             }
603         }
604     }
605
606     /* adjust 2-digit years into the 20th century */
607     if (array[0] >= 0 && array[0] <= 99)
608         array[0] += 1900;
609
610     msec_time = date_msecFromDate(array[0], array[1], array[2],
611                                   array[3], array[4], array[5], array[6]);
612     *rval = msec_time;
613     return JS_TRUE;
614 }
615
616 /*
617  * See ECMA 15.9.4.[3-10];
618  */
619 static JSBool
620 date_UTC(JSContext *cx, uintN argc, Value *vp)
621 {
622     jsdouble msec_time;
623
624     if (!date_msecFromArgs(cx, argc, vp + 2, &msec_time))
625         return JS_FALSE;
626
627     msec_time = TIMECLIP(msec_time);
628
629     vp->setNumber(msec_time);
630     return JS_TRUE;
631 }
632
633 /*
634  * Read and convert decimal digits from s[*i] into *result
635  * while *i < limit. 
636  * 
637  * Succeed if any digits are converted. Advance *i only
638  * as digits are consumed.
639  */
640 static JSBool
641 digits(size_t *result, const jschar *s, size_t *i, size_t limit)
642 {
643     size_t init = *i;
644     *result = 0;
645     while (*i < limit && 
646            ('0' <= s[*i] && s[*i] <= '9')) {
647         *result *= 10;
648         *result += (s[*i] - '0');
649         ++(*i);
650     }
651     return (*i != init);
652 }
653
654 /* 
655  * Read and convert decimal digits to the right of a decimal point,
656  * representing a fractional integer, from s[*i] into *result
657  * while *i < limit. 
658  * 
659  * Succeed if any digits are converted. Advance *i only
660  * as digits are consumed.
661  */
662 static JSBool
663 fractional(jsdouble *result, const jschar *s, size_t *i, size_t limit)
664 {
665     jsdouble factor = 0.1;
666     size_t init = *i;
667     *result = 0.0;
668     while (*i < limit && 
669            ('0' <= s[*i] && s[*i] <= '9')) {
670         *result += (s[*i] - '0') * factor;
671         factor *= 0.1;
672         ++(*i);
673     }
674     return (*i != init);
675 }
676
677 /* 
678  * Read and convert exactly n decimal digits from s[*i] 
679  * to s[min(*i+n,limit)] into *result. 
680  *
681  * Succeed if exactly n digits are converted. Advance *i only
682  * on success.
683  */
684 static JSBool
685 ndigits(size_t n, size_t *result, const jschar *s, size_t* i, size_t limit)
686 {
687     size_t init = *i;
688
689     if (digits(result, s, i, JS_MIN(limit, init+n)))
690         return ((*i - init) == n);
691     
692     *i = init;
693     return JS_FALSE;
694 }
695
696 /* 
697  * Parse a string in one of the date-time formats given by the W3C
698  * "NOTE-datetime" specification. These formats make up a restricted
699  * profile of the ISO 8601 format. Quoted here:
700  *
701  *   The formats are as follows. Exactly the components shown here
702  *   must be present, with exactly this punctuation. Note that the "T"
703  *   appears literally in the string, to indicate the beginning of the
704  *   time element, as specified in ISO 8601.
705  *
706  *   Any combination of the date formats with the time formats is
707  *   allowed, and also either the date or the time can be missing.
708  *
709  *   The specification is silent on the meaning when fields are
710  *   ommitted so the interpretations are a guess, but hopefully a
711  *   reasonable one. We default the month to January, the day to the
712  *   1st, and hours minutes and seconds all to 0. If the date is
713  *   missing entirely then we assume 1970-01-01 so that the time can
714  *   be aded to a date later. If the time is missing then we assume
715  *   00:00 UTC.  If the time is present but the time zone field is
716  *   missing then we use local time.
717  * 
718  * Date part:
719  *
720  *  Year:
721  *     YYYY (eg 1997)
722  *
723  *  Year and month:
724  *     YYYY-MM (eg 1997-07)
725  *
726  *  Complete date:
727  *     YYYY-MM-DD (eg 1997-07-16)
728  *
729  * Time part:
730  *
731  *  Hours and minutes:
732  *     Thh:mmTZD (eg T19:20+01:00)
733  * 
734  *  Hours, minutes and seconds:
735  *     Thh:mm:ssTZD (eg T19:20:30+01:00)
736  *
737  *  Hours, minutes, seconds and a decimal fraction of a second:
738  *     Thh:mm:ss.sTZD (eg T19:20:30.45+01:00)
739  *
740  * where:
741  *
742  *   YYYY = four-digit year or six digit year as +YYYYYY or -YYYYYY
743  *   MM   = two-digit month (01=January, etc.)
744  *   DD   = two-digit day of month (01 through 31)
745  *   hh   = two digits of hour (00 through 23) (am/pm NOT allowed)
746  *   mm   = two digits of minute (00 through 59)
747  *   ss   = two digits of second (00 through 59)
748  *   s    = one or more digits representing a decimal fraction of a second
749  *   TZD  = time zone designator (Z or +hh:mm or -hh:mm or missing for local)
750  */
751
752 static JSBool
753 date_parseISOString(JSLinearString *str, jsdouble *result, JSContext *cx)
754 {
755     jsdouble msec;
756
757     const jschar *s;
758     size_t limit;
759     size_t i = 0;
760     int tzMul = 1;
761     int dateMul = 1;
762     size_t year = 1970;
763     size_t month = 1;
764     size_t day = 1;
765     size_t hour = 0;
766     size_t min = 0;
767     size_t sec = 0;
768     jsdouble frac = 0;
769     bool isLocalTime = JS_FALSE;
770     size_t tzHour = 0;
771     size_t tzMin = 0;
772
773 #define PEEK(ch) (i < limit && s[i] == ch)
774
775 #define NEED(ch)                                                     \
776     JS_BEGIN_MACRO                                                   \
777         if (i >= limit || s[i] != ch) { goto syntax; } else { ++i; } \
778     JS_END_MACRO 
779
780 #define DONE_DATE_UNLESS(ch)                                            \
781     JS_BEGIN_MACRO                                                      \
782         if (i >= limit || s[i] != ch) { goto done_date; } else { ++i; } \
783     JS_END_MACRO 
784
785 #define DONE_UNLESS(ch)                                            \
786     JS_BEGIN_MACRO                                                 \
787         if (i >= limit || s[i] != ch) { goto done; } else { ++i; } \
788     JS_END_MACRO 
789
790 #define NEED_NDIGITS(n, field)                                      \
791     JS_BEGIN_MACRO                                                  \
792         if (!ndigits(n, &field, s, &i, limit)) { goto syntax; }     \
793     JS_END_MACRO 
794
795     s = str->chars();
796     limit = str->length();
797
798     if (PEEK('+') || PEEK('-')) {
799         if (PEEK('-'))
800             dateMul = -1;
801         ++i;
802         NEED_NDIGITS(6, year);
803     } else if (!PEEK('T')) {
804         NEED_NDIGITS(4, year);
805     }
806     DONE_DATE_UNLESS('-');
807     NEED_NDIGITS(2, month);
808     DONE_DATE_UNLESS('-');
809     NEED_NDIGITS(2, day);
810
811  done_date:
812     DONE_UNLESS('T');
813     NEED_NDIGITS(2, hour);
814     NEED(':');
815     NEED_NDIGITS(2, min);
816
817     if (PEEK(':')) {
818         ++i;
819         NEED_NDIGITS(2, sec);
820         if (PEEK('.')) {
821             ++i;
822             if (!fractional(&frac, s, &i, limit))
823                 goto syntax;
824         }
825     }
826
827     if (PEEK('Z')) {
828         ++i;
829     } else if (PEEK('+') || PEEK('-')) {
830         if (PEEK('-'))
831             tzMul = -1;
832         ++i;
833         NEED_NDIGITS(2, tzHour);
834         NEED(':');
835         NEED_NDIGITS(2, tzMin);
836     } else {
837         isLocalTime = JS_TRUE;
838     }
839
840  done:
841     if (year > 275943 // ceil(1e8/365) + 1970
842         || (month == 0 || month > 12)
843         || (day == 0 || day > size_t(DaysInMonth(year,month)))
844         || hour > 24 
845         || ((hour == 24) && (min > 0 || sec > 0))
846         || min > 59 
847         || sec > 59
848         || tzHour > 23
849         || tzMin > 59) 
850         goto syntax;
851
852     if (i != limit)
853         goto syntax;
854
855     month -= 1; /* convert month to 0-based */
856
857     msec = date_msecFromDate(dateMul * (jsdouble)year, month, day,
858                              hour, min, sec,
859                              frac * 1000.0);;
860
861     if (isLocalTime) {
862         msec = UTC(msec, cx);
863     } else {
864         msec -= ((tzMul) * ((tzHour * msPerHour) 
865                             + (tzMin * msPerMinute)));
866     }
867
868     if (msec < -8.64e15 || msec > 8.64e15)
869         goto syntax;
870
871     *result = msec;
872
873     return JS_TRUE;
874
875  syntax:
876     /* syntax error */
877     *result = 0;
878     return JS_FALSE;
879
880 #undef PEEK
881 #undef NEED
882 #undef DONE_UNLESS
883 #undef NEED_NDIGITS
884 }
885
886 static JSBool
887 date_parseString(JSLinearString *str, jsdouble *result, JSContext *cx)
888 {
889     jsdouble msec;
890
891     const jschar *s;
892     size_t limit;
893     size_t i = 0;
894     int year = -1;
895     int mon = -1;
896     int mday = -1;
897     int hour = -1;
898     int min = -1;
899     int sec = -1;
900     int c = -1;
901     int n = -1;
902     int tzoffset = -1;
903     int prevc = 0;
904     JSBool seenplusminus = JS_FALSE;
905     int temp;
906     JSBool seenmonthname = JS_FALSE;
907
908     if (date_parseISOString(str, result, cx))
909         return JS_TRUE;
910
911     s = str->chars();
912     limit = str->length();
913     if (limit == 0)
914         goto syntax;
915     while (i < limit) {
916         c = s[i];
917         i++;
918         if (c <= ' ' || c == ',' || c == '-') {
919             if (c == '-' && '0' <= s[i] && s[i] <= '9') {
920               prevc = c;
921             }
922             continue;
923         }
924         if (c == '(') { /* comments) */
925             int depth = 1;
926             while (i < limit) {
927                 c = s[i];
928                 i++;
929                 if (c == '(') depth++;
930                 else if (c == ')')
931                     if (--depth <= 0)
932                         break;
933             }
934             continue;
935         }
936         if ('0' <= c && c <= '9') {
937             n = c - '0';
938             while (i < limit && '0' <= (c = s[i]) && c <= '9') {
939                 n = n * 10 + c - '0';
940                 i++;
941             }
942
943             /* allow TZA before the year, so
944              * 'Wed Nov 05 21:49:11 GMT-0800 1997'
945              * works */
946
947             /* uses of seenplusminus allow : in TZA, so Java
948              * no-timezone style of GMT+4:30 works
949              */
950
951             if ((prevc == '+' || prevc == '-')/*  && year>=0 */) {
952                 /* make ':' case below change tzoffset */
953                 seenplusminus = JS_TRUE;
954
955                 /* offset */
956                 if (n < 24)
957                     n = n * 60; /* EG. "GMT-3" */
958                 else
959                     n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */
960                 if (prevc == '+')       /* plus means east of GMT */
961                     n = -n;
962                 if (tzoffset != 0 && tzoffset != -1)
963                     goto syntax;
964                 tzoffset = n;
965             } else if (prevc == '/' && mon >= 0 && mday >= 0 && year < 0) {
966                 if (c <= ' ' || c == ',' || c == '/' || i >= limit)
967                     year = n;
968                 else
969                     goto syntax;
970             } else if (c == ':') {
971                 if (hour < 0)
972                     hour = /*byte*/ n;
973                 else if (min < 0)
974                     min = /*byte*/ n;
975                 else
976                     goto syntax;
977             } else if (c == '/') {
978                 /* until it is determined that mon is the actual
979                    month, keep it as 1-based rather than 0-based */
980                 if (mon < 0)
981                     mon = /*byte*/ n;
982                 else if (mday < 0)
983                     mday = /*byte*/ n;
984                 else
985                     goto syntax;
986             } else if (i < limit && c != ',' && c > ' ' && c != '-' && c != '(') {
987                 goto syntax;
988             } else if (seenplusminus && n < 60) {  /* handle GMT-3:30 */
989                 if (tzoffset < 0)
990                     tzoffset -= n;
991                 else
992                     tzoffset += n;
993             } else if (hour >= 0 && min < 0) {
994                 min = /*byte*/ n;
995             } else if (prevc == ':' && min >= 0 && sec < 0) {
996                 sec = /*byte*/ n;
997             } else if (mon < 0) {
998                 mon = /*byte*/n;
999             } else if (mon >= 0 && mday < 0) {
1000                 mday = /*byte*/ n;
1001             } else if (mon >= 0 && mday >= 0 && year < 0) {
1002                 year = n;
1003             } else {
1004                 goto syntax;
1005             }
1006             prevc = 0;
1007         } else if (c == '/' || c == ':' || c == '+' || c == '-') {
1008             prevc = c;
1009         } else {
1010             size_t st = i - 1;
1011             int k;
1012             while (i < limit) {
1013                 c = s[i];
1014                 if (!(('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')))
1015                     break;
1016                 i++;
1017             }
1018             if (i <= st + 1)
1019                 goto syntax;
1020             for (k = JS_ARRAY_LENGTH(wtb); --k >= 0;)
1021                 if (date_regionMatches(wtb[k], 0, s, st, i-st, 1)) {
1022                     int action = ttb[k];
1023                     if (action != 0) {
1024                         if (action < 0) {
1025                             /*
1026                              * AM/PM. Count 12:30 AM as 00:30, 12:30 PM as
1027                              * 12:30, instead of blindly adding 12 if PM.
1028                              */
1029                             JS_ASSERT(action == -1 || action == -2);
1030                             if (hour > 12 || hour < 0) {
1031                                 goto syntax;
1032                             } else {
1033                                 if (action == -1 && hour == 12) { /* am */
1034                                     hour = 0;
1035                                 } else if (action == -2 && hour != 12) { /* pm */
1036                                     hour += 12;
1037                                 }
1038                             }
1039                         } else if (action <= 13) { /* month! */
1040                             /* Adjust mon to be 1-based until the final values
1041                                for mon, mday and year are adjusted below */
1042                             if (seenmonthname) {
1043                                 goto syntax;
1044                             }
1045                             seenmonthname = JS_TRUE;
1046                             temp = /*byte*/ (action - 2) + 1;
1047
1048                             if (mon < 0) {
1049                                 mon = temp;
1050                             } else if (mday < 0) {
1051                                 mday = mon;
1052                                 mon = temp;
1053                             } else if (year < 0) {
1054                                 year = mon;
1055                                 mon = temp;
1056                             } else {
1057                                 goto syntax;
1058                             }
1059                         } else {
1060                             tzoffset = action - 10000;
1061                         }
1062                     }
1063                     break;
1064                 }
1065             if (k < 0)
1066                 goto syntax;
1067             prevc = 0;
1068         }
1069     }
1070     if (year < 0 || mon < 0 || mday < 0)
1071         goto syntax;
1072     /*
1073       Case 1. The input string contains an English month name.
1074               The form of the string can be month f l, or f month l, or
1075               f l month which each evaluate to the same date.
1076               If f and l are both greater than or equal to 70, or
1077               both less than 70, the date is invalid.
1078               The year is taken to be the greater of the values f, l.
1079               If the year is greater than or equal to 70 and less than 100,
1080               it is considered to be the number of years after 1900.
1081       Case 2. The input string is of the form "f/m/l" where f, m and l are
1082               integers, e.g. 7/16/45.
1083               Adjust the mon, mday and year values to achieve 100% MSIE
1084               compatibility.
1085               a. If 0 <= f < 70, f/m/l is interpreted as month/day/year.
1086                  i.  If year < 100, it is the number of years after 1900
1087                  ii. If year >= 100, it is the number of years after 0.
1088               b. If 70 <= f < 100
1089                  i.  If m < 70, f/m/l is interpreted as
1090                      year/month/day where year is the number of years after
1091                      1900.
1092                  ii. If m >= 70, the date is invalid.
1093               c. If f >= 100
1094                  i.  If m < 70, f/m/l is interpreted as
1095                      year/month/day where year is the number of years after 0.
1096                  ii. If m >= 70, the date is invalid.
1097     */
1098     if (seenmonthname) {
1099         if ((mday >= 70 && year >= 70) || (mday < 70 && year < 70)) {
1100             goto syntax;
1101         }
1102         if (mday > year) {
1103             temp = year;
1104             year = mday;
1105             mday = temp;
1106         }
1107         if (year >= 70 && year < 100) {
1108             year += 1900;
1109         }
1110     } else if (mon < 70) { /* (a) month/day/year */
1111         if (year < 100) {
1112             year += 1900;
1113         }
1114     } else if (mon < 100) { /* (b) year/month/day */
1115         if (mday < 70) {
1116             temp = year;
1117             year = mon + 1900;
1118             mon = mday;
1119             mday = temp;
1120         } else {
1121             goto syntax;
1122         }
1123     } else { /* (c) year/month/day */
1124         if (mday < 70) {
1125             temp = year;
1126             year = mon;
1127             mon = mday;
1128             mday = temp;
1129         } else {
1130             goto syntax;
1131         }
1132     }
1133     mon -= 1; /* convert month to 0-based */
1134     if (sec < 0)
1135         sec = 0;
1136     if (min < 0)
1137         min = 0;
1138     if (hour < 0)
1139         hour = 0;
1140
1141     msec = date_msecFromDate(year, mon, mday, hour, min, sec, 0);
1142
1143     if (tzoffset == -1) { /* no time zone specified, have to use local */
1144         msec = UTC(msec, cx);
1145     } else {
1146         msec += tzoffset * msPerMinute;
1147     }
1148
1149     *result = msec;
1150     return JS_TRUE;
1151
1152 syntax:
1153     /* syntax error */
1154     *result = 0;
1155     return JS_FALSE;
1156 }
1157
1158 static JSBool
1159 date_parse(JSContext *cx, uintN argc, Value *vp)
1160 {
1161     JSString *str;
1162     jsdouble result;
1163
1164     if (argc == 0) {
1165         vp->setDouble(js_NaN);
1166         return true;
1167     }
1168     str = js_ValueToString(cx, vp[2]);
1169     if (!str)
1170         return JS_FALSE;
1171     vp[2].setString(str);
1172     JSLinearString *linearStr = str->ensureLinear(cx);
1173     if (!linearStr)
1174         return false;
1175
1176     if (!date_parseString(linearStr, &result, cx)) {
1177         vp->setDouble(js_NaN);
1178         return true;
1179     }
1180
1181     result = TIMECLIP(result);
1182     vp->setNumber(result);
1183     return true;
1184 }
1185
1186 static inline jsdouble
1187 NowAsMillis()
1188 {
1189     return (jsdouble) (PRMJ_Now() / PRMJ_USEC_PER_MSEC);
1190 }
1191
1192 static JSBool
1193 date_now(JSContext *cx, uintN argc, Value *vp)
1194 {
1195     vp->setDouble(NowAsMillis());
1196     return JS_TRUE;
1197 }
1198
1199 #ifdef JS_TRACER
1200 static jsdouble FASTCALL
1201 date_now_tn(JSContext*)
1202 {
1203     return NowAsMillis();
1204 }
1205 #endif
1206
1207 /*
1208  * Get UTC time from the date object. Returns false if the object is not
1209  * Date type.
1210  */
1211 static JSBool
1212 GetUTCTime(JSContext *cx, JSObject *obj, Value *vp, jsdouble *dp)
1213 {
1214     if (!InstanceOf(cx, obj, &js_DateClass, vp ? vp + 2 : NULL))
1215         return JS_FALSE;
1216     *dp = obj->getDateUTCTime().toNumber();
1217     return JS_TRUE;
1218 }
1219
1220 /*
1221  * Set UTC time to a given time and invalidate cached local time.
1222  */
1223 static JSBool
1224 SetUTCTime(JSContext *cx, JSObject *obj, jsdouble t, Value *vp = NULL)
1225 {
1226     JS_ASSERT(obj->isDate());
1227
1228     size_t slotCap = JS_MIN(obj->numSlots(), JSObject::DATE_CLASS_RESERVED_SLOTS);
1229     for (size_t ind = JSObject::JSSLOT_DATE_COMPONENTS_START; ind < slotCap; ind++)
1230         obj->getSlotRef(ind).setUndefined();
1231
1232     obj->setDateUTCTime(DoubleValue(t));
1233     if (vp)
1234         vp->setDouble(t);
1235     return true;
1236 }
1237
1238 static void
1239 SetDateToNaN(JSContext *cx, JSObject *obj, Value *vp = NULL)
1240 {
1241     jsdouble NaN = cx->runtime->NaNValue.getDoubleRef();
1242     SetUTCTime(cx, obj, NaN, vp);
1243 }
1244
1245 /*
1246  * Cache the local time, year, month, and so forth of the object.
1247  * If UTC time is not finite (e.g., NaN), the local time
1248  * slots will be set to the UTC time without conversion.
1249  */
1250 static bool
1251 FillLocalTimes(JSContext *cx, JSObject *obj)
1252 {
1253     JS_ASSERT(obj->isDate());
1254
1255     jsdouble utcTime = obj->getDateUTCTime().toNumber();
1256
1257     /* Make sure there are slots to store the cached information. */
1258     if (obj->numSlots() < JSObject::DATE_CLASS_RESERVED_SLOTS) {
1259         if (!obj->growSlots(cx, JSObject::DATE_CLASS_RESERVED_SLOTS))
1260             return false;
1261     }
1262
1263     if (!JSDOUBLE_IS_FINITE(utcTime)) {
1264         for (size_t ind = JSObject::JSSLOT_DATE_COMPONENTS_START;
1265              ind < JSObject::DATE_CLASS_RESERVED_SLOTS;
1266              ind++) {
1267             obj->setSlot(ind, DoubleValue(utcTime));
1268         }
1269         return true;
1270     }
1271
1272     jsdouble localTime = LocalTime(utcTime, cx);
1273
1274     obj->setSlot(JSObject::JSSLOT_DATE_LOCAL_TIME, DoubleValue(localTime));
1275
1276     jsint year = (jsint) floor(localTime /(msPerDay*365.2425)) + 1970;
1277     jsdouble yearStartTime = (jsdouble) TimeFromYear(year);
1278
1279     /* Adjust the year in case the approximation was wrong, as in YearFromTime. */
1280     jsint yearDays;
1281     if (yearStartTime > localTime) {
1282         year--;
1283         yearStartTime -= (msPerDay * DaysInYear(year));
1284         yearDays = DaysInYear(year);
1285     } else {
1286         yearDays = DaysInYear(year);
1287         jsdouble nextStart = yearStartTime + (msPerDay * yearDays);
1288         if (nextStart <= localTime) {
1289             year++;
1290             yearStartTime = nextStart;
1291             yearDays = DaysInYear(year);
1292         }
1293     }
1294
1295     obj->setSlot(JSObject::JSSLOT_DATE_LOCAL_YEAR, Int32Value(year));
1296
1297     uint64 yearTime = uint64(localTime - yearStartTime);
1298     jsint yearSeconds = uint32(yearTime / 1000);
1299
1300     jsint day = yearSeconds / jsint(SecondsPerDay);
1301
1302     jsint step = -1, next = 30;
1303     jsint month;
1304
1305     do {
1306         if (day <= next) {
1307             month = 0;
1308             break;
1309         }
1310         step = next;
1311         next += ((yearDays == 366) ? 29 : 28);
1312         if (day <= next) {
1313             month = 1;
1314             break;
1315         }
1316         step = next;
1317         if (day <= (next += 31)) {
1318             month = 2;
1319             break;
1320         }
1321         step = next;
1322         if (day <= (next += 30)) {
1323             month = 3;
1324             break;
1325         }
1326         step = next;
1327         if (day <= (next += 31)) {
1328             month = 4;
1329             break;
1330         }
1331         step = next;
1332         if (day <= (next += 30)) {
1333             month = 5;
1334             break;
1335         }
1336         step = next;
1337         if (day <= (next += 31)) {
1338             month = 6;
1339             break;
1340         }
1341         step = next;
1342         if (day <= (next += 31)) {
1343             month = 7;
1344             break;
1345         }
1346         step = next;
1347         if (day <= (next += 30)) {
1348             month = 8;
1349             break;
1350         }
1351         step = next;
1352         if (day <= (next += 31)) {
1353             month = 9;
1354             break;
1355         }
1356         step = next;
1357         if (day <= (next += 30)) {
1358             month = 10;
1359             break;
1360         }
1361         step = next;
1362         month = 11;
1363     } while (0);
1364
1365     obj->setSlot(JSObject::JSSLOT_DATE_LOCAL_MONTH, Int32Value(month));
1366     obj->setSlot(JSObject::JSSLOT_DATE_LOCAL_DATE, Int32Value(day - step));
1367
1368     jsint weekday = WeekDay(localTime);
1369
1370     obj->setSlot(JSObject::JSSLOT_DATE_LOCAL_DAY, Int32Value(weekday));
1371
1372     jsint seconds = yearSeconds % 60;
1373
1374     obj->setSlot(JSObject::JSSLOT_DATE_LOCAL_SECONDS, Int32Value(seconds));
1375
1376     jsint minutes = (yearSeconds / 60) % 60;
1377
1378     obj->setSlot(JSObject::JSSLOT_DATE_LOCAL_MINUTES, Int32Value(minutes));
1379
1380     jsint hours = (yearSeconds / (60 * 60)) % 24;
1381
1382     obj->setSlot(JSObject::JSSLOT_DATE_LOCAL_HOURS, Int32Value(hours));
1383
1384     return true;
1385 }
1386
1387 /* Cache the local times in obj, if necessary. */
1388 static inline JSBool
1389 GetAndCacheLocalTime(JSContext *cx, JSObject *obj, Value *vp, jsdouble *time = NULL)
1390 {
1391     if (!obj || !InstanceOf(cx, obj, &js_DateClass, vp ? vp + 2 : NULL))
1392         return false;
1393
1394     /* If the local time is undefined, we need to fill in the cached values. */
1395     if (obj->getSlot(JSObject::JSSLOT_DATE_LOCAL_TIME).isUndefined()) {
1396         if (!FillLocalTimes(cx, obj))
1397             return false;
1398     }
1399
1400     if (time)
1401         *time = obj->getSlot(JSObject::JSSLOT_DATE_LOCAL_TIME).toDouble();
1402
1403     return true;
1404 }
1405
1406 static inline bool
1407 GetThisUTCTime(JSContext *cx, Value *vp, jsdouble *dp)
1408 {
1409     JSObject *obj = ToObject(cx, &vp[1]);
1410     if (!obj)
1411         return false;
1412     return GetUTCTime(cx, obj, vp, dp);
1413 }
1414
1415 /*
1416  * See ECMA 15.9.5.4 thru 15.9.5.23
1417  */
1418 static JSBool
1419 date_getTime(JSContext *cx, uintN argc, Value *vp)
1420 {
1421     jsdouble result;
1422     if (!GetThisUTCTime(cx, vp, &result))
1423         return false;
1424     vp->setNumber(result);
1425     return true;
1426 }
1427
1428 static JSBool
1429 date_getYear(JSContext *cx, uintN argc, Value *vp)
1430 {
1431     JSObject *obj = ToObject(cx, &vp[1]);
1432     if (!obj)
1433         return false;
1434
1435     if (!GetAndCacheLocalTime(cx, obj, vp))
1436         return false;
1437
1438     Value yearVal = obj->getSlot(JSObject::JSSLOT_DATE_LOCAL_YEAR);
1439     if (yearVal.isInt32()) {
1440         /* Follow ECMA-262 to the letter, contrary to IE JScript. */
1441         jsint year = yearVal.toInt32() - 1900;
1442         vp->setInt32(year);
1443     } else {
1444         *vp = yearVal;
1445     }
1446
1447     return true;
1448 }
1449
1450 static JSBool
1451 date_getFullYear(JSContext *cx, uintN argc, Value *vp)
1452 {
1453     JSObject *obj = ToObject(cx, &vp[1]);
1454     if (!obj)
1455         return false;
1456
1457     if (!GetAndCacheLocalTime(cx, obj, vp))
1458         return JS_FALSE;
1459
1460     *vp = obj->getSlot(JSObject::JSSLOT_DATE_LOCAL_YEAR);
1461     return JS_TRUE;
1462 }
1463
1464 static JSBool
1465 date_getUTCFullYear(JSContext *cx, uintN argc, Value *vp)
1466 {
1467     jsdouble result;
1468     if (!GetThisUTCTime(cx, vp, &result))
1469         return false;
1470
1471     if (JSDOUBLE_IS_FINITE(result))
1472         result = YearFromTime(result);
1473
1474     vp->setNumber(result);
1475     return true;
1476 }
1477
1478 static JSBool
1479 date_getMonth(JSContext *cx, uintN argc, Value *vp)
1480 {
1481     JSObject *obj = ToObject(cx, &vp[1]);
1482     if (!obj)
1483         return false;
1484
1485     if (!GetAndCacheLocalTime(cx, obj, vp))
1486         return false;
1487
1488     *vp = obj->getSlot(JSObject::JSSLOT_DATE_LOCAL_MONTH);
1489     return true;
1490 }
1491
1492 static JSBool
1493 date_getUTCMonth(JSContext *cx, uintN argc, Value *vp)
1494 {
1495     jsdouble result;
1496     if (!GetThisUTCTime(cx, vp, &result))
1497         return false;
1498
1499     if (JSDOUBLE_IS_FINITE(result))
1500         result = MonthFromTime(result);
1501
1502     vp->setNumber(result);
1503     return true;
1504 }
1505
1506 static JSBool
1507 date_getDate(JSContext *cx, uintN argc, Value *vp)
1508 {
1509     JSObject *obj = ToObject(cx, &vp[1]);
1510     if (!obj)
1511         return false;
1512
1513     if (!GetAndCacheLocalTime(cx, obj, vp))
1514         return false;
1515
1516     *vp = obj->getSlot(JSObject::JSSLOT_DATE_LOCAL_DATE);
1517     return true;
1518 }
1519
1520 static JSBool
1521 date_getUTCDate(JSContext *cx, uintN argc, Value *vp)
1522 {
1523     jsdouble result;
1524     if (!GetThisUTCTime(cx, vp, &result))
1525         return false;
1526
1527     if (JSDOUBLE_IS_FINITE(result))
1528         result = DateFromTime(result);
1529
1530     vp->setNumber(result);
1531     return true;
1532 }
1533
1534 static JSBool
1535 date_getDay(JSContext *cx, uintN argc, Value *vp)
1536 {
1537     JSObject *obj = ToObject(cx, &vp[1]);
1538     if (!obj)
1539         return false;
1540
1541     if (!GetAndCacheLocalTime(cx, obj, vp))
1542         return false;
1543
1544     *vp = obj->getSlot(JSObject::JSSLOT_DATE_LOCAL_DAY);
1545     return true;
1546 }
1547
1548 static JSBool
1549 date_getUTCDay(JSContext *cx, uintN argc, Value *vp)
1550 {
1551     jsdouble result;
1552     if (!GetThisUTCTime(cx, vp, &result))
1553         return false;
1554
1555     if (JSDOUBLE_IS_FINITE(result))
1556         result = WeekDay(result);
1557
1558     vp->setNumber(result);
1559     return true;
1560 }
1561
1562 static JSBool
1563 date_getHours(JSContext *cx, uintN argc, Value *vp)
1564 {
1565     JSObject *obj = ToObject(cx, &vp[1]);
1566     if (!obj)
1567         return false;
1568
1569     if (!GetAndCacheLocalTime(cx, obj, vp))
1570         return false;
1571
1572     *vp = obj->getSlot(JSObject::JSSLOT_DATE_LOCAL_HOURS);
1573     return true;
1574 }
1575
1576 static JSBool
1577 date_getUTCHours(JSContext *cx, uintN argc, Value *vp)
1578 {
1579     jsdouble result;
1580     if (!GetThisUTCTime(cx, vp, &result))
1581         return false;
1582
1583     if (JSDOUBLE_IS_FINITE(result))
1584         result = HourFromTime(result);
1585
1586     vp->setNumber(result);
1587     return JS_TRUE;
1588 }
1589
1590 static JSBool
1591 date_getMinutes(JSContext *cx, uintN argc, Value *vp)
1592 {
1593     JSObject *obj = ToObject(cx, &vp[1]);
1594     if (!obj)
1595         return false;
1596
1597     if (!GetAndCacheLocalTime(cx, obj, vp))
1598         return false;
1599
1600     *vp = obj->getSlot(JSObject::JSSLOT_DATE_LOCAL_MINUTES);
1601     return true;
1602 }
1603
1604 static JSBool
1605 date_getUTCMinutes(JSContext *cx, uintN argc, Value *vp)
1606 {
1607     jsdouble result;
1608     if (!GetThisUTCTime(cx, vp, &result))
1609         return false;
1610
1611     if (JSDOUBLE_IS_FINITE(result))
1612         result = MinFromTime(result);
1613
1614     vp->setNumber(result);
1615     return true;
1616 }
1617
1618 /* Date.getSeconds is mapped to getUTCSeconds */
1619
1620 static JSBool
1621 date_getUTCSeconds(JSContext *cx, uintN argc, Value *vp)
1622 {
1623     JSObject *obj = ToObject(cx, &vp[1]);
1624     if (!obj)
1625         return false;
1626
1627     if (!GetAndCacheLocalTime(cx, obj, vp))
1628         return false;
1629
1630     *vp = obj->getSlot(JSObject::JSSLOT_DATE_LOCAL_SECONDS);
1631     return true;
1632 }
1633
1634 /* Date.getMilliseconds is mapped to getUTCMilliseconds */
1635
1636 static JSBool
1637 date_getUTCMilliseconds(JSContext *cx, uintN argc, Value *vp)
1638 {
1639     jsdouble result;
1640     if (!GetThisUTCTime(cx, vp, &result))
1641         return false;
1642
1643     if (JSDOUBLE_IS_FINITE(result))
1644         result = msFromTime(result);
1645
1646     vp->setNumber(result);
1647     return true;
1648 }
1649
1650 static JSBool
1651 date_getTimezoneOffset(JSContext *cx, uintN argc, Value *vp)
1652 {
1653     JSObject *obj = ToObject(cx, &vp[1]);
1654     if (!obj)
1655         return false;
1656
1657     jsdouble utctime;
1658     if (!GetUTCTime(cx, obj, vp, &utctime))
1659         return false;
1660
1661     jsdouble localtime;
1662     if (!GetAndCacheLocalTime(cx, obj, NULL, &localtime))
1663         return false;
1664
1665     /*
1666      * Return the time zone offset in minutes for the current locale that is
1667      * appropriate for this time. This value would be a constant except for
1668      * daylight savings time.
1669      */
1670     jsdouble result = (utctime - localtime) / msPerMinute;
1671     vp->setNumber(result);
1672     return true;
1673 }
1674
1675 static JSBool
1676 date_setTime(JSContext *cx, uintN argc, Value *vp)
1677 {
1678     JSObject *obj = ToObject(cx, &vp[1]);
1679     if (!obj)
1680         return false;
1681
1682     if (!InstanceOf(cx, obj, &js_DateClass, vp + 2))
1683         return false;
1684
1685     if (argc == 0) {
1686         SetDateToNaN(cx, obj, vp);
1687         return true;
1688     }
1689
1690     jsdouble result;
1691     if (!ValueToNumber(cx, vp[2], &result))
1692         return false;
1693
1694     return SetUTCTime(cx, obj, TIMECLIP(result), vp);
1695 }
1696
1697 static JSBool
1698 date_makeTime(JSContext *cx, uintN maxargs, JSBool local, uintN argc, Value *vp)
1699 {
1700     Value *argv;
1701     uintN i;
1702     jsdouble args[4], *argp, *stop;
1703     jsdouble hour, min, sec, msec;
1704     jsdouble lorutime; /* Local or UTC version of *date */
1705
1706     jsdouble msec_time;
1707     jsdouble result;
1708
1709     JSObject *obj = ToObject(cx, &vp[1]);
1710     if (!obj)
1711         return false;
1712
1713     if (!GetUTCTime(cx, obj, vp, &result))
1714         return false;
1715
1716     /* just return NaN if the date is already NaN */
1717     if (!JSDOUBLE_IS_FINITE(result)) {
1718         vp->setNumber(result);
1719         return true;
1720     }
1721
1722     /*
1723      * Satisfy the ECMA rule that if a function is called with
1724      * fewer arguments than the specified formal arguments, the
1725      * remaining arguments are set to undefined.  Seems like all
1726      * the Date.setWhatever functions in ECMA are only varargs
1727      * beyond the first argument; this should be set to undefined
1728      * if it's not given.  This means that "d = new Date();
1729      * d.setMilliseconds()" returns NaN.  Blech.
1730      */
1731     if (argc == 0) {
1732         SetDateToNaN(cx, obj, vp);
1733         return true;
1734     }
1735     if (argc > maxargs)
1736         argc = maxargs;  /* clamp argc */
1737     JS_ASSERT(argc <= 4);
1738
1739     argv = vp + 2;
1740     for (i = 0; i < argc; i++) {
1741         if (!ValueToNumber(cx, argv[i], &args[i]))
1742             return false;
1743         if (!JSDOUBLE_IS_FINITE(args[i])) {
1744             SetDateToNaN(cx, obj, vp);
1745             return true;
1746         }
1747         args[i] = js_DoubleToInteger(args[i]);
1748     }
1749
1750     if (local)
1751         lorutime = LocalTime(result, cx);
1752     else
1753         lorutime = result;
1754
1755     argp = args;
1756     stop = argp + argc;
1757     if (maxargs >= 4 && argp < stop)
1758         hour = *argp++;
1759     else
1760         hour = HourFromTime(lorutime);
1761
1762     if (maxargs >= 3 && argp < stop)
1763         min = *argp++;
1764     else
1765         min = MinFromTime(lorutime);
1766
1767     if (maxargs >= 2 && argp < stop)
1768         sec = *argp++;
1769     else
1770         sec = SecFromTime(lorutime);
1771
1772     if (maxargs >= 1 && argp < stop)
1773         msec = *argp;
1774     else
1775         msec = msFromTime(lorutime);
1776
1777     msec_time = MakeTime(hour, min, sec, msec);
1778     result = MakeDate(Day(lorutime), msec_time);
1779
1780 /*     fprintf(stderr, "%f\n", result); */
1781
1782     if (local)
1783         result = UTC(result, cx);
1784
1785 /*     fprintf(stderr, "%f\n", result); */
1786
1787     return SetUTCTime(cx, obj, TIMECLIP(result), vp);
1788 }
1789
1790 static JSBool
1791 date_setMilliseconds(JSContext *cx, uintN argc, Value *vp)
1792 {
1793     return date_makeTime(cx, 1, JS_TRUE, argc, vp);
1794 }
1795
1796 static JSBool
1797 date_setUTCMilliseconds(JSContext *cx, uintN argc, Value *vp)
1798 {
1799     return date_makeTime(cx, 1, JS_FALSE, argc, vp);
1800 }
1801
1802 static JSBool
1803 date_setSeconds(JSContext *cx, uintN argc, Value *vp)
1804 {
1805     return date_makeTime(cx, 2, JS_TRUE, argc, vp);
1806 }
1807
1808 static JSBool
1809 date_setUTCSeconds(JSContext *cx, uintN argc, Value *vp)
1810 {
1811     return date_makeTime(cx, 2, JS_FALSE, argc, vp);
1812 }
1813
1814 static JSBool
1815 date_setMinutes(JSContext *cx, uintN argc, Value *vp)
1816 {
1817     return date_makeTime(cx, 3, JS_TRUE, argc, vp);
1818 }
1819
1820 static JSBool
1821 date_setUTCMinutes(JSContext *cx, uintN argc, Value *vp)
1822 {
1823     return date_makeTime(cx, 3, JS_FALSE, argc, vp);
1824 }
1825
1826 static JSBool
1827 date_setHours(JSContext *cx, uintN argc, Value *vp)
1828 {
1829     return date_makeTime(cx, 4, JS_TRUE, argc, vp);
1830 }
1831
1832 static JSBool
1833 date_setUTCHours(JSContext *cx, uintN argc, Value *vp)
1834 {
1835     return date_makeTime(cx, 4, JS_FALSE, argc, vp);
1836 }
1837
1838 static JSBool
1839 date_makeDate(JSContext *cx, uintN maxargs, JSBool local, uintN argc, Value *vp)
1840 {
1841     Value *argv;
1842     uintN i;
1843     jsdouble lorutime; /* local or UTC version of *date */
1844     jsdouble args[3], *argp, *stop;
1845     jsdouble year, month, day;
1846     jsdouble result;
1847
1848     JSObject *obj = ToObject(cx, &vp[1]);
1849     if (!obj)
1850         return false;
1851
1852     if (!GetUTCTime(cx, obj, vp, &result))
1853         return false;
1854
1855     /* see complaint about ECMA in date_MakeTime */
1856     if (argc == 0) {
1857         SetDateToNaN(cx, obj, vp);
1858         return true;
1859     }
1860     if (argc > maxargs)
1861         argc = maxargs;   /* clamp argc */
1862     JS_ASSERT(1 <= argc && argc <= 3);
1863
1864     argv = vp + 2;
1865     for (i = 0; i < argc; i++) {
1866         if (!ValueToNumber(cx, argv[i], &args[i]))
1867             return JS_FALSE;
1868         if (!JSDOUBLE_IS_FINITE(args[i])) {
1869             SetDateToNaN(cx, obj, vp);
1870             return true;
1871         }
1872         args[i] = js_DoubleToInteger(args[i]);
1873     }
1874
1875     /* return NaN if date is NaN and we're not setting the year,
1876      * If we are, use 0 as the time. */
1877     if (!(JSDOUBLE_IS_FINITE(result))) {
1878         if (maxargs < 3) {
1879             vp->setDouble(result);
1880             return true;
1881         }
1882         lorutime = +0.;
1883     } else {
1884         lorutime = local ? LocalTime(result, cx) : result;
1885     }
1886
1887     argp = args;
1888     stop = argp + argc;
1889     if (maxargs >= 3 && argp < stop)
1890         year = *argp++;
1891     else
1892         year = YearFromTime(lorutime);
1893
1894     if (maxargs >= 2 && argp < stop)
1895         month = *argp++;
1896     else
1897         month = MonthFromTime(lorutime);
1898
1899     if (maxargs >= 1 && argp < stop)
1900         day = *argp++;
1901     else
1902         day = DateFromTime(lorutime);
1903
1904     day = MakeDay(year, month, day); /* day within year */
1905     result = MakeDate(day, TimeWithinDay(lorutime));
1906
1907     if (local)
1908         result = UTC(result, cx);
1909
1910     return SetUTCTime(cx, obj, TIMECLIP(result), vp);
1911 }
1912
1913 static JSBool
1914 date_setDate(JSContext *cx, uintN argc, Value *vp)
1915 {
1916     return date_makeDate(cx, 1, JS_TRUE, argc, vp);
1917 }
1918
1919 static JSBool
1920 date_setUTCDate(JSContext *cx, uintN argc, Value *vp)
1921 {
1922     return date_makeDate(cx, 1, JS_FALSE, argc, vp);
1923 }
1924
1925 static JSBool
1926 date_setMonth(JSContext *cx, uintN argc, Value *vp)
1927 {
1928     return date_makeDate(cx, 2, JS_TRUE, argc, vp);
1929 }
1930
1931 static JSBool
1932 date_setUTCMonth(JSContext *cx, uintN argc, Value *vp)
1933 {
1934     return date_makeDate(cx, 2, JS_FALSE, argc, vp);
1935 }
1936
1937 static JSBool
1938 date_setFullYear(JSContext *cx, uintN argc, Value *vp)
1939 {
1940     return date_makeDate(cx, 3, JS_TRUE, argc, vp);
1941 }
1942
1943 static JSBool
1944 date_setUTCFullYear(JSContext *cx, uintN argc, Value *vp)
1945 {
1946     return date_makeDate(cx, 3, JS_FALSE, argc, vp);
1947 }
1948
1949 static JSBool
1950 date_setYear(JSContext *cx, uintN argc, Value *vp)
1951 {
1952     JSObject *obj = ToObject(cx, &vp[1]);
1953     if (!obj)
1954         return false;
1955
1956     jsdouble result;
1957     if (!GetUTCTime(cx, obj, vp, &result))
1958         return false;
1959
1960     if (argc == 0) {
1961         /* Call this only after GetUTCTime has verified that obj is Date. */
1962         SetDateToNaN(cx, obj, vp);
1963         return true;
1964     }
1965
1966     jsdouble year;
1967     if (!ValueToNumber(cx, vp[2], &year))
1968         return false;
1969     if (!JSDOUBLE_IS_FINITE(year)) {
1970         SetDateToNaN(cx, obj, vp);
1971         return true;
1972     }
1973     year = js_DoubleToInteger(year);
1974     if (year >= 0 && year <= 99)
1975         year += 1900;
1976
1977     jsdouble t = JSDOUBLE_IS_FINITE(result) ? LocalTime(result, cx) : +0.0;
1978     jsdouble day = MakeDay(year, MonthFromTime(t), DateFromTime(t));
1979     result = MakeDate(day, TimeWithinDay(t));
1980     result = UTC(result, cx);
1981
1982     return SetUTCTime(cx, obj, TIMECLIP(result), vp);
1983 }
1984
1985 /* constants for toString, toUTCString */
1986 static char js_NaN_date_str[] = "Invalid Date";
1987 static const char* days[] =
1988 {
1989    "Sun","Mon","Tue","Wed","Thu","Fri","Sat"
1990 };
1991 static const char* months[] =
1992 {
1993    "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1994 };
1995
1996
1997 // Avoid dependence on PRMJ_FormatTimeUSEnglish, because it
1998 // requires a PRMJTime... which only has 16-bit years.  Sub-ECMA.
1999 static void
2000 print_gmt_string(char* buf, size_t size, jsdouble utctime)
2001 {
2002     JS_snprintf(buf, size, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
2003                 days[WeekDay(utctime)],
2004                 DateFromTime(utctime),
2005                 months[MonthFromTime(utctime)],
2006                 YearFromTime(utctime),
2007                 HourFromTime(utctime),
2008                 MinFromTime(utctime),
2009                 SecFromTime(utctime));
2010 }
2011
2012 static void
2013 print_iso_string(char* buf, size_t size, jsdouble utctime)
2014 {
2015     JS_snprintf(buf, size, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
2016                 YearFromTime(utctime),
2017                 MonthFromTime(utctime) + 1,
2018                 DateFromTime(utctime),
2019                 HourFromTime(utctime),
2020                 MinFromTime(utctime),
2021                 SecFromTime(utctime),
2022                 msFromTime(utctime));
2023 }
2024
2025 static JSBool
2026 date_utc_format(JSContext *cx, Value *vp,
2027                 void (*printFunc)(char*, size_t, jsdouble))
2028 {
2029     jsdouble utctime;
2030     if (!GetThisUTCTime(cx, vp, &utctime))
2031         return false;
2032
2033     char buf[100];
2034     if (!JSDOUBLE_IS_FINITE(utctime))
2035         JS_snprintf(buf, sizeof buf, js_NaN_date_str);
2036     else
2037         (*printFunc)(buf, sizeof buf, utctime);
2038
2039     JSString *str = JS_NewStringCopyZ(cx, buf);
2040     if (!str)
2041         return false;
2042     vp->setString(str);
2043     return true;
2044 }
2045
2046 static JSBool
2047 date_toGMTString(JSContext *cx, uintN argc, Value *vp)
2048 {
2049     return date_utc_format(cx, vp, print_gmt_string);
2050 }
2051
2052 static JSBool
2053 date_toISOString(JSContext *cx, uintN argc, Value *vp)
2054 {
2055     return date_utc_format(cx, vp, print_iso_string);
2056 }
2057
2058 /* ES5 15.9.5.44. */
2059 static JSBool
2060 date_toJSON(JSContext *cx, uintN argc, Value *vp)
2061 {
2062     /* Step 1. */
2063     JSObject *obj = ToObject(cx, &vp[1]);
2064     if (!obj)
2065         return false;
2066
2067     /* Step 2. */
2068     Value &tv = vp[0];
2069     if (!DefaultValue(cx, obj, JSTYPE_NUMBER, &tv))
2070         return false;
2071
2072     /* Step 3. */
2073     if (tv.isDouble() && !JSDOUBLE_IS_FINITE(tv.toDouble())) {
2074         vp->setNull();
2075         return true;
2076     }
2077
2078     /* Step 4. */
2079     Value &toISO = vp[0];
2080     if (!obj->getProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.toISOStringAtom), &toISO))
2081         return false;
2082
2083     /* Step 5. */
2084     if (!js_IsCallable(toISO)) {
2085         JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, NULL,
2086                                      JSMSG_BAD_TOISOSTRING_PROP);
2087         return false;
2088     }
2089
2090     /* Step 6. */
2091     LeaveTrace(cx);
2092     InvokeArgsGuard args;
2093     if (!cx->stack().pushInvokeArgs(cx, 0, &args))
2094         return false;
2095
2096     args.callee() = toISO;
2097     args.thisv().setObject(*obj);
2098
2099     if (!Invoke(cx, args, 0))
2100         return false;
2101     *vp = args.rval();
2102     return true;
2103 }
2104
2105 /* for Date.toLocaleString; interface to PRMJTime date struct.
2106  */
2107 static void
2108 new_explode(jsdouble timeval, PRMJTime *split, JSContext *cx)
2109 {
2110     jsint year = YearFromTime(timeval);
2111
2112     split->tm_usec = (int32) msFromTime(timeval) * 1000;
2113     split->tm_sec = (int8) SecFromTime(timeval);
2114     split->tm_min = (int8) MinFromTime(timeval);
2115     split->tm_hour = (int8) HourFromTime(timeval);
2116     split->tm_mday = (int8) DateFromTime(timeval);
2117     split->tm_mon = (int8) MonthFromTime(timeval);
2118     split->tm_wday = (int8) WeekDay(timeval);
2119     split->tm_year = year;
2120     split->tm_yday = (int16) DayWithinYear(timeval, year);
2121
2122     /* not sure how this affects things, but it doesn't seem
2123        to matter. */
2124     split->tm_isdst = (DaylightSavingTA(timeval, cx) != 0);
2125 }
2126
2127 typedef enum formatspec {
2128     FORMATSPEC_FULL, FORMATSPEC_DATE, FORMATSPEC_TIME
2129 } formatspec;
2130
2131 /* helper function */
2132 static JSBool
2133 date_format(JSContext *cx, jsdouble date, formatspec format, Value *rval)
2134 {
2135     char buf[100];
2136     JSString *str;
2137     char tzbuf[100];
2138     JSBool usetz;
2139     size_t i, tzlen;
2140     PRMJTime split;
2141
2142     if (!JSDOUBLE_IS_FINITE(date)) {
2143         JS_snprintf(buf, sizeof buf, js_NaN_date_str);
2144     } else {
2145         jsdouble local = LocalTime(date, cx);
2146
2147         /* offset from GMT in minutes.  The offset includes daylight savings,
2148            if it applies. */
2149         jsint minutes = (jsint) floor(AdjustTime(date, cx) / msPerMinute);
2150
2151         /* map 510 minutes to 0830 hours */
2152         intN offset = (minutes / 60) * 100 + minutes % 60;
2153
2154         /* print as "Wed Nov 05 19:38:03 GMT-0800 (PST) 1997" The TZA is
2155          * printed as 'GMT-0800' rather than as 'PST' to avoid
2156          * operating-system dependence on strftime (which
2157          * PRMJ_FormatTimeUSEnglish calls, for %Z only.)  win32 prints
2158          * PST as 'Pacific Standard Time.'  This way we always know
2159          * what we're getting, and can parse it if we produce it.
2160          * The OS TZA string is included as a comment.
2161          */
2162
2163         /* get a timezone string from the OS to include as a
2164            comment. */
2165         new_explode(date, &split, cx);
2166         if (PRMJ_FormatTime(tzbuf, sizeof tzbuf, "(%Z)", &split) != 0) {
2167
2168             /* Decide whether to use the resulting timezone string.
2169              *
2170              * Reject it if it contains any non-ASCII, non-alphanumeric
2171              * characters.  It's then likely in some other character
2172              * encoding, and we probably won't display it correctly.
2173              */
2174             usetz = JS_TRUE;
2175             tzlen = strlen(tzbuf);
2176             if (tzlen > 100) {
2177                 usetz = JS_FALSE;
2178             } else {
2179                 for (i = 0; i < tzlen; i++) {
2180                     jschar c = tzbuf[i];
2181                     if (c > 127 ||
2182                         !(isalpha(c) || isdigit(c) ||
2183                           c == ' ' || c == '(' || c == ')')) {
2184                         usetz = JS_FALSE;
2185                     }
2186                 }
2187             }
2188
2189             /* Also reject it if it's not parenthesized or if it's '()'. */
2190             if (tzbuf[0] != '(' || tzbuf[1] == ')')
2191                 usetz = JS_FALSE;
2192         } else
2193             usetz = JS_FALSE;
2194
2195         switch (format) {
2196           case FORMATSPEC_FULL:
2197             /*
2198              * Avoid dependence on PRMJ_FormatTimeUSEnglish, because it
2199              * requires a PRMJTime... which only has 16-bit years.  Sub-ECMA.
2200              */
2201             /* Tue Oct 31 2000 09:41:40 GMT-0800 (PST) */
2202             JS_snprintf(buf, sizeof buf,
2203                         "%s %s %.2d %.4d %.2d:%.2d:%.2d GMT%+.4d%s%s",
2204                         days[WeekDay(local)],
2205                         months[MonthFromTime(local)],
2206                         DateFromTime(local),
2207                         YearFromTime(local),
2208                         HourFromTime(local),
2209                         MinFromTime(local),
2210                         SecFromTime(local),
2211                         offset,
2212                         usetz ? " " : "",
2213                         usetz ? tzbuf : "");
2214             break;
2215           case FORMATSPEC_DATE:
2216             /* Tue Oct 31 2000 */
2217             JS_snprintf(buf, sizeof buf,
2218                         "%s %s %.2d %.4d",
2219                         days[WeekDay(local)],
2220                         months[MonthFromTime(local)],
2221                         DateFromTime(local),
2222                         YearFromTime(local));
2223             break;
2224           case FORMATSPEC_TIME:
2225             /* 09:41:40 GMT-0800 (PST) */
2226             JS_snprintf(buf, sizeof buf,
2227                         "%.2d:%.2d:%.2d GMT%+.4d%s%s",
2228                         HourFromTime(local),
2229                         MinFromTime(local),
2230                         SecFromTime(local),
2231                         offset,
2232                         usetz ? " " : "",
2233                         usetz ? tzbuf : "");
2234             break;
2235         }
2236     }
2237
2238     str = JS_NewStringCopyZ(cx, buf);
2239     if (!str)
2240         return JS_FALSE;
2241     rval->setString(str);
2242     return JS_TRUE;
2243 }
2244
2245 static JSBool
2246 date_toLocaleHelper(JSContext *cx, JSObject *obj, const char *format, Value *vp)
2247 {
2248     char buf[100];
2249     JSString *str;
2250     PRMJTime split;
2251     jsdouble utctime;
2252
2253     if (!GetUTCTime(cx, obj, vp, &utctime))
2254         return false;
2255
2256     if (!JSDOUBLE_IS_FINITE(utctime)) {
2257         JS_snprintf(buf, sizeof buf, js_NaN_date_str);
2258     } else {
2259         intN result_len;
2260         jsdouble local = LocalTime(utctime, cx);
2261         new_explode(local, &split, cx);
2262
2263         /* let PRMJTime format it.       */
2264         result_len = PRMJ_FormatTime(buf, sizeof buf, format, &split);
2265
2266         /* If it failed, default to toString. */
2267         if (result_len == 0)
2268             return date_format(cx, utctime, FORMATSPEC_FULL, vp);
2269
2270         /* Hacked check against undesired 2-digit year 00/00/00 form. */
2271         if (strcmp(format, "%x") == 0 && result_len >= 6 &&
2272             /* Format %x means use OS settings, which may have 2-digit yr, so
2273                hack end of 3/11/22 or 11.03.22 or 11Mar22 to use 4-digit yr...*/
2274             !isdigit(buf[result_len - 3]) &&
2275             isdigit(buf[result_len - 2]) && isdigit(buf[result_len - 1]) &&
2276             /* ...but not if starts with 4-digit year, like 2022/3/11. */
2277             !(isdigit(buf[0]) && isdigit(buf[1]) &&
2278               isdigit(buf[2]) && isdigit(buf[3]))) {
2279             JS_snprintf(buf + (result_len - 2), (sizeof buf) - (result_len - 2),
2280                         "%d", js_DateGetYear(cx, obj));
2281         }
2282
2283     }
2284
2285     if (cx->localeCallbacks && cx->localeCallbacks->localeToUnicode)
2286         return cx->localeCallbacks->localeToUnicode(cx, buf, Jsvalify(vp));
2287
2288     str = JS_NewStringCopyZ(cx, buf);
2289     if (!str)
2290         return false;
2291     vp->setString(str);
2292     return true;
2293 }
2294
2295 static JSBool
2296 date_toLocaleString(JSContext *cx, uintN argc, Value *vp)
2297 {
2298     JSObject *obj = ToObject(cx, &vp[1]);
2299     if (!obj)
2300         return false;
2301
2302     /*
2303      * Use '%#c' for windows, because '%c' is backward-compatible and non-y2k
2304      * with msvc; '%#c' requests that a full year be used in the result string.
2305      */
2306     return date_toLocaleHelper(cx, obj,
2307 #if defined(_WIN32) && !defined(__MWERKS__)
2308                                    "%#c"
2309 #else
2310                                    "%c"
2311 #endif
2312                                , vp);
2313 }
2314
2315 static JSBool
2316 date_toLocaleDateString(JSContext *cx, uintN argc, Value *vp)
2317 {
2318     JSObject *obj = ToObject(cx, &vp[1]);
2319     if (!obj)
2320         return false;
2321
2322     /*
2323      * Use '%#x' for windows, because '%x' is backward-compatible and non-y2k
2324      * with msvc; '%#x' requests that a full year be used in the result string.
2325      */
2326     return date_toLocaleHelper(cx, obj,
2327 #if defined(_WIN32) && !defined(__MWERKS__)
2328                                    "%#x"
2329 #else
2330                                    "%x"
2331 #endif
2332                                , vp);
2333 }
2334
2335 static JSBool
2336 date_toLocaleTimeString(JSContext *cx, uintN argc, Value *vp)
2337 {
2338     JSObject *obj = ToObject(cx, &vp[1]);
2339     if (!obj)
2340         return false;
2341
2342     return date_toLocaleHelper(cx, obj, "%X", vp);
2343 }
2344
2345 static JSBool
2346 date_toLocaleFormat(JSContext *cx, uintN argc, Value *vp)
2347 {
2348     if (argc == 0)
2349         return date_toLocaleString(cx, argc, vp);
2350
2351     JSObject *obj = ToObject(cx, &vp[1]);
2352     if (!obj)
2353         return false;
2354
2355     JSString *fmt = js_ValueToString(cx, vp[2]);
2356     if (!fmt)
2357         return false;
2358     vp[2].setString(fmt);
2359     JSAutoByteString fmtbytes(cx, fmt);
2360     if (!fmtbytes)
2361         return false;
2362
2363     return date_toLocaleHelper(cx, obj, fmtbytes.ptr(), vp);
2364 }
2365
2366 static JSBool
2367 date_toTimeString(JSContext *cx, uintN argc, Value *vp)
2368 {
2369     jsdouble utctime;
2370     if (!GetThisUTCTime(cx, vp, &utctime))
2371         return false;
2372     return date_format(cx, utctime, FORMATSPEC_TIME, vp);
2373 }
2374
2375 static JSBool
2376 date_toDateString(JSContext *cx, uintN argc, Value *vp)
2377 {
2378     jsdouble utctime;
2379     if (!GetThisUTCTime(cx, vp, &utctime))
2380         return false;
2381     return date_format(cx, utctime, FORMATSPEC_DATE, vp);
2382 }
2383
2384 #if JS_HAS_TOSOURCE
2385 #include <string.h>
2386 #include "jsnum.h"
2387
2388 static JSBool
2389 date_toSource(JSContext *cx, uintN argc, Value *vp)
2390 {
2391     jsdouble utctime;
2392     if (!GetThisUTCTime(cx, vp, &utctime))
2393         return false;
2394
2395     ToCStringBuf cbuf;
2396     char *numStr = NumberToCString(cx, &cbuf, utctime);
2397     if (!numStr) {
2398         JS_ReportOutOfMemory(cx);
2399         return false;
2400     }
2401
2402     char *bytes = JS_smprintf("(new %s(%s))", js_Date_str, numStr);
2403     if (!bytes) {
2404         JS_ReportOutOfMemory(cx);
2405         return false;
2406     }
2407
2408     JSString *str = JS_NewStringCopyZ(cx, bytes);
2409     js_free(bytes);
2410     if (!str)
2411         return false;
2412     vp->setString(str);
2413     return true;
2414 }
2415 #endif
2416
2417 static JSBool
2418 date_toString(JSContext *cx, uintN argc, Value *vp)
2419 {
2420     jsdouble utctime;
2421     if (!GetThisUTCTime(cx, vp, &utctime))
2422         return false;
2423
2424     return date_format(cx, utctime, FORMATSPEC_FULL, vp);
2425 }
2426
2427 static JSBool
2428 date_valueOf(JSContext *cx, uintN argc, Value *vp)
2429 {
2430     /*
2431      * It is an error to call date_valueOf on a non-date object, but we don't
2432      * need to check for that explicitly here because every path calls
2433      * GetUTCTime, which does the check.
2434      */
2435
2436     /* If called directly with no arguments, convert to a time number. */
2437     if (argc == 0)
2438         return date_getTime(cx, argc, vp);
2439
2440     /* Verify this before extracting a string from the first argument. */
2441     JSObject *obj = ToObject(cx, &vp[1]);
2442     if (!obj)
2443         return false;
2444
2445     /* Convert to number only if the hint was given, otherwise favor string. */
2446     JSString *str = js_ValueToString(cx, vp[2]);
2447     if (!str)
2448         return false;
2449     JSLinearString *linear_str = str->ensureLinear(cx);
2450     if (!linear_str)
2451         return false;
2452     JSAtom *number_str = cx->runtime->atomState.typeAtoms[JSTYPE_NUMBER];
2453     if (EqualStrings(linear_str, number_str))
2454         return date_getTime(cx, argc, vp);
2455     return date_toString(cx, argc, vp);
2456 }
2457
2458 // Don't really need an argument here, but we don't support arg-less builtins
2459 JS_DEFINE_TRCINFO_1(date_now,
2460     (1, (static, DOUBLE, date_now_tn, CONTEXT, 0, nanojit::ACCSET_STORE_ANY)))
2461
2462 static JSFunctionSpec date_static_methods[] = {
2463     JS_FN("UTC",                 date_UTC,                MAXARGS,0),
2464     JS_FN("parse",               date_parse,              1,0),
2465     JS_TN("now",                 date_now,                0,0, &date_now_trcinfo),
2466     JS_FS_END
2467 };
2468
2469 static JSFunctionSpec date_methods[] = {
2470     JS_FN("getTime",             date_getTime,            0,0),
2471     JS_FN("getTimezoneOffset",   date_getTimezoneOffset,  0,0),
2472     JS_FN("getYear",             date_getYear,            0,0),
2473     JS_FN("getFullYear",         date_getFullYear,        0,0),
2474     JS_FN("getUTCFullYear",      date_getUTCFullYear,     0,0),
2475     JS_FN("getMonth",            date_getMonth,           0,0),
2476     JS_FN("getUTCMonth",         date_getUTCMonth,        0,0),
2477     JS_FN("getDate",             date_getDate,            0,0),
2478     JS_FN("getUTCDate",          date_getUTCDate,         0,0),
2479     JS_FN("getDay",              date_getDay,             0,0),
2480     JS_FN("getUTCDay",           date_getUTCDay,          0,0),
2481     JS_FN("getHours",            date_getHours,           0,0),
2482     JS_FN("getUTCHours",         date_getUTCHours,        0,0),
2483     JS_FN("getMinutes",          date_getMinutes,         0,0),
2484     JS_FN("getUTCMinutes",       date_getUTCMinutes,      0,0),
2485     JS_FN("getSeconds",          date_getUTCSeconds,      0,0),
2486     JS_FN("getUTCSeconds",       date_getUTCSeconds,      0,0),
2487     JS_FN("getMilliseconds",     date_getUTCMilliseconds, 0,0),
2488     JS_FN("getUTCMilliseconds",  date_getUTCMilliseconds, 0,0),
2489     JS_FN("setTime",             date_setTime,            1,0),
2490     JS_FN("setYear",             date_setYear,            1,0),
2491     JS_FN("setFullYear",         date_setFullYear,        3,0),
2492     JS_FN("setUTCFullYear",      date_setUTCFullYear,     3,0),
2493     JS_FN("setMonth",            date_setMonth,           2,0),
2494     JS_FN("setUTCMonth",         date_setUTCMonth,        2,0),
2495     JS_FN("setDate",             date_setDate,            1,0),
2496     JS_FN("setUTCDate",          date_setUTCDate,         1,0),
2497     JS_FN("setHours",            date_setHours,           4,0),
2498     JS_FN("setUTCHours",         date_setUTCHours,        4,0),
2499     JS_FN("setMinutes",          date_setMinutes,         3,0),
2500     JS_FN("setUTCMinutes",       date_setUTCMinutes,      3,0),
2501     JS_FN("setSeconds",          date_setSeconds,         2,0),
2502     JS_FN("setUTCSeconds",       date_setUTCSeconds,      2,0),
2503     JS_FN("setMilliseconds",     date_setMilliseconds,    1,0),
2504     JS_FN("setUTCMilliseconds",  date_setUTCMilliseconds, 1,0),
2505     JS_FN("toUTCString",         date_toGMTString,        0,0),
2506     JS_FN(js_toLocaleString_str, date_toLocaleString,     0,0),
2507     JS_FN("toLocaleDateString",  date_toLocaleDateString, 0,0),
2508     JS_FN("toLocaleTimeString",  date_toLocaleTimeString, 0,0),
2509     JS_FN("toLocaleFormat",      date_toLocaleFormat,     0,0),
2510     JS_FN("toDateString",        date_toDateString,       0,0),
2511     JS_FN("toTimeString",        date_toTimeString,       0,0),
2512     JS_FN("toISOString",         date_toISOString,        0,0),
2513     JS_FN(js_toJSON_str,         date_toJSON,             1,0),
2514 #if JS_HAS_TOSOURCE
2515     JS_FN(js_toSource_str,       date_toSource,           0,0),
2516 #endif
2517     JS_FN(js_toString_str,       date_toString,           0,0),
2518     JS_FN(js_valueOf_str,        date_valueOf,            0,0),
2519     JS_FS_END
2520 };
2521
2522 JSBool
2523 js_Date(JSContext *cx, uintN argc, Value *vp)
2524 {
2525     /* Date called as function. */
2526     if (!IsConstructing(vp))
2527         return date_format(cx, NowAsMillis(), FORMATSPEC_FULL, vp);
2528
2529     Value *argv = vp + 2;
2530
2531     /* Date called as constructor. */
2532     jsdouble d;
2533     if (argc == 0) {
2534         d = NowAsMillis();
2535     } else if (argc == 1) {
2536         if (!argv[0].isString()) {
2537             /* the argument is a millisecond number */
2538             if (!ValueToNumber(cx, argv[0], &d))
2539                 return false;
2540             d = TIMECLIP(d);
2541         } else {
2542             /* the argument is a string; parse it. */
2543             JSString *str = js_ValueToString(cx, argv[0]);
2544             if (!str)
2545                 return false;
2546             argv[0].setString(str);
2547             JSLinearString *linearStr = str->ensureLinear(cx);
2548             if (!linearStr)
2549                 return false;
2550
2551             if (!date_parseString(linearStr, &d, cx))
2552                 d = js_NaN;
2553             else
2554                 d = TIMECLIP(d);
2555         }
2556     } else {
2557         jsdouble msec_time;
2558         if (!date_msecFromArgs(cx, argc, argv, &msec_time))
2559             return false;
2560
2561         if (JSDOUBLE_IS_FINITE(msec_time)) {
2562             msec_time = UTC(msec_time, cx);
2563             msec_time = TIMECLIP(msec_time);
2564         }
2565         d = msec_time;
2566     }
2567
2568     JSObject *obj = js_NewDateObjectMsec(cx, d);
2569     if (!obj)
2570         return false;
2571     vp->setObject(*obj);
2572
2573     return true;
2574 }
2575
2576 JSObject *
2577 js_InitDateClass(JSContext *cx, JSObject *obj)
2578 {
2579     /* set static LocalTZA */
2580     LocalTZA = -(PRMJ_LocalGMTDifference() * msPerSecond);
2581     JSObject *proto = js_InitClass(cx, obj, NULL, &js_DateClass, js_Date, MAXARGS,
2582                                    NULL, date_methods, NULL, date_static_methods);
2583     if (!proto)
2584         return NULL;
2585
2586     AutoObjectRooter tvr(cx, proto);
2587
2588     SetDateToNaN(cx, proto);
2589
2590     /*
2591      * ES5 B.2.6:
2592      *   The Function object that is the initial value of
2593      *   Date.prototype.toGMTString is the same Function
2594      *   object that is the initial value of
2595      *   Date.prototype.toUTCString.
2596      */
2597     AutoValueRooter toUTCStringFun(cx);
2598     jsid toUTCStringId = ATOM_TO_JSID(cx->runtime->atomState.toUTCStringAtom);
2599     jsid toGMTStringId = ATOM_TO_JSID(cx->runtime->atomState.toGMTStringAtom);
2600     if (!js_GetProperty(cx, proto, toUTCStringId, toUTCStringFun.addr()) ||
2601         !js_DefineProperty(cx, proto, toGMTStringId, toUTCStringFun.addr(),
2602                            PropertyStub, StrictPropertyStub, 0)) {
2603         return NULL;
2604     }
2605
2606     return proto;
2607 }
2608
2609 JS_FRIEND_API(JSObject *)
2610 js_NewDateObjectMsec(JSContext *cx, jsdouble msec_time)
2611 {
2612     JSObject *obj = NewBuiltinClassInstance(cx, &js_DateClass);
2613     if (!obj || !obj->ensureSlots(cx, JSObject::DATE_CLASS_RESERVED_SLOTS))
2614         return NULL;
2615     if (!SetUTCTime(cx, obj, msec_time))
2616         return NULL;
2617     return obj;
2618 }
2619
2620 JS_FRIEND_API(JSObject *)
2621 js_NewDateObject(JSContext* cx, int year, int mon, int mday,
2622                  int hour, int min, int sec)
2623 {
2624     JSObject *obj;
2625     jsdouble msec_time;
2626
2627     JS_ASSERT(mon < 12);
2628     msec_time = date_msecFromDate(year, mon, mday, hour, min, sec, 0);
2629     obj = js_NewDateObjectMsec(cx, UTC(msec_time, cx));
2630     return obj;
2631 }
2632
2633 JS_FRIEND_API(JSBool)
2634 js_DateIsValid(JSContext *cx, JSObject* obj)
2635 {
2636     jsdouble utctime;
2637     return GetUTCTime(cx, obj, NULL, &utctime) && !JSDOUBLE_IS_NaN(utctime);
2638 }
2639
2640 JS_FRIEND_API(int)
2641 js_DateGetYear(JSContext *cx, JSObject* obj)
2642 {
2643     jsdouble localtime;
2644
2645     /* Preserve legacy API behavior of returning 0 for invalid dates. */
2646     if (!GetAndCacheLocalTime(cx, obj, NULL, &localtime) ||
2647         JSDOUBLE_IS_NaN(localtime)) {
2648         return 0;
2649     }
2650
2651     return (int) YearFromTime(localtime);
2652 }
2653
2654 JS_FRIEND_API(int)
2655 js_DateGetMonth(JSContext *cx, JSObject* obj)
2656 {
2657     jsdouble localtime;
2658
2659     if (!GetAndCacheLocalTime(cx, obj, NULL, &localtime) ||
2660         JSDOUBLE_IS_NaN(localtime)) {
2661         return 0;
2662     }
2663
2664     return (int) MonthFromTime(localtime);
2665 }
2666
2667 JS_FRIEND_API(int)
2668 js_DateGetDate(JSContext *cx, JSObject* obj)
2669 {
2670     jsdouble localtime;
2671
2672     if (!GetAndCacheLocalTime(cx, obj, NULL, &localtime) ||
2673         JSDOUBLE_IS_NaN(localtime)) {
2674         return 0;
2675     }
2676
2677     return (int) DateFromTime(localtime);
2678 }
2679
2680 JS_FRIEND_API(int)
2681 js_DateGetHours(JSContext *cx, JSObject* obj)
2682 {
2683     jsdouble localtime;
2684
2685     if (!GetAndCacheLocalTime(cx, obj, NULL, &localtime) ||
2686         JSDOUBLE_IS_NaN(localtime)) {
2687         return 0;
2688     }
2689
2690     return (int) HourFromTime(localtime);
2691 }
2692
2693 JS_FRIEND_API(int)
2694 js_DateGetMinutes(JSContext *cx, JSObject* obj)
2695 {
2696     jsdouble localtime;
2697
2698     if (!GetAndCacheLocalTime(cx, obj, NULL, &localtime) ||
2699         JSDOUBLE_IS_NaN(localtime)) {
2700         return 0;
2701     }
2702
2703     return (int) MinFromTime(localtime);
2704 }
2705
2706 JS_FRIEND_API(int)
2707 js_DateGetSeconds(JSContext *cx, JSObject* obj)
2708 {
2709     jsdouble utctime;
2710
2711     if (!GetUTCTime(cx, obj, NULL, &utctime) || JSDOUBLE_IS_NaN(utctime))
2712         return 0;
2713
2714     return (int) SecFromTime(utctime);
2715 }
2716
2717 JS_FRIEND_API(jsdouble)
2718 js_DateGetMsecSinceEpoch(JSContext *cx, JSObject *obj)
2719 {
2720     jsdouble utctime;
2721     if (!GetUTCTime(cx, obj, NULL, &utctime))
2722         return 0;
2723     return utctime;
2724 }
2725
2726 #ifdef JS_THREADSAFE
2727 #include "prinrval.h"
2728
2729 JS_FRIEND_API(uint32)
2730 js_IntervalNow()
2731 {
2732     return uint32(PR_IntervalToMilliseconds(PR_IntervalNow()));
2733 }
2734
2735 #else /* !JS_THREADSAFE */
2736
2737 JS_FRIEND_API(uint32)
2738 js_IntervalNow()
2739 {
2740     return uint32(PRMJ_Now() / PRMJ_USEC_PER_MSEC);
2741 }
2742 #endif