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