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