1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
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."
15 * Frederick Brooks, 'The Second-System Effect'.
20 #include "mozilla/Atomics.h"
21 #include "mozilla/Casting.h"
22 #include "mozilla/FloatingPoint.h"
23 #include "mozilla/Sprintf.h"
24 #include "mozilla/TextUtils.h"
33 #include "jsfriendapi.h"
37 #ifdef JS_HAS_TEMPORAL_API
38 # include "builtin/temporal/Instant.h"
40 #include "js/CallAndConstruct.h" // JS::IsCallable
41 #include "js/Conversions.h"
43 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
44 #include "js/LocaleSensitive.h"
45 #include "js/Object.h" // JS::GetBuiltinClass
46 #include "js/PropertySpec.h"
47 #include "js/Wrapper.h"
48 #include "util/DifferentialTesting.h"
49 #include "util/StringBuffer.h"
50 #include "util/Text.h"
51 #include "vm/DateObject.h"
52 #include "vm/DateTime.h"
53 #include "vm/GlobalObject.h"
54 #include "vm/Interpreter.h"
55 #include "vm/JSContext.h"
56 #include "vm/JSObject.h"
57 #include "vm/StringType.h"
59 #include "vm/Warnings.h"
61 #include "vm/Compartment-inl.h" // For js::UnwrapAndTypeCheckThis
62 #include "vm/GeckoProfiler-inl.h"
63 #include "vm/JSObject-inl.h"
67 using mozilla::Atomic
;
68 using mozilla::BitwiseCast
;
69 using mozilla::IsAsciiAlpha
;
70 using mozilla::IsAsciiDigit
;
71 using mozilla::IsAsciiLowercaseAlpha
;
72 using mozilla::NumbersAreIdentical
;
73 using mozilla::Relaxed
;
75 using JS::AutoCheckCannotGC
;
76 using JS::ClippedTime
;
78 using JS::GetBuiltinClass
;
82 // When this value is non-zero, we'll round the time by this resolution.
83 static Atomic
<uint32_t, Relaxed
> sResolutionUsec
;
84 // This is not implemented yet, but we will use this to know to jitter the time
86 static Atomic
<bool, Relaxed
> sJitter
;
87 // The callback we will use for the Gecko implementation of Timer
89 static Atomic
<JS::ReduceMicrosecondTimePrecisionCallback
, Relaxed
>
90 sReduceMicrosecondTimePrecisionCallback
;
93 * The JS 'Date' object is patterned after the Java 'Date' object.
98 * print(today.toLocaleString());
100 * weekDay = today.getDay();
103 * These Java (and ECMA-262) methods are supported:
106 * getDate (getUTCDate)
108 * getHours (getUTCHours)
109 * getMinutes (getUTCMinutes)
110 * getMonth (getUTCMonth)
111 * getSeconds (getUTCSeconds)
112 * getMilliseconds (getUTCMilliseconds)
116 * getFullYear (getUTCFullYear)
118 * setDate (setUTCDate)
119 * setHours (setUTCHours)
120 * setMinutes (setUTCMinutes)
121 * setMonth (setUTCMonth)
122 * setSeconds (setUTCSeconds)
123 * setMilliseconds (setUTCMilliseconds)
125 * setYear (setFullYear, setUTCFullYear)
126 * toGMTString (toUTCString)
131 * These Java methods are not supported
142 class DateTimeHelper
{
145 static double localTZA(DateTimeInfo::ForceUTC forceUTC
, double t
,
146 DateTimeInfo::TimeZoneOffset offset
);
148 static int equivalentYearForDST(int year
);
149 static bool isRepresentableAsTime32(double t
);
150 static double daylightSavingTA(DateTimeInfo::ForceUTC forceUTC
, double t
);
151 static double adjustTime(DateTimeInfo::ForceUTC forceUTC
, double date
);
152 static PRMJTime
toPRMJTime(DateTimeInfo::ForceUTC forceUTC
, double localTime
,
157 static double localTime(DateTimeInfo::ForceUTC forceUTC
, double t
);
158 static double UTC(DateTimeInfo::ForceUTC forceUTC
, double t
);
159 static JSString
* timeZoneComment(JSContext
* cx
,
160 DateTimeInfo::ForceUTC forceUTC
,
161 const char* locale
, double utcTime
,
164 static size_t formatTime(DateTimeInfo::ForceUTC forceUTC
, char* buf
,
165 size_t buflen
, const char* fmt
, double utcTime
,
172 static DateTimeInfo::ForceUTC
ForceUTC(const Realm
* realm
) {
173 return realm
->creationOptions().forceUTC() ? DateTimeInfo::ForceUTC::Yes
174 : DateTimeInfo::ForceUTC::No
;
177 // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
178 // 5.2.5 Mathematical Operations
179 static inline double PositiveModulo(double dividend
, double divisor
) {
180 MOZ_ASSERT(divisor
> 0);
181 MOZ_ASSERT(std::isfinite(divisor
));
183 double result
= fmod(dividend
, divisor
);
187 return result
+ (+0.0);
190 static inline double Day(double t
) { return floor(t
/ msPerDay
); }
192 static double TimeWithinDay(double t
) { return PositiveModulo(t
, msPerDay
); }
195 static inline bool IsLeapYear(double year
) {
196 MOZ_ASSERT(ToInteger(year
) == year
);
197 return fmod(year
, 4) == 0 && (fmod(year
, 100) != 0 || fmod(year
, 400) == 0);
200 static inline double DayFromYear(double y
) {
201 return 365 * (y
- 1970) + floor((y
- 1969) / 4.0) -
202 floor((y
- 1901) / 100.0) + floor((y
- 1601) / 400.0);
205 static inline double TimeFromYear(double y
) {
206 return DayFromYear(y
) * msPerDay
;
210 struct YearMonthDay
{
218 * This function returns the year, month and day corresponding to a given
219 * time value. The implementation closely follows (w.r.t. types and variable
220 * names) the algorithm shown in Figure 12 of [1].
222 * A key point of the algorithm is that it works on the so called
223 * Computational calendar where years run from March to February -- this
224 * largely avoids complications with leap years. The algorithm finds the
225 * date in the Computation calendar and then maps it to the Gregorian
228 * [1] Neri C, Schneider L., "Euclidean affine functions and their
229 * application to calendar algorithms."
230 * Softw Pract Exper. 2023;53(4):937-970. doi: 10.1002/spe.3172
231 * https://onlinelibrary.wiley.com/doi/full/10.1002/spe.3172
233 static YearMonthDay
ToYearMonthDay(double t
) {
234 MOZ_ASSERT(ToInteger(t
) == t
);
236 // Calendar cycles repeat every 400 years in the Gregorian calendar: a
237 // leap day is added every 4 years, removed every 100 years and added
238 // every 400 years. The number of days in 400 years is cycleInDays.
239 constexpr uint32_t cycleInYears
= 400;
240 constexpr uint32_t cycleInDays
= cycleInYears
* 365 + (cycleInYears
/ 4) -
241 (cycleInYears
/ 100) + (cycleInYears
/ 400);
242 static_assert(cycleInDays
== 146097, "Wrong calculation of cycleInDays.");
244 // The natural epoch for the Computational calendar is 0000/Mar/01 and
245 // there are rataDie1970Jan1 = 719468 days from this date to 1970/Jan/01,
246 // the epoch used by ES2024, 21.4.1.1.
247 constexpr uint32_t rataDie1970Jan1
= 719468;
249 constexpr uint32_t maxU32
= std::numeric_limits
<uint32_t>::max();
251 // Let N_U be the number of days since the 1970/Jan/01. This function sets
252 // N = N_U + K, where K = rataDie1970Jan1 + s * cycleInDays and s is an
253 // integer number (to be chosen). Then, it evaluates 4 * N + 3 on uint32_t
254 // operands so that N must be positive and, to prevent overflow,
255 // 4 * N + 3 <= maxU32 <=> N <= (maxU32 - 3) / 4.
256 // Therefore, we must have 0 <= N_U + K <= (maxU32 - 3) / 4 or, in other
257 // words, N_U must be in [minDays, maxDays] = [-K, (maxU32 - 3) / 4 - K].
258 // Notice that this interval moves cycleInDays positions to the left when
259 // s is incremented. We chose s to get the interval's mid-point as close
260 // as possible to 0. For this, we wish to have:
261 // K ~= (maxU32 - 3) / 4 - K <=> 2 * K ~= (maxU32 - 3) / 4 <=>
262 // K ~= (maxU32 - 3) / 8 <=>
263 // rataDie1970Jan1 + s * cycleInDays ~= (maxU32 - 3) / 8 <=>
264 // s ~= ((maxU32 - 3) / 8 - rataDie1970Jan1) / cycleInDays ~= 3669.8.
265 // Therefore, we chose s = 3670. The shift and correction constants
266 // (see [1]) are then:
267 constexpr uint32_t s
= 3670;
268 constexpr uint32_t K
= rataDie1970Jan1
+ s
* cycleInDays
;
269 constexpr uint32_t L
= s
* cycleInYears
;
271 // [minDays, maxDays] correspond to a date range from -1'468'000/Mar/01 to
273 constexpr int32_t minDays
= -int32_t(K
);
274 constexpr int32_t maxDays
= (maxU32
- 3) / 4 - K
;
275 static_assert(minDays
== -536'895'458, "Wrong calculation of minDays or K.");
276 static_assert(maxDays
== 536'846'365, "Wrong calculation of maxDays or K.");
278 // These are hard limits for the algorithm and far greater than the
279 // range [-8.64e15, 8.64e15] required by ES2024 21.4.1.1. Callers must
280 // ensure this function is not called out of the hard limits and,
281 // preferably, not outside the ES2024 limits.
282 constexpr int64_t minTime
= minDays
* int64_t(msPerDay
);
283 [[maybe_unused
]] constexpr int64_t maxTime
= maxDays
* int64_t(msPerDay
);
284 MOZ_ASSERT(double(minTime
) <= t
&& t
<= double(maxTime
));
285 const int64_t time
= int64_t(t
);
287 // Since time is the number of milliseconds since the epoch, 1970/Jan/01,
288 // one might expect N_U = time / uint64_t(msPerDay) is the number of days
289 // since epoch. There's a catch tough. Consider, for instance, half day
290 // before the epoch, that is, t = -0.5 * msPerDay. This falls on
291 // 1969/Dec/31 and should correspond to N_U = -1 but the above gives
292 // N_U = 0. Indeed, t / msPerDay = -0.5 but integer division truncates
293 // towards 0 (C++ [expr.mul]/4) and not towards -infinity as needed, so
294 // that time / uint64_t(msPerDay) = 0. To workaround this issue we perform
295 // the division on positive operands so that truncations towards 0 and
296 // -infinity are equivalent. For this, set u = time - minTime, which is
297 // positive as asserted above. Then, perform the division u / msPerDay and
298 // to the result add minTime / msPerDay = minDays to cancel the
299 // subtraction of minTime.
300 const uint64_t u
= uint64_t(time
- minTime
);
301 const int32_t N_U
= int32_t(u
/ uint64_t(msPerDay
)) + minDays
;
302 MOZ_ASSERT(minDays
<= N_U
&& N_U
<= maxDays
);
304 const uint32_t N
= uint32_t(N_U
) + K
;
306 // Some magic numbers have been explained above but, unfortunately,
307 // others with no precise interpretation do appear. They mostly come
308 // from numerical approximations of Euclidean affine functions (see [1])
309 // which are faster for the CPU to calculate. Unfortunately, no compiler
310 // can do these optimizations.
312 // Century C and year of the century N_C:
313 const uint32_t N_1
= 4 * N
+ 3;
314 const uint32_t C
= N_1
/ 146097;
315 const uint32_t N_C
= N_1
% 146097 / 4;
317 // Year of the century Z and day of the year N_Y:
318 const uint32_t N_2
= 4 * N_C
+ 3;
319 const uint64_t P_2
= uint64_t(2939745) * N_2
;
320 const uint32_t Z
= uint32_t(P_2
/ 4294967296);
321 const uint32_t N_Y
= uint32_t(P_2
% 4294967296) / 2939745 / 4;
324 const uint32_t Y
= 100 * C
+ Z
;
326 // Month M and day D.
327 // The expression for N_3 has been adapted to account for the difference
328 // between month numbers in ES5 15.9.1.4 (from 0 to 11) and [1] (from 1
329 // to 12). This is done by subtracting 65536 from the original
330 // expression so that M decreases by 1 and so does M_G further down.
331 const uint32_t N_3
= 2141 * N_Y
+ 132377; // 132377 = 197913 - 65536
332 const uint32_t M
= N_3
/ 65536;
333 const uint32_t D
= N_3
% 65536 / 2141;
335 // Map from Computational to Gregorian calendar. Notice also the year
336 // correction and the type change and that Jan/01 is day 306 of the
337 // Computational calendar, cf. Table 1. [1]
338 constexpr uint32_t daysFromMar01ToJan01
= 306;
339 const uint32_t J
= N_Y
>= daysFromMar01ToJan01
;
340 const int32_t Y_G
= int32_t((Y
- L
) + J
);
341 const uint32_t M_G
= J
? M
- 12 : M
;
342 const uint32_t D_G
= D
+ 1;
344 return {Y_G
, M_G
, D_G
};
347 static double YearFromTime(double t
) {
348 if (!std::isfinite(t
)) {
351 auto const year
= ToYearMonthDay(t
).year
;
356 static double DayWithinYear(double t
, double year
) {
357 MOZ_ASSERT_IF(std::isfinite(t
), YearFromTime(t
) == year
);
358 return Day(t
) - DayFromYear(year
);
361 static double MonthFromTime(double t
) {
362 if (!std::isfinite(t
)) {
365 const auto month
= ToYearMonthDay(t
).month
;
366 return double(month
);
370 static double DateFromTime(double t
) {
371 if (!std::isfinite(t
)) {
374 const auto day
= ToYearMonthDay(t
).day
;
379 static int WeekDay(double t
) {
381 * We can't assert TimeClip(t) == t because we call this function with
382 * local times, which can be offset outside TimeClip's permitted range.
384 MOZ_ASSERT(ToInteger(t
) == t
);
385 int result
= (int(Day(t
)) + 4) % 7;
392 static inline int DayFromMonth(int month
, bool isLeapYear
) {
394 * The following array contains the day of year for the first day of
395 * each month, where index 0 is January, and day 0 is January 1.
397 static const int firstDayOfMonth
[2][13] = {
398 {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
399 {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}};
401 MOZ_ASSERT(0 <= month
&& month
<= 12);
402 return firstDayOfMonth
[isLeapYear
][month
];
405 template <typename T
>
406 static inline int DayFromMonth(T month
, bool isLeapYear
) = delete;
408 /* ES5 15.9.1.12 (out of order to accommodate DaylightSavingTA). */
409 static double MakeDay(double year
, double month
, double date
) {
411 if (!std::isfinite(year
) || !std::isfinite(month
) || !std::isfinite(date
)) {
416 double y
= ToInteger(year
);
417 double m
= ToInteger(month
);
418 double dt
= ToInteger(date
);
421 double ym
= y
+ floor(m
/ 12);
424 int mn
= int(PositiveModulo(m
, 12));
427 bool leap
= IsLeapYear(ym
);
429 double yearday
= floor(TimeFromYear(ym
) / msPerDay
);
430 double monthday
= DayFromMonth(mn
, leap
);
432 return yearday
+ monthday
+ dt
- 1;
435 /* ES5 15.9.1.13 (out of order to accommodate DaylightSavingTA). */
436 static inline double MakeDate(double day
, double time
) {
438 if (!std::isfinite(day
) || !std::isfinite(time
)) {
443 return day
* msPerDay
+ time
;
446 JS_PUBLIC_API
double JS::MakeDate(double year
, unsigned month
, unsigned day
) {
447 MOZ_ASSERT(month
<= 11);
448 MOZ_ASSERT(day
>= 1 && day
<= 31);
450 return ::MakeDate(MakeDay(year
, month
, day
), 0);
453 JS_PUBLIC_API
double JS::MakeDate(double year
, unsigned month
, unsigned day
,
455 MOZ_ASSERT(month
<= 11);
456 MOZ_ASSERT(day
>= 1 && day
<= 31);
458 return ::MakeDate(MakeDay(year
, month
, day
), time
);
461 JS_PUBLIC_API
double JS::YearFromTime(double time
) {
462 const auto clipped
= TimeClip(time
);
463 if (!clipped
.isValid()) {
466 return ::YearFromTime(clipped
.toDouble());
469 JS_PUBLIC_API
double JS::MonthFromTime(double time
) {
470 const auto clipped
= TimeClip(time
);
471 if (!clipped
.isValid()) {
474 return ::MonthFromTime(clipped
.toDouble());
477 JS_PUBLIC_API
double JS::DayFromTime(double time
) {
478 const auto clipped
= TimeClip(time
);
479 if (!clipped
.isValid()) {
482 return DateFromTime(clipped
.toDouble());
485 JS_PUBLIC_API
double JS::DayFromYear(double year
) {
486 return ::DayFromYear(year
);
489 JS_PUBLIC_API
double JS::DayWithinYear(double time
, double year
) {
490 const auto clipped
= TimeClip(time
);
491 if (!clipped
.isValid()) {
494 return ::DayWithinYear(clipped
.toDouble(), year
);
497 JS_PUBLIC_API
void JS::SetReduceMicrosecondTimePrecisionCallback(
498 JS::ReduceMicrosecondTimePrecisionCallback callback
) {
499 sReduceMicrosecondTimePrecisionCallback
= callback
;
502 JS_PUBLIC_API
JS::ReduceMicrosecondTimePrecisionCallback
503 JS::GetReduceMicrosecondTimePrecisionCallback() {
504 return sReduceMicrosecondTimePrecisionCallback
;
507 JS_PUBLIC_API
void JS::SetTimeResolutionUsec(uint32_t resolution
, bool jitter
) {
508 sResolutionUsec
= resolution
;
513 // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
514 // 20.3.1.7 LocalTZA ( t, isUTC )
515 double DateTimeHelper::localTZA(DateTimeInfo::ForceUTC forceUTC
, double t
,
516 DateTimeInfo::TimeZoneOffset offset
) {
517 MOZ_ASSERT(std::isfinite(t
));
519 int64_t milliseconds
= static_cast<int64_t>(t
);
520 int32_t offsetMilliseconds
=
521 DateTimeInfo::getOffsetMilliseconds(forceUTC
, milliseconds
, offset
);
522 return static_cast<double>(offsetMilliseconds
);
525 // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
526 // 20.3.1.8 LocalTime ( t )
527 double DateTimeHelper::localTime(DateTimeInfo::ForceUTC forceUTC
, double t
) {
528 if (!std::isfinite(t
)) {
532 MOZ_ASSERT(StartOfTime
<= t
&& t
<= EndOfTime
);
533 return t
+ localTZA(forceUTC
, t
, DateTimeInfo::TimeZoneOffset::UTC
);
536 // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
537 // 20.3.1.9 UTC ( t )
538 double DateTimeHelper::UTC(DateTimeInfo::ForceUTC forceUTC
, double t
) {
539 if (!std::isfinite(t
)) {
543 if (t
< (StartOfTime
- msPerDay
) || t
> (EndOfTime
+ msPerDay
)) {
547 return t
- localTZA(forceUTC
, t
, DateTimeInfo::TimeZoneOffset::Local
);
551 * Find a year for which any given date will fall on the same weekday.
553 * This function should be used with caution when used other than
554 * for determining DST; it hasn't been proven not to produce an
555 * incorrect year for times near year boundaries.
557 int DateTimeHelper::equivalentYearForDST(int year
) {
559 * Years and leap years on which Jan 1 is a Sunday, Monday, etc.
561 * yearStartingWith[0][i] is an example non-leap year where
562 * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
564 * yearStartingWith[1][i] is an example leap year where
565 * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
567 * Keep two different mappings, one for past years (< 1970), and a
568 * different one for future years (> 2037).
570 static const int pastYearStartingWith
[2][7] = {
571 {1978, 1973, 1974, 1975, 1981, 1971, 1977},
572 {1984, 1996, 1980, 1992, 1976, 1988, 1972}};
573 static const int futureYearStartingWith
[2][7] = {
574 {2034, 2035, 2030, 2031, 2037, 2027, 2033},
575 {2012, 2024, 2036, 2020, 2032, 2016, 2028}};
577 int day
= int(DayFromYear(year
) + 4) % 7;
582 const auto& yearStartingWith
=
583 year
< 1970 ? pastYearStartingWith
: futureYearStartingWith
;
584 return yearStartingWith
[IsLeapYear(year
)][day
];
587 // Return true if |t| is representable as a 32-bit time_t variable, that means
588 // the year is in [1970, 2038).
589 bool DateTimeHelper::isRepresentableAsTime32(double t
) {
590 return 0.0 <= t
&& t
< 2145916800000.0;
594 double DateTimeHelper::daylightSavingTA(DateTimeInfo::ForceUTC forceUTC
,
596 if (!std::isfinite(t
)) {
601 * If earlier than 1970 or after 2038, potentially beyond the ken of
602 * many OSes, map it to an equivalent year before asking.
604 if (!isRepresentableAsTime32(t
)) {
605 int year
= equivalentYearForDST(int(YearFromTime(t
)));
606 double day
= MakeDay(year
, MonthFromTime(t
), DateFromTime(t
));
607 t
= MakeDate(day
, TimeWithinDay(t
));
610 int64_t utcMilliseconds
= static_cast<int64_t>(t
);
611 int32_t offsetMilliseconds
=
612 DateTimeInfo::getDSTOffsetMilliseconds(forceUTC
, utcMilliseconds
);
613 return static_cast<double>(offsetMilliseconds
);
616 double DateTimeHelper::adjustTime(DateTimeInfo::ForceUTC forceUTC
,
618 double localTZA
= DateTimeInfo::localTZA(forceUTC
);
619 double t
= daylightSavingTA(forceUTC
, date
) + localTZA
;
620 t
= (localTZA
>= 0) ? fmod(t
, msPerDay
) : -fmod(msPerDay
- t
, msPerDay
);
625 double DateTimeHelper::localTime(DateTimeInfo::ForceUTC forceUTC
, double t
) {
626 return t
+ adjustTime(forceUTC
, t
);
629 double DateTimeHelper::UTC(DateTimeInfo::ForceUTC forceUTC
, double t
) {
630 // Following the ES2017 specification creates undesirable results at DST
631 // transitions. For example when transitioning from PST to PDT,
632 // |new Date(2016,2,13,2,0,0).toTimeString()| returns the string value
633 // "01:00:00 GMT-0800 (PST)" instead of "03:00:00 GMT-0700 (PDT)". Follow
634 // V8 and subtract one hour before computing the offset.
635 // Spec bug: https://bugs.ecmascript.org/show_bug.cgi?id=4007
638 adjustTime(forceUTC
, t
- DateTimeInfo::localTZA(forceUTC
) - msPerHour
);
640 #endif /* JS_HAS_INTL_API */
642 static double LocalTime(DateTimeInfo::ForceUTC forceUTC
, double t
) {
643 return DateTimeHelper::localTime(forceUTC
, t
);
646 static double UTC(DateTimeInfo::ForceUTC forceUTC
, double t
) {
647 return DateTimeHelper::UTC(forceUTC
, t
);
651 static double HourFromTime(double t
) {
652 return PositiveModulo(floor(t
/ msPerHour
), HoursPerDay
);
655 static double MinFromTime(double t
) {
656 return PositiveModulo(floor(t
/ msPerMinute
), MinutesPerHour
);
659 static double SecFromTime(double t
) {
660 return PositiveModulo(floor(t
/ msPerSecond
), SecondsPerMinute
);
663 static double msFromTime(double t
) { return PositiveModulo(t
, msPerSecond
); }
666 static double MakeTime(double hour
, double min
, double sec
, double ms
) {
668 if (!std::isfinite(hour
) || !std::isfinite(min
) || !std::isfinite(sec
) ||
669 !std::isfinite(ms
)) {
674 double h
= ToInteger(hour
);
677 double m
= ToInteger(min
);
680 double s
= ToInteger(sec
);
683 double milli
= ToInteger(ms
);
686 return h
* msPerHour
+ m
* msPerMinute
+ s
* msPerSecond
+ milli
;
690 * end of ECMA 'support' functions
693 // ES2017 draft rev (TODO: Add git hash when PR 642 is merged.)
695 // Date.UTC(year [, month [, date [, hours [, minutes [, seconds [, ms]]]]]])
696 static bool date_UTC(JSContext
* cx
, unsigned argc
, Value
* vp
) {
697 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "Date", "UTC");
698 CallArgs args
= CallArgsFromVp(argc
, vp
);
702 if (!ToNumber(cx
, args
.get(0), &y
)) {
708 if (args
.length() >= 2) {
709 if (!ToNumber(cx
, args
[1], &m
)) {
718 if (args
.length() >= 3) {
719 if (!ToNumber(cx
, args
[2], &dt
)) {
728 if (args
.length() >= 4) {
729 if (!ToNumber(cx
, args
[3], &h
)) {
738 if (args
.length() >= 5) {
739 if (!ToNumber(cx
, args
[4], &min
)) {
748 if (args
.length() >= 6) {
749 if (!ToNumber(cx
, args
[5], &s
)) {
758 if (args
.length() >= 7) {
759 if (!ToNumber(cx
, args
[6], &milli
)) {
768 if (!std::isnan(y
)) {
769 double yint
= ToInteger(y
);
770 if (0 <= yint
&& yint
<= 99) {
777 TimeClip(MakeDate(MakeDay(yr
, m
, dt
), MakeTime(h
, min
, s
, milli
)));
778 args
.rval().set(TimeValue(time
));
783 * Read and convert decimal digits from s[*i] into *result
786 * Succeed if any digits are converted. Advance *i only
787 * as digits are consumed.
789 template <typename CharT
>
790 static bool ParseDigits(size_t* result
, const CharT
* s
, size_t* i
,
794 while (*i
< limit
&& ('0' <= s
[*i
] && s
[*i
] <= '9')) {
796 *result
+= (s
[*i
] - '0');
803 * Read and convert decimal digits to the right of a decimal point,
804 * representing a fractional integer, from s[*i] into *result
805 * while *i < limit, up to 3 digits. Consumes any digits beyond 3
806 * without affecting the result.
808 * Succeed if any digits are converted. Advance *i only
809 * as digits are consumed.
811 template <typename CharT
>
812 static bool ParseFractional(int* result
, const CharT
* s
, size_t* i
,
817 for (; *i
< limit
&& ('0' <= s
[*i
] && s
[*i
] <= '9'); ++(*i
)) {
818 if (*i
- init
>= 3) {
819 // If we're past 3 digits, do nothing with it, but continue to
820 // consume the remainder of the digits
823 *result
+= (s
[*i
] - '0') * factor
;
830 * Read and convert exactly n decimal digits from s[*i]
831 * to s[min(*i+n,limit)] into *result.
833 * Succeed if exactly n digits are converted. Advance *i only
836 template <typename CharT
>
837 static bool ParseDigitsN(size_t n
, size_t* result
, const CharT
* s
, size_t* i
,
841 if (ParseDigits(result
, s
, i
, std::min(limit
, init
+ n
))) {
842 return (*i
- init
) == n
;
850 * Read and convert n or less decimal digits from s[*i]
851 * to s[min(*i+n,limit)] into *result.
853 * Succeed only if greater than zero but less than or equal to n digits are
854 * converted. Advance *i only on success.
856 template <typename CharT
>
857 static bool ParseDigitsNOrLess(size_t n
, size_t* result
, const CharT
* s
,
858 size_t* i
, size_t limit
) {
861 if (ParseDigits(result
, s
, i
, std::min(limit
, init
+ n
))) {
862 return ((*i
- init
) > 0) && ((*i
- init
) <= n
);
870 * Parse a string according to the formats specified in the standard:
872 * https://tc39.es/ecma262/#sec-date-time-string-format
873 * https://tc39.es/ecma262/#sec-expanded-years
875 * These formats are based upon a simplification of the ISO 8601 Extended
876 * Format. As per the spec omitted month and day values are defaulted to '01',
877 * omitted HH:mm:ss values are defaulted to '00' and an omitted sss field is
878 * defaulted to '000'.
880 * For cross compatibility we allow the following extensions.
884 * One or more decimal digits for milliseconds:
885 * The specification requires exactly three decimal digits for
886 * the fractional part but we allow for one or more digits.
888 * Time zone specifier without ':':
889 * We allow the time zone to be specified without a ':' character.
890 * E.g. "T19:00:00+0700" is equivalent to "T19:00:00+07:00".
898 * YYYY-MM (eg 1997-07)
901 * YYYY-MM-DD (eg 1997-07-16)
906 * Thh:mmTZD (eg T19:20+01:00)
908 * Hours, minutes and seconds:
909 * Thh:mm:ssTZD (eg T19:20:30+01:00)
911 * Hours, minutes, seconds and a decimal fraction of a second:
912 * Thh:mm:ss.sssTZD (eg T19:20:30.45+01:00)
916 * YYYY = four-digit year or six digit year as +YYYYYY or -YYYYYY
917 * MM = two-digit month (01=January, etc.)
918 * DD = two-digit day of month (01 through 31)
919 * hh = two digits of hour (00 through 24) (am/pm NOT allowed)
920 * mm = two digits of minute (00 through 59)
921 * ss = two digits of second (00 through 59)
922 * sss = one or more digits representing a decimal fraction of a second
923 * TZD = time zone designator (Z or +hh:mm or -hh:mm or missing for local)
925 template <typename CharT
>
926 static bool ParseISOStyleDate(DateTimeInfo::ForceUTC forceUTC
, const CharT
* s
,
927 size_t length
, ClippedTime
* result
) {
938 bool isLocalTime
= false;
942 #define PEEK(ch) (i < length && s[i] == ch)
945 if (i >= length || s[i] != ch) { \
951 #define DONE_DATE_UNLESS(ch) \
952 if (i >= length || s[i] != ch) { \
958 #define DONE_UNLESS(ch) \
959 if (i >= length || s[i] != ch) { \
965 #define NEED_NDIGITS(n, field) \
966 if (!ParseDigitsN(n, &field, s, &i, length)) { \
970 if (PEEK('+') || PEEK('-')) {
975 NEED_NDIGITS(6, year
);
977 // https://tc39.es/ecma262/#sec-expanded-years
978 // -000000 is not a valid expanded year.
979 if (year
== 0 && dateMul
== -1) {
983 NEED_NDIGITS(4, year
);
985 DONE_DATE_UNLESS('-');
986 NEED_NDIGITS(2, month
);
987 DONE_DATE_UNLESS('-');
988 NEED_NDIGITS(2, day
);
997 NEED_NDIGITS(2, hour
);
999 NEED_NDIGITS(2, min
);
1003 NEED_NDIGITS(2, sec
);
1006 if (!ParseFractional(&msec
, s
, &i
, length
)) {
1014 } else if (PEEK('+') || PEEK('-')) {
1019 NEED_NDIGITS(2, tzHour
);
1021 * Non-standard extension to the ISO date format (permitted by ES5):
1022 * allow "-0700" as a time zone offset, not just "-07:00".
1027 NEED_NDIGITS(2, tzMin
);
1033 if (year
> 275943 // ceil(1e8/365) + 1970
1034 || month
== 0 || month
> 12 || day
== 0 || day
> 31 || hour
> 24 ||
1035 (hour
== 24 && (min
> 0 || sec
> 0 || msec
> 0)) || min
> 59 ||
1036 sec
> 59 || tzHour
> 23 || tzMin
> 59) {
1044 month
-= 1; /* convert month to 0-based */
1046 double date
= MakeDate(MakeDay(dateMul
* double(year
), month
, day
),
1047 MakeTime(hour
, min
, sec
, msec
));
1050 date
= UTC(forceUTC
, date
);
1052 date
-= tzMul
* (tzHour
* msPerHour
+ tzMin
* msPerMinute
);
1055 *result
= TimeClip(date
);
1056 return NumbersAreIdentical(date
, result
->toDouble());
1064 int FixupNonFullYear(int year
) {
1067 } else if (year
>= 50 && year
< 100) {
1073 template <typename CharT
>
1074 bool IsPrefixOfKeyword(const CharT
* s
, size_t len
, const char* keyword
) {
1075 while (len
> 0 && *keyword
) {
1076 MOZ_ASSERT(IsAsciiAlpha(*s
));
1077 MOZ_ASSERT(IsAsciiLowercaseAlpha(*keyword
));
1079 if (unicode::ToLowerCase(static_cast<Latin1Char
>(*s
)) != *keyword
) {
1090 template <typename CharT
>
1091 bool MatchesKeyword(const CharT
* s
, size_t len
, const char* keyword
) {
1093 MOZ_ASSERT(IsAsciiAlpha(*s
));
1094 MOZ_ASSERT(IsAsciiLowercaseAlpha(*keyword
) || *keyword
== '\0');
1096 if (unicode::ToLowerCase(static_cast<Latin1Char
>(*s
)) != *keyword
) {
1104 return *keyword
== '\0';
1107 static constexpr const char* const month_prefixes
[] = {
1108 "jan", "feb", "mar", "apr", "may", "jun",
1109 "jul", "aug", "sep", "oct", "nov", "dec",
1113 * Given a string s of length >= 3, checks if it begins,
1114 * case-insensitive, with the given lower case prefix.
1116 template <typename CharT
>
1117 bool StartsWithMonthPrefix(const CharT
* s
, const char* prefix
) {
1118 MOZ_ASSERT(strlen(prefix
) == 3);
1120 for (size_t i
= 0; i
< 3; ++i
) {
1121 MOZ_ASSERT(IsAsciiAlpha(*s
));
1122 MOZ_ASSERT(IsAsciiLowercaseAlpha(*prefix
));
1124 if (unicode::ToLowerCase(static_cast<Latin1Char
>(*s
)) != *prefix
) {
1134 template <typename CharT
>
1135 bool IsMonthName(const CharT
* s
, size_t len
, int* mon
) {
1136 // Month abbreviations < 3 chars are not accepted.
1141 for (size_t m
= 0; m
< std::size(month_prefixes
); ++m
) {
1142 if (StartsWithMonthPrefix(s
, month_prefixes
[m
])) {
1143 // Use numeric value.
1153 * Try to parse the following date formats:
1161 * Returns true and fills all out parameters when successfully parsed
1162 * dashed-date. Otherwise returns false and leaves out parameters untouched.
1164 template <typename CharT
>
1165 static bool TryParseDashedDatePrefix(const CharT
* s
, size_t length
,
1166 size_t* indexOut
, int* yearOut
,
1167 int* monOut
, int* mdayOut
) {
1168 size_t i
= *indexOut
;
1172 if (!ParseDigitsNOrLess(6, &mday
, s
, &i
, length
)) {
1175 size_t mdayDigits
= i
- pre
;
1177 if (i
>= length
|| s
[i
] != '-') {
1183 if (*monOut
== -1) {
1184 // If month wasn't already set by ParseDate, it must be in the middle of
1185 // this format, let's look for it
1187 for (; i
< length
; i
++) {
1188 if (!IsAsciiAlpha(s
[i
])) {
1193 if (!IsMonthName(s
+ start
, i
- start
, &mon
)) {
1197 if (i
>= length
|| s
[i
] != '-') {
1205 if (!ParseDigitsNOrLess(6, &year
, s
, &i
, length
)) {
1208 size_t yearDigits
= i
- pre
;
1210 if (i
< length
&& IsAsciiDigit(s
[i
])) {
1214 // Swap the mday and year if the year wasn't specified in full.
1215 if (mday
> 31 && year
<= 31 && yearDigits
< 4) {
1216 std::swap(mday
, year
);
1217 std::swap(mdayDigits
, yearDigits
);
1220 if (mday
> 31 || mdayDigits
> 2) {
1224 if (yearDigits
< 4) {
1225 year
= FixupNonFullYear(year
);
1230 if (*monOut
== -1) {
1238 * Try to parse dates in the style of YYYY-MM-DD which do not conform to
1239 * the formal standard from ParseISOStyleDate. This includes cases such as
1241 * - Year does not have 4 digits
1242 * - Month or mday has 1 digit
1243 * - Space in between date and time, rather than a 'T'
1245 * Regarding the last case, this function only parses out the date, returning
1246 * to ParseDate to finish parsing the time and timezone, if present.
1248 * Returns true and fills all out parameters when successfully parsed
1249 * dashed-date. Otherwise returns false and leaves out parameters untouched.
1251 template <typename CharT
>
1252 static bool TryParseDashedNumericDatePrefix(const CharT
* s
, size_t length
,
1253 size_t* indexOut
, int* yearOut
,
1254 int* monOut
, int* mdayOut
) {
1255 size_t i
= *indexOut
;
1258 if (!ParseDigitsNOrLess(6, &first
, s
, &i
, length
)) {
1262 if (i
>= length
|| s
[i
] != '-') {
1268 if (!ParseDigitsNOrLess(2, &second
, s
, &i
, length
)) {
1272 if (i
>= length
|| s
[i
] != '-') {
1278 if (!ParseDigitsNOrLess(6, &third
, s
, &i
, length
)) {
1286 // 1 or 2 digits for the first number is tricky; 1-12 means it's a month, 0 or
1287 // >31 means it's a year, and 13-31 is invalid due to ambiguity.
1288 if (first
>= 1 && first
<= 12) {
1290 } else if (first
== 0 || first
> 31) {
1297 // If month hasn't been set yet, it's definitely the 2nd number
1300 // If it has, the next number is the mday
1305 // The third number is probably the mday...
1308 // But otherwise, it's the year
1312 if (mon
< 1 || mon
> 12 || mday
< 1 || mday
> 31) {
1317 year
= FixupNonFullYear(year
);
1327 struct CharsAndAction
{
1332 static constexpr const char* const days_of_week
[] = {
1333 "monday", "tuesday", "wednesday", "thursday",
1334 "friday", "saturday", "sunday"};
1336 static constexpr CharsAndAction keywords
[] = {
1341 // Time zone abbreviations.
1342 { "gmt", 10000 + 0 },
1344 { "ut", 10000 + 0 },
1345 { "utc", 10000 + 0 },
1346 { "est", 10000 + 5 * 60 },
1347 { "edt", 10000 + 4 * 60 },
1348 { "cst", 10000 + 6 * 60 },
1349 { "cdt", 10000 + 5 * 60 },
1350 { "mst", 10000 + 7 * 60 },
1351 { "mdt", 10000 + 6 * 60 },
1352 { "pst", 10000 + 8 * 60 },
1353 { "pdt", 10000 + 7 * 60 },
1358 constexpr size_t MinKeywordLength(const CharsAndAction (&keywords
)[N
]) {
1359 size_t min
= size_t(-1);
1360 for (const CharsAndAction
& keyword
: keywords
) {
1361 min
= std::min(min
, std::char_traits
<char>::length(keyword
.chars
));
1366 template <typename CharT
>
1367 static bool ParseDate(DateTimeInfo::ForceUTC forceUTC
, const CharT
* s
,
1368 size_t length
, ClippedTime
* result
,
1369 bool* countLateWeekday
) {
1374 if (ParseISOStyleDate(forceUTC
, s
, length
, result
)) {
1380 bool seenMonthName
= false;
1382 // Before we begin, we need to scrub any words from the beginning of the
1383 // string up to the first number, recording the month if we encounter it
1384 for (; index
< length
; index
++) {
1387 if (strchr(" ,.-/", c
)) {
1390 if (!IsAsciiAlpha(c
)) {
1394 size_t start
= index
;
1396 for (; index
< length
; index
++) {
1397 if (!IsAsciiAlpha(s
[index
])) {
1402 if (index
>= length
) {
1406 if (IsMonthName(s
+ start
, index
- start
, &mon
)) {
1407 seenMonthName
= true;
1408 // If the next digit is a number, we need to break so it
1409 // gets parsed as mday
1410 if (IsAsciiDigit(s
[index
])) {
1414 // Reject numbers directly after letters e.g. foo2
1415 if (IsAsciiDigit(s
[index
]) && IsAsciiAlpha(s
[index
- 1])) {
1429 // One of '+', '-', ':', '/', or 0 (the default value).
1432 bool seenPlusMinus
= false;
1433 bool seenFullYear
= false;
1434 bool negativeYear
= false;
1435 // Includes "GMT", "UTC", "UT", and "Z" timezone keywords
1436 bool seenGmtAbbr
= false;
1437 // For telemetry purposes
1438 bool seenLateWeekday
= false;
1440 // Try parsing the leading dashed-date.
1442 // If successfully parsed, index is updated to the end of the date part,
1443 // and year, mon, mday are set to the date.
1444 // Continue parsing optional time + tzOffset parts.
1446 // Otherwise, this is no-op.
1448 TryParseDashedDatePrefix(s
, length
, &index
, &year
, &mon
, &mday
) ||
1449 TryParseDashedNumericDatePrefix(s
, length
, &index
, &year
, &mon
, &mday
);
1451 if (isDashedDate
&& index
< length
&& strchr("T:+", s
[index
])) {
1455 while (index
< length
) {
1459 // Normalize U+202F (NARROW NO-BREAK SPACE). This character appears between
1460 // the AM/PM markers for |date.toLocaleString("en")|. We have to normalize
1461 // it for backward compatibility reasons.
1466 if ((c
== '+' || c
== '-') &&
1467 // Reject + or - after timezone (still allowing for negative year)
1468 ((seenPlusMinus
&& year
!= -1) ||
1469 // Reject timezones like "1995-09-26 -04:30" (if the - is right up
1470 // against the previous number, it will get parsed as a time,
1471 // see the other comment below)
1472 (year
!= -1 && hour
== -1 && !seenGmtAbbr
&&
1473 !IsAsciiDigit(s
[index
- 2])))) {
1477 // Spaces, ASCII control characters, periods, and commas are simply ignored.
1478 if (c
<= ' ' || c
== '.' || c
== ',') {
1482 // Parse delimiter characters. Save them to the side for future use.
1483 if (c
== '/' || c
== ':' || c
== '+') {
1488 // Dashes are delimiters if they're immediately followed by a number field.
1489 // If they're not followed by a number field, they're simply ignored.
1491 if (index
< length
&& IsAsciiDigit(s
[index
])) {
1497 // Skip over comments -- text inside matching parentheses. (Comments
1498 // themselves may contain comments as long as all the parentheses properly
1499 // match up. And apparently comments, including nested ones, may validly be
1500 // terminated by end of input...)
1503 while (index
< length
) {
1508 } else if (c
== ')') {
1517 // Parse a number field.
1518 if (IsAsciiDigit(c
)) {
1519 size_t partStart
= index
- 1;
1520 uint32_t u
= c
- '0';
1521 while (index
< length
) {
1523 if (!IsAsciiDigit(c
)) {
1526 u
= u
* 10 + (c
- '0');
1529 size_t partLength
= index
- partStart
;
1531 // See above for why we have to normalize U+202F.
1539 * Allow TZA before the year, so 'Wed Nov 05 21:49:11 GMT-0800 1997'
1542 * Uses of seenPlusMinus allow ':' in TZA, so Java no-timezone style
1543 * of GMT+4:30 works.
1546 if (prevc
== '-' && (tzOffset
!= 0 || seenPlusMinus
) && partLength
>= 4 &&
1548 // Parse as a negative, possibly zero-padded year if
1549 // 1. the preceding character is '-',
1550 // 2. the TZA is not 'GMT' (tested by |tzOffset != 0|),
1551 // 3. or a TZA was already parsed |seenPlusMinus == true|,
1552 // 4. the part length is at least 4 (to parse '-08' as a TZA),
1553 // 5. and we did not already parse a year |year < 0|.
1555 seenFullYear
= true;
1556 negativeYear
= true;
1557 } else if ((prevc
== '+' || prevc
== '-') &&
1558 // "1995-09-26-04:30" needs to be parsed as a time,
1560 (seenGmtAbbr
|| hour
!= -1)) {
1561 /* Make ':' case below change tzOffset. */
1562 seenPlusMinus
= true;
1565 if (n
< 24 && partLength
<= 2) {
1566 n
= n
* 60; /* EG. "GMT-3" */
1568 n
= n
% 100 + n
/ 100 * 60; /* eg "GMT-0430" */
1571 if (prevc
== '+') /* plus means east of GMT */
1574 // Reject if not preceded by 'GMT' or if a time zone offset
1575 // was already parsed.
1576 if (tzOffset
!= 0 && tzOffset
!= -1) {
1581 } else if (prevc
== '/' && mon
>= 0 && mday
>= 0 && year
< 0) {
1582 if (c
<= ' ' || c
== ',' || c
== '/' || index
>= length
) {
1587 } else if (c
== ':') {
1590 } else if (min
< 0) {
1595 } else if (c
== '/') {
1597 * Until it is determined that mon is the actual month, keep
1598 * it as 1-based rather than 0-based.
1602 } else if (mday
< 0) {
1607 } else if (index
< length
&& c
!= ',' && c
> ' ' && c
!= '-' &&
1609 // Allow '.' as a delimiter until seconds have been parsed
1610 // (this allows the decimal for milliseconds)
1611 (c
!= '.' || sec
!= -1) &&
1612 // Allow zulu time e.g. "09/26/1995 16:00Z", or
1613 // '+' directly after time e.g. 00:00+0500
1614 !(hour
!= -1 && strchr("Zz+", c
)) &&
1615 // Allow month or AM/PM directly after a number
1616 (!IsAsciiAlpha(c
) ||
1617 (mon
!= -1 && !(strchr("AaPp", c
) && index
< length
- 1 &&
1618 strchr("Mm", s
[index
+ 1]))))) {
1620 } else if (seenPlusMinus
&& n
< 60) { /* handle GMT-3:30 */
1626 } else if (hour
>= 0 && min
< 0) {
1628 } else if (prevc
== ':' && min
>= 0 && sec
< 0) {
1632 if (!ParseFractional(&msec
, s
, &index
, length
)) {
1636 } else if (mon
< 0) {
1638 } else if (mon
>= 0 && mday
< 0) {
1640 } else if (mon
>= 0 && mday
>= 0 && year
< 0) {
1642 seenFullYear
= partLength
>= 4;
1651 // Parse fields that are words: ASCII letters spelling out in English AM/PM,
1652 // day of week, month, or an extremely limited set of legacy time zone
1654 if (IsAsciiAlpha(c
)) {
1655 size_t start
= index
- 1;
1656 while (index
< length
) {
1658 if (!IsAsciiAlpha(c
)) {
1664 // There must be at least as many letters as in the shortest keyword.
1665 constexpr size_t MinLength
= MinKeywordLength(keywords
);
1666 if (index
- start
< MinLength
) {
1670 // Completely ignore days of the week, and don't derive any semantics
1672 bool isLateWeekday
= false;
1673 for (const char* weekday
: days_of_week
) {
1674 if (IsPrefixOfKeyword(s
+ start
, index
- start
, weekday
)) {
1675 isLateWeekday
= true;
1676 seenLateWeekday
= true;
1680 if (isLateWeekday
) {
1685 // Record a month if it is a month name. Note that some numbers are
1686 // initially treated as months; if a numeric field has already been
1687 // interpreted as a month, store that value to the actually appropriate
1688 // date component and set the month here.
1690 if (IsMonthName(s
+ start
, index
- start
, &tryMonth
)) {
1691 if (seenMonthName
) {
1692 // Overwrite the previous month name
1698 seenMonthName
= true;
1702 } else if (mday
< 0) {
1705 } else if (year
< 0) {
1707 // If the date is of the form f l month, then when month is
1708 // reached we have f in mon and l in mday. In order to be
1709 // consistent with the f month l and month f l forms, we need to
1710 // swap so that f is in mday and l is in year.
1725 size_t k
= std::size(keywords
);
1727 const CharsAndAction
& keyword
= keywords
[k
];
1729 // If the field doesn't match the keyword, try the next one.
1730 if (!MatchesKeyword(s
+ start
, index
- start
, keyword
.chars
)) {
1734 int action
= keyword
.action
;
1736 if (action
== 10000) {
1740 // Perform action tests from smallest action values to largest.
1742 // Adjust a previously-specified hour for AM/PM accordingly (taking care
1743 // to treat 12:xx AM as 00:xx, 12:xx PM as 12:xx).
1745 MOZ_ASSERT(action
== -1 || action
== -2);
1746 if (hour
> 12 || hour
< 0) {
1750 if (action
== -1 && hour
== 12) {
1752 } else if (action
== -2 && hour
!= 12) {
1759 // Finally, record a time zone offset.
1760 MOZ_ASSERT(action
>= 10000);
1761 tzOffset
= action
- 10000;
1765 if (k
== size_t(-1)) {
1773 // Any other character fails to parse.
1777 // Handle cases where the input is a single number. Single numbers >= 1000
1778 // are handled by the spec (ParseISOStyleDate), so we don't need to account
1780 if (mon
!= -1 && year
< 0 && mday
< 0) {
1781 // Reject 13-31 for Chrome parity
1782 if (mon
>= 13 && mon
<= 31) {
1787 if (mon
>= 1 && mon
<= 12) {
1788 // 1-12 is parsed as a month with the year defaulted to 2001
1789 // (again, for Chrome parity)
1792 year
= FixupNonFullYear(mon
);
1797 if (year
< 0 || mon
< 0 || mday
< 0) {
1801 if (!isDashedDate
) {
1802 // NOTE: TryParseDashedDatePrefix already handles the following fixup.
1805 * Case 1. The input string contains an English month name.
1806 * The form of the string can be month f l, or f month l, or
1807 * f l month which each evaluate to the same date.
1808 * If f and l are both greater than or equal to 100 the date
1811 * The year is taken to be either l, f if f > 31, or whichever
1814 * Case 2. The input string is of the form "f/m/l" where f, m and l are
1815 * integers, e.g. 7/16/45. mon, mday and year values are adjusted
1816 * to achieve Chrome compatibility.
1818 * a. If 0 < f <= 12 and 0 < l <= 31, f/m/l is interpreted as
1820 * b. If 31 < f and 0 < m <= 12 and 0 < l <= 31 f/m/l is
1821 * interpreted as year/month/day
1823 if (seenMonthName
) {
1824 if (mday
>= 100 && mon
>= 100) {
1828 if (year
> 0 && (mday
== 0 || mday
> 31) && !seenFullYear
) {
1834 if (mday
<= 0 || mday
> 31) {
1838 } else if (0 < mon
&& mon
<= 12 && 0 < mday
&& mday
<= 31) {
1839 /* (a) month/day/year */
1841 /* (b) year/month/day */
1842 if (mon
> 31 && mday
<= 12 && year
<= 31 && !seenFullYear
) {
1852 // If the year is greater than or equal to 50 and less than 100, it is
1853 // considered to be the number of years after 1900. If the year is less
1854 // than 50 it is considered to be the number of years after 2000,
1855 // otherwise it is considered to be the number of years after 0.
1856 if (!seenFullYear
) {
1857 year
= FixupNonFullYear(year
);
1865 mon
-= 1; /* convert month to 0-based */
1877 MakeDate(MakeDay(year
, mon
, mday
), MakeTime(hour
, min
, sec
, msec
));
1879 if (tzOffset
== -1) { /* no time zone specified, have to use local */
1880 date
= UTC(forceUTC
, date
);
1882 date
+= tzOffset
* msPerMinute
;
1885 // Setting this down here so that it only counts the telemetry in
1886 // the case of a successful parse.
1887 if (seenLateWeekday
) {
1888 *countLateWeekday
= true;
1891 *result
= TimeClip(date
);
1895 static bool ParseDate(DateTimeInfo::ForceUTC forceUTC
, JSLinearString
* s
,
1896 ClippedTime
* result
, JSContext
* cx
) {
1897 bool countLateWeekday
= false;
1901 AutoCheckCannotGC nogc
;
1902 success
= s
->hasLatin1Chars()
1903 ? ParseDate(forceUTC
, s
->latin1Chars(nogc
), s
->length(),
1904 result
, &countLateWeekday
)
1905 : ParseDate(forceUTC
, s
->twoByteChars(nogc
), s
->length(),
1906 result
, &countLateWeekday
);
1909 // We are running telemetry to see if support for day of week after
1910 // mday can be dropped. It is being done here to keep
1911 // JSRuntime::setUseCounter out of AutoCheckCannotGC's scope.
1912 if (countLateWeekday
) {
1913 cx
->runtime()->setUseCounter(cx
->global(), JSUseCounter::LATE_WEEKDAY
);
1915 if (!cx
->realm()->warnedAboutDateLateWeekday
) {
1916 if (!WarnNumberASCII(cx
, JSMSG_DEPRECATED_LATE_WEEKDAY
)) {
1917 // Proceed as if nothing happened if warning fails
1918 if (cx
->isExceptionPending()) {
1919 cx
->clearPendingException();
1922 cx
->realm()->warnedAboutDateLateWeekday
= true;
1929 static bool date_parse(JSContext
* cx
, unsigned argc
, Value
* vp
) {
1930 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "Date", "parse");
1931 CallArgs args
= CallArgsFromVp(argc
, vp
);
1932 if (args
.length() == 0) {
1933 args
.rval().setNaN();
1937 JSString
* str
= ToString
<CanGC
>(cx
, args
[0]);
1942 JSLinearString
* linearStr
= str
->ensureLinear(cx
);
1948 if (!ParseDate(ForceUTC(cx
->realm()), linearStr
, &result
, cx
)) {
1949 args
.rval().setNaN();
1953 args
.rval().set(TimeValue(result
));
1957 static ClippedTime
NowAsMillis(JSContext
* cx
) {
1958 if (js::SupportDifferentialTesting()) {
1962 double now
= PRMJ_Now();
1963 bool clampAndJitter
= cx
->realm()->behaviors().clampAndJitterTime();
1964 if (clampAndJitter
&& sReduceMicrosecondTimePrecisionCallback
) {
1965 now
= sReduceMicrosecondTimePrecisionCallback(
1966 now
, cx
->realm()->behaviors().reduceTimerPrecisionCallerType().value(),
1968 } else if (clampAndJitter
&& sResolutionUsec
) {
1969 double clamped
= floor(now
/ sResolutionUsec
) * sResolutionUsec
;
1972 // Calculate a random midpoint for jittering. In the browser, we are
1973 // adversarial: Web Content may try to calculate the midpoint themselves
1974 // and use that to bypass it's security. In the JS Shell, we are not
1975 // adversarial, we want to jitter the time to recreate the operating
1976 // environment, but we do not concern ourselves with trying to prevent an
1977 // attacker from calculating the midpoint themselves. So we use a very
1978 // simple, very fast CRC with a hardcoded seed.
1980 uint64_t midpoint
= BitwiseCast
<uint64_t>(clamped
);
1981 midpoint
^= 0x0F00DD1E2BAD2DED; // XOR in a 'secret'
1982 // MurmurHash3 internal component from
1983 // https://searchfox.org/mozilla-central/rev/61d400da1c692453c2dc2c1cf37b616ce13dea5b/dom/canvas/MurmurHash3.cpp#85
1984 midpoint
^= midpoint
>> 33;
1985 midpoint
*= uint64_t{0xFF51AFD7ED558CCD};
1986 midpoint
^= midpoint
>> 33;
1987 midpoint
*= uint64_t{0xC4CEB9FE1A85EC53};
1988 midpoint
^= midpoint
>> 33;
1989 midpoint
%= sResolutionUsec
;
1991 if (now
> clamped
+ midpoint
) { // We're jittering up to the next step
1992 now
= clamped
+ sResolutionUsec
;
1993 } else { // We're staying at the clamped value
1996 } else { // No jitter, only clamping
2001 return TimeClip(now
/ PRMJ_USEC_PER_MSEC
);
2004 JS::ClippedTime
js::DateNow(JSContext
* cx
) { return NowAsMillis(cx
); }
2006 bool js::date_now(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2007 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "Date", "now");
2008 CallArgs args
= CallArgsFromVp(argc
, vp
);
2009 args
.rval().set(TimeValue(NowAsMillis(cx
)));
2013 DateTimeInfo::ForceUTC
DateObject::forceUTC() const {
2014 return ForceUTC(realm());
2017 void DateObject::setUTCTime(ClippedTime t
) {
2018 for (size_t ind
= COMPONENTS_START_SLOT
; ind
< RESERVED_SLOTS
; ind
++) {
2019 setReservedSlot(ind
, UndefinedValue());
2022 setFixedSlot(UTC_TIME_SLOT
, TimeValue(t
));
2025 void DateObject::setUTCTime(ClippedTime t
, MutableHandleValue vp
) {
2027 vp
.set(TimeValue(t
));
2030 void DateObject::fillLocalTimeSlots() {
2031 const int32_t utcTZOffset
=
2032 DateTimeInfo::utcToLocalStandardOffsetSeconds(forceUTC());
2034 /* Check if the cache is already populated. */
2035 if (!getReservedSlot(LOCAL_TIME_SLOT
).isUndefined() &&
2036 getReservedSlot(UTC_TIME_ZONE_OFFSET_SLOT
).toInt32() == utcTZOffset
) {
2040 /* Remember time zone used to generate the local cache. */
2041 setReservedSlot(UTC_TIME_ZONE_OFFSET_SLOT
, Int32Value(utcTZOffset
));
2043 double utcTime
= UTCTime().toNumber();
2045 if (!std::isfinite(utcTime
)) {
2046 for (size_t ind
= COMPONENTS_START_SLOT
; ind
< RESERVED_SLOTS
; ind
++) {
2047 setReservedSlot(ind
, DoubleValue(utcTime
));
2052 double localTime
= LocalTime(forceUTC(), utcTime
);
2054 setReservedSlot(LOCAL_TIME_SLOT
, DoubleValue(localTime
));
2056 const auto [year
, month
, day
] = ToYearMonthDay(localTime
);
2058 setReservedSlot(LOCAL_YEAR_SLOT
, Int32Value(year
));
2059 setReservedSlot(LOCAL_MONTH_SLOT
, Int32Value(int32_t(month
)));
2060 setReservedSlot(LOCAL_DATE_SLOT
, Int32Value(int32_t(day
)));
2062 int weekday
= WeekDay(localTime
);
2063 setReservedSlot(LOCAL_DAY_SLOT
, Int32Value(weekday
));
2065 double yearStartTime
= TimeFromYear(year
);
2066 uint64_t yearTime
= uint64_t(localTime
- yearStartTime
);
2067 int32_t yearSeconds
= int32_t(yearTime
/ 1000);
2068 setReservedSlot(LOCAL_SECONDS_INTO_YEAR_SLOT
, Int32Value(yearSeconds
));
2071 MOZ_ALWAYS_INLINE
bool IsDate(HandleValue v
) {
2072 return v
.isObject() && v
.toObject().is
<DateObject
>();
2076 * See ECMA 15.9.5.4 thru 15.9.5.23
2079 static bool date_getTime(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2080 CallArgs args
= CallArgsFromVp(argc
, vp
);
2082 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "getTime");
2087 args
.rval().set(unwrapped
->UTCTime());
2091 static bool date_getYear(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2092 CallArgs args
= CallArgsFromVp(argc
, vp
);
2094 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "getYear");
2099 unwrapped
->fillLocalTimeSlots();
2101 Value yearVal
= unwrapped
->localYear();
2102 if (yearVal
.isInt32()) {
2103 /* Follow ECMA-262 to the letter, contrary to IE JScript. */
2104 int year
= yearVal
.toInt32() - 1900;
2105 args
.rval().setInt32(year
);
2107 args
.rval().set(yearVal
);
2112 static bool date_getFullYear(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2113 CallArgs args
= CallArgsFromVp(argc
, vp
);
2115 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "getFullYear");
2120 unwrapped
->fillLocalTimeSlots();
2121 args
.rval().set(unwrapped
->localYear());
2125 static bool date_getUTCFullYear(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2126 CallArgs args
= CallArgsFromVp(argc
, vp
);
2129 UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "getUTCFullYear");
2134 double result
= unwrapped
->UTCTime().toNumber();
2135 if (std::isfinite(result
)) {
2136 result
= YearFromTime(result
);
2139 args
.rval().setNumber(result
);
2143 static bool date_getMonth(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2144 CallArgs args
= CallArgsFromVp(argc
, vp
);
2146 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "getMonth");
2151 unwrapped
->fillLocalTimeSlots();
2152 args
.rval().set(unwrapped
->localMonth());
2156 static bool date_getUTCMonth(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2157 CallArgs args
= CallArgsFromVp(argc
, vp
);
2159 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "getUTCMonth");
2164 double d
= unwrapped
->UTCTime().toNumber();
2165 args
.rval().setNumber(MonthFromTime(d
));
2169 static bool date_getDate(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2170 CallArgs args
= CallArgsFromVp(argc
, vp
);
2172 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "getDate");
2177 unwrapped
->fillLocalTimeSlots();
2179 args
.rval().set(unwrapped
->localDate());
2183 static bool date_getUTCDate(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2184 CallArgs args
= CallArgsFromVp(argc
, vp
);
2186 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "getUTCDate");
2191 double result
= unwrapped
->UTCTime().toNumber();
2192 if (std::isfinite(result
)) {
2193 result
= DateFromTime(result
);
2196 args
.rval().setNumber(result
);
2200 static bool date_getDay(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2201 CallArgs args
= CallArgsFromVp(argc
, vp
);
2203 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "getDay");
2208 unwrapped
->fillLocalTimeSlots();
2209 args
.rval().set(unwrapped
->localDay());
2213 static bool date_getUTCDay(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2214 CallArgs args
= CallArgsFromVp(argc
, vp
);
2216 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "getUTCDay");
2221 double result
= unwrapped
->UTCTime().toNumber();
2222 if (std::isfinite(result
)) {
2223 result
= WeekDay(result
);
2226 args
.rval().setNumber(result
);
2230 static bool date_getHours(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2231 CallArgs args
= CallArgsFromVp(argc
, vp
);
2233 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "getHours");
2238 unwrapped
->fillLocalTimeSlots();
2240 // Note: localSecondsIntoYear is guaranteed to return an
2241 // int32 or NaN after the call to fillLocalTimeSlots.
2242 Value yearSeconds
= unwrapped
->localSecondsIntoYear();
2243 if (yearSeconds
.isDouble()) {
2244 MOZ_ASSERT(std::isnan(yearSeconds
.toDouble()));
2245 args
.rval().set(yearSeconds
);
2247 args
.rval().setInt32((yearSeconds
.toInt32() / int(SecondsPerHour
)) %
2253 static bool date_getUTCHours(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2254 CallArgs args
= CallArgsFromVp(argc
, vp
);
2256 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "getUTCHours");
2261 double result
= unwrapped
->UTCTime().toNumber();
2262 if (std::isfinite(result
)) {
2263 result
= HourFromTime(result
);
2266 args
.rval().setNumber(result
);
2270 static bool date_getMinutes(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2271 CallArgs args
= CallArgsFromVp(argc
, vp
);
2273 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "getMinutes");
2278 unwrapped
->fillLocalTimeSlots();
2280 // Note: localSecondsIntoYear is guaranteed to return an
2281 // int32 or NaN after the call to fillLocalTimeSlots.
2282 Value yearSeconds
= unwrapped
->localSecondsIntoYear();
2283 if (yearSeconds
.isDouble()) {
2284 MOZ_ASSERT(std::isnan(yearSeconds
.toDouble()));
2285 args
.rval().set(yearSeconds
);
2287 args
.rval().setInt32((yearSeconds
.toInt32() / int(SecondsPerMinute
)) %
2288 int(MinutesPerHour
));
2293 static bool date_getUTCMinutes(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2294 CallArgs args
= CallArgsFromVp(argc
, vp
);
2297 UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "getUTCMinutes");
2302 double result
= unwrapped
->UTCTime().toNumber();
2303 if (std::isfinite(result
)) {
2304 result
= MinFromTime(result
);
2307 args
.rval().setNumber(result
);
2311 static bool date_getSeconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2312 CallArgs args
= CallArgsFromVp(argc
, vp
);
2314 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "getSeconds");
2319 unwrapped
->fillLocalTimeSlots();
2321 // Note: localSecondsIntoYear is guaranteed to return an
2322 // int32 or NaN after the call to fillLocalTimeSlots.
2323 Value yearSeconds
= unwrapped
->localSecondsIntoYear();
2324 if (yearSeconds
.isDouble()) {
2325 MOZ_ASSERT(std::isnan(yearSeconds
.toDouble()));
2326 args
.rval().set(yearSeconds
);
2328 args
.rval().setInt32(yearSeconds
.toInt32() % int(SecondsPerMinute
));
2333 static bool date_getUTCSeconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2334 CallArgs args
= CallArgsFromVp(argc
, vp
);
2337 UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "getUTCSeconds");
2342 double result
= unwrapped
->UTCTime().toNumber();
2343 if (std::isfinite(result
)) {
2344 result
= SecFromTime(result
);
2347 args
.rval().setNumber(result
);
2352 * Date.getMilliseconds is mapped to getUTCMilliseconds. As long as no
2353 * supported time zone has a fractional-second component, the differences in
2354 * their specifications aren't observable.
2356 * The 'tz' database explicitly does not support fractional-second time zones.
2357 * For example the Netherlands observed Amsterdam Mean Time, estimated to be
2358 * UT +00:19:32.13, from 1909 to 1937, but in tzdata AMT is defined as exactly
2362 static bool getMilliseconds(JSContext
* cx
, unsigned argc
, Value
* vp
,
2363 const char* methodName
) {
2364 CallArgs args
= CallArgsFromVp(argc
, vp
);
2366 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, methodName
);
2371 double result
= unwrapped
->UTCTime().toNumber();
2372 if (std::isfinite(result
)) {
2373 result
= msFromTime(result
);
2376 args
.rval().setNumber(result
);
2380 static bool date_getMilliseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2381 return getMilliseconds(cx
, argc
, vp
, "getMilliseconds");
2384 static bool date_getUTCMilliseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2385 return getMilliseconds(cx
, argc
, vp
, "getUTCMilliseconds");
2388 static bool date_getTimezoneOffset(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2389 CallArgs args
= CallArgsFromVp(argc
, vp
);
2392 UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "getTimezoneOffset");
2397 unwrapped
->fillLocalTimeSlots();
2399 double utctime
= unwrapped
->UTCTime().toNumber();
2400 double localtime
= unwrapped
->localTime().toDouble();
2403 * Return the time zone offset in minutes for the current locale that is
2404 * appropriate for this time. This value would be a constant except for
2405 * daylight savings time.
2407 double result
= (utctime
- localtime
) / msPerMinute
;
2408 args
.rval().setNumber(result
);
2412 static bool date_setTime(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2413 CallArgs args
= CallArgsFromVp(argc
, vp
);
2415 Rooted
<DateObject
*> unwrapped(
2416 cx
, UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "setTime"));
2421 if (args
.length() == 0) {
2422 unwrapped
->setUTCTime(ClippedTime::invalid(), args
.rval());
2427 if (!ToNumber(cx
, args
[0], &result
)) {
2431 unwrapped
->setUTCTime(TimeClip(result
), args
.rval());
2435 static bool GetMsecsOrDefault(JSContext
* cx
, const CallArgs
& args
, unsigned i
,
2436 double t
, double* millis
) {
2437 if (args
.length() <= i
) {
2438 *millis
= msFromTime(t
);
2441 return ToNumber(cx
, args
[i
], millis
);
2444 static bool GetSecsOrDefault(JSContext
* cx
, const CallArgs
& args
, unsigned i
,
2445 double t
, double* sec
) {
2446 if (args
.length() <= i
) {
2447 *sec
= SecFromTime(t
);
2450 return ToNumber(cx
, args
[i
], sec
);
2453 static bool GetMinsOrDefault(JSContext
* cx
, const CallArgs
& args
, unsigned i
,
2454 double t
, double* mins
) {
2455 if (args
.length() <= i
) {
2456 *mins
= MinFromTime(t
);
2459 return ToNumber(cx
, args
[i
], mins
);
2462 /* ES6 20.3.4.23. */
2463 static bool date_setMilliseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2464 CallArgs args
= CallArgsFromVp(argc
, vp
);
2467 Rooted
<DateObject
*> unwrapped(
2468 cx
, UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "setMilliseconds"));
2472 double t
= LocalTime(unwrapped
->forceUTC(), unwrapped
->UTCTime().toNumber());
2476 if (!ToNumber(cx
, args
.get(0), &ms
)) {
2481 double time
= MakeTime(HourFromTime(t
), MinFromTime(t
), SecFromTime(t
), ms
);
2484 ClippedTime u
= TimeClip(UTC(unwrapped
->forceUTC(), MakeDate(Day(t
), time
)));
2487 unwrapped
->setUTCTime(u
, args
.rval());
2491 /* ES5 15.9.5.29. */
2492 static bool date_setUTCMilliseconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2493 CallArgs args
= CallArgsFromVp(argc
, vp
);
2495 Rooted
<DateObject
*> unwrapped(
2496 cx
, UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "setUTCMilliseconds"));
2502 double t
= unwrapped
->UTCTime().toNumber();
2506 if (!ToNumber(cx
, args
.get(0), &milli
)) {
2510 MakeTime(HourFromTime(t
), MinFromTime(t
), SecFromTime(t
), milli
);
2513 ClippedTime v
= TimeClip(MakeDate(Day(t
), time
));
2516 unwrapped
->setUTCTime(v
, args
.rval());
2520 /* ES6 20.3.4.26. */
2521 static bool date_setSeconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2522 CallArgs args
= CallArgsFromVp(argc
, vp
);
2524 Rooted
<DateObject
*> unwrapped(
2525 cx
, UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "setSeconds"));
2531 double t
= LocalTime(unwrapped
->forceUTC(), unwrapped
->UTCTime().toNumber());
2535 if (!ToNumber(cx
, args
.get(0), &s
)) {
2541 if (!GetMsecsOrDefault(cx
, args
, 1, t
, &milli
)) {
2547 MakeDate(Day(t
), MakeTime(HourFromTime(t
), MinFromTime(t
), s
, milli
));
2550 ClippedTime u
= TimeClip(UTC(unwrapped
->forceUTC(), date
));
2553 unwrapped
->setUTCTime(u
, args
.rval());
2557 /* ES5 15.9.5.32. */
2558 static bool date_setUTCSeconds(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2559 CallArgs args
= CallArgsFromVp(argc
, vp
);
2561 Rooted
<DateObject
*> unwrapped(
2562 cx
, UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "setUTCSeconds"));
2568 double t
= unwrapped
->UTCTime().toNumber();
2572 if (!ToNumber(cx
, args
.get(0), &s
)) {
2578 if (!GetMsecsOrDefault(cx
, args
, 1, t
, &milli
)) {
2584 MakeDate(Day(t
), MakeTime(HourFromTime(t
), MinFromTime(t
), s
, milli
));
2587 ClippedTime v
= TimeClip(date
);
2590 unwrapped
->setUTCTime(v
, args
.rval());
2594 /* ES6 20.3.4.24. */
2595 static bool date_setMinutes(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2596 CallArgs args
= CallArgsFromVp(argc
, vp
);
2598 Rooted
<DateObject
*> unwrapped(
2599 cx
, UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "setMinutes"));
2605 double t
= LocalTime(unwrapped
->forceUTC(), unwrapped
->UTCTime().toNumber());
2609 if (!ToNumber(cx
, args
.get(0), &m
)) {
2615 if (!GetSecsOrDefault(cx
, args
, 1, t
, &s
)) {
2621 if (!GetMsecsOrDefault(cx
, args
, 2, t
, &milli
)) {
2626 double date
= MakeDate(Day(t
), MakeTime(HourFromTime(t
), m
, s
, milli
));
2629 ClippedTime u
= TimeClip(UTC(unwrapped
->forceUTC(), date
));
2632 unwrapped
->setUTCTime(u
, args
.rval());
2636 /* ES5 15.9.5.34. */
2637 static bool date_setUTCMinutes(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2638 CallArgs args
= CallArgsFromVp(argc
, vp
);
2640 Rooted
<DateObject
*> unwrapped(
2641 cx
, UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "setUTCMinutes"));
2647 double t
= unwrapped
->UTCTime().toNumber();
2651 if (!ToNumber(cx
, args
.get(0), &m
)) {
2657 if (!GetSecsOrDefault(cx
, args
, 1, t
, &s
)) {
2663 if (!GetMsecsOrDefault(cx
, args
, 2, t
, &milli
)) {
2668 double date
= MakeDate(Day(t
), MakeTime(HourFromTime(t
), m
, s
, milli
));
2671 ClippedTime v
= TimeClip(date
);
2674 unwrapped
->setUTCTime(v
, args
.rval());
2678 /* ES5 15.9.5.35. */
2679 static bool date_setHours(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2680 CallArgs args
= CallArgsFromVp(argc
, vp
);
2682 Rooted
<DateObject
*> unwrapped(
2683 cx
, UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "setHours"));
2689 double t
= LocalTime(unwrapped
->forceUTC(), unwrapped
->UTCTime().toNumber());
2693 if (!ToNumber(cx
, args
.get(0), &h
)) {
2699 if (!GetMinsOrDefault(cx
, args
, 1, t
, &m
)) {
2705 if (!GetSecsOrDefault(cx
, args
, 2, t
, &s
)) {
2711 if (!GetMsecsOrDefault(cx
, args
, 3, t
, &milli
)) {
2716 double date
= MakeDate(Day(t
), MakeTime(h
, m
, s
, milli
));
2719 ClippedTime u
= TimeClip(UTC(unwrapped
->forceUTC(), date
));
2722 unwrapped
->setUTCTime(u
, args
.rval());
2726 /* ES5 15.9.5.36. */
2727 static bool date_setUTCHours(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2728 CallArgs args
= CallArgsFromVp(argc
, vp
);
2730 Rooted
<DateObject
*> unwrapped(
2731 cx
, UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "setUTCHours"));
2737 double t
= unwrapped
->UTCTime().toNumber();
2741 if (!ToNumber(cx
, args
.get(0), &h
)) {
2747 if (!GetMinsOrDefault(cx
, args
, 1, t
, &m
)) {
2753 if (!GetSecsOrDefault(cx
, args
, 2, t
, &s
)) {
2759 if (!GetMsecsOrDefault(cx
, args
, 3, t
, &milli
)) {
2764 double newDate
= MakeDate(Day(t
), MakeTime(h
, m
, s
, milli
));
2767 ClippedTime v
= TimeClip(newDate
);
2770 unwrapped
->setUTCTime(v
, args
.rval());
2774 /* ES5 15.9.5.37. */
2775 static bool date_setDate(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2776 CallArgs args
= CallArgsFromVp(argc
, vp
);
2778 Rooted
<DateObject
*> unwrapped(
2779 cx
, UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "setDate"));
2785 double t
= LocalTime(unwrapped
->forceUTC(), unwrapped
->UTCTime().toNumber());
2789 if (!ToNumber(cx
, args
.get(0), &date
)) {
2794 double newDate
= MakeDate(MakeDay(YearFromTime(t
), MonthFromTime(t
), date
),
2798 ClippedTime u
= TimeClip(UTC(unwrapped
->forceUTC(), newDate
));
2801 unwrapped
->setUTCTime(u
, args
.rval());
2805 static bool date_setUTCDate(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2806 CallArgs args
= CallArgsFromVp(argc
, vp
);
2808 Rooted
<DateObject
*> unwrapped(
2809 cx
, UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "setUTCDate"));
2815 double t
= unwrapped
->UTCTime().toNumber();
2819 if (!ToNumber(cx
, args
.get(0), &date
)) {
2824 double newDate
= MakeDate(MakeDay(YearFromTime(t
), MonthFromTime(t
), date
),
2828 ClippedTime v
= TimeClip(newDate
);
2831 unwrapped
->setUTCTime(v
, args
.rval());
2835 static bool GetDateOrDefault(JSContext
* cx
, const CallArgs
& args
, unsigned i
,
2836 double t
, double* date
) {
2837 if (args
.length() <= i
) {
2838 *date
= DateFromTime(t
);
2841 return ToNumber(cx
, args
[i
], date
);
2844 static bool GetMonthOrDefault(JSContext
* cx
, const CallArgs
& args
, unsigned i
,
2845 double t
, double* month
) {
2846 if (args
.length() <= i
) {
2847 *month
= MonthFromTime(t
);
2850 return ToNumber(cx
, args
[i
], month
);
2853 /* ES5 15.9.5.38. */
2854 static bool date_setMonth(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2855 CallArgs args
= CallArgsFromVp(argc
, vp
);
2857 Rooted
<DateObject
*> unwrapped(
2858 cx
, UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "setMonth"));
2864 double t
= LocalTime(unwrapped
->forceUTC(), unwrapped
->UTCTime().toNumber());
2868 if (!ToNumber(cx
, args
.get(0), &m
)) {
2874 if (!GetDateOrDefault(cx
, args
, 1, t
, &date
)) {
2880 MakeDate(MakeDay(YearFromTime(t
), m
, date
), TimeWithinDay(t
));
2883 ClippedTime u
= TimeClip(UTC(unwrapped
->forceUTC(), newDate
));
2886 unwrapped
->setUTCTime(u
, args
.rval());
2890 /* ES5 15.9.5.39. */
2891 static bool date_setUTCMonth(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2892 CallArgs args
= CallArgsFromVp(argc
, vp
);
2894 Rooted
<DateObject
*> unwrapped(
2895 cx
, UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "setUTCMonth"));
2901 double t
= unwrapped
->UTCTime().toNumber();
2905 if (!ToNumber(cx
, args
.get(0), &m
)) {
2911 if (!GetDateOrDefault(cx
, args
, 1, t
, &date
)) {
2917 MakeDate(MakeDay(YearFromTime(t
), m
, date
), TimeWithinDay(t
));
2920 ClippedTime v
= TimeClip(newDate
);
2923 unwrapped
->setUTCTime(v
, args
.rval());
2927 static double ThisLocalTimeOrZero(DateTimeInfo::ForceUTC forceUTC
,
2928 Handle
<DateObject
*> dateObj
) {
2929 double t
= dateObj
->UTCTime().toNumber();
2930 if (std::isnan(t
)) {
2933 return LocalTime(forceUTC
, t
);
2936 static double ThisUTCTimeOrZero(Handle
<DateObject
*> dateObj
) {
2937 double t
= dateObj
->as
<DateObject
>().UTCTime().toNumber();
2938 return std::isnan(t
) ? +0 : t
;
2941 /* ES5 15.9.5.40. */
2942 static bool date_setFullYear(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2943 CallArgs args
= CallArgsFromVp(argc
, vp
);
2945 Rooted
<DateObject
*> unwrapped(
2946 cx
, UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "setFullYear"));
2952 double t
= ThisLocalTimeOrZero(unwrapped
->forceUTC(), unwrapped
);
2956 if (!ToNumber(cx
, args
.get(0), &y
)) {
2962 if (!GetMonthOrDefault(cx
, args
, 1, t
, &m
)) {
2968 if (!GetDateOrDefault(cx
, args
, 2, t
, &date
)) {
2973 double newDate
= MakeDate(MakeDay(y
, m
, date
), TimeWithinDay(t
));
2976 ClippedTime u
= TimeClip(UTC(unwrapped
->forceUTC(), newDate
));
2979 unwrapped
->setUTCTime(u
, args
.rval());
2983 /* ES5 15.9.5.41. */
2984 static bool date_setUTCFullYear(JSContext
* cx
, unsigned argc
, Value
* vp
) {
2985 CallArgs args
= CallArgsFromVp(argc
, vp
);
2987 Rooted
<DateObject
*> unwrapped(
2988 cx
, UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "setUTCFullYear"));
2994 double t
= ThisUTCTimeOrZero(unwrapped
);
2998 if (!ToNumber(cx
, args
.get(0), &y
)) {
3004 if (!GetMonthOrDefault(cx
, args
, 1, t
, &m
)) {
3010 if (!GetDateOrDefault(cx
, args
, 2, t
, &date
)) {
3015 double newDate
= MakeDate(MakeDay(y
, m
, date
), TimeWithinDay(t
));
3018 ClippedTime v
= TimeClip(newDate
);
3021 unwrapped
->setUTCTime(v
, args
.rval());
3025 /* ES5 Annex B.2.5. */
3026 static bool date_setYear(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3027 CallArgs args
= CallArgsFromVp(argc
, vp
);
3029 Rooted
<DateObject
*> unwrapped(
3030 cx
, UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "setYear"));
3036 double t
= ThisLocalTimeOrZero(unwrapped
->forceUTC(), unwrapped
);
3040 if (!ToNumber(cx
, args
.get(0), &y
)) {
3045 if (std::isnan(y
)) {
3046 unwrapped
->setUTCTime(ClippedTime::invalid(), args
.rval());
3051 double yint
= ToInteger(y
);
3052 if (0 <= yint
&& yint
<= 99) {
3057 double day
= MakeDay(yint
, MonthFromTime(t
), DateFromTime(t
));
3060 double u
= UTC(unwrapped
->forceUTC(), MakeDate(day
, TimeWithinDay(t
)));
3063 unwrapped
->setUTCTime(TimeClip(u
), args
.rval());
3067 /* constants for toString, toUTCString */
3068 static const char* const days
[] = {"Sun", "Mon", "Tue", "Wed",
3069 "Thu", "Fri", "Sat"};
3070 static const char* const months
[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
3071 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
3074 static bool date_toUTCString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3075 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "Date.prototype", "toUTCString");
3076 CallArgs args
= CallArgsFromVp(argc
, vp
);
3078 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "toUTCString");
3083 double utctime
= unwrapped
->UTCTime().toNumber();
3084 if (!std::isfinite(utctime
)) {
3085 args
.rval().setString(cx
->names().Invalid_Date_
);
3090 SprintfLiteral(buf
, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
3091 days
[int(WeekDay(utctime
))], int(DateFromTime(utctime
)),
3092 months
[int(MonthFromTime(utctime
))],
3093 int(YearFromTime(utctime
)), int(HourFromTime(utctime
)),
3094 int(MinFromTime(utctime
)), int(SecFromTime(utctime
)));
3096 JSString
* str
= NewStringCopyZ
<CanGC
>(cx
, buf
);
3101 args
.rval().setString(str
);
3105 /* ES6 draft 2015-01-15 20.3.4.36. */
3106 static bool date_toISOString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3107 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "Date.prototype", "toISOString");
3108 CallArgs args
= CallArgsFromVp(argc
, vp
);
3110 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "toISOString");
3115 double utctime
= unwrapped
->UTCTime().toNumber();
3116 if (!std::isfinite(utctime
)) {
3117 JS_ReportErrorNumberASCII(cx
, js::GetErrorMessage
, nullptr,
3118 JSMSG_INVALID_DATE
);
3123 int year
= int(YearFromTime(utctime
));
3124 if (year
< 0 || year
> 9999) {
3125 SprintfLiteral(buf
, "%+.6d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
3126 int(YearFromTime(utctime
)), int(MonthFromTime(utctime
)) + 1,
3127 int(DateFromTime(utctime
)), int(HourFromTime(utctime
)),
3128 int(MinFromTime(utctime
)), int(SecFromTime(utctime
)),
3129 int(msFromTime(utctime
)));
3131 SprintfLiteral(buf
, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
3132 int(YearFromTime(utctime
)), int(MonthFromTime(utctime
)) + 1,
3133 int(DateFromTime(utctime
)), int(HourFromTime(utctime
)),
3134 int(MinFromTime(utctime
)), int(SecFromTime(utctime
)),
3135 int(msFromTime(utctime
)));
3138 JSString
* str
= NewStringCopyZ
<CanGC
>(cx
, buf
);
3142 args
.rval().setString(str
);
3146 /* ES5 15.9.5.44. */
3147 static bool date_toJSON(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3148 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "Date.prototype", "toJSON");
3149 CallArgs args
= CallArgsFromVp(argc
, vp
);
3152 RootedObject
obj(cx
, ToObject(cx
, args
.thisv()));
3158 RootedValue
tv(cx
, ObjectValue(*obj
));
3159 if (!ToPrimitive(cx
, JSTYPE_NUMBER
, &tv
)) {
3164 if (tv
.isDouble() && !std::isfinite(tv
.toDouble())) {
3165 args
.rval().setNull();
3170 RootedValue
toISO(cx
);
3171 if (!GetProperty(cx
, obj
, obj
, cx
->names().toISOString
, &toISO
)) {
3176 if (!IsCallable(toISO
)) {
3177 JS_ReportErrorNumberASCII(cx
, js::GetErrorMessage
, nullptr,
3178 JSMSG_BAD_TOISOSTRING_PROP
);
3183 return Call(cx
, toISO
, obj
, args
.rval());
3187 JSString
* DateTimeHelper::timeZoneComment(JSContext
* cx
,
3188 DateTimeInfo::ForceUTC forceUTC
,
3189 const char* locale
, double utcTime
,
3192 JS_ReportErrorNumberASCII(cx
, GetErrorMessage
, nullptr,
3193 JSMSG_DEFAULT_LOCALE_ERROR
);
3197 char16_t tzbuf
[100];
3201 char16_t
* timeZoneStart
= tzbuf
+ 2;
3202 constexpr size_t remainingSpace
=
3203 std::size(tzbuf
) - 2 - 1; // for the trailing ')'
3205 int64_t utcMilliseconds
= static_cast<int64_t>(utcTime
);
3206 if (!DateTimeInfo::timeZoneDisplayName(
3207 forceUTC
, timeZoneStart
, remainingSpace
, utcMilliseconds
, locale
)) {
3208 JS_ReportOutOfMemory(cx
);
3212 // Reject if the result string is empty.
3213 size_t len
= js_strlen(timeZoneStart
);
3215 return cx
->names().empty_
;
3218 // Parenthesize the returned display name.
3219 timeZoneStart
[len
] = ')';
3221 return NewStringCopyN
<CanGC
>(cx
, tzbuf
, 2 + len
+ 1);
3224 /* Interface to PRMJTime date struct. */
3225 PRMJTime
DateTimeHelper::toPRMJTime(DateTimeInfo::ForceUTC forceUTC
,
3226 double localTime
, double utcTime
) {
3227 double year
= YearFromTime(localTime
);
3230 prtm
.tm_usec
= int32_t(msFromTime(localTime
)) * 1000;
3231 prtm
.tm_sec
= int8_t(SecFromTime(localTime
));
3232 prtm
.tm_min
= int8_t(MinFromTime(localTime
));
3233 prtm
.tm_hour
= int8_t(HourFromTime(localTime
));
3234 prtm
.tm_mday
= int8_t(DateFromTime(localTime
));
3235 prtm
.tm_mon
= int8_t(MonthFromTime(localTime
));
3236 prtm
.tm_wday
= int8_t(WeekDay(localTime
));
3237 prtm
.tm_year
= year
;
3238 prtm
.tm_yday
= int16_t(DayWithinYear(localTime
, year
));
3239 prtm
.tm_isdst
= (daylightSavingTA(forceUTC
, utcTime
) != 0);
3244 size_t DateTimeHelper::formatTime(DateTimeInfo::ForceUTC forceUTC
, char* buf
,
3245 size_t buflen
, const char* fmt
,
3246 double utcTime
, double localTime
) {
3247 PRMJTime prtm
= toPRMJTime(forceUTC
, localTime
, utcTime
);
3249 // If an equivalent year was used to compute the date/time components, use
3250 // the same equivalent year to determine the time zone name and offset in
3251 // PRMJ_FormatTime(...).
3252 int timeZoneYear
= isRepresentableAsTime32(utcTime
)
3254 : equivalentYearForDST(prtm
.tm_year
);
3255 int offsetInSeconds
= (int)floor((localTime
- utcTime
) / msPerSecond
);
3257 return PRMJ_FormatTime(buf
, buflen
, fmt
, &prtm
, timeZoneYear
,
3261 JSString
* DateTimeHelper::timeZoneComment(JSContext
* cx
,
3262 DateTimeInfo::ForceUTC forceUTC
,
3263 const char* locale
, double utcTime
,
3268 formatTime(forceUTC
, tzbuf
, sizeof tzbuf
, " (%Z)", utcTime
, localTime
);
3270 // Decide whether to use the resulting time zone string.
3272 // Reject it if it contains any non-ASCII or non-printable characters.
3273 // It's then likely in some other character encoding, and we probably
3274 // won't display it correctly.
3276 for (size_t i
= 0; i
< tzlen
; i
++) {
3277 char16_t c
= tzbuf
[i
];
3278 if (!IsAsciiPrintable(c
)) {
3284 // Also reject it if it's not parenthesized or if it's ' ()'.
3285 if (tzbuf
[0] != ' ' || tzbuf
[1] != '(' || tzbuf
[2] == ')') {
3290 return NewStringCopyN
<CanGC
>(cx
, tzbuf
, tzlen
);
3294 return cx
->names().empty_
;
3296 #endif /* JS_HAS_INTL_API */
3298 enum class FormatSpec
{ DateTime
, Date
, Time
};
3300 static bool FormatDate(JSContext
* cx
, DateTimeInfo::ForceUTC forceUTC
,
3301 const char* locale
, double utcTime
, FormatSpec format
,
3302 MutableHandleValue rval
) {
3303 if (!std::isfinite(utcTime
)) {
3304 rval
.setString(cx
->names().Invalid_Date_
);
3308 MOZ_ASSERT(NumbersAreIdentical(TimeClip(utcTime
).toDouble(), utcTime
));
3310 double localTime
= LocalTime(forceUTC
, utcTime
);
3313 RootedString
timeZoneComment(cx
);
3314 if (format
== FormatSpec::DateTime
|| format
== FormatSpec::Time
) {
3315 // Offset from GMT in minutes. The offset includes daylight savings,
3317 int minutes
= (int)trunc((localTime
- utcTime
) / msPerMinute
);
3319 // Map 510 minutes to 0830 hours.
3320 offset
= (minutes
/ 60) * 100 + minutes
% 60;
3322 // Print as "Wed Nov 05 1997 19:38:03 GMT-0800 (PST)".
3324 // The TZA is printed as 'GMT-0800' rather than as 'PST' to avoid
3325 // operating-system dependence on strftime (which PRMJ_FormatTime
3326 // calls, for %Z only.) win32 prints PST as 'Pacific Standard Time.'
3327 // This way we always know what we're getting, and can parse it if
3328 // we produce it. The OS time zone string is included as a comment.
3330 // When ICU is used to retrieve the time zone string, the localized
3331 // 'long' name format from CLDR is used. For example when the default
3332 // locale is "en-US", PST is displayed as 'Pacific Standard Time', but
3333 // when it is "ru", 'Тихоокеанское стандартное время' is used. This
3334 // also means the time zone string may not fit into Latin-1.
3336 // Get a time zone string from the OS or ICU to include as a comment.
3337 timeZoneComment
= DateTimeHelper::timeZoneComment(cx
, forceUTC
, locale
,
3338 utcTime
, localTime
);
3339 if (!timeZoneComment
) {
3346 case FormatSpec::DateTime
:
3347 /* Tue Oct 31 2000 09:41:40 GMT-0800 */
3348 SprintfLiteral(buf
, "%s %s %.2d %.4d %.2d:%.2d:%.2d GMT%+.4d",
3349 days
[int(WeekDay(localTime
))],
3350 months
[int(MonthFromTime(localTime
))],
3351 int(DateFromTime(localTime
)), int(YearFromTime(localTime
)),
3352 int(HourFromTime(localTime
)), int(MinFromTime(localTime
)),
3353 int(SecFromTime(localTime
)), offset
);
3355 case FormatSpec::Date
:
3356 /* Tue Oct 31 2000 */
3357 SprintfLiteral(buf
, "%s %s %.2d %.4d", days
[int(WeekDay(localTime
))],
3358 months
[int(MonthFromTime(localTime
))],
3359 int(DateFromTime(localTime
)),
3360 int(YearFromTime(localTime
)));
3362 case FormatSpec::Time
:
3363 /* 09:41:40 GMT-0800 */
3364 SprintfLiteral(buf
, "%.2d:%.2d:%.2d GMT%+.4d",
3365 int(HourFromTime(localTime
)), int(MinFromTime(localTime
)),
3366 int(SecFromTime(localTime
)), offset
);
3370 RootedString
str(cx
, NewStringCopyZ
<CanGC
>(cx
, buf
));
3375 // Append the time zone string if present.
3376 if (timeZoneComment
&& !timeZoneComment
->empty()) {
3377 str
= js::ConcatStrings
<CanGC
>(cx
, str
, timeZoneComment
);
3383 rval
.setString(str
);
3387 #if !JS_HAS_INTL_API
3388 static bool ToLocaleFormatHelper(JSContext
* cx
, DateObject
* unwrapped
,
3389 const char* format
, MutableHandleValue rval
) {
3390 DateTimeInfo::ForceUTC forceUTC
= unwrapped
->forceUTC();
3391 const char* locale
= unwrapped
->realm()->getLocale();
3392 double utcTime
= unwrapped
->UTCTime().toNumber();
3395 if (!std::isfinite(utcTime
)) {
3396 strcpy(buf
, "InvalidDate");
3398 double localTime
= LocalTime(forceUTC
, utcTime
);
3400 /* Let PRMJTime format it. */
3401 size_t result_len
= DateTimeHelper::formatTime(forceUTC
, buf
, sizeof buf
,
3402 format
, utcTime
, localTime
);
3404 /* If it failed, default to toString. */
3405 if (result_len
== 0) {
3406 return FormatDate(cx
, forceUTC
, locale
, utcTime
, FormatSpec::DateTime
,
3410 /* Hacked check against undesired 2-digit year 00/00/00 form. */
3411 if (strcmp(format
, "%x") == 0 && result_len
>= 6 &&
3412 /* Format %x means use OS settings, which may have 2-digit yr, so
3413 hack end of 3/11/22 or 11.03.22 or 11Mar22 to use 4-digit yr...*/
3414 !IsAsciiDigit(buf
[result_len
- 3]) &&
3415 IsAsciiDigit(buf
[result_len
- 2]) &&
3416 IsAsciiDigit(buf
[result_len
- 1]) &&
3417 /* ...but not if starts with 4-digit year, like 2022/3/11. */
3418 !(IsAsciiDigit(buf
[0]) && IsAsciiDigit(buf
[1]) &&
3419 IsAsciiDigit(buf
[2]) && IsAsciiDigit(buf
[3]))) {
3420 int year
= int(YearFromTime(localTime
));
3421 snprintf(buf
+ (result_len
- 2), (sizeof buf
) - (result_len
- 2), "%d",
3426 if (cx
->runtime()->localeCallbacks
&&
3427 cx
->runtime()->localeCallbacks
->localeToUnicode
) {
3428 return cx
->runtime()->localeCallbacks
->localeToUnicode(cx
, buf
, rval
);
3431 JSString
* str
= NewStringCopyZ
<CanGC
>(cx
, buf
);
3435 rval
.setString(str
);
3440 static bool date_toLocaleString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3441 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "Date.prototype", "toLocaleString");
3442 CallArgs args
= CallArgsFromVp(argc
, vp
);
3445 UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "toLocaleString");
3451 * Use '%#c' for windows, because '%c' is backward-compatible and non-y2k
3452 * with msvc; '%#c' requests that a full year be used in the result string.
3454 static const char format
[] =
3455 # if defined(_WIN32)
3462 return ToLocaleFormatHelper(cx
, unwrapped
, format
, args
.rval());
3465 static bool date_toLocaleDateString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3466 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "Date.prototype",
3467 "toLocaleDateString");
3468 CallArgs args
= CallArgsFromVp(argc
, vp
);
3471 UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "toLocaleDateString");
3477 * Use '%#x' for windows, because '%x' is backward-compatible and non-y2k
3478 * with msvc; '%#x' requests that a full year be used in the result string.
3480 static const char format
[] =
3481 # if defined(_WIN32)
3488 return ToLocaleFormatHelper(cx
, unwrapped
, format
, args
.rval());
3491 static bool date_toLocaleTimeString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3492 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "Date.prototype",
3493 "toLocaleTimeString");
3494 CallArgs args
= CallArgsFromVp(argc
, vp
);
3497 UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "toLocaleTimeString");
3502 return ToLocaleFormatHelper(cx
, unwrapped
, "%X", args
.rval());
3504 #endif /* !JS_HAS_INTL_API */
3506 static bool date_toTimeString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3507 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "Date.prototype", "toTimeString");
3508 CallArgs args
= CallArgsFromVp(argc
, vp
);
3511 UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "toTimeString");
3516 return FormatDate(cx
, unwrapped
->forceUTC(), unwrapped
->realm()->getLocale(),
3517 unwrapped
->UTCTime().toNumber(), FormatSpec::Time
,
3521 static bool date_toDateString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3522 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "Date.prototype", "toDateString");
3523 CallArgs args
= CallArgsFromVp(argc
, vp
);
3526 UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "toDateString");
3531 return FormatDate(cx
, unwrapped
->forceUTC(), unwrapped
->realm()->getLocale(),
3532 unwrapped
->UTCTime().toNumber(), FormatSpec::Date
,
3536 static bool date_toSource(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3537 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "Date.prototype", "toSource");
3538 CallArgs args
= CallArgsFromVp(argc
, vp
);
3540 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "toSource");
3545 JSStringBuilder
sb(cx
);
3546 if (!sb
.append("(new Date(") ||
3547 !NumberValueToStringBuffer(unwrapped
->UTCTime(), sb
) ||
3552 JSString
* str
= sb
.finishString();
3556 args
.rval().setString(str
);
3560 bool date_toString(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3561 AutoJSMethodProfilerEntry
pseudoFrame(cx
, "Date.prototype", "toString");
3562 CallArgs args
= CallArgsFromVp(argc
, vp
);
3564 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "toString");
3569 return FormatDate(cx
, unwrapped
->forceUTC(), unwrapped
->realm()->getLocale(),
3570 unwrapped
->UTCTime().toNumber(), FormatSpec::DateTime
,
3574 bool js::date_valueOf(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3575 CallArgs args
= CallArgsFromVp(argc
, vp
);
3577 auto* unwrapped
= UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "valueOf");
3582 args
.rval().set(unwrapped
->UTCTime());
3586 // ES6 20.3.4.45 Date.prototype[@@toPrimitive]
3587 static bool date_toPrimitive(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3588 CallArgs args
= CallArgsFromVp(argc
, vp
);
3591 if (!args
.thisv().isObject()) {
3592 ReportIncompatible(cx
, args
);
3598 if (!GetFirstArgumentAsTypeHint(cx
, args
, &hint
)) {
3601 if (hint
== JSTYPE_UNDEFINED
) {
3602 hint
= JSTYPE_STRING
;
3605 args
.rval().set(args
.thisv());
3606 RootedObject
obj(cx
, &args
.thisv().toObject());
3607 return OrdinaryToPrimitive(cx
, obj
, hint
, args
.rval());
3610 #if JS_HAS_TEMPORAL_API
3612 * Date.prototype.toTemporalInstant ( )
3614 static bool date_toTemporalInstant(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3615 CallArgs args
= CallArgsFromVp(argc
, vp
);
3619 UnwrapAndTypeCheckThis
<DateObject
>(cx
, args
, "toTemporalInstant");
3625 double utctime
= unwrapped
->UTCTime().toNumber();
3626 if (!std::isfinite(utctime
)) {
3627 JS_ReportErrorNumberASCII(cx
, js::GetErrorMessage
, nullptr,
3628 JSMSG_INVALID_DATE
);
3631 MOZ_ASSERT(IsInteger(utctime
));
3633 auto instant
= temporal::Instant::fromMilliseconds(int64_t(utctime
));
3634 MOZ_ASSERT(temporal::IsValidEpochInstant(instant
));
3637 auto* result
= temporal::CreateTemporalInstant(cx
, instant
);
3641 args
.rval().setObject(*result
);
3644 #endif /* JS_HAS_TEMPORAL_API */
3646 static const JSFunctionSpec date_static_methods
[] = {
3647 JS_FN("UTC", date_UTC
, 7, 0), JS_FN("parse", date_parse
, 1, 0),
3648 JS_FN("now", date_now
, 0, 0), JS_FS_END
};
3650 static const JSFunctionSpec date_methods
[] = {
3651 JS_FN("getTime", date_getTime
, 0, 0),
3652 JS_FN("getTimezoneOffset", date_getTimezoneOffset
, 0, 0),
3653 JS_FN("getYear", date_getYear
, 0, 0),
3654 JS_FN("getFullYear", date_getFullYear
, 0, 0),
3655 JS_FN("getUTCFullYear", date_getUTCFullYear
, 0, 0),
3656 JS_FN("getMonth", date_getMonth
, 0, 0),
3657 JS_FN("getUTCMonth", date_getUTCMonth
, 0, 0),
3658 JS_FN("getDate", date_getDate
, 0, 0),
3659 JS_FN("getUTCDate", date_getUTCDate
, 0, 0),
3660 JS_FN("getDay", date_getDay
, 0, 0),
3661 JS_FN("getUTCDay", date_getUTCDay
, 0, 0),
3662 JS_FN("getHours", date_getHours
, 0, 0),
3663 JS_FN("getUTCHours", date_getUTCHours
, 0, 0),
3664 JS_FN("getMinutes", date_getMinutes
, 0, 0),
3665 JS_FN("getUTCMinutes", date_getUTCMinutes
, 0, 0),
3666 JS_FN("getSeconds", date_getSeconds
, 0, 0),
3667 JS_FN("getUTCSeconds", date_getUTCSeconds
, 0, 0),
3668 JS_FN("getMilliseconds", date_getMilliseconds
, 0, 0),
3669 JS_FN("getUTCMilliseconds", date_getUTCMilliseconds
, 0, 0),
3670 JS_FN("setTime", date_setTime
, 1, 0),
3671 JS_FN("setYear", date_setYear
, 1, 0),
3672 JS_FN("setFullYear", date_setFullYear
, 3, 0),
3673 JS_FN("setUTCFullYear", date_setUTCFullYear
, 3, 0),
3674 JS_FN("setMonth", date_setMonth
, 2, 0),
3675 JS_FN("setUTCMonth", date_setUTCMonth
, 2, 0),
3676 JS_FN("setDate", date_setDate
, 1, 0),
3677 JS_FN("setUTCDate", date_setUTCDate
, 1, 0),
3678 JS_FN("setHours", date_setHours
, 4, 0),
3679 JS_FN("setUTCHours", date_setUTCHours
, 4, 0),
3680 JS_FN("setMinutes", date_setMinutes
, 3, 0),
3681 JS_FN("setUTCMinutes", date_setUTCMinutes
, 3, 0),
3682 JS_FN("setSeconds", date_setSeconds
, 2, 0),
3683 JS_FN("setUTCSeconds", date_setUTCSeconds
, 2, 0),
3684 JS_FN("setMilliseconds", date_setMilliseconds
, 1, 0),
3685 JS_FN("setUTCMilliseconds", date_setUTCMilliseconds
, 1, 0),
3686 JS_FN("toUTCString", date_toUTCString
, 0, 0),
3687 #if JS_HAS_TEMPORAL_API
3688 JS_FN("toTemporalInstant", date_toTemporalInstant
, 0, 0),
3691 JS_SELF_HOSTED_FN("toLocaleString", "Date_toLocaleString", 0, 0),
3692 JS_SELF_HOSTED_FN("toLocaleDateString", "Date_toLocaleDateString", 0, 0),
3693 JS_SELF_HOSTED_FN("toLocaleTimeString", "Date_toLocaleTimeString", 0, 0),
3695 JS_FN("toLocaleString", date_toLocaleString
, 0, 0),
3696 JS_FN("toLocaleDateString", date_toLocaleDateString
, 0, 0),
3697 JS_FN("toLocaleTimeString", date_toLocaleTimeString
, 0, 0),
3699 JS_FN("toDateString", date_toDateString
, 0, 0),
3700 JS_FN("toTimeString", date_toTimeString
, 0, 0),
3701 JS_FN("toISOString", date_toISOString
, 0, 0),
3702 JS_FN("toJSON", date_toJSON
, 1, 0),
3703 JS_FN("toSource", date_toSource
, 0, 0),
3704 JS_FN("toString", date_toString
, 0, 0),
3705 JS_FN("valueOf", date_valueOf
, 0, 0),
3706 JS_SYM_FN(toPrimitive
, date_toPrimitive
, 1, JSPROP_READONLY
),
3709 static bool NewDateObject(JSContext
* cx
, const CallArgs
& args
, ClippedTime t
) {
3710 MOZ_ASSERT(args
.isConstructing());
3712 RootedObject
proto(cx
);
3713 if (!GetPrototypeFromBuiltinConstructor(cx
, args
, JSProto_Date
, &proto
)) {
3717 JSObject
* obj
= NewDateObjectMsec(cx
, t
, proto
);
3722 args
.rval().setObject(*obj
);
3726 static bool ToDateString(JSContext
* cx
, const CallArgs
& args
, ClippedTime t
) {
3727 return FormatDate(cx
, ForceUTC(cx
->realm()), cx
->realm()->getLocale(),
3728 t
.toDouble(), FormatSpec::DateTime
, args
.rval());
3731 static bool DateNoArguments(JSContext
* cx
, const CallArgs
& args
) {
3732 MOZ_ASSERT(args
.length() == 0);
3734 ClippedTime now
= NowAsMillis(cx
);
3736 if (args
.isConstructing()) {
3737 return NewDateObject(cx
, args
, now
);
3740 return ToDateString(cx
, args
, now
);
3743 static bool DateOneArgument(JSContext
* cx
, const CallArgs
& args
) {
3744 MOZ_ASSERT(args
.length() == 1);
3746 if (args
.isConstructing()) {
3747 if (args
[0].isObject()) {
3748 RootedObject
obj(cx
, &args
[0].toObject());
3751 if (!GetBuiltinClass(cx
, obj
, &cls
)) {
3755 if (cls
== ESClass::Date
) {
3756 RootedValue
unboxed(cx
);
3757 if (!Unbox(cx
, obj
, &unboxed
)) {
3761 return NewDateObject(cx
, args
, TimeClip(unboxed
.toNumber()));
3765 if (!ToPrimitive(cx
, args
[0])) {
3770 if (args
[0].isString()) {
3771 JSLinearString
* linearStr
= args
[0].toString()->ensureLinear(cx
);
3776 if (!ParseDate(ForceUTC(cx
->realm()), linearStr
, &t
, cx
)) {
3777 t
= ClippedTime::invalid();
3781 if (!ToNumber(cx
, args
[0], &d
)) {
3787 return NewDateObject(cx
, args
, t
);
3790 return ToDateString(cx
, args
, NowAsMillis(cx
));
3793 static bool DateMultipleArguments(JSContext
* cx
, const CallArgs
& args
) {
3794 MOZ_ASSERT(args
.length() >= 2);
3797 if (args
.isConstructing()) {
3800 if (!ToNumber(cx
, args
[0], &y
)) {
3806 if (!ToNumber(cx
, args
[1], &m
)) {
3812 if (args
.length() >= 3) {
3813 if (!ToNumber(cx
, args
[2], &dt
)) {
3822 if (args
.length() >= 4) {
3823 if (!ToNumber(cx
, args
[3], &h
)) {
3832 if (args
.length() >= 5) {
3833 if (!ToNumber(cx
, args
[4], &min
)) {
3842 if (args
.length() >= 6) {
3843 if (!ToNumber(cx
, args
[5], &s
)) {
3852 if (args
.length() >= 7) {
3853 if (!ToNumber(cx
, args
[6], &milli
)) {
3862 if (!std::isnan(y
)) {
3863 double yint
= ToInteger(y
);
3864 if (0 <= yint
&& yint
<= 99) {
3870 double finalDate
= MakeDate(MakeDay(yr
, m
, dt
), MakeTime(h
, min
, s
, milli
));
3873 return NewDateObject(cx
, args
,
3874 TimeClip(UTC(ForceUTC(cx
->realm()), finalDate
)));
3877 return ToDateString(cx
, args
, NowAsMillis(cx
));
3880 static bool DateConstructor(JSContext
* cx
, unsigned argc
, Value
* vp
) {
3881 AutoJSConstructorProfilerEntry
pseudoFrame(cx
, "Date");
3882 CallArgs args
= CallArgsFromVp(argc
, vp
);
3884 if (args
.length() == 0) {
3885 return DateNoArguments(cx
, args
);
3888 if (args
.length() == 1) {
3889 return DateOneArgument(cx
, args
);
3892 return DateMultipleArguments(cx
, args
);
3895 static bool FinishDateClassInit(JSContext
* cx
, HandleObject ctor
,
3896 HandleObject proto
) {
3898 * Date.prototype.toGMTString has the same initial value as
3899 * Date.prototype.toUTCString.
3901 RootedValue
toUTCStringFun(cx
);
3902 RootedId
toUTCStringId(cx
, NameToId(cx
->names().toUTCString
));
3903 RootedId
toGMTStringId(cx
, NameToId(cx
->names().toGMTString
));
3904 return NativeGetProperty(cx
, proto
.as
<NativeObject
>(), toUTCStringId
,
3906 NativeDefineDataProperty(cx
, proto
.as
<NativeObject
>(), toGMTStringId
,
3910 static const ClassSpec DateObjectClassSpec
= {
3911 GenericCreateConstructor
<DateConstructor
, 7, gc::AllocKind::FUNCTION
>,
3912 GenericCreatePrototype
<DateObject
>,
3913 date_static_methods
,
3917 FinishDateClassInit
};
3919 const JSClass
DateObject::class_
= {"Date",
3920 JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS
) |
3921 JSCLASS_HAS_CACHED_PROTO(JSProto_Date
),
3922 JS_NULL_CLASS_OPS
, &DateObjectClassSpec
};
3924 const JSClass
DateObject::protoClass_
= {
3925 "Date.prototype", JSCLASS_HAS_CACHED_PROTO(JSProto_Date
), JS_NULL_CLASS_OPS
,
3926 &DateObjectClassSpec
};
3928 JSObject
* js::NewDateObjectMsec(JSContext
* cx
, ClippedTime t
,
3929 HandleObject proto
/* = nullptr */) {
3930 DateObject
* obj
= NewObjectWithClassProto
<DateObject
>(cx
, proto
);
3938 JS_PUBLIC_API JSObject
* JS::NewDateObject(JSContext
* cx
, ClippedTime time
) {
3941 return NewDateObjectMsec(cx
, time
);
3944 JS_PUBLIC_API JSObject
* js::NewDateObject(JSContext
* cx
, int year
, int mon
,
3945 int mday
, int hour
, int min
,
3947 MOZ_ASSERT(mon
< 12);
3949 MakeDate(MakeDay(year
, mon
, mday
), MakeTime(hour
, min
, sec
, 0.0));
3950 return NewDateObjectMsec(cx
, TimeClip(UTC(ForceUTC(cx
->realm()), msec_time
)));
3953 JS_PUBLIC_API
bool js::DateIsValid(JSContext
* cx
, HandleObject obj
,
3956 if (!GetBuiltinClass(cx
, obj
, &cls
)) {
3960 if (cls
!= ESClass::Date
) {
3965 RootedValue
unboxed(cx
);
3966 if (!Unbox(cx
, obj
, &unboxed
)) {
3970 *isValid
= !std::isnan(unboxed
.toNumber());
3974 JS_PUBLIC_API JSObject
* JS::NewDateObject(JSContext
* cx
, int year
, int mon
,
3975 int mday
, int hour
, int min
,
3979 return js::NewDateObject(cx
, year
, mon
, mday
, hour
, min
, sec
);
3982 JS_PUBLIC_API
bool JS::ObjectIsDate(JSContext
* cx
, Handle
<JSObject
*> obj
,
3987 if (!GetBuiltinClass(cx
, obj
, &cls
)) {
3991 *isDate
= cls
== ESClass::Date
;
3995 JS_PUBLIC_API
bool js::DateGetMsecSinceEpoch(JSContext
* cx
, HandleObject obj
,
3996 double* msecsSinceEpoch
) {
3998 if (!GetBuiltinClass(cx
, obj
, &cls
)) {
4002 if (cls
!= ESClass::Date
) {
4003 *msecsSinceEpoch
= 0;
4007 RootedValue
unboxed(cx
);
4008 if (!Unbox(cx
, obj
, &unboxed
)) {
4012 *msecsSinceEpoch
= unboxed
.toNumber();
4016 JS_PUBLIC_API
bool JS::IsISOStyleDate(JSContext
* cx
,
4017 const JS::Latin1Chars
& str
) {
4019 return ParseISOStyleDate(ForceUTC(cx
->realm()), str
.begin().get(),
4020 str
.length(), &result
);