1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=8 sts=4 et sw=4 tw=99:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /*
8  * JS date methods.
9  *
10  * "For example, OS/360 devotes 26 bytes of the permanently
11  *  resident date-turnover routine to the proper handling of
12  *  December 31 on leap years (when it is Day 366).  That
13  *  might have been left to the operator."
14  *
15  * Frederick Brooks, 'The Second-System Effect'.
16  */
17 
18 #include "jsdate.h"
19 
20 #include "mozilla/ArrayUtils.h"
21 #include "mozilla/FloatingPoint.h"
22 #include "mozilla/Sprintf.h"
23 
24 #include <ctype.h>
25 #include <math.h>
26 #include <string.h>
27 
28 #include "jsapi.h"
29 #include "jscntxt.h"
30 #include "jsnum.h"
31 #include "jsobj.h"
32 #include "jsprf.h"
33 #include "jsstr.h"
34 #include "jstypes.h"
35 #include "jsutil.h"
36 #include "jswrapper.h"
37 
38 #include "js/Conversions.h"
39 #include "js/Date.h"
40 #include "vm/DateTime.h"
41 #include "vm/GlobalObject.h"
42 #include "vm/Interpreter.h"
43 #include "vm/String.h"
44 #include "vm/StringBuffer.h"
45 #include "vm/Time.h"
46 
47 #include "jsobjinlines.h"
48 
49 using namespace js;
50 
51 using mozilla::ArrayLength;
52 using mozilla::IsFinite;
53 using mozilla::IsNaN;
54 using mozilla::NumbersAreIdentical;
55 
56 using JS::AutoCheckCannotGC;
57 using JS::ClippedTime;
58 using JS::GenericNaN;
59 using JS::TimeClip;
60 using JS::ToInteger;
61 
62 /*
63  * The JS 'Date' object is patterned after the Java 'Date' object.
64  * Here is a script:
65  *
66  *    today = new Date();
67  *
68  *    print(today.toLocaleString());
69  *
70  *    weekDay = today.getDay();
71  *
72  *
73  * These Java (and ECMA-262) methods are supported:
74  *
75  *     UTC
76  *     getDate (getUTCDate)
77  *     getDay (getUTCDay)
78  *     getHours (getUTCHours)
79  *     getMinutes (getUTCMinutes)
80  *     getMonth (getUTCMonth)
81  *     getSeconds (getUTCSeconds)
82  *     getMilliseconds (getUTCMilliseconds)
83  *     getTime
84  *     getTimezoneOffset
85  *     getYear
86  *     getFullYear (getUTCFullYear)
87  *     parse
88  *     setDate (setUTCDate)
89  *     setHours (setUTCHours)
90  *     setMinutes (setUTCMinutes)
91  *     setMonth (setUTCMonth)
92  *     setSeconds (setUTCSeconds)
93  *     setMilliseconds (setUTCMilliseconds)
94  *     setTime
95  *     setYear (setFullYear, setUTCFullYear)
96  *     toGMTString (toUTCString)
97  *     toLocaleString
98  *     toString
99  *
100  *
101  * These Java methods are not supported
102  *
103  *     setDay
104  *     before
105  *     after
106  *     equals
107  *     hashCode
108  */
109 
110 static inline double
111 Day(double t)
112 {
113     return floor(t / msPerDay);
114 }
115 
116 static double
117 TimeWithinDay(double t)
118 {
119     double result = fmod(t, msPerDay);
120     if (result < 0)
121         result += msPerDay;
122     return result;
123 }
124 
125 /* ES5 15.9.1.3. */
126 static inline bool
127 IsLeapYear(double year)
128 {
129     MOZ_ASSERT(ToInteger(year) == year);
130     return fmod(year, 4) == 0 && (fmod(year, 100) != 0 || fmod(year, 400) == 0);
131 }
132 
133 static inline double
134 DaysInYear(double year)
135 {
136     if (!IsFinite(year))
137         return GenericNaN();
138     return IsLeapYear(year) ? 366 : 365;
139 }
140 
141 static inline double
142 DayFromYear(double y)
143 {
144     return 365 * (y - 1970) +
145            floor((y - 1969) / 4.0) -
146            floor((y - 1901) / 100.0) +
147            floor((y - 1601) / 400.0);
148 }
149 
150 static inline double
151 TimeFromYear(double y)
152 {
153     return DayFromYear(y) * msPerDay;
154 }
155 
156 static double
157 YearFromTime(double t)
158 {
159     if (!IsFinite(t))
160         return GenericNaN();
161 
162     MOZ_ASSERT(ToInteger(t) == t);
163 
164     double y = floor(t / (msPerDay * 365.2425)) + 1970;
165     double t2 = TimeFromYear(y);
166 
167     /*
168      * Adjust the year if the approximation was wrong.  Since the year was
169      * computed using the average number of ms per year, it will usually
170      * be wrong for dates within several hours of a year transition.
171      */
172     if (t2 > t) {
173         y--;
174     } else {
175         if (t2 + msPerDay * DaysInYear(y) <= t)
176             y++;
177     }
178     return y;
179 }
180 
181 static inline int
182 DaysInFebruary(double year)
183 {
184     return IsLeapYear(year) ? 29 : 28;
185 }
186 
187 /* ES5 15.9.1.4. */
188 static inline double
189 DayWithinYear(double t, double year)
190 {
191     MOZ_ASSERT_IF(IsFinite(t), YearFromTime(t) == year);
192     return Day(t) - DayFromYear(year);
193 }
194 
195 static double
196 MonthFromTime(double t)
197 {
198     if (!IsFinite(t))
199         return GenericNaN();
200 
201     double year = YearFromTime(t);
202     double d = DayWithinYear(t, year);
203 
204     int step;
205     if (d < (step = 31))
206         return 0;
207     if (d < (step += DaysInFebruary(year)))
208         return 1;
209     if (d < (step += 31))
210         return 2;
211     if (d < (step += 30))
212         return 3;
213     if (d < (step += 31))
214         return 4;
215     if (d < (step += 30))
216         return 5;
217     if (d < (step += 31))
218         return 6;
219     if (d < (step += 31))
220         return 7;
221     if (d < (step += 30))
222         return 8;
223     if (d < (step += 31))
224         return 9;
225     if (d < (step += 30))
226         return 10;
227     return 11;
228 }
229 
230 /* ES5 15.9.1.5. */
231 static double
232 DateFromTime(double t)
233 {
234     if (!IsFinite(t))
235         return GenericNaN();
236 
237     double year = YearFromTime(t);
238     double d = DayWithinYear(t, year);
239 
240     int next;
241     if (d <= (next = 30))
242         return d + 1;
243     int step = next;
244     if (d <= (next += DaysInFebruary(year)))
245         return d - step;
246     step = next;
247     if (d <= (next += 31))
248         return d - step;
249     step = next;
250     if (d <= (next += 30))
251         return d - step;
252     step = next;
253     if (d <= (next += 31))
254         return d - step;
255     step = next;
256     if (d <= (next += 30))
257         return d - step;
258     step = next;
259     if (d <= (next += 31))
260         return d - step;
261     step = next;
262     if (d <= (next += 31))
263         return d - step;
264     step = next;
265     if (d <= (next += 30))
266         return d - step;
267     step = next;
268     if (d <= (next += 31))
269         return d - step;
270     step = next;
271     if (d <= (next += 30))
272         return d - step;
273     step = next;
274     return d - step;
275 }
276 
277 /* ES5 15.9.1.6. */
278 static int
279 WeekDay(double t)
280 {
281     /*
282      * We can't assert TimeClip(t) == t because we call this function with
283      * local times, which can be offset outside TimeClip's permitted range.
284      */
285     MOZ_ASSERT(ToInteger(t) == t);
286     int result = (int(Day(t)) + 4) % 7;
287     if (result < 0)
288         result += 7;
289     return result;
290 }
291 
292 static inline int
293 DayFromMonth(int month, bool isLeapYear)
294 {
295     /*
296      * The following array contains the day of year for the first day of
297      * each month, where index 0 is January, and day 0 is January 1.
298      */
299     static const int firstDayOfMonth[2][13] = {
300         {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
301         {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}
302     };
303 
304     MOZ_ASSERT(0 <= month && month <= 12);
305     return firstDayOfMonth[isLeapYear][month];
306 }
307 
308 template<typename T>
309 static inline int
310 DayFromMonth(T month, bool isLeapYear) = delete;
311 
312 /* ES5 15.9.1.12 (out of order to accommodate DaylightSavingTA). */
313 static double
314 MakeDay(double year, double month, double date)
315 {
316     /* Step 1. */
317     if (!IsFinite(year) || !IsFinite(month) || !IsFinite(date))
318         return GenericNaN();
319 
320     /* Steps 2-4. */
321     double y = ToInteger(year);
322     double m = ToInteger(month);
323     double dt = ToInteger(date);
324 
325     /* Step 5. */
326     double ym = y + floor(m / 12);
327 
328     /* Step 6. */
329     int mn = int(fmod(m, 12.0));
330     if (mn < 0)
331         mn += 12;
332 
333     /* Steps 7-8. */
334     bool leap = IsLeapYear(ym);
335 
336     double yearday = floor(TimeFromYear(ym) / msPerDay);
337     double monthday = DayFromMonth(mn, leap);
338 
339     return yearday + monthday + dt - 1;
340 }
341 
342 /* ES5 15.9.1.13 (out of order to accommodate DaylightSavingTA). */
343 static inline double
344 MakeDate(double day, double time)
345 {
346     /* Step 1. */
347     if (!IsFinite(day) || !IsFinite(time))
348         return GenericNaN();
349 
350     /* Step 2. */
351     return day * msPerDay + time;
352 }
353 
354 JS_PUBLIC_API(double)
355 JS::MakeDate(double year, unsigned month, unsigned day)
356 {
357     return ::MakeDate(MakeDay(year, month, day), 0);
358 }
359 
360 JS_PUBLIC_API(double)
361 JS::YearFromTime(double time)
362 {
363     return ::YearFromTime(time);
364 }
365 
366 JS_PUBLIC_API(double)
367 JS::MonthFromTime(double time)
368 {
369     return ::MonthFromTime(time);
370 }
371 
372 JS_PUBLIC_API(double)
373 JS::DayFromTime(double time)
374 {
375     return DateFromTime(time);
376 }
377 
378 JS_PUBLIC_API(double)
379 JS::DayFromYear(double year)
380 {
381     return ::DayFromYear(year);
382 }
383 
384 JS_PUBLIC_API(double)
385 JS::DayWithinYear(double time, double year)
386 {
387     return ::DayWithinYear(time, year);
388 }
389 
390 /*
391  * Find a year for which any given date will fall on the same weekday.
392  *
393  * This function should be used with caution when used other than
394  * for determining DST; it hasn't been proven not to produce an
395  * incorrect year for times near year boundaries.
396  */
397 static int
398 EquivalentYearForDST(int year)
399 {
400     /*
401      * Years and leap years on which Jan 1 is a Sunday, Monday, etc.
402      *
403      * yearStartingWith[0][i] is an example non-leap year where
404      * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
405      *
406      * yearStartingWith[1][i] is an example leap year where
407      * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
408      */
409     static const int yearStartingWith[2][7] = {
410         {1978, 1973, 1974, 1975, 1981, 1971, 1977},
411         {1984, 1996, 1980, 1992, 1976, 1988, 1972}
412     };
413 
414     int day = int(DayFromYear(year) + 4) % 7;
415     if (day < 0)
416         day += 7;
417 
418     return yearStartingWith[IsLeapYear(year)][day];
419 }
420 
421 /* ES5 15.9.1.8. */
422 static double
423 DaylightSavingTA(double t)
424 {
425     if (!IsFinite(t))
426         return GenericNaN();
427 
428     /*
429      * If earlier than 1970 or after 2038, potentially beyond the ken of
430      * many OSes, map it to an equivalent year before asking.
431      */
432     if (t < 0.0 || t > 2145916800000.0) {
433         int year = EquivalentYearForDST(int(YearFromTime(t)));
434         double day = MakeDay(year, MonthFromTime(t), DateFromTime(t));
435         t = MakeDate(day, TimeWithinDay(t));
436     }
437 
438     int64_t utcMilliseconds = static_cast<int64_t>(t);
439     int64_t offsetMilliseconds = DateTimeInfo::getDSTOffsetMilliseconds(utcMilliseconds);
440     return static_cast<double>(offsetMilliseconds);
441 }
442 
443 static double
444 AdjustTime(double date)
445 {
446     double localTZA = DateTimeInfo::localTZA();
447     double t = DaylightSavingTA(date) + localTZA;
448     t = (localTZA >= 0) ? fmod(t, msPerDay) : -fmod(msPerDay - t, msPerDay);
449     return t;
450 }
451 
452 /* ES5 15.9.1.9. */
453 static double
454 LocalTime(double t)
455 {
456     return t + AdjustTime(t);
457 }
458 
459 static double
460 UTC(double t)
461 {
462     return t - AdjustTime(t - DateTimeInfo::localTZA());
463 }
464 
465 /* ES5 15.9.1.10. */
466 static double
467 HourFromTime(double t)
468 {
469     double result = fmod(floor(t/msPerHour), HoursPerDay);
470     if (result < 0)
471         result += HoursPerDay;
472     return result;
473 }
474 
475 static double
476 MinFromTime(double t)
477 {
478     double result = fmod(floor(t / msPerMinute), MinutesPerHour);
479     if (result < 0)
480         result += MinutesPerHour;
481     return result;
482 }
483 
484 static double
485 SecFromTime(double t)
486 {
487     double result = fmod(floor(t / msPerSecond), SecondsPerMinute);
488     if (result < 0)
489         result += SecondsPerMinute;
490     return result;
491 }
492 
493 static double
494 msFromTime(double t)
495 {
496     double result = fmod(t, msPerSecond);
497     if (result < 0)
498         result += msPerSecond;
499     return result;
500 }
501 
502 /* ES5 15.9.1.11. */
503 static double
504 MakeTime(double hour, double min, double sec, double ms)
505 {
506     /* Step 1. */
507     if (!IsFinite(hour) ||
508         !IsFinite(min) ||
509         !IsFinite(sec) ||
510         !IsFinite(ms))
511     {
512         return GenericNaN();
513     }
514 
515     /* Step 2. */
516     double h = ToInteger(hour);
517 
518     /* Step 3. */
519     double m = ToInteger(min);
520 
521     /* Step 4. */
522     double s = ToInteger(sec);
523 
524     /* Step 5. */
525     double milli = ToInteger(ms);
526 
527     /* Steps 6-7. */
528     return h * msPerHour + m * msPerMinute + s * msPerSecond + milli;
529 }
530 
531 /**
532  * end of ECMA 'support' functions
533  */
534 
535 /* for use by date_parse */
536 
537 static const char* const wtb[] = {
538     "am", "pm",
539     "monday", "tuesday", "wednesday", "thursday", "friday",
540     "saturday", "sunday",
541     "january", "february", "march", "april", "may", "june",
542     "july", "august", "september", "october", "november", "december",
543     "gmt", "ut", "utc",
544     "est", "edt",
545     "cst", "cdt",
546     "mst", "mdt",
547     "pst", "pdt"
548     /* time zone table needs to be expanded */
549 };
550 
551 static const int ttb[] = {
552     -1, -2, 0, 0, 0, 0, 0, 0, 0,       /* AM/PM */
553     2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
554     10000 + 0, 10000 + 0, 10000 + 0,   /* GMT/UT/UTC */
555     10000 + 5 * 60, 10000 + 4 * 60,    /* EST/EDT */
556     10000 + 6 * 60, 10000 + 5 * 60,    /* CST/CDT */
557     10000 + 7 * 60, 10000 + 6 * 60,    /* MST/MDT */
558     10000 + 8 * 60, 10000 + 7 * 60     /* PST/PDT */
559 };
560 
561 template <typename CharT>
562 static bool
563 RegionMatches(const char* s1, int s1off, const CharT* s2, int s2off, int count)
564 {
565     while (count > 0 && s1[s1off] && s2[s2off]) {
566         if (unicode::ToLowerCase(s1[s1off]) != unicode::ToLowerCase(s2[s2off]))
567             break;
568 
569         s1off++;
570         s2off++;
571         count--;
572     }
573 
574     return count == 0;
575 }
576 
577 /* ES6 20.3.3.4. */
578 static bool
579 date_UTC(JSContext* cx, unsigned argc, Value* vp)
580 {
581     CallArgs args = CallArgsFromVp(argc, vp);
582 
583     // Steps 1-2.
584     double y;
585     if (!ToNumber(cx, args.get(0), &y))
586         return false;
587 
588     // Steps 3-4.
589     double m;
590     if (!ToNumber(cx, args.get(1), &m))
591         return false;
592 
593     // Steps 5-6.
594     double dt;
595     if (args.length() >= 3) {
596         if (!ToNumber(cx, args[2], &dt))
597             return false;
598     } else {
599         dt = 1;
600     }
601 
602     // Steps 7-8.
603     double h;
604     if (args.length() >= 4) {
605         if (!ToNumber(cx, args[3], &h))
606             return false;
607     } else {
608         h = 0;
609     }
610 
611     // Steps 9-10.
612     double min;
613     if (args.length() >= 5) {
614         if (!ToNumber(cx, args[4], &min))
615             return false;
616     } else {
617         min = 0;
618     }
619 
620     // Steps 11-12.
621     double s;
622     if (args.length() >= 6) {
623         if (!ToNumber(cx, args[5], &s))
624             return false;
625     } else {
626         s = 0;
627     }
628 
629     // Steps 13-14.
630     double milli;
631     if (args.length() >= 7) {
632         if (!ToNumber(cx, args[6], &milli))
633             return false;
634     } else {
635         milli = 0;
636     }
637 
638     // Step 15.
639     double yr = y;
640     if (!IsNaN(y)) {
641         double yint = ToInteger(y);
642         if (0 <= yint && yint <= 99)
643             yr = 1900 + yint;
644     }
645 
646     // Step 16.
647     ClippedTime time = TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)));
648     args.rval().set(TimeValue(time));
649     return true;
650 }
651 
652 /*
653  * Read and convert decimal digits from s[*i] into *result
654  * while *i < limit.
655  *
656  * Succeed if any digits are converted. Advance *i only
657  * as digits are consumed.
658  */
659 template <typename CharT>
660 static bool
661 ParseDigits(size_t* result, const CharT* s, size_t* i, size_t limit)
662 {
663     size_t init = *i;
664     *result = 0;
665     while (*i < limit && ('0' <= s[*i] && s[*i] <= '9')) {
666         *result *= 10;
667         *result += (s[*i] - '0');
668         ++(*i);
669     }
670     return *i != init;
671 }
672 
673 /*
674  * Read and convert decimal digits to the right of a decimal point,
675  * representing a fractional integer, from s[*i] into *result
676  * while *i < limit.
677  *
678  * Succeed if any digits are converted. Advance *i only
679  * as digits are consumed.
680  */
681 template <typename CharT>
682 static bool
683 ParseFractional(double* result, const CharT* s, size_t* i, size_t limit)
684 {
685     double factor = 0.1;
686     size_t init = *i;
687     *result = 0.0;
688     while (*i < limit && ('0' <= s[*i] && s[*i] <= '9')) {
689         *result += (s[*i] - '0') * factor;
690         factor *= 0.1;
691         ++(*i);
692     }
693     return *i != init;
694 }
695 
696 /*
697  * Read and convert exactly n decimal digits from s[*i]
698  * to s[min(*i+n,limit)] into *result.
699  *
700  * Succeed if exactly n digits are converted. Advance *i only
701  * on success.
702  */
703 template <typename CharT>
704 static bool
705 ParseDigitsN(size_t n, size_t* result, const CharT* s, size_t* i, size_t limit)
706 {
707     size_t init = *i;
708 
709     if (ParseDigits(result, s, i, Min(limit, init + n)))
710         return (*i - init) == n;
711 
712     *i = init;
713     return false;
714 }
715 
716 /*
717  * Read and convert n or less decimal digits from s[*i]
718  * to s[min(*i+n,limit)] into *result.
719  *
720  * Succeed only if greater than zero but less than or equal to n digits are
721  * converted. Advance *i only on success.
722  */
723 template <typename CharT>
724 static bool
725 ParseDigitsNOrLess(size_t n, size_t* result, const CharT* s, size_t* i, size_t limit)
726 {
727     size_t init = *i;
728 
729     if (ParseDigits(result, s, i, Min(limit, init + n)))
730         return ((*i - init) > 0) && ((*i - init) <= n);
731 
732     *i = init;
733     return false;
734 }
735 
736 static int
737 DaysInMonth(int year, int month)
738 {
739     bool leap = IsLeapYear(year);
740     int result = int(DayFromMonth(month, leap) - DayFromMonth(month - 1, leap));
741     return result;
742 }
743 
744 /*
745  * Parse a string in one of the date-time formats given by the W3C
746  * "NOTE-datetime" specification. These formats make up a restricted
747  * profile of the ISO 8601 format. Quoted here:
748  *
749  *   Any combination of the date formats with the time formats is
750  *   allowed, and also either the date or the time can be missing.
751  *
752  *   The specification is silent on the meaning when fields are
753  *   ommitted so the interpretations are a guess, but hopefully a
754  *   reasonable one. We default the month to January, the day to the
755  *   1st, and hours minutes and seconds all to 0. If the date is
756  *   missing entirely then we assume 1970-01-01 so that the time can
757  *   be aded to a date later. If the time is missing then we assume
758  *   00:00 UTC.  If the time is present but the time zone field is
759  *   missing then we use local time.
760  *
761  * For the sake of cross compatibility with other implementations we
762  * make a few exceptions to the standard: months, days, hours, minutes
763  * and seconds may be either one or two digits long, and the 'T' from
764  * the time part may be replaced with a space. Given that, a date time
765  * like "1999-1-1 1:1:1" will parse successfully.
766  *
767  * Date part:
768  *
769  *  Year:
770  *     YYYY (eg 1997)
771  *
772  *  Year and month:
773  *     YYYY-MM (eg 1997-07)
774  *
775  *  Complete date:
776  *     YYYY-MM-DD (eg 1997-07-16)
777  *
778  * Time part:
779  *
780  *  Hours and minutes:
781  *     Thh:mmTZD (eg T19:20+01:00)
782  *
783  *  Hours, minutes and seconds:
784  *     Thh:mm:ssTZD (eg T19:20:30+01:00)
785  *
786  *  Hours, minutes, seconds and a decimal fraction of a second:
787  *     Thh:mm:ss.sTZD (eg T19:20:30.45+01:00)
788  *
789  * where:
790  *
791  *   YYYY = four-digit year or six digit year as +YYYYYY or -YYYYYY
792  *   MM   = one or two-digit month (01=January, etc.)
793  *   DD   = one or two-digit day of month (01 through 31)
794  *   hh   = one or two digits of hour (00 through 23) (am/pm NOT allowed)
795  *   mm   = one or two digits of minute (00 through 59)
796  *   ss   = one or two digits of second (00 through 59)
797  *   s    = one or more digits representing a decimal fraction of a second
798  *   TZD  = time zone designator (Z or +hh:mm or -hh:mm or missing for local)
799  */
800 template <typename CharT>
801 static bool
802 ParseISOStyleDate(const CharT* s, size_t length, ClippedTime* result)
803 {
804     size_t i = 0;
805     int tzMul = 1;
806     int dateMul = 1;
807     size_t year = 1970;
808     size_t month = 1;
809     size_t day = 1;
810     size_t hour = 0;
811     size_t min = 0;
812     size_t sec = 0;
813     double frac = 0;
814     bool isLocalTime = false;
815     size_t tzHour = 0;
816     size_t tzMin = 0;
817 
818 #define PEEK(ch) (i < length && s[i] == ch)
819 
820 #define NEED(ch)                                                               \
821     if (i >= length || s[i] != ch) { return false; } else { ++i; }
822 
823 #define DONE_DATE_UNLESS(ch)                                                   \
824     if (i >= length || s[i] != ch) { goto done_date; } else { ++i; }
825 
826 #define DONE_UNLESS(ch)                                                        \
827     if (i >= length || s[i] != ch) { goto done; } else { ++i; }
828 
829 #define NEED_NDIGITS(n, field)                                                 \
830     if (!ParseDigitsN(n, &field, s, &i, length)) { return false; }
831 
832 #define NEED_NDIGITS_OR_LESS(n, field)                                         \
833     if (!ParseDigitsNOrLess(n, &field, s, &i, length)) { return false; }
834 
835     if (PEEK('+') || PEEK('-')) {
836         if (PEEK('-'))
837             dateMul = -1;
838         ++i;
839         NEED_NDIGITS(6, year);
840     } else if (!PEEK('T')) {
841         NEED_NDIGITS(4, year);
842     }
843     DONE_DATE_UNLESS('-');
844     NEED_NDIGITS_OR_LESS(2, month);
845     DONE_DATE_UNLESS('-');
846     NEED_NDIGITS_OR_LESS(2, day);
847 
848  done_date:
849     if (PEEK('T') || PEEK(' '))
850         i++;
851     else
852         goto done;
853 
854     NEED_NDIGITS_OR_LESS(2, hour);
855     NEED(':');
856     NEED_NDIGITS_OR_LESS(2, min);
857 
858     if (PEEK(':')) {
859         ++i;
860         NEED_NDIGITS_OR_LESS(2, sec);
861         if (PEEK('.')) {
862             ++i;
863             if (!ParseFractional(&frac, s, &i, length))
864                 return false;
865         }
866     }
867 
868     if (PEEK('Z')) {
869         ++i;
870     } else if (PEEK('+') || PEEK('-')) {
871         if (PEEK('-'))
872             tzMul = -1;
873         ++i;
874         NEED_NDIGITS(2, tzHour);
875         /*
876          * Non-standard extension to the ISO date format (permitted by ES5):
877          * allow "-0700" as a time zone offset, not just "-07:00".
878          */
879         if (PEEK(':'))
880             ++i;
881         NEED_NDIGITS(2, tzMin);
882     } else {
883         isLocalTime = true;
884     }
885 
886  done:
887     if (year > 275943 // ceil(1e8/365) + 1970
888         || (month == 0 || month > 12)
889         || (day == 0 || day > size_t(DaysInMonth(year,month)))
890         || hour > 24
891         || ((hour == 24) && (min > 0 || sec > 0 || frac > 0))
892         || min > 59
893         || sec > 59
894         || tzHour > 23
895         || tzMin > 59)
896     {
897         return false;
898     }
899 
900     if (i != length)
901         return false;
902 
903     month -= 1; /* convert month to 0-based */
904 
905     double msec = MakeDate(MakeDay(dateMul * double(year), month, day),
906                            MakeTime(hour, min, sec, frac * 1000.0));
907 
908     if (isLocalTime)
909         msec = UTC(msec);
910     else
911         msec -= tzMul * (tzHour * msPerHour + tzMin * msPerMinute);
912 
913     *result = TimeClip(msec);
914     return NumbersAreIdentical(msec, result->toDouble());
915 
916 #undef PEEK
917 #undef NEED
918 #undef DONE_UNLESS
919 #undef NEED_NDIGITS
920 }
921 
922 template <typename CharT>
923 static bool
924 ParseDate(const CharT* s, size_t length, ClippedTime* result)
925 {
926     if (ParseISOStyleDate(s, length, result))
927         return true;
928 
929     if (length == 0)
930         return false;
931 
932     int year = -1;
933     int mon = -1;
934     int mday = -1;
935     int hour = -1;
936     int min = -1;
937     int sec = -1;
938     int tzOffset = -1;
939 
940     int prevc = 0;
941 
942     bool seenPlusMinus = false;
943     bool seenMonthName = false;
944 
945     size_t i = 0;
946     while (i < length) {
947         int c = s[i];
948         i++;
949         if (c <= ' ' || c == ',' || c == '-') {
950             if (c == '-' && '0' <= s[i] && s[i] <= '9')
951                 prevc = c;
952             continue;
953         }
954         if (c == '(') { /* comments) */
955             int depth = 1;
956             while (i < length) {
957                 c = s[i];
958                 i++;
959                 if (c == '(') {
960                     depth++;
961                 } else if (c == ')') {
962                     if (--depth <= 0)
963                         break;
964                 }
965             }
966             continue;
967         }
968         if ('0' <= c && c <= '9') {
969             int n = c - '0';
970             while (i < length && '0' <= (c = s[i]) && c <= '9') {
971                 n = n * 10 + c - '0';
972                 i++;
973             }
974 
975             /*
976              * Allow TZA before the year, so 'Wed Nov 05 21:49:11 GMT-0800 1997'
977              * works.
978              *
979              * Uses of seenPlusMinus allow ':' in TZA, so Java no-timezone style
980              * of GMT+4:30 works.
981              */
982 
983             if ((prevc == '+' || prevc == '-')/*  && year>=0 */) {
984                 /* Make ':' case below change tzOffset. */
985                 seenPlusMinus = true;
986 
987                 /* offset */
988                 if (n < 24)
989                     n = n * 60; /* EG. "GMT-3" */
990                 else
991                     n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */
992 
993                 if (prevc == '+')       /* plus means east of GMT */
994                     n = -n;
995 
996                 if (tzOffset != 0 && tzOffset != -1)
997                     return false;
998 
999                 tzOffset = n;
1000             } else if (prevc == '/' && mon >= 0 && mday >= 0 && year < 0) {
1001                 if (c <= ' ' || c == ',' || c == '/' || i >= length)
1002                     year = n;
1003                 else
1004                     return false;
1005             } else if (c == ':') {
1006                 if (hour < 0)
1007                     hour = /*byte*/ n;
1008                 else if (min < 0)
1009                     min = /*byte*/ n;
1010                 else
1011                     return false;
1012             } else if (c == '/') {
1013                 /*
1014                  * Until it is determined that mon is the actual month, keep
1015                  * it as 1-based rather than 0-based.
1016                  */
1017                 if (mon < 0)
1018                     mon = /*byte*/ n;
1019                 else if (mday < 0)
1020                     mday = /*byte*/ n;
1021                 else
1022                     return false;
1023             } else if (i < length && c != ',' && c > ' ' && c != '-' && c != '(') {
1024                 return false;
1025             } else if (seenPlusMinus && n < 60) {  /* handle GMT-3:30 */
1026                 if (tzOffset < 0)
1027                     tzOffset -= n;
1028                 else
1029                     tzOffset += n;
1030             } else if (hour >= 0 && min < 0) {
1031                 min = /*byte*/ n;
1032             } else if (prevc == ':' && min >= 0 && sec < 0) {
1033                 sec = /*byte*/ n;
1034             } else if (mon < 0) {
1035                 mon = /*byte*/n;
1036             } else if (mon >= 0 && mday < 0) {
1037                 mday = /*byte*/ n;
1038             } else if (mon >= 0 && mday >= 0 && year < 0) {
1039                 year = n;
1040             } else {
1041                 return false;
1042             }
1043             prevc = 0;
1044         } else if (c == '/' || c == ':' || c == '+' || c == '-') {
1045             prevc = c;
1046         } else {
1047             size_t st = i - 1;
1048             while (i < length) {
1049                 c = s[i];
1050                 if (!(('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')))
1051                     break;
1052                 i++;
1053             }
1054 
1055             if (i <= st + 1)
1056                 return false;
1057 
1058             int k;
1059             for (k = ArrayLength(wtb); --k >= 0;) {
1060                 if (RegionMatches(wtb[k], 0, s, st, i - st)) {
1061                     int action = ttb[k];
1062                     if (action != 0) {
1063                         if (action < 0) {
1064                             /*
1065                              * AM/PM. Count 12:30 AM as 00:30, 12:30 PM as
1066                              * 12:30, instead of blindly adding 12 if PM.
1067                              */
1068                             MOZ_ASSERT(action == -1 || action == -2);
1069                             if (hour > 12 || hour < 0)
1070                                 return false;
1071 
1072                             if (action == -1 && hour == 12) /* am */
1073                                 hour = 0;
1074                             else if (action == -2 && hour != 12) /* pm */
1075                                 hour += 12;
1076                         } else if (action <= 13) { /* month! */
1077                             /*
1078                              * Adjust mon to be 1-based until the final values
1079                              * for mon, mday and year are adjusted below.
1080                              */
1081                             if (seenMonthName)
1082                                 return false;
1083 
1084                             seenMonthName = true;
1085                             int temp = /*byte*/ (action - 2) + 1;
1086 
1087                             if (mon < 0) {
1088                                 mon = temp;
1089                             } else if (mday < 0) {
1090                                 mday = mon;
1091                                 mon = temp;
1092                             } else if (year < 0) {
1093                                 year = mon;
1094                                 mon = temp;
1095                             } else {
1096                                 return false;
1097                             }
1098                         } else {
1099                             tzOffset = action - 10000;
1100                         }
1101                     }
1102                     break;
1103                 }
1104             }
1105 
1106             if (k < 0)
1107                 return false;
1108 
1109             prevc = 0;
1110         }
1111     }
1112 
1113     if (year < 0 || mon < 0 || mday < 0)
1114         return false;
1115 
1116     /*
1117      * Case 1. The input string contains an English month name.
1118      *         The form of the string can be month f l, or f month l, or
1119      *         f l month which each evaluate to the same date.
1120      *         If f and l are both greater than or equal to 100 the date
1121      *         is invalid.
1122      *
1123      *         The year is taken to be either the greater of the values f, l or
1124      *         whichever is set to zero. If the year is greater than or equal to
1125      *         50 and less than 100, it is considered to be the number of years
1126      *         after 1900. If the year is less than 50 it is considered to be the
1127      *         number of years after 2000, otherwise it is considered to be the
1128      *         number of years after 0.
1129      *
1130      * Case 2. The input string is of the form "f/m/l" where f, m and l are
1131      *         integers, e.g. 7/16/45. mon, mday and year values are adjusted
1132      *         to achieve Chrome compatibility.
1133      *
1134      *         a. If 0 < f <= 12 and 0 < l <= 31, f/m/l is interpreted as
1135      *         month/day/year.
1136      *            i.  If year < 50, it is the number of years after 2000
1137      *            ii. If year >= 50, it is the number of years after 1900.
1138      *           iii. If year >= 100, it is the number of years after 0.
1139      *         b. If 31 < f and 0 < m <= 12 and 0 < l <= 31 f/m/l is
1140      *         interpreted as year/month/day
1141      *            i.  If year < 50, it is the number of years after 2000
1142      *            ii. If year >= 50, it is the number of years after 1900.
1143      *           iii. If year >= 100, it is the number of years after 0.
1144      */
1145     if (seenMonthName) {
1146         if (mday >= 100 && mon >= 100)
1147             return false;
1148 
1149         if (year > 0 && (mday == 0 || mday > year)) {
1150             int temp = year;
1151             year = mday;
1152             mday = temp;
1153         }
1154 
1155         if (mday <= 0 || mday > 31)
1156             return false;
1157 
1158     } else if (0 < mon && mon <= 12 && 0 < mday && mday <= 31) {
1159         /* (a) month/day/year */
1160     } else {
1161         /* (b) year/month/day */
1162         if (mon > 31 && mday <= 12 && year <= 31) {
1163             int temp = year;
1164             year = mon;
1165             mon = mday;
1166             mday = temp;
1167         } else {
1168             return false;
1169         }
1170     }
1171 
1172     if (year < 50)
1173         year += 2000;
1174     else if (year >= 50 && year < 100)
1175         year += 1900;
1176 
1177     mon -= 1; /* convert month to 0-based */
1178     if (sec < 0)
1179         sec = 0;
1180     if (min < 0)
1181         min = 0;
1182     if (hour < 0)
1183         hour = 0;
1184 
1185     double msec = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0));
1186 
1187     if (tzOffset == -1) /* no time zone specified, have to use local */
1188         msec = UTC(msec);
1189     else
1190         msec += tzOffset * msPerMinute;
1191 
1192     *result = TimeClip(msec);
1193     return true;
1194 }
1195 
1196 static bool
1197 ParseDate(JSLinearString* s, ClippedTime* result)
1198 {
1199     AutoCheckCannotGC nogc;
1200     return s->hasLatin1Chars()
1201            ? ParseDate(s->latin1Chars(nogc), s->length(), result)
1202            : ParseDate(s->twoByteChars(nogc), s->length(), result);
1203 }
1204 
1205 static bool
1206 date_parse(JSContext* cx, unsigned argc, Value* vp)
1207 {
1208     CallArgs args = CallArgsFromVp(argc, vp);
1209     if (args.length() == 0) {
1210         args.rval().setNaN();
1211         return true;
1212     }
1213 
1214     JSString* str = ToString<CanGC>(cx, args[0]);
1215     if (!str)
1216         return false;
1217 
1218     JSLinearString* linearStr = str->ensureLinear(cx);
1219     if (!linearStr)
1220         return false;
1221 
1222     ClippedTime result;
1223     if (!ParseDate(linearStr, &result)) {
1224         args.rval().setNaN();
1225         return true;
1226     }
1227 
1228     args.rval().set(TimeValue(result));
1229     return true;
1230 }
1231 
1232 static ClippedTime
1233 NowAsMillis()
1234 {
1235     const double maxResolutionMs = 2;
1236     double timestamp = static_cast<double>(PRMJ_Now()) / PRMJ_USEC_PER_MSEC;
1237     timestamp = floor(timestamp / maxResolutionMs) * maxResolutionMs;
1238     return TimeClip(timestamp);
1239 }
1240 
1241 bool
1242 js::date_now(JSContext* cx, unsigned argc, Value* vp)
1243 {
1244     CallArgs args = CallArgsFromVp(argc, vp);
1245     args.rval().set(TimeValue(NowAsMillis()));
1246     return true;
1247 }
1248 
1249 void
1250 DateObject::setUTCTime(ClippedTime t)
1251 {
1252     for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++)
1253         setReservedSlot(ind, UndefinedValue());
1254 
1255     setFixedSlot(UTC_TIME_SLOT, TimeValue(t));
1256 }
1257 
1258 void
1259 DateObject::setUTCTime(ClippedTime t, MutableHandleValue vp)
1260 {
1261     setUTCTime(t);
1262     vp.set(TimeValue(t));
1263 }
1264 
1265 void
1266 DateObject::fillLocalTimeSlots()
1267 {
1268     /* Check if the cache is already populated. */
1269     if (!getReservedSlot(LOCAL_TIME_SLOT).isUndefined() &&
1270         getReservedSlot(TZA_SLOT).toDouble() == DateTimeInfo::localTZA())
1271     {
1272         return;
1273     }
1274 
1275     /* Remember time zone used to generate the local cache. */
1276     setReservedSlot(TZA_SLOT, DoubleValue(DateTimeInfo::localTZA()));
1277 
1278     double utcTime = UTCTime().toNumber();
1279 
1280     if (!IsFinite(utcTime)) {
1281         for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++)
1282             setReservedSlot(ind, DoubleValue(utcTime));
1283         return;
1284     }
1285 
1286     double localTime = LocalTime(utcTime);
1287 
1288     setReservedSlot(LOCAL_TIME_SLOT, DoubleValue(localTime));
1289 
1290     int year = (int) floor(localTime /(msPerDay * 365.2425)) + 1970;
1291     double yearStartTime = TimeFromYear(year);
1292 
1293     /* Adjust the year in case the approximation was wrong, as in YearFromTime. */
1294     int yearDays;
1295     if (yearStartTime > localTime) {
1296         year--;
1297         yearStartTime -= (msPerDay * DaysInYear(year));
1298         yearDays = DaysInYear(year);
1299     } else {
1300         yearDays = DaysInYear(year);
1301         double nextStart = yearStartTime + (msPerDay * yearDays);
1302         if (nextStart <= localTime) {
1303             year++;
1304             yearStartTime = nextStart;
1305             yearDays = DaysInYear(year);
1306         }
1307     }
1308 
1309     setReservedSlot(LOCAL_YEAR_SLOT, Int32Value(year));
1310 
1311     uint64_t yearTime = uint64_t(localTime - yearStartTime);
1312     int yearSeconds = uint32_t(yearTime / 1000);
1313 
1314     int day = yearSeconds / int(SecondsPerDay);
1315 
1316     int step = -1, next = 30;
1317     int month;
1318 
1319     do {
1320         if (day <= next) {
1321             month = 0;
1322             break;
1323         }
1324         step = next;
1325         next += ((yearDays == 366) ? 29 : 28);
1326         if (day <= next) {
1327             month = 1;
1328             break;
1329         }
1330         step = next;
1331         if (day <= (next += 31)) {
1332             month = 2;
1333             break;
1334         }
1335         step = next;
1336         if (day <= (next += 30)) {
1337             month = 3;
1338             break;
1339         }
1340         step = next;
1341         if (day <= (next += 31)) {
1342             month = 4;
1343             break;
1344         }
1345         step = next;
1346         if (day <= (next += 30)) {
1347             month = 5;
1348             break;
1349         }
1350         step = next;
1351         if (day <= (next += 31)) {
1352             month = 6;
1353             break;
1354         }
1355         step = next;
1356         if (day <= (next += 31)) {
1357             month = 7;
1358             break;
1359         }
1360         step = next;
1361         if (day <= (next += 30)) {
1362             month = 8;
1363             break;
1364         }
1365         step = next;
1366         if (day <= (next += 31)) {
1367             month = 9;
1368             break;
1369         }
1370         step = next;
1371         if (day <= (next += 30)) {
1372             month = 10;
1373             break;
1374         }
1375         step = next;
1376         month = 11;
1377     } while (0);
1378 
1379     setReservedSlot(LOCAL_MONTH_SLOT, Int32Value(month));
1380     setReservedSlot(LOCAL_DATE_SLOT, Int32Value(day - step));
1381 
1382     int weekday = WeekDay(localTime);
1383     setReservedSlot(LOCAL_DAY_SLOT, Int32Value(weekday));
1384 
1385     setReservedSlot(LOCAL_SECONDS_INTO_YEAR_SLOT, Int32Value(yearSeconds));
1386 }
1387 
1388 inline double
1389 DateObject::cachedLocalTime()
1390 {
1391     fillLocalTimeSlots();
1392     return getReservedSlot(LOCAL_TIME_SLOT).toDouble();
1393 }
1394 
1395 MOZ_ALWAYS_INLINE bool
1396 IsDate(HandleValue v)
1397 {
1398     return v.isObject() && v.toObject().is<DateObject>();
1399 }
1400 
1401 /*
1402  * See ECMA 15.9.5.4 thru 15.9.5.23
1403  */
1404 /* static */ MOZ_ALWAYS_INLINE bool
1405 DateObject::getTime_impl(JSContext* cx, const CallArgs& args)
1406 {
1407     args.rval().set(args.thisv().toObject().as<DateObject>().UTCTime());
1408     return true;
1409 }
1410 
1411 static bool
1412 date_getTime(JSContext* cx, unsigned argc, Value* vp)
1413 {
1414     CallArgs args = CallArgsFromVp(argc, vp);
1415     return CallNonGenericMethod<IsDate, DateObject::getTime_impl>(cx, args);
1416 }
1417 
1418 /* static */ MOZ_ALWAYS_INLINE bool
1419 DateObject::getYear_impl(JSContext* cx, const CallArgs& args)
1420 {
1421     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
1422     dateObj->fillLocalTimeSlots();
1423 
1424     Value yearVal = dateObj->getReservedSlot(LOCAL_YEAR_SLOT);
1425     if (yearVal.isInt32()) {
1426         /* Follow ECMA-262 to the letter, contrary to IE JScript. */
1427         int year = yearVal.toInt32() - 1900;
1428         args.rval().setInt32(year);
1429     } else {
1430         args.rval().set(yearVal);
1431     }
1432 
1433     return true;
1434 }
1435 
1436 static bool
1437 date_getYear(JSContext* cx, unsigned argc, Value* vp)
1438 {
1439     CallArgs args = CallArgsFromVp(argc, vp);
1440     return CallNonGenericMethod<IsDate, DateObject::getYear_impl>(cx, args);
1441 }
1442 
1443 /* static */ MOZ_ALWAYS_INLINE bool
1444 DateObject::getFullYear_impl(JSContext* cx, const CallArgs& args)
1445 {
1446     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
1447     dateObj->fillLocalTimeSlots();
1448 
1449     args.rval().set(dateObj->getReservedSlot(LOCAL_YEAR_SLOT));
1450     return true;
1451 }
1452 
1453 static bool
1454 date_getFullYear(JSContext* cx, unsigned argc, Value* vp)
1455 {
1456     CallArgs args = CallArgsFromVp(argc, vp);
1457     return CallNonGenericMethod<IsDate, DateObject::getFullYear_impl>(cx, args);
1458 }
1459 
1460 /* static */ MOZ_ALWAYS_INLINE bool
1461 DateObject::getUTCFullYear_impl(JSContext* cx, const CallArgs& args)
1462 {
1463     double result = args.thisv().toObject().as<DateObject>().UTCTime().toNumber();
1464     if (IsFinite(result))
1465         result = YearFromTime(result);
1466 
1467     args.rval().setNumber(result);
1468     return true;
1469 }
1470 
1471 static bool
1472 date_getUTCFullYear(JSContext* cx, unsigned argc, Value* vp)
1473 {
1474     CallArgs args = CallArgsFromVp(argc, vp);
1475     return CallNonGenericMethod<IsDate, DateObject::getUTCFullYear_impl>(cx, args);
1476 }
1477 
1478 /* static */ MOZ_ALWAYS_INLINE bool
1479 DateObject::getMonth_impl(JSContext* cx, const CallArgs& args)
1480 {
1481     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
1482     dateObj->fillLocalTimeSlots();
1483 
1484     args.rval().set(dateObj->getReservedSlot(LOCAL_MONTH_SLOT));
1485     return true;
1486 }
1487 
1488 static bool
1489 date_getMonth(JSContext* cx, unsigned argc, Value* vp)
1490 {
1491     CallArgs args = CallArgsFromVp(argc, vp);
1492     return CallNonGenericMethod<IsDate, DateObject::getMonth_impl>(cx, args);
1493 }
1494 
1495 /* static */ MOZ_ALWAYS_INLINE bool
1496 DateObject::getUTCMonth_impl(JSContext* cx, const CallArgs& args)
1497 {
1498     double d = args.thisv().toObject().as<DateObject>().UTCTime().toNumber();
1499     args.rval().setNumber(MonthFromTime(d));
1500     return true;
1501 }
1502 
1503 static bool
1504 date_getUTCMonth(JSContext* cx, unsigned argc, Value* vp)
1505 {
1506     CallArgs args = CallArgsFromVp(argc, vp);
1507     return CallNonGenericMethod<IsDate, DateObject::getUTCMonth_impl>(cx, args);
1508 }
1509 
1510 /* static */ MOZ_ALWAYS_INLINE bool
1511 DateObject::getDate_impl(JSContext* cx, const CallArgs& args)
1512 {
1513     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
1514     dateObj->fillLocalTimeSlots();
1515 
1516     args.rval().set(dateObj->getReservedSlot(LOCAL_DATE_SLOT));
1517     return true;
1518 }
1519 
1520 static bool
1521 date_getDate(JSContext* cx, unsigned argc, Value* vp)
1522 {
1523     CallArgs args = CallArgsFromVp(argc, vp);
1524     return CallNonGenericMethod<IsDate, DateObject::getDate_impl>(cx, args);
1525 }
1526 
1527 /* static */ MOZ_ALWAYS_INLINE bool
1528 DateObject::getUTCDate_impl(JSContext* cx, const CallArgs& args)
1529 {
1530     double result = args.thisv().toObject().as<DateObject>().UTCTime().toNumber();
1531     if (IsFinite(result))
1532         result = DateFromTime(result);
1533 
1534     args.rval().setNumber(result);
1535     return true;
1536 }
1537 
1538 static bool
1539 date_getUTCDate(JSContext* cx, unsigned argc, Value* vp)
1540 {
1541     CallArgs args = CallArgsFromVp(argc, vp);
1542     return CallNonGenericMethod<IsDate, DateObject::getUTCDate_impl>(cx, args);
1543 }
1544 
1545 /* static */ MOZ_ALWAYS_INLINE bool
1546 DateObject::getDay_impl(JSContext* cx, const CallArgs& args)
1547 {
1548     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
1549     dateObj->fillLocalTimeSlots();
1550 
1551     args.rval().set(dateObj->getReservedSlot(LOCAL_DAY_SLOT));
1552     return true;
1553 }
1554 
1555 static bool
1556 date_getDay(JSContext* cx, unsigned argc, Value* vp)
1557 {
1558     CallArgs args = CallArgsFromVp(argc, vp);
1559     return CallNonGenericMethod<IsDate, DateObject::getDay_impl>(cx, args);
1560 }
1561 
1562 /* static */ MOZ_ALWAYS_INLINE bool
1563 DateObject::getUTCDay_impl(JSContext* cx, const CallArgs& args)
1564 {
1565     double result = args.thisv().toObject().as<DateObject>().UTCTime().toNumber();
1566     if (IsFinite(result))
1567         result = WeekDay(result);
1568 
1569     args.rval().setNumber(result);
1570     return true;
1571 }
1572 
1573 static bool
1574 date_getUTCDay(JSContext* cx, unsigned argc, Value* vp)
1575 {
1576     CallArgs args = CallArgsFromVp(argc, vp);
1577     return CallNonGenericMethod<IsDate, DateObject::getUTCDay_impl>(cx, args);
1578 }
1579 
1580 /* static */ MOZ_ALWAYS_INLINE bool
1581 DateObject::getHours_impl(JSContext* cx, const CallArgs& args)
1582 {
1583     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
1584     dateObj->fillLocalTimeSlots();
1585 
1586     // Note: LOCAL_SECONDS_INTO_YEAR_SLOT is guaranteed to contain an
1587     // int32 or NaN after the call to fillLocalTimeSlots.
1588     Value yearSeconds = dateObj->getReservedSlot(LOCAL_SECONDS_INTO_YEAR_SLOT);
1589     if (yearSeconds.isDouble()) {
1590         MOZ_ASSERT(IsNaN(yearSeconds.toDouble()));
1591         args.rval().set(yearSeconds);
1592     } else {
1593         args.rval().setInt32((yearSeconds.toInt32() / int(SecondsPerHour)) % int(HoursPerDay));
1594     }
1595     return true;
1596 }
1597 
1598 static bool
1599 date_getHours(JSContext* cx, unsigned argc, Value* vp)
1600 {
1601     CallArgs args = CallArgsFromVp(argc, vp);
1602     return CallNonGenericMethod<IsDate, DateObject::getHours_impl>(cx, args);
1603 }
1604 
1605 /* static */ MOZ_ALWAYS_INLINE bool
1606 DateObject::getUTCHours_impl(JSContext* cx, const CallArgs& args)
1607 {
1608     double result = args.thisv().toObject().as<DateObject>().UTCTime().toNumber();
1609     if (IsFinite(result))
1610         result = HourFromTime(result);
1611 
1612     args.rval().setNumber(result);
1613     return true;
1614 }
1615 
1616 static bool
1617 date_getUTCHours(JSContext* cx, unsigned argc, Value* vp)
1618 {
1619     CallArgs args = CallArgsFromVp(argc, vp);
1620     return CallNonGenericMethod<IsDate, DateObject::getUTCHours_impl>(cx, args);
1621 }
1622 
1623 /* static */ MOZ_ALWAYS_INLINE bool
1624 DateObject::getMinutes_impl(JSContext* cx, const CallArgs& args)
1625 {
1626     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
1627     dateObj->fillLocalTimeSlots();
1628 
1629     // Note: LOCAL_SECONDS_INTO_YEAR_SLOT is guaranteed to contain an
1630     // int32 or NaN after the call to fillLocalTimeSlots.
1631     Value yearSeconds = dateObj->getReservedSlot(LOCAL_SECONDS_INTO_YEAR_SLOT);
1632     if (yearSeconds.isDouble()) {
1633         MOZ_ASSERT(IsNaN(yearSeconds.toDouble()));
1634         args.rval().set(yearSeconds);
1635     } else {
1636         args.rval().setInt32((yearSeconds.toInt32() / int(SecondsPerMinute)) % int(MinutesPerHour));
1637     }
1638     return true;
1639 }
1640 
1641 static bool
1642 date_getMinutes(JSContext* cx, unsigned argc, Value* vp)
1643 {
1644     CallArgs args = CallArgsFromVp(argc, vp);
1645     return CallNonGenericMethod<IsDate, DateObject::getMinutes_impl>(cx, args);
1646 }
1647 
1648 /* static */ MOZ_ALWAYS_INLINE bool
1649 DateObject::getUTCMinutes_impl(JSContext* cx, const CallArgs& args)
1650 {
1651     double result = args.thisv().toObject().as<DateObject>().UTCTime().toNumber();
1652     if (IsFinite(result))
1653         result = MinFromTime(result);
1654 
1655     args.rval().setNumber(result);
1656     return true;
1657 }
1658 
1659 static bool
1660 date_getUTCMinutes(JSContext* cx, unsigned argc, Value* vp)
1661 {
1662     CallArgs args = CallArgsFromVp(argc, vp);
1663     return CallNonGenericMethod<IsDate, DateObject::getUTCMinutes_impl>(cx, args);
1664 }
1665 
1666 /*
1667  * Date.getSeconds is mapped to getUTCSeconds. As long as no supported time
1668  * zone has a fractional-minute component, the differences in their
1669  * specifications aren't observable.
1670  *
1671  * We'll have to split the implementations if a new time zone with a
1672  * fractional-minute component is introduced or once we implement ES6's
1673  * 20.3.1.7 Local Time Zone Adjustment: time zones with adjustments like that
1674  * did historically exist, e.g.
1675  * https://en.wikipedia.org/wiki/UTC%E2%88%9200:25:21
1676  */
1677 
1678 /* static */ MOZ_ALWAYS_INLINE bool
1679 DateObject::getUTCSeconds_impl(JSContext* cx, const CallArgs& args)
1680 {
1681     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
1682     dateObj->fillLocalTimeSlots();
1683 
1684     // Note: LOCAL_SECONDS_INTO_YEAR_SLOT is guaranteed to contain an
1685     // int32 or NaN after the call to fillLocalTimeSlots.
1686     Value yearSeconds = dateObj->getReservedSlot(LOCAL_SECONDS_INTO_YEAR_SLOT);
1687     if (yearSeconds.isDouble()) {
1688         MOZ_ASSERT(IsNaN(yearSeconds.toDouble()));
1689         args.rval().set(yearSeconds);
1690     } else {
1691         args.rval().setInt32(yearSeconds.toInt32() % int(SecondsPerMinute));
1692     }
1693     return true;
1694 }
1695 
1696 static bool
1697 date_getUTCSeconds(JSContext* cx, unsigned argc, Value* vp)
1698 {
1699     CallArgs args = CallArgsFromVp(argc, vp);
1700     return CallNonGenericMethod<IsDate, DateObject::getUTCSeconds_impl>(cx, args);
1701 }
1702 /*
1703  * Date.getMilliseconds is mapped to getUTCMilliseconds for the same reasons
1704  * that getSeconds is mapped to getUTCSeconds (see above).  No known LocalTZA
1705  * has *ever* included a fractional-second component, however, so we can keep
1706  * this simplification even if we stop implementing ES5 local-time computation
1707  * semantics.
1708  */
1709 
1710 /* static */ MOZ_ALWAYS_INLINE bool
1711 DateObject::getUTCMilliseconds_impl(JSContext* cx, const CallArgs& args)
1712 {
1713     double result = args.thisv().toObject().as<DateObject>().UTCTime().toNumber();
1714     if (IsFinite(result))
1715         result = msFromTime(result);
1716 
1717     args.rval().setNumber(result);
1718     return true;
1719 }
1720 
1721 static bool
1722 date_getUTCMilliseconds(JSContext* cx, unsigned argc, Value* vp)
1723 {
1724     CallArgs args = CallArgsFromVp(argc, vp);
1725     return CallNonGenericMethod<IsDate, DateObject::getUTCMilliseconds_impl>(cx, args);
1726 }
1727 
1728 /* static */ MOZ_ALWAYS_INLINE bool
1729 DateObject::getTimezoneOffset_impl(JSContext* cx, const CallArgs& args)
1730 {
1731     DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
1732     double utctime = dateObj->UTCTime().toNumber();
1733     double localtime = dateObj->cachedLocalTime();
1734 
1735     /*
1736      * Return the time zone offset in minutes for the current locale that is
1737      * appropriate for this time. This value would be a constant except for
1738      * daylight savings time.
1739      */
1740     double result = (utctime - localtime) / msPerMinute;
1741     args.rval().setNumber(result);
1742     return true;
1743 }
1744 
1745 static bool
1746 date_getTimezoneOffset(JSContext* cx, unsigned argc, Value* vp)
1747 {
1748     CallArgs args = CallArgsFromVp(argc, vp);
1749     return CallNonGenericMethod<IsDate, DateObject::getTimezoneOffset_impl>(cx, args);
1750 }
1751 
1752 MOZ_ALWAYS_INLINE bool
1753 date_setTime_impl(JSContext* cx, const CallArgs& args)
1754 {
1755     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
1756     if (args.length() == 0) {
1757         dateObj->setUTCTime(ClippedTime::invalid(), args.rval());
1758         return true;
1759     }
1760 
1761     double result;
1762     if (!ToNumber(cx, args[0], &result))
1763         return false;
1764 
1765     dateObj->setUTCTime(TimeClip(result), args.rval());
1766     return true;
1767 }
1768 
1769 static bool
1770 date_setTime(JSContext* cx, unsigned argc, Value* vp)
1771 {
1772     CallArgs args = CallArgsFromVp(argc, vp);
1773     return CallNonGenericMethod<IsDate, date_setTime_impl>(cx, args);
1774 }
1775 
1776 static bool
1777 GetMsecsOrDefault(JSContext* cx, const CallArgs& args, unsigned i, double t, double* millis)
1778 {
1779     if (args.length() <= i) {
1780         *millis = msFromTime(t);
1781         return true;
1782     }
1783     return ToNumber(cx, args[i], millis);
1784 }
1785 
1786 static bool
1787 GetSecsOrDefault(JSContext* cx, const CallArgs& args, unsigned i, double t, double* sec)
1788 {
1789     if (args.length() <= i) {
1790         *sec = SecFromTime(t);
1791         return true;
1792     }
1793     return ToNumber(cx, args[i], sec);
1794 }
1795 
1796 static bool
1797 GetMinsOrDefault(JSContext* cx, const CallArgs& args, unsigned i, double t, double* mins)
1798 {
1799     if (args.length() <= i) {
1800         *mins = MinFromTime(t);
1801         return true;
1802     }
1803     return ToNumber(cx, args[i], mins);
1804 }
1805 
1806 /* ES6 20.3.4.23. */
1807 MOZ_ALWAYS_INLINE bool
1808 date_setMilliseconds_impl(JSContext* cx, const CallArgs& args)
1809 {
1810     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
1811 
1812     // Steps 1-2.
1813     double t = LocalTime(dateObj->UTCTime().toNumber());
1814 
1815     // Steps 3-4.
1816     double ms;
1817     if (!ToNumber(cx, args.get(0), &ms))
1818         return false;
1819 
1820     // Step 5.
1821     double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms);
1822 
1823     // Step 6.
1824     ClippedTime u = TimeClip(UTC(MakeDate(Day(t), time)));
1825 
1826     // Steps 7-8.
1827     dateObj->setUTCTime(u, args.rval());
1828     return true;
1829 }
1830 
1831 static bool
1832 date_setMilliseconds(JSContext* cx, unsigned argc, Value* vp)
1833 {
1834     CallArgs args = CallArgsFromVp(argc, vp);
1835     return CallNonGenericMethod<IsDate, date_setMilliseconds_impl>(cx, args);
1836 }
1837 
1838 /* ES5 15.9.5.29. */
1839 MOZ_ALWAYS_INLINE bool
1840 date_setUTCMilliseconds_impl(JSContext* cx, const CallArgs& args)
1841 {
1842     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
1843 
1844     /* Step 1. */
1845     double t = dateObj->UTCTime().toNumber();
1846 
1847     /* Step 2. */
1848     double milli;
1849     if (!ToNumber(cx, args.get(0), &milli))
1850         return false;
1851     double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli);
1852 
1853     /* Step 3. */
1854     ClippedTime v = TimeClip(MakeDate(Day(t), time));
1855 
1856     /* Steps 4-5. */
1857     dateObj->setUTCTime(v, args.rval());
1858     return true;
1859 }
1860 
1861 static bool
1862 date_setUTCMilliseconds(JSContext* cx, unsigned argc, Value* vp)
1863 {
1864     CallArgs args = CallArgsFromVp(argc, vp);
1865     return CallNonGenericMethod<IsDate, date_setUTCMilliseconds_impl>(cx, args);
1866 }
1867 
1868 /* ES5 15.9.5.30. */
1869 MOZ_ALWAYS_INLINE bool
1870 date_setSeconds_impl(JSContext* cx, const CallArgs& args)
1871 {
1872     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
1873 
1874     // Steps 1-2.
1875     double t = LocalTime(dateObj->UTCTime().toNumber());
1876 
1877     // Steps 3-4.
1878     double s;
1879     if (!ToNumber(cx, args.get(0), &s))
1880         return false;
1881 
1882     // Steps 5-6.
1883     double milli;
1884     if (!GetMsecsOrDefault(cx, args, 1, t, &milli))
1885         return false;
1886 
1887     // Step 7.
1888     double date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
1889 
1890     // Step 8.
1891     ClippedTime u = TimeClip(UTC(date));
1892 
1893     // Step 9.
1894     dateObj->setUTCTime(u, args.rval());
1895     return true;
1896 }
1897 
1898 /* ES6 20.3.4.26. */
1899 static bool
1900 date_setSeconds(JSContext* cx, unsigned argc, Value* vp)
1901 {
1902     CallArgs args = CallArgsFromVp(argc, vp);
1903     return CallNonGenericMethod<IsDate, date_setSeconds_impl>(cx, args);
1904 }
1905 
1906 MOZ_ALWAYS_INLINE bool
1907 date_setUTCSeconds_impl(JSContext* cx, const CallArgs& args)
1908 {
1909     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
1910 
1911     /* Step 1. */
1912     double t = dateObj->UTCTime().toNumber();
1913 
1914     /* Step 2. */
1915     double s;
1916     if (!ToNumber(cx, args.get(0), &s))
1917         return false;
1918 
1919     /* Step 3. */
1920     double milli;
1921     if (!GetMsecsOrDefault(cx, args, 1, t, &milli))
1922         return false;
1923 
1924     /* Step 4. */
1925     double date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
1926 
1927     /* Step 5. */
1928     ClippedTime v = TimeClip(date);
1929 
1930     /* Steps 6-7. */
1931     dateObj->setUTCTime(v, args.rval());
1932     return true;
1933 }
1934 
1935 /* ES5 15.9.5.32. */
1936 static bool
1937 date_setUTCSeconds(JSContext* cx, unsigned argc, Value* vp)
1938 {
1939     CallArgs args = CallArgsFromVp(argc, vp);
1940     return CallNonGenericMethod<IsDate, date_setUTCSeconds_impl>(cx, args);
1941 }
1942 
1943 MOZ_ALWAYS_INLINE bool
1944 date_setMinutes_impl(JSContext* cx, const CallArgs& args)
1945 {
1946     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
1947 
1948     // Steps 1-2.
1949     double t = LocalTime(dateObj->UTCTime().toNumber());
1950 
1951     // Steps 3-4.
1952     double m;
1953     if (!ToNumber(cx, args.get(0), &m))
1954         return false;
1955 
1956     // Steps 5-6.
1957     double s;
1958     if (!GetSecsOrDefault(cx, args, 1, t, &s))
1959         return false;
1960 
1961     // Steps 7-8.
1962     double milli;
1963     if (!GetMsecsOrDefault(cx, args, 2, t, &milli))
1964         return false;
1965 
1966     // Step 9.
1967     double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
1968 
1969     // Step 10.
1970     ClippedTime u = TimeClip(UTC(date));
1971 
1972     // Steps 11-12.
1973     dateObj->setUTCTime(u, args.rval());
1974     return true;
1975 }
1976 
1977 /* ES6 20.3.4.24. */
1978 static bool
1979 date_setMinutes(JSContext* cx, unsigned argc, Value* vp)
1980 {
1981     // Steps 1-2 (the effectful parts).
1982     CallArgs args = CallArgsFromVp(argc, vp);
1983     return CallNonGenericMethod<IsDate, date_setMinutes_impl>(cx, args);
1984 }
1985 
1986 MOZ_ALWAYS_INLINE bool
1987 date_setUTCMinutes_impl(JSContext* cx, const CallArgs& args)
1988 {
1989     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
1990 
1991     /* Step 1. */
1992     double t = dateObj->UTCTime().toNumber();
1993 
1994     /* Step 2. */
1995     double m;
1996     if (!ToNumber(cx, args.get(0), &m))
1997         return false;
1998 
1999     /* Step 3. */
2000     double s;
2001     if (!GetSecsOrDefault(cx, args, 1, t, &s))
2002         return false;
2003 
2004     /* Step 4. */
2005     double milli;
2006     if (!GetMsecsOrDefault(cx, args, 2, t, &milli))
2007         return false;
2008 
2009     /* Step 5. */
2010     double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
2011 
2012     /* Step 6. */
2013     ClippedTime v = TimeClip(date);
2014 
2015     /* Steps 7-8. */
2016     dateObj->setUTCTime(v, args.rval());
2017     return true;
2018 }
2019 
2020 /* ES5 15.9.5.34. */
2021 static bool
2022 date_setUTCMinutes(JSContext* cx, unsigned argc, Value* vp)
2023 {
2024     CallArgs args = CallArgsFromVp(argc, vp);
2025     return CallNonGenericMethod<IsDate, date_setUTCMinutes_impl>(cx, args);
2026 }
2027 
2028 MOZ_ALWAYS_INLINE bool
2029 date_setHours_impl(JSContext* cx, const CallArgs& args)
2030 {
2031     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
2032 
2033     // Steps 1-2.
2034     double t = LocalTime(dateObj->UTCTime().toNumber());
2035 
2036     // Steps 3-4.
2037     double h;
2038     if (!ToNumber(cx, args.get(0), &h))
2039         return false;
2040 
2041     // Steps 5-6.
2042     double m;
2043     if (!GetMinsOrDefault(cx, args, 1, t, &m))
2044         return false;
2045 
2046     // Steps 7-8.
2047     double s;
2048     if (!GetSecsOrDefault(cx, args, 2, t, &s))
2049         return false;
2050 
2051     // Steps 9-10.
2052     double milli;
2053     if (!GetMsecsOrDefault(cx, args, 3, t, &milli))
2054         return false;
2055 
2056     // Step 11.
2057     double date = MakeDate(Day(t), MakeTime(h, m, s, milli));
2058 
2059     // Step 12.
2060     ClippedTime u = TimeClip(UTC(date));
2061 
2062     // Steps 13-14.
2063     dateObj->setUTCTime(u, args.rval());
2064     return true;
2065 }
2066 
2067 /* ES5 15.9.5.35. */
2068 static bool
2069 date_setHours(JSContext* cx, unsigned argc, Value* vp)
2070 {
2071     CallArgs args = CallArgsFromVp(argc, vp);
2072     return CallNonGenericMethod<IsDate, date_setHours_impl>(cx, args);
2073 }
2074 
2075 MOZ_ALWAYS_INLINE bool
2076 date_setUTCHours_impl(JSContext* cx, const CallArgs& args)
2077 {
2078     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
2079 
2080     /* Step 1. */
2081     double t = dateObj->UTCTime().toNumber();
2082 
2083     /* Step 2. */
2084     double h;
2085     if (!ToNumber(cx, args.get(0), &h))
2086         return false;
2087 
2088     /* Step 3. */
2089     double m;
2090     if (!GetMinsOrDefault(cx, args, 1, t, &m))
2091         return false;
2092 
2093     /* Step 4. */
2094     double s;
2095     if (!GetSecsOrDefault(cx, args, 2, t, &s))
2096         return false;
2097 
2098     /* Step 5. */
2099     double milli;
2100     if (!GetMsecsOrDefault(cx, args, 3, t, &milli))
2101         return false;
2102 
2103     /* Step 6. */
2104     double newDate = MakeDate(Day(t), MakeTime(h, m, s, milli));
2105 
2106     /* Step 7. */
2107     ClippedTime v = TimeClip(newDate);
2108 
2109     /* Steps 8-9. */
2110     dateObj->setUTCTime(v, args.rval());
2111     return true;
2112 }
2113 
2114 /* ES5 15.9.5.36. */
2115 static bool
2116 date_setUTCHours(JSContext* cx, unsigned argc, Value* vp)
2117 {
2118     CallArgs args = CallArgsFromVp(argc, vp);
2119     return CallNonGenericMethod<IsDate, date_setUTCHours_impl>(cx, args);
2120 }
2121 
2122 MOZ_ALWAYS_INLINE bool
2123 date_setDate_impl(JSContext* cx, const CallArgs& args)
2124 {
2125     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
2126 
2127     /* Step 1. */
2128     double t = LocalTime(dateObj->UTCTime().toNumber());
2129 
2130     /* Step 2. */
2131     double date;
2132     if (!ToNumber(cx, args.get(0), &date))
2133         return false;
2134 
2135     /* Step 3. */
2136     double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t));
2137 
2138     /* Step 4. */
2139     ClippedTime u = TimeClip(UTC(newDate));
2140 
2141     /* Steps 5-6. */
2142     dateObj->setUTCTime(u, args.rval());
2143     return true;
2144 }
2145 
2146 /* ES5 15.9.5.37. */
2147 static bool
2148 date_setDate(JSContext* cx, unsigned argc, Value* vp)
2149 {
2150     CallArgs args = CallArgsFromVp(argc, vp);
2151     return CallNonGenericMethod<IsDate, date_setDate_impl>(cx, args);
2152 }
2153 
2154 MOZ_ALWAYS_INLINE bool
2155 date_setUTCDate_impl(JSContext* cx, const CallArgs& args)
2156 {
2157     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
2158 
2159     /* Step 1. */
2160     double t = dateObj->UTCTime().toNumber();
2161 
2162     /* Step 2. */
2163     double date;
2164     if (!ToNumber(cx, args.get(0), &date))
2165         return false;
2166 
2167     /* Step 3. */
2168     double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t));
2169 
2170     /* Step 4. */
2171     ClippedTime v = TimeClip(newDate);
2172 
2173     /* Steps 5-6. */
2174     dateObj->setUTCTime(v, args.rval());
2175     return true;
2176 }
2177 
2178 static bool
2179 date_setUTCDate(JSContext* cx, unsigned argc, Value* vp)
2180 {
2181     CallArgs args = CallArgsFromVp(argc, vp);
2182     return CallNonGenericMethod<IsDate, date_setUTCDate_impl>(cx, args);
2183 }
2184 
2185 static bool
2186 GetDateOrDefault(JSContext* cx, const CallArgs& args, unsigned i, double t, double* date)
2187 {
2188     if (args.length() <= i) {
2189         *date = DateFromTime(t);
2190         return true;
2191     }
2192     return ToNumber(cx, args[i], date);
2193 }
2194 
2195 static bool
2196 GetMonthOrDefault(JSContext* cx, const CallArgs& args, unsigned i, double t, double* month)
2197 {
2198     if (args.length() <= i) {
2199         *month = MonthFromTime(t);
2200         return true;
2201     }
2202     return ToNumber(cx, args[i], month);
2203 }
2204 
2205 /* ES5 15.9.5.38. */
2206 MOZ_ALWAYS_INLINE bool
2207 date_setMonth_impl(JSContext* cx, const CallArgs& args)
2208 {
2209     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
2210 
2211     /* Step 1. */
2212     double t = LocalTime(dateObj->UTCTime().toNumber());
2213 
2214     /* Step 2. */
2215     double m;
2216     if (!ToNumber(cx, args.get(0), &m))
2217         return false;
2218 
2219     /* Step 3. */
2220     double date;
2221     if (!GetDateOrDefault(cx, args, 1, t, &date))
2222         return false;
2223 
2224     /* Step 4. */
2225     double newDate = MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t));
2226 
2227     /* Step 5. */
2228     ClippedTime u = TimeClip(UTC(newDate));
2229 
2230     /* Steps 6-7. */
2231     dateObj->setUTCTime(u, args.rval());
2232     return true;
2233 }
2234 
2235 static bool
2236 date_setMonth(JSContext* cx, unsigned argc, Value* vp)
2237 {
2238     CallArgs args = CallArgsFromVp(argc, vp);
2239     return CallNonGenericMethod<IsDate, date_setMonth_impl>(cx, args);
2240 }
2241 
2242 /* ES5 15.9.5.39. */
2243 MOZ_ALWAYS_INLINE bool
2244 date_setUTCMonth_impl(JSContext* cx, const CallArgs& args)
2245 {
2246     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
2247 
2248     /* Step 1. */
2249     double t = dateObj->UTCTime().toNumber();
2250 
2251     /* Step 2. */
2252     double m;
2253     if (!ToNumber(cx, args.get(0), &m))
2254         return false;
2255 
2256     /* Step 3. */
2257     double date;
2258     if (!GetDateOrDefault(cx, args, 1, t, &date))
2259         return false;
2260 
2261     /* Step 4. */
2262     double newDate = MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t));
2263 
2264     /* Step 5. */
2265     ClippedTime v = TimeClip(newDate);
2266 
2267     /* Steps 6-7. */
2268     dateObj->setUTCTime(v, args.rval());
2269     return true;
2270 }
2271 
2272 static bool
2273 date_setUTCMonth(JSContext* cx, unsigned argc, Value* vp)
2274 {
2275     CallArgs args = CallArgsFromVp(argc, vp);
2276     return CallNonGenericMethod<IsDate, date_setUTCMonth_impl>(cx, args);
2277 }
2278 
2279 static double
2280 ThisLocalTimeOrZero(Handle<DateObject*> dateObj)
2281 {
2282     double t = dateObj->UTCTime().toNumber();
2283     if (IsNaN(t))
2284         return +0;
2285     return LocalTime(t);
2286 }
2287 
2288 static double
2289 ThisUTCTimeOrZero(Handle<DateObject*> dateObj)
2290 {
2291     double t = dateObj->as<DateObject>().UTCTime().toNumber();
2292     return IsNaN(t) ? +0 : t;
2293 }
2294 
2295 /* ES5 15.9.5.40. */
2296 MOZ_ALWAYS_INLINE bool
2297 date_setFullYear_impl(JSContext* cx, const CallArgs& args)
2298 {
2299     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
2300 
2301     /* Step 1. */
2302     double t = ThisLocalTimeOrZero(dateObj);
2303 
2304     /* Step 2. */
2305     double y;
2306     if (!ToNumber(cx, args.get(0), &y))
2307         return false;
2308 
2309     /* Step 3. */
2310     double m;
2311     if (!GetMonthOrDefault(cx, args, 1, t, &m))
2312         return false;
2313 
2314     /* Step 4. */
2315     double date;
2316     if (!GetDateOrDefault(cx, args, 2, t, &date))
2317         return false;
2318 
2319     /* Step 5. */
2320     double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t));
2321 
2322     /* Step 6. */
2323     ClippedTime u = TimeClip(UTC(newDate));
2324 
2325     /* Steps 7-8. */
2326     dateObj->setUTCTime(u, args.rval());
2327     return true;
2328 }
2329 
2330 static bool
2331 date_setFullYear(JSContext* cx, unsigned argc, Value* vp)
2332 {
2333     CallArgs args = CallArgsFromVp(argc, vp);
2334     return CallNonGenericMethod<IsDate, date_setFullYear_impl>(cx, args);
2335 }
2336 
2337 /* ES5 15.9.5.41. */
2338 MOZ_ALWAYS_INLINE bool
2339 date_setUTCFullYear_impl(JSContext* cx, const CallArgs& args)
2340 {
2341     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
2342 
2343     /* Step 1. */
2344     double t = ThisUTCTimeOrZero(dateObj);
2345 
2346     /* Step 2. */
2347     double y;
2348     if (!ToNumber(cx, args.get(0), &y))
2349         return false;
2350 
2351     /* Step 3. */
2352     double m;
2353     if (!GetMonthOrDefault(cx, args, 1, t, &m))
2354         return false;
2355 
2356     /* Step 4. */
2357     double date;
2358     if (!GetDateOrDefault(cx, args, 2, t, &date))
2359         return false;
2360 
2361     /* Step 5. */
2362     double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t));
2363 
2364     /* Step 6. */
2365     ClippedTime v = TimeClip(newDate);
2366 
2367     /* Steps 7-8. */
2368     dateObj->setUTCTime(v, args.rval());
2369     return true;
2370 }
2371 
2372 static bool
2373 date_setUTCFullYear(JSContext* cx, unsigned argc, Value* vp)
2374 {
2375     CallArgs args = CallArgsFromVp(argc, vp);
2376     return CallNonGenericMethod<IsDate, date_setUTCFullYear_impl>(cx, args);
2377 }
2378 
2379 /* ES5 Annex B.2.5. */
2380 MOZ_ALWAYS_INLINE bool
2381 date_setYear_impl(JSContext* cx, const CallArgs& args)
2382 {
2383     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
2384 
2385     /* Step 1. */
2386     double t = ThisLocalTimeOrZero(dateObj);
2387 
2388     /* Step 2. */
2389     double y;
2390     if (!ToNumber(cx, args.get(0), &y))
2391         return false;
2392 
2393     /* Step 3. */
2394     if (IsNaN(y)) {
2395         dateObj->setUTCTime(ClippedTime::invalid(), args.rval());
2396         return true;
2397     }
2398 
2399     /* Step 4. */
2400     double yint = ToInteger(y);
2401     if (0 <= yint && yint <= 99)
2402         yint += 1900;
2403 
2404     /* Step 5. */
2405     double day = MakeDay(yint, MonthFromTime(t), DateFromTime(t));
2406 
2407     /* Step 6. */
2408     double u = UTC(MakeDate(day, TimeWithinDay(t)));
2409 
2410     /* Steps 7-8. */
2411     dateObj->setUTCTime(TimeClip(u), args.rval());
2412     return true;
2413 }
2414 
2415 static bool
2416 date_setYear(JSContext* cx, unsigned argc, Value* vp)
2417 {
2418     CallArgs args = CallArgsFromVp(argc, vp);
2419     return CallNonGenericMethod<IsDate, date_setYear_impl>(cx, args);
2420 }
2421 
2422 /* constants for toString, toUTCString */
2423 static const char js_NaN_date_str[] = "Invalid Date";
2424 static const char * const days[] =
2425 {
2426    "Sun","Mon","Tue","Wed","Thu","Fri","Sat"
2427 };
2428 static const char * const months[] =
2429 {
2430    "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
2431 };
2432 
2433 /* ES5 B.2.6. */
2434 MOZ_ALWAYS_INLINE bool
2435 date_toGMTString_impl(JSContext* cx, const CallArgs& args)
2436 {
2437     double utctime = args.thisv().toObject().as<DateObject>().UTCTime().toNumber();
2438 
2439     JSString* str;
2440     if (!IsFinite(utctime)) {
2441         str = NewStringCopyZ<CanGC>(cx, js_NaN_date_str);
2442     } else {
2443         char buf[100];
2444         SprintfLiteral(buf, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
2445                        days[int(WeekDay(utctime))],
2446                        int(DateFromTime(utctime)),
2447                        months[int(MonthFromTime(utctime))],
2448                        int(YearFromTime(utctime)),
2449                        int(HourFromTime(utctime)),
2450                        int(MinFromTime(utctime)),
2451                        int(SecFromTime(utctime)));
2452 
2453         str = NewStringCopyZ<CanGC>(cx, buf);
2454     }
2455 
2456     if (!str)
2457         return false;
2458     args.rval().setString(str);
2459     return true;
2460 }
2461 
2462 static bool
2463 date_toGMTString(JSContext* cx, unsigned argc, Value* vp)
2464 {
2465     CallArgs args = CallArgsFromVp(argc, vp);
2466     return CallNonGenericMethod<IsDate, date_toGMTString_impl>(cx, args);
2467 }
2468 
2469 /* ES6 draft 2015-01-15 20.3.4.36. */
2470 MOZ_ALWAYS_INLINE bool
2471 date_toISOString_impl(JSContext* cx, const CallArgs& args)
2472 {
2473     double utctime = args.thisv().toObject().as<DateObject>().UTCTime().toNumber();
2474     if (!IsFinite(utctime)) {
2475         JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_INVALID_DATE);
2476         return false;
2477     }
2478 
2479     char buf[100];
2480     int year = int(YearFromTime(utctime));
2481     if (year < 0 || year > 9999) {
2482         SprintfLiteral(buf, "%+.6d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
2483                        int(YearFromTime(utctime)),
2484                        int(MonthFromTime(utctime)) + 1,
2485                        int(DateFromTime(utctime)),
2486                        int(HourFromTime(utctime)),
2487                        int(MinFromTime(utctime)),
2488                        int(SecFromTime(utctime)),
2489                        int(msFromTime(utctime)));
2490     } else {
2491         SprintfLiteral(buf, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
2492                        int(YearFromTime(utctime)),
2493                        int(MonthFromTime(utctime)) + 1,
2494                        int(DateFromTime(utctime)),
2495                        int(HourFromTime(utctime)),
2496                        int(MinFromTime(utctime)),
2497                        int(SecFromTime(utctime)),
2498                        int(msFromTime(utctime)));
2499     }
2500 
2501     JSString* str = NewStringCopyZ<CanGC>(cx, buf);
2502     if (!str)
2503         return false;
2504     args.rval().setString(str);
2505     return true;
2506 
2507 }
2508 
2509 static bool
2510 date_toISOString(JSContext* cx, unsigned argc, Value* vp)
2511 {
2512     CallArgs args = CallArgsFromVp(argc, vp);
2513     return CallNonGenericMethod<IsDate, date_toISOString_impl>(cx, args);
2514 }
2515 
2516 /* ES5 15.9.5.44. */
2517 static bool
2518 date_toJSON(JSContext* cx, unsigned argc, Value* vp)
2519 {
2520     CallArgs args = CallArgsFromVp(argc, vp);
2521 
2522     /* Step 1. */
2523     RootedObject obj(cx, ToObject(cx, args.thisv()));
2524     if (!obj)
2525         return false;
2526 
2527     /* Step 2. */
2528     RootedValue tv(cx, ObjectValue(*obj));
2529     if (!ToPrimitive(cx, JSTYPE_NUMBER, &tv))
2530         return false;
2531 
2532     /* Step 3. */
2533     if (tv.isDouble() && !IsFinite(tv.toDouble())) {
2534         args.rval().setNull();
2535         return true;
2536     }
2537 
2538     /* Step 4. */
2539     RootedValue toISO(cx);
2540     if (!GetProperty(cx, obj, obj, cx->names().toISOString, &toISO))
2541         return false;
2542 
2543     /* Step 5. */
2544     if (!IsCallable(toISO)) {
2545         JS_ReportErrorFlagsAndNumberASCII(cx, JSREPORT_ERROR, js::GetErrorMessage, nullptr,
2546                                           JSMSG_BAD_TOISOSTRING_PROP);
2547         return false;
2548     }
2549 
2550     /* Step 6. */
2551     return Call(cx, toISO, obj, args.rval());
2552 }
2553 
2554 /* Interface to PRMJTime date struct. */
2555 static PRMJTime
2556 ToPRMJTime(double localTime)
2557 {
2558     double year = YearFromTime(localTime);
2559 
2560     PRMJTime prtm;
2561     prtm.tm_usec = int32_t(msFromTime(localTime)) * 1000;
2562     prtm.tm_sec = int8_t(SecFromTime(localTime));
2563     prtm.tm_min = int8_t(MinFromTime(localTime));
2564     prtm.tm_hour = int8_t(HourFromTime(localTime));
2565     prtm.tm_mday = int8_t(DateFromTime(localTime));
2566     prtm.tm_mon = int8_t(MonthFromTime(localTime));
2567     prtm.tm_wday = int8_t(WeekDay(localTime));
2568     prtm.tm_year = year;
2569     prtm.tm_yday = int16_t(DayWithinYear(localTime, year));
2570 
2571     // XXX: DaylightSavingTA expects utc-time argument.
2572     prtm.tm_isdst = (DaylightSavingTA(localTime) != 0);
2573 
2574     return prtm;
2575 }
2576 
2577 enum class FormatSpec {
2578     DateTime,
2579     Date,
2580     Time
2581 };
2582 
2583 static bool
2584 FormatDate(JSContext* cx, double utcTime, FormatSpec format, MutableHandleValue rval)
2585 {
2586     JSString* str;
2587     if (!IsFinite(utcTime)) {
2588         str = NewStringCopyZ<CanGC>(cx, js_NaN_date_str);
2589     } else {
2590         MOZ_ASSERT(NumbersAreIdentical(TimeClip(utcTime).toDouble(), utcTime));
2591 
2592         double localTime = LocalTime(utcTime);
2593 
2594         int offset = 0;
2595         char tzbuf[100];
2596         bool usetz = false;
2597         if (format == FormatSpec::DateTime || format == FormatSpec::Time) {
2598             /*
2599              * Offset from GMT in minutes.  The offset includes daylight
2600              * savings, if it applies.
2601              */
2602             int minutes = (int) floor((localTime - utcTime) / msPerMinute);
2603 
2604             /* Map 510 minutes to 0830 hours. */
2605             offset = (minutes / 60) * 100 + minutes % 60;
2606 
2607             /*
2608              * Print as "Wed Nov 05 19:38:03 GMT-0800 (PST) 1997".
2609              *
2610              * The TZA is printed as 'GMT-0800' rather than as 'PST' to avoid
2611              * operating-system dependence on strftime (which PRMJ_FormatTime
2612              * calls, for %Z only.)  win32 prints PST as
2613              * 'Pacific Standard Time.'  This way we always know what we're
2614              * getting, and can parse it if we produce it.  The OS time zone
2615              * string is included as a comment.
2616              */
2617 
2618             /* get a time zone string from the OS to include as a comment. */
2619             PRMJTime prtm = ToPRMJTime(utcTime);
2620             size_t tzlen = PRMJ_FormatTime(tzbuf, sizeof tzbuf, "(%Z)", &prtm);
2621             if (tzlen != 0) {
2622                 /*
2623                  * Decide whether to use the resulting time zone string.
2624                  *
2625                  * Reject it if it contains any non-ASCII, non-alphanumeric
2626                  * characters.  It's then likely in some other character
2627                  * encoding, and we probably won't display it correctly.
2628                  */
2629                 usetz = true;
2630                 for (size_t i = 0; i < tzlen; i++) {
2631                     char16_t c = tzbuf[i];
2632                     if (c > 127 || !(isalnum(c) || c == ' ' || c == '(' || c == ')' || c == '.')) {
2633                         usetz = false;
2634                         break;
2635                     }
2636                 }
2637 
2638                 /* Also reject it if it's not parenthesized or if it's '()'. */
2639                 if (tzbuf[0] != '(' || tzbuf[1] == ')')
2640                     usetz = false;
2641             }
2642         }
2643 
2644         char buf[100];
2645         switch (format) {
2646           case FormatSpec::DateTime:
2647             /* Tue Oct 31 2000 09:41:40 GMT-0800 (PST) */
2648             SprintfLiteral(buf, "%s %s %.2d %.4d %.2d:%.2d:%.2d GMT%+.4d%s%s",
2649                            days[int(WeekDay(localTime))],
2650                            months[int(MonthFromTime(localTime))],
2651                            int(DateFromTime(localTime)),
2652                            int(YearFromTime(localTime)),
2653                            int(HourFromTime(localTime)),
2654                            int(MinFromTime(localTime)),
2655                            int(SecFromTime(localTime)),
2656                            offset,
2657                            usetz ? " " : "",
2658                            usetz ? tzbuf : "");
2659             break;
2660           case FormatSpec::Date:
2661             /* Tue Oct 31 2000 */
2662             SprintfLiteral(buf, "%s %s %.2d %.4d",
2663                            days[int(WeekDay(localTime))],
2664                            months[int(MonthFromTime(localTime))],
2665                            int(DateFromTime(localTime)),
2666                            int(YearFromTime(localTime)));
2667             break;
2668           case FormatSpec::Time:
2669             /* 09:41:40 GMT-0800 (PST) */
2670             SprintfLiteral(buf, "%.2d:%.2d:%.2d GMT%+.4d%s%s",
2671                            int(HourFromTime(localTime)),
2672                            int(MinFromTime(localTime)),
2673                            int(SecFromTime(localTime)),
2674                            offset,
2675                            usetz ? " " : "",
2676                            usetz ? tzbuf : "");
2677             break;
2678         }
2679 
2680         str = NewStringCopyZ<CanGC>(cx, buf);
2681     }
2682 
2683     if (!str)
2684         return false;
2685     rval.setString(str);
2686     return true;
2687 }
2688 
2689 static bool
2690 ToLocaleFormatHelper(JSContext* cx, HandleObject obj, const char* format, MutableHandleValue rval)
2691 {
2692     double utcTime = obj->as<DateObject>().UTCTime().toNumber();
2693 
2694     char buf[100];
2695     if (!IsFinite(utcTime)) {
2696         strcpy(buf, js_NaN_date_str);
2697     } else {
2698         double localTime = LocalTime(utcTime);
2699         PRMJTime prtm = ToPRMJTime(localTime);
2700 
2701         /* Let PRMJTime format it. */
2702         size_t result_len = PRMJ_FormatTime(buf, sizeof buf, format, &prtm);
2703 
2704         /* If it failed, default to toString. */
2705         if (result_len == 0)
2706             return FormatDate(cx, utcTime, FormatSpec::DateTime, rval);
2707 
2708         /* Hacked check against undesired 2-digit year 00/00/00 form. */
2709         if (strcmp(format, "%x") == 0 && result_len >= 6 &&
2710             /* Format %x means use OS settings, which may have 2-digit yr, so
2711                hack end of 3/11/22 or 11.03.22 or 11Mar22 to use 4-digit yr...*/
2712             !isdigit(buf[result_len - 3]) &&
2713             isdigit(buf[result_len - 2]) && isdigit(buf[result_len - 1]) &&
2714             /* ...but not if starts with 4-digit year, like 2022/3/11. */
2715             !(isdigit(buf[0]) && isdigit(buf[1]) &&
2716               isdigit(buf[2]) && isdigit(buf[3])))
2717         {
2718             int year = int(YearFromTime(localTime));
2719             snprintf(buf + (result_len - 2), (sizeof buf) - (result_len - 2), "%d", year);
2720         }
2721 
2722     }
2723 
2724     if (cx->runtime()->localeCallbacks && cx->runtime()->localeCallbacks->localeToUnicode)
2725         return cx->runtime()->localeCallbacks->localeToUnicode(cx, buf, rval);
2726 
2727     JSString* str = NewStringCopyZ<CanGC>(cx, buf);
2728     if (!str)
2729         return false;
2730     rval.setString(str);
2731     return true;
2732 }
2733 
2734 #if !EXPOSE_INTL_API
2735 /* ES5 15.9.5.5. */
2736 MOZ_ALWAYS_INLINE bool
2737 date_toLocaleString_impl(JSContext* cx, const CallArgs& args)
2738 {
2739     /*
2740      * Use '%#c' for windows, because '%c' is backward-compatible and non-y2k
2741      * with msvc; '%#c' requests that a full year be used in the result string.
2742      */
2743     static const char format[] =
2744 #if defined(_WIN32) && !defined(__MWERKS__)
2745                                    "%#c"
2746 #else
2747                                    "%c"
2748 #endif
2749                                    ;
2750 
2751     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
2752     return ToLocaleFormatHelper(cx, dateObj, format, args.rval());
2753 }
2754 
2755 static bool
2756 date_toLocaleString(JSContext* cx, unsigned argc, Value* vp)
2757 {
2758     CallArgs args = CallArgsFromVp(argc, vp);
2759     return CallNonGenericMethod<IsDate, date_toLocaleString_impl>(cx, args);
2760 }
2761 
2762 /* ES5 15.9.5.6. */
2763 MOZ_ALWAYS_INLINE bool
2764 date_toLocaleDateString_impl(JSContext* cx, const CallArgs& args)
2765 {
2766     /*
2767      * Use '%#x' for windows, because '%x' is backward-compatible and non-y2k
2768      * with msvc; '%#x' requests that a full year be used in the result string.
2769      */
2770     static const char format[] =
2771 #if defined(_WIN32) && !defined(__MWERKS__)
2772                                    "%#x"
2773 #else
2774                                    "%x"
2775 #endif
2776                                    ;
2777 
2778     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
2779     return ToLocaleFormatHelper(cx, dateObj, format, args.rval());
2780 }
2781 
2782 static bool
2783 date_toLocaleDateString(JSContext* cx, unsigned argc, Value* vp)
2784 {
2785     CallArgs args = CallArgsFromVp(argc, vp);
2786     return CallNonGenericMethod<IsDate, date_toLocaleDateString_impl>(cx, args);
2787 }
2788 
2789 /* ES5 15.9.5.7. */
2790 MOZ_ALWAYS_INLINE bool
2791 date_toLocaleTimeString_impl(JSContext* cx, const CallArgs& args)
2792 {
2793     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
2794     return ToLocaleFormatHelper(cx, dateObj, "%X", args.rval());
2795 }
2796 
2797 static bool
2798 date_toLocaleTimeString(JSContext* cx, unsigned argc, Value* vp)
2799 {
2800     CallArgs args = CallArgsFromVp(argc, vp);
2801     return CallNonGenericMethod<IsDate, date_toLocaleTimeString_impl>(cx, args);
2802 }
2803 #endif /* !EXPOSE_INTL_API */
2804 
2805 MOZ_ALWAYS_INLINE bool
2806 date_toLocaleFormat_impl(JSContext* cx, const CallArgs& args)
2807 {
2808     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
2809 
2810     if (args.length() == 0) {
2811         /*
2812          * Use '%#c' for windows, because '%c' is backward-compatible and non-y2k
2813          * with msvc; '%#c' requests that a full year be used in the result string.
2814          */
2815         static const char format[] =
2816 #if defined(_WIN32) && !defined(__MWERKS__)
2817                                        "%#c"
2818 #else
2819                                        "%c"
2820 #endif
2821                                        ;
2822 
2823         return ToLocaleFormatHelper(cx, dateObj, format, args.rval());
2824     }
2825 
2826     RootedString fmt(cx, ToString<CanGC>(cx, args[0]));
2827     if (!fmt)
2828         return false;
2829 
2830     JSAutoByteString fmtbytes(cx, fmt);
2831     if (!fmtbytes)
2832         return false;
2833 
2834     return ToLocaleFormatHelper(cx, dateObj, fmtbytes.ptr(), args.rval());
2835 }
2836 
2837 static bool
2838 date_toLocaleFormat(JSContext* cx, unsigned argc, Value* vp)
2839 {
2840     CallArgs args = CallArgsFromVp(argc, vp);
2841     return CallNonGenericMethod<IsDate, date_toLocaleFormat_impl>(cx, args);
2842 }
2843 
2844 /* ES5 15.9.5.4. */
2845 MOZ_ALWAYS_INLINE bool
2846 date_toTimeString_impl(JSContext* cx, const CallArgs& args)
2847 {
2848     return FormatDate(cx, args.thisv().toObject().as<DateObject>().UTCTime().toNumber(),
2849                       FormatSpec::Time, args.rval());
2850 }
2851 
2852 static bool
2853 date_toTimeString(JSContext* cx, unsigned argc, Value* vp)
2854 {
2855     CallArgs args = CallArgsFromVp(argc, vp);
2856     return CallNonGenericMethod<IsDate, date_toTimeString_impl>(cx, args);
2857 }
2858 
2859 /* ES5 15.9.5.3. */
2860 MOZ_ALWAYS_INLINE bool
2861 date_toDateString_impl(JSContext* cx, const CallArgs& args)
2862 {
2863     return FormatDate(cx, args.thisv().toObject().as<DateObject>().UTCTime().toNumber(),
2864                       FormatSpec::Date, args.rval());
2865 }
2866 
2867 static bool
2868 date_toDateString(JSContext* cx, unsigned argc, Value* vp)
2869 {
2870     CallArgs args = CallArgsFromVp(argc, vp);
2871     return CallNonGenericMethod<IsDate, date_toDateString_impl>(cx, args);
2872 }
2873 
2874 #if JS_HAS_TOSOURCE
2875 MOZ_ALWAYS_INLINE bool
2876 date_toSource_impl(JSContext* cx, const CallArgs& args)
2877 {
2878     StringBuffer sb(cx);
2879     if (!sb.append("(new Date(") ||
2880         !NumberValueToStringBuffer(cx, args.thisv().toObject().as<DateObject>().UTCTime(), sb) ||
2881         !sb.append("))"))
2882     {
2883         return false;
2884     }
2885 
2886     JSString* str = sb.finishString();
2887     if (!str)
2888         return false;
2889     args.rval().setString(str);
2890     return true;
2891 }
2892 
2893 static bool
2894 date_toSource(JSContext* cx, unsigned argc, Value* vp)
2895 {
2896     CallArgs args = CallArgsFromVp(argc, vp);
2897     return CallNonGenericMethod<IsDate, date_toSource_impl>(cx, args);
2898 }
2899 #endif
2900 
2901 MOZ_ALWAYS_INLINE bool
2902 IsObject(HandleValue v)
2903 {
2904     return v.isObject();
2905 }
2906 
2907 // ES6 20.3.4.41.
2908 MOZ_ALWAYS_INLINE bool
2909 date_toString_impl(JSContext* cx, const CallArgs& args)
2910 {
2911     // Step 1.
2912     RootedObject obj(cx, &args.thisv().toObject());
2913 
2914     // Step 2.
2915     ESClass cls;
2916     if (!GetBuiltinClass(cx, obj, &cls))
2917         return false;
2918 
2919     double tv;
2920     if (cls != ESClass::Date) {
2921         // Step 2.
2922         tv = GenericNaN();
2923     } else {
2924         // Step 3.
2925         RootedValue unboxed(cx);
2926         if (!Unbox(cx, obj, &unboxed))
2927             return false;
2928 
2929         tv = unboxed.toNumber();
2930     }
2931 
2932     // Step 4.
2933     return FormatDate(cx, tv, FormatSpec::DateTime, args.rval());
2934 }
2935 
2936 bool
2937 date_toString(JSContext* cx, unsigned argc, Value* vp)
2938 {
2939     // Step 1.
2940     CallArgs args = CallArgsFromVp(argc, vp);
2941     return CallNonGenericMethod<IsObject, date_toString_impl>(cx, args);
2942 }
2943 
2944 MOZ_ALWAYS_INLINE bool
2945 date_valueOf_impl(JSContext* cx, const CallArgs& args)
2946 {
2947     Rooted<DateObject*> dateObj(cx, &args.thisv().toObject().as<DateObject>());
2948     args.rval().set(dateObj->UTCTime());
2949     return true;
2950 }
2951 
2952 bool
2953 js::date_valueOf(JSContext* cx, unsigned argc, Value* vp)
2954 {
2955     CallArgs args = CallArgsFromVp(argc, vp);
2956     return CallNonGenericMethod<IsDate, date_valueOf_impl>(cx, args);
2957 }
2958 
2959 // ES6 20.3.4.45 Date.prototype[@@toPrimitive]
2960 static bool
2961 date_toPrimitive(JSContext* cx, unsigned argc, Value* vp)
2962 {
2963     CallArgs args = CallArgsFromVp(argc, vp);
2964 
2965     // Steps 1-2.
2966     if (!args.thisv().isObject()) {
2967         ReportIncompatible(cx, args);
2968         return false;
2969     }
2970 
2971     // Steps 3-5.
2972     JSType hint;
2973     if (!GetFirstArgumentAsTypeHint(cx, args, &hint))
2974         return false;
2975     if (hint == JSTYPE_VOID)
2976         hint = JSTYPE_STRING;
2977 
2978     args.rval().set(args.thisv());
2979     RootedObject obj(cx, &args.thisv().toObject());
2980     return OrdinaryToPrimitive(cx, obj, hint, args.rval());
2981 }
2982 
2983 static const JSFunctionSpec date_static_methods[] = {
2984     JS_FN("UTC",                 date_UTC,                7,0),
2985     JS_FN("parse",               date_parse,              1,0),
2986     JS_FN("now",                 date_now,                0,0),
2987     JS_FS_END
2988 };
2989 
2990 static const JSFunctionSpec date_methods[] = {
2991     JS_FN("getTime",             date_getTime,            0,0),
2992     JS_FN("getTimezoneOffset",   date_getTimezoneOffset,  0,0),
2993     JS_FN("getYear",             date_getYear,            0,0),
2994     JS_FN("getFullYear",         date_getFullYear,        0,0),
2995     JS_FN("getUTCFullYear",      date_getUTCFullYear,     0,0),
2996     JS_FN("getMonth",            date_getMonth,           0,0),
2997     JS_FN("getUTCMonth",         date_getUTCMonth,        0,0),
2998     JS_FN("getDate",             date_getDate,            0,0),
2999     JS_FN("getUTCDate",          date_getUTCDate,         0,0),
3000     JS_FN("getDay",              date_getDay,             0,0),
3001     JS_FN("getUTCDay",           date_getUTCDay,          0,0),
3002     JS_FN("getHours",            date_getHours,           0,0),
3003     JS_FN("getUTCHours",         date_getUTCHours,        0,0),
3004     JS_FN("getMinutes",          date_getMinutes,         0,0),
3005     JS_FN("getUTCMinutes",       date_getUTCMinutes,      0,0),
3006     JS_FN("getSeconds",          date_getUTCSeconds,      0,0),
3007     JS_FN("getUTCSeconds",       date_getUTCSeconds,      0,0),
3008     JS_FN("getMilliseconds",     date_getUTCMilliseconds, 0,0),
3009     JS_FN("getUTCMilliseconds",  date_getUTCMilliseconds, 0,0),
3010     JS_FN("setTime",             date_setTime,            1,0),
3011     JS_FN("setYear",             date_setYear,            1,0),
3012     JS_FN("setFullYear",         date_setFullYear,        3,0),
3013     JS_FN("setUTCFullYear",      date_setUTCFullYear,     3,0),
3014     JS_FN("setMonth",            date_setMonth,           2,0),
3015     JS_FN("setUTCMonth",         date_setUTCMonth,        2,0),
3016     JS_FN("setDate",             date_setDate,            1,0),
3017     JS_FN("setUTCDate",          date_setUTCDate,         1,0),
3018     JS_FN("setHours",            date_setHours,           4,0),
3019     JS_FN("setUTCHours",         date_setUTCHours,        4,0),
3020     JS_FN("setMinutes",          date_setMinutes,         3,0),
3021     JS_FN("setUTCMinutes",       date_setUTCMinutes,      3,0),
3022     JS_FN("setSeconds",          date_setSeconds,         2,0),
3023     JS_FN("setUTCSeconds",       date_setUTCSeconds,      2,0),
3024     JS_FN("setMilliseconds",     date_setMilliseconds,    1,0),
3025     JS_FN("setUTCMilliseconds",  date_setUTCMilliseconds, 1,0),
3026     JS_FN("toUTCString",         date_toGMTString,        0,0),
3027     JS_FN("toLocaleFormat",      date_toLocaleFormat,     0,0),
3028 #if EXPOSE_INTL_API
3029     JS_SELF_HOSTED_FN(js_toLocaleString_str, "Date_toLocaleString", 0,0),
3030     JS_SELF_HOSTED_FN("toLocaleDateString", "Date_toLocaleDateString", 0,0),
3031     JS_SELF_HOSTED_FN("toLocaleTimeString", "Date_toLocaleTimeString", 0,0),
3032 #else
3033     JS_FN(js_toLocaleString_str, date_toLocaleString,     0,0),
3034     JS_FN("toLocaleDateString",  date_toLocaleDateString, 0,0),
3035     JS_FN("toLocaleTimeString",  date_toLocaleTimeString, 0,0),
3036 #endif
3037     JS_FN("toDateString",        date_toDateString,       0,0),
3038     JS_FN("toTimeString",        date_toTimeString,       0,0),
3039     JS_FN("toISOString",         date_toISOString,        0,0),
3040     JS_FN(js_toJSON_str,         date_toJSON,             1,0),
3041 #if JS_HAS_TOSOURCE
3042     JS_FN(js_toSource_str,       date_toSource,           0,0),
3043 #endif
3044     JS_FN(js_toString_str,       date_toString,           0,0),
3045     JS_FN(js_valueOf_str,        date_valueOf,            0,0),
3046     JS_SYM_FN(toPrimitive,       date_toPrimitive,        1,JSPROP_READONLY),
3047     JS_FS_END
3048 };
3049 
3050 static bool
3051 NewDateObject(JSContext* cx, const CallArgs& args, ClippedTime t)
3052 {
3053     MOZ_ASSERT(args.isConstructing());
3054 
3055     RootedObject proto(cx);
3056     RootedObject newTarget(cx, &args.newTarget().toObject());
3057     if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
3058         return false;
3059 
3060     JSObject* obj = NewDateObjectMsec(cx, t, proto);
3061     if (!obj)
3062         return false;
3063 
3064     args.rval().setObject(*obj);
3065     return true;
3066 }
3067 
3068 static bool
3069 ToDateString(JSContext* cx, const CallArgs& args, ClippedTime t)
3070 {
3071     return FormatDate(cx, t.toDouble(), FormatSpec::DateTime, args.rval());
3072 }
3073 
3074 static bool
3075 DateNoArguments(JSContext* cx, const CallArgs& args)
3076 {
3077     MOZ_ASSERT(args.length() == 0);
3078 
3079     ClippedTime now = NowAsMillis();
3080 
3081     if (args.isConstructing())
3082         return NewDateObject(cx, args, now);
3083 
3084     return ToDateString(cx, args, now);
3085 }
3086 
3087 static bool
3088 DateOneArgument(JSContext* cx, const CallArgs& args)
3089 {
3090     MOZ_ASSERT(args.length() == 1);
3091 
3092     if (args.isConstructing()) {
3093         if (args[0].isObject()) {
3094             RootedObject obj(cx, &args[0].toObject());
3095 
3096             ESClass cls;
3097             if (!GetBuiltinClass(cx, obj, &cls))
3098                 return false;
3099 
3100             if (cls == ESClass::Date) {
3101                 RootedValue unboxed(cx);
3102                 if (!Unbox(cx, obj, &unboxed))
3103                     return false;
3104 
3105                 return NewDateObject(cx, args, TimeClip(unboxed.toNumber()));
3106             }
3107         }
3108 
3109         if (!ToPrimitive(cx, args[0]))
3110             return false;
3111 
3112         ClippedTime t;
3113         if (args[0].isString()) {
3114             JSLinearString* linearStr = args[0].toString()->ensureLinear(cx);
3115             if (!linearStr)
3116                 return false;
3117 
3118             if (!ParseDate(linearStr, &t))
3119                 t = ClippedTime::invalid();
3120         } else {
3121             double d;
3122             if (!ToNumber(cx, args[0], &d))
3123                 return false;
3124             t = TimeClip(d);
3125         }
3126 
3127         return NewDateObject(cx, args, t);
3128     }
3129 
3130     return ToDateString(cx, args, NowAsMillis());
3131 }
3132 
3133 static bool
3134 DateMultipleArguments(JSContext* cx, const CallArgs& args)
3135 {
3136     MOZ_ASSERT(args.length() >= 2);
3137 
3138     // Step 3.
3139     if (args.isConstructing()) {
3140         // Steps 3a-b.
3141         double y;
3142         if (!ToNumber(cx, args[0], &y))
3143             return false;
3144 
3145         // Steps 3c-d.
3146         double m;
3147         if (!ToNumber(cx, args[1], &m))
3148             return false;
3149 
3150         // Steps 3e-f.
3151         double dt;
3152         if (args.length() >= 3) {
3153             if (!ToNumber(cx, args[2], &dt))
3154                 return false;
3155         } else {
3156             dt = 1;
3157         }
3158 
3159         // Steps 3g-h.
3160         double h;
3161         if (args.length() >= 4) {
3162             if (!ToNumber(cx, args[3], &h))
3163                 return false;
3164         } else {
3165             h = 0;
3166         }
3167 
3168         // Steps 3i-j.
3169         double min;
3170         if (args.length() >= 5) {
3171             if (!ToNumber(cx, args[4], &min))
3172                 return false;
3173         } else {
3174             min = 0;
3175         }
3176 
3177         // Steps 3k-l.
3178         double s;
3179         if (args.length() >= 6) {
3180             if (!ToNumber(cx, args[5], &s))
3181                 return false;
3182         } else {
3183             s = 0;
3184         }
3185 
3186         // Steps 3m-n.
3187         double milli;
3188         if (args.length() >= 7) {
3189             if (!ToNumber(cx, args[6], &milli))
3190                 return false;
3191         } else {
3192             milli = 0;
3193         }
3194 
3195         // Step 3o.
3196         double yr = y;
3197         if (!IsNaN(y)) {
3198             double yint = ToInteger(y);
3199             if (0 <= yint && yint <= 99)
3200                 yr = 1900 + yint;
3201         }
3202 
3203         // Step 3p.
3204         double finalDate = MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli));
3205 
3206         // Steps 3q-t.
3207         return NewDateObject(cx, args, TimeClip(UTC(finalDate)));
3208     }
3209 
3210     return ToDateString(cx, args, NowAsMillis());
3211 }
3212 
3213 bool
3214 js::DateConstructor(JSContext* cx, unsigned argc, Value* vp)
3215 {
3216     CallArgs args = CallArgsFromVp(argc, vp);
3217 
3218     if (args.length() == 0)
3219         return DateNoArguments(cx, args);
3220 
3221     if (args.length() == 1)
3222         return DateOneArgument(cx, args);
3223 
3224     return DateMultipleArguments(cx, args);
3225 }
3226 
3227 // ES6 final draft 20.3.4.
3228 static JSObject*
3229 CreateDatePrototype(JSContext* cx, JSProtoKey key)
3230 {
3231     return cx->global()->createBlankPrototype(cx, &DateObject::protoClass_);
3232 }
3233 
3234 static bool
3235 FinishDateClassInit(JSContext* cx, HandleObject ctor, HandleObject proto)
3236 {
3237     /*
3238      * Date.prototype.toGMTString has the same initial value as
3239      * Date.prototype.toUTCString.
3240      */
3241     RootedValue toUTCStringFun(cx);
3242     RootedId toUTCStringId(cx, NameToId(cx->names().toUTCString));
3243     RootedId toGMTStringId(cx, NameToId(cx->names().toGMTString));
3244     return NativeGetProperty(cx, proto.as<NativeObject>(), toUTCStringId, &toUTCStringFun) &&
3245            NativeDefineProperty(cx, proto.as<NativeObject>(), toGMTStringId, toUTCStringFun,
3246                                 nullptr, nullptr, 0);
3247 }
3248 
3249 static const ClassSpec DateObjectClassSpec = {
3250     GenericCreateConstructor<DateConstructor, 7, gc::AllocKind::FUNCTION>,
3251     CreateDatePrototype,
3252     date_static_methods,
3253     nullptr,
3254     date_methods,
3255     nullptr,
3256     FinishDateClassInit
3257 };
3258 
3259 const Class DateObject::class_ = {
3260     js_Date_str,
3261     JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
3262     JSCLASS_HAS_CACHED_PROTO(JSProto_Date),
3263     JS_NULL_CLASS_OPS,
3264     &DateObjectClassSpec
3265 };
3266 
3267 static const ClassSpec DateObjectProtoClassSpec = {
3268     DELEGATED_CLASSSPEC(DateObject::class_.spec),
3269     nullptr,
3270     nullptr,
3271     nullptr,
3272     nullptr,
3273     nullptr,
3274     nullptr,
3275     ClassSpec::IsDelegated
3276 };
3277 
3278 const Class DateObject::protoClass_ = {
3279     js_Object_str,
3280     JSCLASS_HAS_CACHED_PROTO(JSProto_Date),
3281     JS_NULL_CLASS_OPS,
3282     &DateObjectProtoClassSpec
3283 };
3284 
3285 JSObject*
3286 js::NewDateObjectMsec(JSContext* cx, ClippedTime t, HandleObject proto /* = nullptr */)
3287 {
3288     JSObject* obj = NewObjectWithClassProto(cx, &DateObject::class_, proto);
3289     if (!obj)
3290         return nullptr;
3291     obj->as<DateObject>().setUTCTime(t);
3292     return obj;
3293 }
3294 
3295 JS_FRIEND_API(JSObject*)
3296 js::NewDateObject(JSContext* cx, int year, int mon, int mday,
3297                   int hour, int min, int sec)
3298 {
3299     MOZ_ASSERT(mon < 12);
3300     double msec_time = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0.0));
3301     return NewDateObjectMsec(cx, TimeClip(UTC(msec_time)));
3302 }
3303 
3304 JS_FRIEND_API(bool)
3305 js::DateIsValid(JSContext* cx, HandleObject obj, bool* isValid)
3306 {
3307     ESClass cls;
3308     if (!GetBuiltinClass(cx, obj, &cls))
3309         return false;
3310 
3311     if (cls != ESClass::Date) {
3312         *isValid = false;
3313         return true;
3314     }
3315 
3316     RootedValue unboxed(cx);
3317     if (!Unbox(cx, obj, &unboxed))
3318         return false;
3319 
3320     *isValid = !IsNaN(unboxed.toNumber());
3321     return true;
3322 }
3323 
3324 JS_FRIEND_API(bool)
3325 js::DateGetMsecSinceEpoch(JSContext* cx, HandleObject obj, double* msecsSinceEpoch)
3326 {
3327     ESClass cls;
3328     if (!GetBuiltinClass(cx, obj, &cls))
3329         return false;
3330 
3331     if (cls != ESClass::Date) {
3332         *msecsSinceEpoch = 0;
3333         return true;
3334     }
3335 
3336     RootedValue unboxed(cx);
3337     if (!Unbox(cx, obj, &unboxed))
3338         return false;
3339 
3340     *msecsSinceEpoch = unboxed.toNumber();
3341     return true;
3342 }
3343