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