Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / js / src / jsdate.cpp
blob71ba567d6307addc4c04d35eb44c7515b49e162a
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/. */
7 /*
8 * JS date methods.
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'.
18 #include "jsdate.h"
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"
26 #include <algorithm>
27 #include <cstring>
28 #include <iterator>
29 #include <math.h>
30 #include <string.h>
32 #include "jsapi.h"
33 #include "jsfriendapi.h"
34 #include "jsnum.h"
35 #include "jstypes.h"
37 #ifdef JS_HAS_TEMPORAL_API
38 # include "builtin/temporal/Instant.h"
39 #endif
40 #include "js/CallAndConstruct.h" // JS::IsCallable
41 #include "js/Conversions.h"
42 #include "js/Date.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"
58 #include "vm/Time.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"
65 using namespace js;
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;
77 using JS::GenericNaN;
78 using JS::GetBuiltinClass;
79 using JS::TimeClip;
80 using JS::ToInteger;
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
85 // in the JS shell
86 static Atomic<bool, Relaxed> sJitter;
87 // The callback we will use for the Gecko implementation of Timer
88 // Clamping/Jittering
89 static Atomic<JS::ReduceMicrosecondTimePrecisionCallback, Relaxed>
90 sReduceMicrosecondTimePrecisionCallback;
93 * The JS 'Date' object is patterned after the Java 'Date' object.
94 * Here is a script:
96 * today = new Date();
98 * print(today.toLocaleString());
100 * weekDay = today.getDay();
103 * These Java (and ECMA-262) methods are supported:
105 * UTC
106 * getDate (getUTCDate)
107 * getDay (getUTCDay)
108 * getHours (getUTCHours)
109 * getMinutes (getUTCMinutes)
110 * getMonth (getUTCMonth)
111 * getSeconds (getUTCSeconds)
112 * getMilliseconds (getUTCMilliseconds)
113 * getTime
114 * getTimezoneOffset
115 * getYear
116 * getFullYear (getUTCFullYear)
117 * parse
118 * setDate (setUTCDate)
119 * setHours (setUTCHours)
120 * setMinutes (setUTCMinutes)
121 * setMonth (setUTCMonth)
122 * setSeconds (setUTCSeconds)
123 * setMilliseconds (setUTCMilliseconds)
124 * setTime
125 * setYear (setFullYear, setUTCFullYear)
126 * toGMTString (toUTCString)
127 * toLocaleString
128 * toString
131 * These Java methods are not supported
133 * setDay
134 * before
135 * after
136 * equals
137 * hashCode
140 namespace {
142 class DateTimeHelper {
143 private:
144 #if JS_HAS_INTL_API
145 static double localTZA(DateTimeInfo::ForceUTC forceUTC, double t,
146 DateTimeInfo::TimeZoneOffset offset);
147 #else
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,
153 double utcTime);
154 #endif
156 public:
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,
162 double localTime);
163 #if !JS_HAS_INTL_API
164 static size_t formatTime(DateTimeInfo::ForceUTC forceUTC, char* buf,
165 size_t buflen, const char* fmt, double utcTime,
166 double localTime);
167 #endif
170 } // namespace
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);
184 if (result < 0) {
185 result += 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); }
194 /* ES5 15.9.1.3. */
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;
209 namespace {
210 struct YearMonthDay {
211 int32_t year;
212 uint32_t month;
213 uint32_t day;
215 } // namespace
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
226 * calendar.
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
272 // 1'471'805/Jun/05.
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;
323 // Year Y:
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)) {
349 return GenericNaN();
351 auto const year = ToYearMonthDay(t).year;
352 return double(year);
355 /* ES5 15.9.1.4. */
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)) {
363 return GenericNaN();
365 const auto month = ToYearMonthDay(t).month;
366 return double(month);
369 /* ES5 15.9.1.5. */
370 static double DateFromTime(double t) {
371 if (!std::isfinite(t)) {
372 return GenericNaN();
374 const auto day = ToYearMonthDay(t).day;
375 return double(day);
378 /* ES5 15.9.1.6. */
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;
386 if (result < 0) {
387 result += 7;
389 return result;
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) {
410 /* Step 1. */
411 if (!std::isfinite(year) || !std::isfinite(month) || !std::isfinite(date)) {
412 return GenericNaN();
415 /* Steps 2-4. */
416 double y = ToInteger(year);
417 double m = ToInteger(month);
418 double dt = ToInteger(date);
420 /* Step 5. */
421 double ym = y + floor(m / 12);
423 /* Step 6. */
424 int mn = int(PositiveModulo(m, 12));
426 /* Steps 7-8. */
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) {
437 /* Step 1. */
438 if (!std::isfinite(day) || !std::isfinite(time)) {
439 return GenericNaN();
442 /* Step 2. */
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,
454 double time) {
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()) {
464 return GenericNaN();
466 return ::YearFromTime(clipped.toDouble());
469 JS_PUBLIC_API double JS::MonthFromTime(double time) {
470 const auto clipped = TimeClip(time);
471 if (!clipped.isValid()) {
472 return GenericNaN();
474 return ::MonthFromTime(clipped.toDouble());
477 JS_PUBLIC_API double JS::DayFromTime(double time) {
478 const auto clipped = TimeClip(time);
479 if (!clipped.isValid()) {
480 return GenericNaN();
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()) {
492 return GenericNaN();
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;
509 sJitter = jitter;
512 #if JS_HAS_INTL_API
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)) {
529 return GenericNaN();
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)) {
540 return GenericNaN();
543 if (t < (StartOfTime - msPerDay) || t > (EndOfTime + msPerDay)) {
544 return GenericNaN();
547 return t - localTZA(forceUTC, t, DateTimeInfo::TimeZoneOffset::Local);
549 #else
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;
578 if (day < 0) {
579 day += 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;
593 /* ES5 15.9.1.8. */
594 double DateTimeHelper::daylightSavingTA(DateTimeInfo::ForceUTC forceUTC,
595 double t) {
596 if (!std::isfinite(t)) {
597 return GenericNaN();
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,
617 double date) {
618 double localTZA = DateTimeInfo::localTZA(forceUTC);
619 double t = daylightSavingTA(forceUTC, date) + localTZA;
620 t = (localTZA >= 0) ? fmod(t, msPerDay) : -fmod(msPerDay - t, msPerDay);
621 return t;
624 /* ES5 15.9.1.9. */
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
637 return t -
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);
650 /* ES5 15.9.1.10. */
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); }
665 /* ES5 15.9.1.11. */
666 static double MakeTime(double hour, double min, double sec, double ms) {
667 /* Step 1. */
668 if (!std::isfinite(hour) || !std::isfinite(min) || !std::isfinite(sec) ||
669 !std::isfinite(ms)) {
670 return GenericNaN();
673 /* Step 2. */
674 double h = ToInteger(hour);
676 /* Step 3. */
677 double m = ToInteger(min);
679 /* Step 4. */
680 double s = ToInteger(sec);
682 /* Step 5. */
683 double milli = ToInteger(ms);
685 /* Steps 6-7. */
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.)
694 // 20.3.3.4
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);
700 // Step 1.
701 double y;
702 if (!ToNumber(cx, args.get(0), &y)) {
703 return false;
706 // Step 2.
707 double m;
708 if (args.length() >= 2) {
709 if (!ToNumber(cx, args[1], &m)) {
710 return false;
712 } else {
713 m = 0;
716 // Step 3.
717 double dt;
718 if (args.length() >= 3) {
719 if (!ToNumber(cx, args[2], &dt)) {
720 return false;
722 } else {
723 dt = 1;
726 // Step 4.
727 double h;
728 if (args.length() >= 4) {
729 if (!ToNumber(cx, args[3], &h)) {
730 return false;
732 } else {
733 h = 0;
736 // Step 5.
737 double min;
738 if (args.length() >= 5) {
739 if (!ToNumber(cx, args[4], &min)) {
740 return false;
742 } else {
743 min = 0;
746 // Step 6.
747 double s;
748 if (args.length() >= 6) {
749 if (!ToNumber(cx, args[5], &s)) {
750 return false;
752 } else {
753 s = 0;
756 // Step 7.
757 double milli;
758 if (args.length() >= 7) {
759 if (!ToNumber(cx, args[6], &milli)) {
760 return false;
762 } else {
763 milli = 0;
766 // Step 8.
767 double yr = y;
768 if (!std::isnan(y)) {
769 double yint = ToInteger(y);
770 if (0 <= yint && yint <= 99) {
771 yr = 1900 + yint;
775 // Step 9.
776 ClippedTime time =
777 TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)));
778 args.rval().set(TimeValue(time));
779 return true;
783 * Read and convert decimal digits from s[*i] into *result
784 * while *i < limit.
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,
791 size_t limit) {
792 size_t init = *i;
793 *result = 0;
794 while (*i < limit && ('0' <= s[*i] && s[*i] <= '9')) {
795 *result *= 10;
796 *result += (s[*i] - '0');
797 ++(*i);
799 return *i != init;
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,
813 size_t limit) {
814 int factor = 100;
815 size_t init = *i;
816 *result = 0;
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
821 continue;
823 *result += (s[*i] - '0') * factor;
824 factor /= 10;
826 return *i != init;
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
834 * on success.
836 template <typename CharT>
837 static bool ParseDigitsN(size_t n, size_t* result, const CharT* s, size_t* i,
838 size_t limit) {
839 size_t init = *i;
841 if (ParseDigits(result, s, i, std::min(limit, init + n))) {
842 return (*i - init) == n;
845 *i = init;
846 return false;
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) {
859 size_t init = *i;
861 if (ParseDigits(result, s, i, std::min(limit, init + n))) {
862 return ((*i - init) > 0) && ((*i - init) <= n);
865 *i = init;
866 return false;
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.
882 * These are:
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".
892 * Date part:
894 * Year:
895 * YYYY (eg 1997)
897 * Year and month:
898 * YYYY-MM (eg 1997-07)
900 * Complete date:
901 * YYYY-MM-DD (eg 1997-07-16)
903 * Time part:
905 * Hours and minutes:
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)
914 * where:
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) {
928 size_t i = 0;
929 int tzMul = 1;
930 int dateMul = 1;
931 size_t year = 1970;
932 size_t month = 1;
933 size_t day = 1;
934 size_t hour = 0;
935 size_t min = 0;
936 size_t sec = 0;
937 int msec = 0;
938 bool isLocalTime = false;
939 size_t tzHour = 0;
940 size_t tzMin = 0;
942 #define PEEK(ch) (i < length && s[i] == ch)
944 #define NEED(ch) \
945 if (i >= length || s[i] != ch) { \
946 return false; \
947 } else { \
948 ++i; \
951 #define DONE_DATE_UNLESS(ch) \
952 if (i >= length || s[i] != ch) { \
953 goto done_date; \
954 } else { \
955 ++i; \
958 #define DONE_UNLESS(ch) \
959 if (i >= length || s[i] != ch) { \
960 goto done; \
961 } else { \
962 ++i; \
965 #define NEED_NDIGITS(n, field) \
966 if (!ParseDigitsN(n, &field, s, &i, length)) { \
967 return false; \
970 if (PEEK('+') || PEEK('-')) {
971 if (PEEK('-')) {
972 dateMul = -1;
974 ++i;
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) {
980 return false;
982 } else {
983 NEED_NDIGITS(4, year);
985 DONE_DATE_UNLESS('-');
986 NEED_NDIGITS(2, month);
987 DONE_DATE_UNLESS('-');
988 NEED_NDIGITS(2, day);
990 done_date:
991 if (PEEK('T')) {
992 ++i;
993 } else {
994 goto done;
997 NEED_NDIGITS(2, hour);
998 NEED(':');
999 NEED_NDIGITS(2, min);
1001 if (PEEK(':')) {
1002 ++i;
1003 NEED_NDIGITS(2, sec);
1004 if (PEEK('.')) {
1005 ++i;
1006 if (!ParseFractional(&msec, s, &i, length)) {
1007 return false;
1012 if (PEEK('Z')) {
1013 ++i;
1014 } else if (PEEK('+') || PEEK('-')) {
1015 if (PEEK('-')) {
1016 tzMul = -1;
1018 ++i;
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".
1024 if (PEEK(':')) {
1025 ++i;
1027 NEED_NDIGITS(2, tzMin);
1028 } else {
1029 isLocalTime = true;
1032 done:
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) {
1037 return false;
1040 if (i != length) {
1041 return false;
1044 month -= 1; /* convert month to 0-based */
1046 double date = MakeDate(MakeDay(dateMul * double(year), month, day),
1047 MakeTime(hour, min, sec, msec));
1049 if (isLocalTime) {
1050 date = UTC(forceUTC, date);
1051 } else {
1052 date -= tzMul * (tzHour * msPerHour + tzMin * msPerMinute);
1055 *result = TimeClip(date);
1056 return NumbersAreIdentical(date, result->toDouble());
1058 #undef PEEK
1059 #undef NEED
1060 #undef DONE_UNLESS
1061 #undef NEED_NDIGITS
1064 int FixupNonFullYear(int year) {
1065 if (year < 50) {
1066 year += 2000;
1067 } else if (year >= 50 && year < 100) {
1068 year += 1900;
1070 return year;
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) {
1080 break;
1083 s++, keyword++;
1084 len--;
1087 return len == 0;
1090 template <typename CharT>
1091 bool MatchesKeyword(const CharT* s, size_t len, const char* keyword) {
1092 while (len > 0) {
1093 MOZ_ASSERT(IsAsciiAlpha(*s));
1094 MOZ_ASSERT(IsAsciiLowercaseAlpha(*keyword) || *keyword == '\0');
1096 if (unicode::ToLowerCase(static_cast<Latin1Char>(*s)) != *keyword) {
1097 return false;
1100 ++s, ++keyword;
1101 --len;
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) {
1125 return false;
1128 ++s, ++prefix;
1131 return true;
1134 template <typename CharT>
1135 bool IsMonthName(const CharT* s, size_t len, int* mon) {
1136 // Month abbreviations < 3 chars are not accepted.
1137 if (len < 3) {
1138 return false;
1141 for (size_t m = 0; m < std::size(month_prefixes); ++m) {
1142 if (StartsWithMonthPrefix(s, month_prefixes[m])) {
1143 // Use numeric value.
1144 *mon = m + 1;
1145 return true;
1149 return false;
1153 * Try to parse the following date formats:
1154 * dd-MMM-yyyy
1155 * dd-MMM-yy
1156 * MMM-dd-yyyy
1157 * MMM-dd-yy
1158 * yyyy-MMM-dd
1159 * yy-MMM-dd
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;
1170 size_t pre = i;
1171 size_t mday;
1172 if (!ParseDigitsNOrLess(6, &mday, s, &i, length)) {
1173 return false;
1175 size_t mdayDigits = i - pre;
1177 if (i >= length || s[i] != '-') {
1178 return false;
1180 ++i;
1182 int mon = 0;
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
1186 size_t start = i;
1187 for (; i < length; i++) {
1188 if (!IsAsciiAlpha(s[i])) {
1189 break;
1193 if (!IsMonthName(s + start, i - start, &mon)) {
1194 return false;
1197 if (i >= length || s[i] != '-') {
1198 return false;
1200 ++i;
1203 pre = i;
1204 size_t year;
1205 if (!ParseDigitsNOrLess(6, &year, s, &i, length)) {
1206 return false;
1208 size_t yearDigits = i - pre;
1210 if (i < length && IsAsciiDigit(s[i])) {
1211 return false;
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) {
1221 return false;
1224 if (yearDigits < 4) {
1225 year = FixupNonFullYear(year);
1228 *indexOut = i;
1229 *yearOut = year;
1230 if (*monOut == -1) {
1231 *monOut = mon;
1233 *mdayOut = mday;
1234 return true;
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;
1257 size_t first;
1258 if (!ParseDigitsNOrLess(6, &first, s, &i, length)) {
1259 return false;
1262 if (i >= length || s[i] != '-') {
1263 return false;
1265 ++i;
1267 size_t second;
1268 if (!ParseDigitsNOrLess(2, &second, s, &i, length)) {
1269 return false;
1272 if (i >= length || s[i] != '-') {
1273 return false;
1275 ++i;
1277 size_t third;
1278 if (!ParseDigitsNOrLess(6, &third, s, &i, length)) {
1279 return false;
1282 int year;
1283 int mon = -1;
1284 int mday = -1;
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) {
1289 mon = first;
1290 } else if (first == 0 || first > 31) {
1291 year = first;
1292 } else {
1293 return false;
1296 if (mon < 0) {
1297 // If month hasn't been set yet, it's definitely the 2nd number
1298 mon = second;
1299 } else {
1300 // If it has, the next number is the mday
1301 mday = second;
1304 if (mday < 0) {
1305 // The third number is probably the mday...
1306 mday = third;
1307 } else {
1308 // But otherwise, it's the year
1309 year = third;
1312 if (mon < 1 || mon > 12 || mday < 1 || mday > 31) {
1313 return false;
1316 if (year < 100) {
1317 year = FixupNonFullYear(year);
1320 *indexOut = i;
1321 *yearOut = year;
1322 *monOut = mon;
1323 *mdayOut = mday;
1324 return true;
1327 struct CharsAndAction {
1328 const char* chars;
1329 int action;
1332 static constexpr const char* const days_of_week[] = {
1333 "monday", "tuesday", "wednesday", "thursday",
1334 "friday", "saturday", "sunday"};
1336 static constexpr CharsAndAction keywords[] = {
1337 // clang-format off
1338 // AM/PM
1339 { "am", -1 },
1340 { "pm", -2 },
1341 // Time zone abbreviations.
1342 { "gmt", 10000 + 0 },
1343 { "z", 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 },
1354 // clang-format on
1357 template <size_t N>
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));
1363 return min;
1366 template <typename CharT>
1367 static bool ParseDate(DateTimeInfo::ForceUTC forceUTC, const CharT* s,
1368 size_t length, ClippedTime* result,
1369 bool* countLateWeekday) {
1370 if (length == 0) {
1371 return false;
1374 if (ParseISOStyleDate(forceUTC, s, length, result)) {
1375 return true;
1378 size_t index = 0;
1379 int mon = -1;
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++) {
1385 int c = s[index];
1387 if (strchr(" ,.-/", c)) {
1388 continue;
1390 if (!IsAsciiAlpha(c)) {
1391 break;
1394 size_t start = index;
1395 index++;
1396 for (; index < length; index++) {
1397 if (!IsAsciiAlpha(s[index])) {
1398 break;
1402 if (index >= length) {
1403 return false;
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])) {
1411 break;
1413 } else {
1414 // Reject numbers directly after letters e.g. foo2
1415 if (IsAsciiDigit(s[index]) && IsAsciiAlpha(s[index - 1])) {
1416 return false;
1421 int year = -1;
1422 int mday = -1;
1423 int hour = -1;
1424 int min = -1;
1425 int sec = -1;
1426 int msec = 0;
1427 int tzOffset = -1;
1429 // One of '+', '-', ':', '/', or 0 (the default value).
1430 int prevc = 0;
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.
1447 bool isDashedDate =
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])) {
1452 return false;
1455 while (index < length) {
1456 int c = s[index];
1457 index++;
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.
1462 if (c == 0x202F) {
1463 c = ' ';
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])))) {
1474 return false;
1477 // Spaces, ASCII control characters, periods, and commas are simply ignored.
1478 if (c <= ' ' || c == '.' || c == ',') {
1479 continue;
1482 // Parse delimiter characters. Save them to the side for future use.
1483 if (c == '/' || c == ':' || c == '+') {
1484 prevc = c;
1485 continue;
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.
1490 if (c == '-') {
1491 if (index < length && IsAsciiDigit(s[index])) {
1492 prevc = c;
1494 continue;
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...)
1501 if (c == '(') {
1502 int depth = 1;
1503 while (index < length) {
1504 c = s[index];
1505 index++;
1506 if (c == '(') {
1507 depth++;
1508 } else if (c == ')') {
1509 if (--depth <= 0) {
1510 break;
1514 continue;
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) {
1522 c = s[index];
1523 if (!IsAsciiDigit(c)) {
1524 break;
1526 u = u * 10 + (c - '0');
1527 index++;
1529 size_t partLength = index - partStart;
1531 // See above for why we have to normalize U+202F.
1532 if (c == 0x202F) {
1533 c = ' ';
1536 int n = int(u);
1539 * Allow TZA before the year, so 'Wed Nov 05 21:49:11 GMT-0800 1997'
1540 * works.
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 &&
1547 year < 0) {
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|.
1554 year = n;
1555 seenFullYear = true;
1556 negativeYear = true;
1557 } else if ((prevc == '+' || prevc == '-') &&
1558 // "1995-09-26-04:30" needs to be parsed as a time,
1559 // not a time zone
1560 (seenGmtAbbr || hour != -1)) {
1561 /* Make ':' case below change tzOffset. */
1562 seenPlusMinus = true;
1564 /* offset */
1565 if (n < 24 && partLength <= 2) {
1566 n = n * 60; /* EG. "GMT-3" */
1567 } else {
1568 n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */
1571 if (prevc == '+') /* plus means east of GMT */
1572 n = -n;
1574 // Reject if not preceded by 'GMT' or if a time zone offset
1575 // was already parsed.
1576 if (tzOffset != 0 && tzOffset != -1) {
1577 return false;
1580 tzOffset = n;
1581 } else if (prevc == '/' && mon >= 0 && mday >= 0 && year < 0) {
1582 if (c <= ' ' || c == ',' || c == '/' || index >= length) {
1583 year = n;
1584 } else {
1585 return false;
1587 } else if (c == ':') {
1588 if (hour < 0) {
1589 hour = /*byte*/ n;
1590 } else if (min < 0) {
1591 min = /*byte*/ n;
1592 } else {
1593 return false;
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.
1600 if (mon < 0) {
1601 mon = /*byte*/ n;
1602 } else if (mday < 0) {
1603 mday = /*byte*/ n;
1604 } else {
1605 return false;
1607 } else if (index < length && c != ',' && c > ' ' && c != '-' &&
1608 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]))))) {
1619 return false;
1620 } else if (seenPlusMinus && n < 60) { /* handle GMT-3:30 */
1621 if (tzOffset < 0) {
1622 tzOffset -= n;
1623 } else {
1624 tzOffset += n;
1626 } else if (hour >= 0 && min < 0) {
1627 min = /*byte*/ n;
1628 } else if (prevc == ':' && min >= 0 && sec < 0) {
1629 sec = /*byte*/ n;
1630 if (c == '.') {
1631 index++;
1632 if (!ParseFractional(&msec, s, &index, length)) {
1633 return false;
1636 } else if (mon < 0) {
1637 mon = /*byte*/ n;
1638 } else if (mon >= 0 && mday < 0) {
1639 mday = /*byte*/ n;
1640 } else if (mon >= 0 && mday >= 0 && year < 0) {
1641 year = n;
1642 seenFullYear = partLength >= 4;
1643 } else {
1644 return false;
1647 prevc = 0;
1648 continue;
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
1653 // abbreviations.
1654 if (IsAsciiAlpha(c)) {
1655 size_t start = index - 1;
1656 while (index < length) {
1657 c = s[index];
1658 if (!IsAsciiAlpha(c)) {
1659 break;
1661 index++;
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) {
1667 return false;
1670 // Completely ignore days of the week, and don't derive any semantics
1671 // from them.
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;
1677 break;
1680 if (isLateWeekday) {
1681 prevc = 0;
1682 continue;
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.
1689 int tryMonth;
1690 if (IsMonthName(s + start, index - start, &tryMonth)) {
1691 if (seenMonthName) {
1692 // Overwrite the previous month name
1693 mon = tryMonth;
1694 prevc = 0;
1695 continue;
1698 seenMonthName = true;
1700 if (mon < 0) {
1701 mon = tryMonth;
1702 } else if (mday < 0) {
1703 mday = mon;
1704 mon = tryMonth;
1705 } else if (year < 0) {
1706 if (mday > 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.
1711 year = mday;
1712 mday = mon;
1713 } else {
1714 year = mon;
1716 mon = tryMonth;
1717 } else {
1718 return false;
1721 prevc = 0;
1722 continue;
1725 size_t k = std::size(keywords);
1726 while (k-- > 0) {
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)) {
1731 continue;
1734 int action = keyword.action;
1736 if (action == 10000) {
1737 seenGmtAbbr = true;
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).
1744 if (action < 0) {
1745 MOZ_ASSERT(action == -1 || action == -2);
1746 if (hour > 12 || hour < 0) {
1747 return false;
1750 if (action == -1 && hour == 12) {
1751 hour = 0;
1752 } else if (action == -2 && hour != 12) {
1753 hour += 12;
1756 break;
1759 // Finally, record a time zone offset.
1760 MOZ_ASSERT(action >= 10000);
1761 tzOffset = action - 10000;
1762 break;
1765 if (k == size_t(-1)) {
1766 return false;
1769 prevc = 0;
1770 continue;
1773 // Any other character fails to parse.
1774 return false;
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
1779 // for that here.
1780 if (mon != -1 && year < 0 && mday < 0) {
1781 // Reject 13-31 for Chrome parity
1782 if (mon >= 13 && mon <= 31) {
1783 return false;
1786 mday = 1;
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)
1790 year = 2001;
1791 } else {
1792 year = FixupNonFullYear(mon);
1793 mon = 1;
1797 if (year < 0 || mon < 0 || mday < 0) {
1798 return false;
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
1809 * is invalid.
1811 * The year is taken to be either l, f if f > 31, or whichever
1812 * is set to zero.
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
1819 * month/day/year.
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) {
1825 return false;
1828 if (year > 0 && (mday == 0 || mday > 31) && !seenFullYear) {
1829 int temp = year;
1830 year = mday;
1831 mday = temp;
1834 if (mday <= 0 || mday > 31) {
1835 return false;
1838 } else if (0 < mon && mon <= 12 && 0 < mday && mday <= 31) {
1839 /* (a) month/day/year */
1840 } else {
1841 /* (b) year/month/day */
1842 if (mon > 31 && mday <= 12 && year <= 31 && !seenFullYear) {
1843 int temp = year;
1844 year = mon;
1845 mon = mday;
1846 mday = temp;
1847 } else {
1848 return false;
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);
1860 if (negativeYear) {
1861 year = -year;
1865 mon -= 1; /* convert month to 0-based */
1866 if (sec < 0) {
1867 sec = 0;
1869 if (min < 0) {
1870 min = 0;
1872 if (hour < 0) {
1873 hour = 0;
1876 double date =
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);
1881 } else {
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);
1892 return true;
1895 static bool ParseDate(DateTimeInfo::ForceUTC forceUTC, JSLinearString* s,
1896 ClippedTime* result, JSContext* cx) {
1897 bool countLateWeekday = false;
1898 bool success;
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;
1926 return success;
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();
1934 return true;
1937 JSString* str = ToString<CanGC>(cx, args[0]);
1938 if (!str) {
1939 return false;
1942 JSLinearString* linearStr = str->ensureLinear(cx);
1943 if (!linearStr) {
1944 return false;
1947 ClippedTime result;
1948 if (!ParseDate(ForceUTC(cx->realm()), linearStr, &result, cx)) {
1949 args.rval().setNaN();
1950 return true;
1953 args.rval().set(TimeValue(result));
1954 return true;
1957 static ClippedTime NowAsMillis(JSContext* cx) {
1958 if (js::SupportDifferentialTesting()) {
1959 return TimeClip(0);
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(),
1967 cx);
1968 } else if (clampAndJitter && sResolutionUsec) {
1969 double clamped = floor(now / sResolutionUsec) * sResolutionUsec;
1971 if (sJitter) {
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
1994 now = clamped;
1996 } else { // No jitter, only clamping
1997 now = clamped;
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)));
2010 return true;
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) {
2026 setUTCTime(t);
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) {
2037 return;
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));
2049 return;
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");
2083 if (!unwrapped) {
2084 return false;
2087 args.rval().set(unwrapped->UTCTime());
2088 return true;
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");
2095 if (!unwrapped) {
2096 return false;
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);
2106 } else {
2107 args.rval().set(yearVal);
2109 return true;
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");
2116 if (!unwrapped) {
2117 return false;
2120 unwrapped->fillLocalTimeSlots();
2121 args.rval().set(unwrapped->localYear());
2122 return true;
2125 static bool date_getUTCFullYear(JSContext* cx, unsigned argc, Value* vp) {
2126 CallArgs args = CallArgsFromVp(argc, vp);
2128 auto* unwrapped =
2129 UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCFullYear");
2130 if (!unwrapped) {
2131 return false;
2134 double result = unwrapped->UTCTime().toNumber();
2135 if (std::isfinite(result)) {
2136 result = YearFromTime(result);
2139 args.rval().setNumber(result);
2140 return true;
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");
2147 if (!unwrapped) {
2148 return false;
2151 unwrapped->fillLocalTimeSlots();
2152 args.rval().set(unwrapped->localMonth());
2153 return true;
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");
2160 if (!unwrapped) {
2161 return false;
2164 double d = unwrapped->UTCTime().toNumber();
2165 args.rval().setNumber(MonthFromTime(d));
2166 return true;
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");
2173 if (!unwrapped) {
2174 return false;
2177 unwrapped->fillLocalTimeSlots();
2179 args.rval().set(unwrapped->localDate());
2180 return true;
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");
2187 if (!unwrapped) {
2188 return false;
2191 double result = unwrapped->UTCTime().toNumber();
2192 if (std::isfinite(result)) {
2193 result = DateFromTime(result);
2196 args.rval().setNumber(result);
2197 return true;
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");
2204 if (!unwrapped) {
2205 return false;
2208 unwrapped->fillLocalTimeSlots();
2209 args.rval().set(unwrapped->localDay());
2210 return true;
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");
2217 if (!unwrapped) {
2218 return false;
2221 double result = unwrapped->UTCTime().toNumber();
2222 if (std::isfinite(result)) {
2223 result = WeekDay(result);
2226 args.rval().setNumber(result);
2227 return true;
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");
2234 if (!unwrapped) {
2235 return false;
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);
2246 } else {
2247 args.rval().setInt32((yearSeconds.toInt32() / int(SecondsPerHour)) %
2248 int(HoursPerDay));
2250 return true;
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");
2257 if (!unwrapped) {
2258 return false;
2261 double result = unwrapped->UTCTime().toNumber();
2262 if (std::isfinite(result)) {
2263 result = HourFromTime(result);
2266 args.rval().setNumber(result);
2267 return true;
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");
2274 if (!unwrapped) {
2275 return false;
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);
2286 } else {
2287 args.rval().setInt32((yearSeconds.toInt32() / int(SecondsPerMinute)) %
2288 int(MinutesPerHour));
2290 return true;
2293 static bool date_getUTCMinutes(JSContext* cx, unsigned argc, Value* vp) {
2294 CallArgs args = CallArgsFromVp(argc, vp);
2296 auto* unwrapped =
2297 UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCMinutes");
2298 if (!unwrapped) {
2299 return false;
2302 double result = unwrapped->UTCTime().toNumber();
2303 if (std::isfinite(result)) {
2304 result = MinFromTime(result);
2307 args.rval().setNumber(result);
2308 return true;
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");
2315 if (!unwrapped) {
2316 return false;
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);
2327 } else {
2328 args.rval().setInt32(yearSeconds.toInt32() % int(SecondsPerMinute));
2330 return true;
2333 static bool date_getUTCSeconds(JSContext* cx, unsigned argc, Value* vp) {
2334 CallArgs args = CallArgsFromVp(argc, vp);
2336 auto* unwrapped =
2337 UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCSeconds");
2338 if (!unwrapped) {
2339 return false;
2342 double result = unwrapped->UTCTime().toNumber();
2343 if (std::isfinite(result)) {
2344 result = SecFromTime(result);
2347 args.rval().setNumber(result);
2348 return true;
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
2359 * UT +00:19:32.
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);
2367 if (!unwrapped) {
2368 return false;
2371 double result = unwrapped->UTCTime().toNumber();
2372 if (std::isfinite(result)) {
2373 result = msFromTime(result);
2376 args.rval().setNumber(result);
2377 return true;
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);
2391 auto* unwrapped =
2392 UnwrapAndTypeCheckThis<DateObject>(cx, args, "getTimezoneOffset");
2393 if (!unwrapped) {
2394 return false;
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);
2409 return true;
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"));
2417 if (!unwrapped) {
2418 return false;
2421 if (args.length() == 0) {
2422 unwrapped->setUTCTime(ClippedTime::invalid(), args.rval());
2423 return true;
2426 double result;
2427 if (!ToNumber(cx, args[0], &result)) {
2428 return false;
2431 unwrapped->setUTCTime(TimeClip(result), args.rval());
2432 return true;
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);
2439 return true;
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);
2448 return true;
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);
2457 return true;
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);
2466 // Step 1.
2467 Rooted<DateObject*> unwrapped(
2468 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setMilliseconds"));
2469 if (!unwrapped) {
2470 return false;
2472 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2474 // Step 2.
2475 double ms;
2476 if (!ToNumber(cx, args.get(0), &ms)) {
2477 return false;
2480 // Step 3.
2481 double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms);
2483 // Step 4.
2484 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), MakeDate(Day(t), time)));
2486 // Steps 5-6.
2487 unwrapped->setUTCTime(u, args.rval());
2488 return true;
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"));
2497 if (!unwrapped) {
2498 return false;
2501 /* Step 1. */
2502 double t = unwrapped->UTCTime().toNumber();
2504 /* Step 2. */
2505 double milli;
2506 if (!ToNumber(cx, args.get(0), &milli)) {
2507 return false;
2509 double time =
2510 MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli);
2512 /* Step 3. */
2513 ClippedTime v = TimeClip(MakeDate(Day(t), time));
2515 /* Steps 4-5. */
2516 unwrapped->setUTCTime(v, args.rval());
2517 return true;
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"));
2526 if (!unwrapped) {
2527 return false;
2530 // Steps 1-2.
2531 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2533 // Steps 3-4.
2534 double s;
2535 if (!ToNumber(cx, args.get(0), &s)) {
2536 return false;
2539 // Steps 5-6.
2540 double milli;
2541 if (!GetMsecsOrDefault(cx, args, 1, t, &milli)) {
2542 return false;
2545 // Step 7.
2546 double date =
2547 MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
2549 // Step 8.
2550 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), date));
2552 // Step 9.
2553 unwrapped->setUTCTime(u, args.rval());
2554 return true;
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"));
2563 if (!unwrapped) {
2564 return false;
2567 /* Step 1. */
2568 double t = unwrapped->UTCTime().toNumber();
2570 /* Step 2. */
2571 double s;
2572 if (!ToNumber(cx, args.get(0), &s)) {
2573 return false;
2576 /* Step 3. */
2577 double milli;
2578 if (!GetMsecsOrDefault(cx, args, 1, t, &milli)) {
2579 return false;
2582 /* Step 4. */
2583 double date =
2584 MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
2586 /* Step 5. */
2587 ClippedTime v = TimeClip(date);
2589 /* Steps 6-7. */
2590 unwrapped->setUTCTime(v, args.rval());
2591 return true;
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"));
2600 if (!unwrapped) {
2601 return false;
2604 // Steps 1-2.
2605 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2607 // Steps 3-4.
2608 double m;
2609 if (!ToNumber(cx, args.get(0), &m)) {
2610 return false;
2613 // Steps 5-6.
2614 double s;
2615 if (!GetSecsOrDefault(cx, args, 1, t, &s)) {
2616 return false;
2619 // Steps 7-8.
2620 double milli;
2621 if (!GetMsecsOrDefault(cx, args, 2, t, &milli)) {
2622 return false;
2625 // Step 9.
2626 double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
2628 // Step 10.
2629 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), date));
2631 // Steps 11-12.
2632 unwrapped->setUTCTime(u, args.rval());
2633 return true;
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"));
2642 if (!unwrapped) {
2643 return false;
2646 /* Step 1. */
2647 double t = unwrapped->UTCTime().toNumber();
2649 /* Step 2. */
2650 double m;
2651 if (!ToNumber(cx, args.get(0), &m)) {
2652 return false;
2655 /* Step 3. */
2656 double s;
2657 if (!GetSecsOrDefault(cx, args, 1, t, &s)) {
2658 return false;
2661 /* Step 4. */
2662 double milli;
2663 if (!GetMsecsOrDefault(cx, args, 2, t, &milli)) {
2664 return false;
2667 /* Step 5. */
2668 double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
2670 /* Step 6. */
2671 ClippedTime v = TimeClip(date);
2673 /* Steps 7-8. */
2674 unwrapped->setUTCTime(v, args.rval());
2675 return true;
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"));
2684 if (!unwrapped) {
2685 return false;
2688 // Steps 1-2.
2689 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2691 // Steps 3-4.
2692 double h;
2693 if (!ToNumber(cx, args.get(0), &h)) {
2694 return false;
2697 // Steps 5-6.
2698 double m;
2699 if (!GetMinsOrDefault(cx, args, 1, t, &m)) {
2700 return false;
2703 // Steps 7-8.
2704 double s;
2705 if (!GetSecsOrDefault(cx, args, 2, t, &s)) {
2706 return false;
2709 // Steps 9-10.
2710 double milli;
2711 if (!GetMsecsOrDefault(cx, args, 3, t, &milli)) {
2712 return false;
2715 // Step 11.
2716 double date = MakeDate(Day(t), MakeTime(h, m, s, milli));
2718 // Step 12.
2719 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), date));
2721 // Steps 13-14.
2722 unwrapped->setUTCTime(u, args.rval());
2723 return true;
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"));
2732 if (!unwrapped) {
2733 return false;
2736 /* Step 1. */
2737 double t = unwrapped->UTCTime().toNumber();
2739 /* Step 2. */
2740 double h;
2741 if (!ToNumber(cx, args.get(0), &h)) {
2742 return false;
2745 /* Step 3. */
2746 double m;
2747 if (!GetMinsOrDefault(cx, args, 1, t, &m)) {
2748 return false;
2751 /* Step 4. */
2752 double s;
2753 if (!GetSecsOrDefault(cx, args, 2, t, &s)) {
2754 return false;
2757 /* Step 5. */
2758 double milli;
2759 if (!GetMsecsOrDefault(cx, args, 3, t, &milli)) {
2760 return false;
2763 /* Step 6. */
2764 double newDate = MakeDate(Day(t), MakeTime(h, m, s, milli));
2766 /* Step 7. */
2767 ClippedTime v = TimeClip(newDate);
2769 /* Steps 8-9. */
2770 unwrapped->setUTCTime(v, args.rval());
2771 return true;
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"));
2780 if (!unwrapped) {
2781 return false;
2784 /* Step 1. */
2785 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2787 /* Step 2. */
2788 double date;
2789 if (!ToNumber(cx, args.get(0), &date)) {
2790 return false;
2793 /* Step 3. */
2794 double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date),
2795 TimeWithinDay(t));
2797 /* Step 4. */
2798 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), newDate));
2800 /* Steps 5-6. */
2801 unwrapped->setUTCTime(u, args.rval());
2802 return true;
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"));
2810 if (!unwrapped) {
2811 return false;
2814 /* Step 1. */
2815 double t = unwrapped->UTCTime().toNumber();
2817 /* Step 2. */
2818 double date;
2819 if (!ToNumber(cx, args.get(0), &date)) {
2820 return false;
2823 /* Step 3. */
2824 double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date),
2825 TimeWithinDay(t));
2827 /* Step 4. */
2828 ClippedTime v = TimeClip(newDate);
2830 /* Steps 5-6. */
2831 unwrapped->setUTCTime(v, args.rval());
2832 return true;
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);
2839 return true;
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);
2848 return true;
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"));
2859 if (!unwrapped) {
2860 return false;
2863 /* Step 1. */
2864 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2866 /* Step 2. */
2867 double m;
2868 if (!ToNumber(cx, args.get(0), &m)) {
2869 return false;
2872 /* Step 3. */
2873 double date;
2874 if (!GetDateOrDefault(cx, args, 1, t, &date)) {
2875 return false;
2878 /* Step 4. */
2879 double newDate =
2880 MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t));
2882 /* Step 5. */
2883 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), newDate));
2885 /* Steps 6-7. */
2886 unwrapped->setUTCTime(u, args.rval());
2887 return true;
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"));
2896 if (!unwrapped) {
2897 return false;
2900 /* Step 1. */
2901 double t = unwrapped->UTCTime().toNumber();
2903 /* Step 2. */
2904 double m;
2905 if (!ToNumber(cx, args.get(0), &m)) {
2906 return false;
2909 /* Step 3. */
2910 double date;
2911 if (!GetDateOrDefault(cx, args, 1, t, &date)) {
2912 return false;
2915 /* Step 4. */
2916 double newDate =
2917 MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t));
2919 /* Step 5. */
2920 ClippedTime v = TimeClip(newDate);
2922 /* Steps 6-7. */
2923 unwrapped->setUTCTime(v, args.rval());
2924 return true;
2927 static double ThisLocalTimeOrZero(DateTimeInfo::ForceUTC forceUTC,
2928 Handle<DateObject*> dateObj) {
2929 double t = dateObj->UTCTime().toNumber();
2930 if (std::isnan(t)) {
2931 return +0;
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"));
2947 if (!unwrapped) {
2948 return false;
2951 /* Step 1. */
2952 double t = ThisLocalTimeOrZero(unwrapped->forceUTC(), unwrapped);
2954 /* Step 2. */
2955 double y;
2956 if (!ToNumber(cx, args.get(0), &y)) {
2957 return false;
2960 /* Step 3. */
2961 double m;
2962 if (!GetMonthOrDefault(cx, args, 1, t, &m)) {
2963 return false;
2966 /* Step 4. */
2967 double date;
2968 if (!GetDateOrDefault(cx, args, 2, t, &date)) {
2969 return false;
2972 /* Step 5. */
2973 double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t));
2975 /* Step 6. */
2976 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), newDate));
2978 /* Steps 7-8. */
2979 unwrapped->setUTCTime(u, args.rval());
2980 return true;
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"));
2989 if (!unwrapped) {
2990 return false;
2993 /* Step 1. */
2994 double t = ThisUTCTimeOrZero(unwrapped);
2996 /* Step 2. */
2997 double y;
2998 if (!ToNumber(cx, args.get(0), &y)) {
2999 return false;
3002 /* Step 3. */
3003 double m;
3004 if (!GetMonthOrDefault(cx, args, 1, t, &m)) {
3005 return false;
3008 /* Step 4. */
3009 double date;
3010 if (!GetDateOrDefault(cx, args, 2, t, &date)) {
3011 return false;
3014 /* Step 5. */
3015 double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t));
3017 /* Step 6. */
3018 ClippedTime v = TimeClip(newDate);
3020 /* Steps 7-8. */
3021 unwrapped->setUTCTime(v, args.rval());
3022 return true;
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"));
3031 if (!unwrapped) {
3032 return false;
3035 /* Step 1. */
3036 double t = ThisLocalTimeOrZero(unwrapped->forceUTC(), unwrapped);
3038 /* Step 2. */
3039 double y;
3040 if (!ToNumber(cx, args.get(0), &y)) {
3041 return false;
3044 /* Step 3. */
3045 if (std::isnan(y)) {
3046 unwrapped->setUTCTime(ClippedTime::invalid(), args.rval());
3047 return true;
3050 /* Step 4. */
3051 double yint = ToInteger(y);
3052 if (0 <= yint && yint <= 99) {
3053 yint += 1900;
3056 /* Step 5. */
3057 double day = MakeDay(yint, MonthFromTime(t), DateFromTime(t));
3059 /* Step 6. */
3060 double u = UTC(unwrapped->forceUTC(), MakeDate(day, TimeWithinDay(t)));
3062 /* Steps 7-8. */
3063 unwrapped->setUTCTime(TimeClip(u), args.rval());
3064 return true;
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"};
3073 /* ES5 B.2.6. */
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");
3079 if (!unwrapped) {
3080 return false;
3083 double utctime = unwrapped->UTCTime().toNumber();
3084 if (!std::isfinite(utctime)) {
3085 args.rval().setString(cx->names().Invalid_Date_);
3086 return true;
3089 char buf[100];
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);
3097 if (!str) {
3098 return false;
3101 args.rval().setString(str);
3102 return true;
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");
3111 if (!unwrapped) {
3112 return false;
3115 double utctime = unwrapped->UTCTime().toNumber();
3116 if (!std::isfinite(utctime)) {
3117 JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
3118 JSMSG_INVALID_DATE);
3119 return false;
3122 char buf[100];
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)));
3130 } else {
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);
3139 if (!str) {
3140 return false;
3142 args.rval().setString(str);
3143 return true;
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);
3151 /* Step 1. */
3152 RootedObject obj(cx, ToObject(cx, args.thisv()));
3153 if (!obj) {
3154 return false;
3157 /* Step 2. */
3158 RootedValue tv(cx, ObjectValue(*obj));
3159 if (!ToPrimitive(cx, JSTYPE_NUMBER, &tv)) {
3160 return false;
3163 /* Step 3. */
3164 if (tv.isDouble() && !std::isfinite(tv.toDouble())) {
3165 args.rval().setNull();
3166 return true;
3169 /* Step 4. */
3170 RootedValue toISO(cx);
3171 if (!GetProperty(cx, obj, obj, cx->names().toISOString, &toISO)) {
3172 return false;
3175 /* Step 5. */
3176 if (!IsCallable(toISO)) {
3177 JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
3178 JSMSG_BAD_TOISOSTRING_PROP);
3179 return false;
3182 /* Step 6. */
3183 return Call(cx, toISO, obj, args.rval());
3186 #if JS_HAS_INTL_API
3187 JSString* DateTimeHelper::timeZoneComment(JSContext* cx,
3188 DateTimeInfo::ForceUTC forceUTC,
3189 const char* locale, double utcTime,
3190 double localTime) {
3191 if (!locale) {
3192 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
3193 JSMSG_DEFAULT_LOCALE_ERROR);
3194 return nullptr;
3197 char16_t tzbuf[100];
3198 tzbuf[0] = ' ';
3199 tzbuf[1] = '(';
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);
3209 return nullptr;
3212 // Reject if the result string is empty.
3213 size_t len = js_strlen(timeZoneStart);
3214 if (len == 0) {
3215 return cx->names().empty_;
3218 // Parenthesize the returned display name.
3219 timeZoneStart[len] = ')';
3221 return NewStringCopyN<CanGC>(cx, tzbuf, 2 + len + 1);
3223 #else
3224 /* Interface to PRMJTime date struct. */
3225 PRMJTime DateTimeHelper::toPRMJTime(DateTimeInfo::ForceUTC forceUTC,
3226 double localTime, double utcTime) {
3227 double year = YearFromTime(localTime);
3229 PRMJTime prtm;
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);
3241 return prtm;
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)
3253 ? prtm.tm_year
3254 : equivalentYearForDST(prtm.tm_year);
3255 int offsetInSeconds = (int)floor((localTime - utcTime) / msPerSecond);
3257 return PRMJ_FormatTime(buf, buflen, fmt, &prtm, timeZoneYear,
3258 offsetInSeconds);
3261 JSString* DateTimeHelper::timeZoneComment(JSContext* cx,
3262 DateTimeInfo::ForceUTC forceUTC,
3263 const char* locale, double utcTime,
3264 double localTime) {
3265 char tzbuf[100];
3267 size_t tzlen =
3268 formatTime(forceUTC, tzbuf, sizeof tzbuf, " (%Z)", utcTime, localTime);
3269 if (tzlen != 0) {
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.
3275 bool usetz = true;
3276 for (size_t i = 0; i < tzlen; i++) {
3277 char16_t c = tzbuf[i];
3278 if (!IsAsciiPrintable(c)) {
3279 usetz = false;
3280 break;
3284 // Also reject it if it's not parenthesized or if it's ' ()'.
3285 if (tzbuf[0] != ' ' || tzbuf[1] != '(' || tzbuf[2] == ')') {
3286 usetz = false;
3289 if (usetz) {
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_);
3305 return true;
3308 MOZ_ASSERT(NumbersAreIdentical(TimeClip(utcTime).toDouble(), utcTime));
3310 double localTime = LocalTime(forceUTC, utcTime);
3312 int offset = 0;
3313 RootedString timeZoneComment(cx);
3314 if (format == FormatSpec::DateTime || format == FormatSpec::Time) {
3315 // Offset from GMT in minutes. The offset includes daylight savings,
3316 // if it applies.
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) {
3340 return false;
3344 char buf[100];
3345 switch (format) {
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);
3354 break;
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)));
3361 break;
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);
3367 break;
3370 RootedString str(cx, NewStringCopyZ<CanGC>(cx, buf));
3371 if (!str) {
3372 return false;
3375 // Append the time zone string if present.
3376 if (timeZoneComment && !timeZoneComment->empty()) {
3377 str = js::ConcatStrings<CanGC>(cx, str, timeZoneComment);
3378 if (!str) {
3379 return false;
3383 rval.setString(str);
3384 return true;
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();
3394 char buf[100];
3395 if (!std::isfinite(utcTime)) {
3396 strcpy(buf, "InvalidDate");
3397 } else {
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,
3407 rval);
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",
3422 year);
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);
3432 if (!str) {
3433 return false;
3435 rval.setString(str);
3436 return true;
3439 /* ES5 15.9.5.5. */
3440 static bool date_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
3441 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toLocaleString");
3442 CallArgs args = CallArgsFromVp(argc, vp);
3444 auto* unwrapped =
3445 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toLocaleString");
3446 if (!unwrapped) {
3447 return false;
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)
3456 "%#c"
3457 # else
3458 "%c"
3459 # endif
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);
3470 auto* unwrapped =
3471 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toLocaleDateString");
3472 if (!unwrapped) {
3473 return false;
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)
3482 "%#x"
3483 # else
3484 "%x"
3485 # endif
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);
3496 auto* unwrapped =
3497 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toLocaleTimeString");
3498 if (!unwrapped) {
3499 return false;
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);
3510 auto* unwrapped =
3511 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toTimeString");
3512 if (!unwrapped) {
3513 return false;
3516 return FormatDate(cx, unwrapped->forceUTC(), unwrapped->realm()->getLocale(),
3517 unwrapped->UTCTime().toNumber(), FormatSpec::Time,
3518 args.rval());
3521 static bool date_toDateString(JSContext* cx, unsigned argc, Value* vp) {
3522 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toDateString");
3523 CallArgs args = CallArgsFromVp(argc, vp);
3525 auto* unwrapped =
3526 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toDateString");
3527 if (!unwrapped) {
3528 return false;
3531 return FormatDate(cx, unwrapped->forceUTC(), unwrapped->realm()->getLocale(),
3532 unwrapped->UTCTime().toNumber(), FormatSpec::Date,
3533 args.rval());
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");
3541 if (!unwrapped) {
3542 return false;
3545 JSStringBuilder sb(cx);
3546 if (!sb.append("(new Date(") ||
3547 !NumberValueToStringBuffer(unwrapped->UTCTime(), sb) ||
3548 !sb.append("))")) {
3549 return false;
3552 JSString* str = sb.finishString();
3553 if (!str) {
3554 return false;
3556 args.rval().setString(str);
3557 return true;
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");
3565 if (!unwrapped) {
3566 return false;
3569 return FormatDate(cx, unwrapped->forceUTC(), unwrapped->realm()->getLocale(),
3570 unwrapped->UTCTime().toNumber(), FormatSpec::DateTime,
3571 args.rval());
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");
3578 if (!unwrapped) {
3579 return false;
3582 args.rval().set(unwrapped->UTCTime());
3583 return true;
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);
3590 // Steps 1-2.
3591 if (!args.thisv().isObject()) {
3592 ReportIncompatible(cx, args);
3593 return false;
3596 // Steps 3-5.
3597 JSType hint;
3598 if (!GetFirstArgumentAsTypeHint(cx, args, &hint)) {
3599 return false;
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);
3617 // Step 1.
3618 auto* unwrapped =
3619 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toTemporalInstant");
3620 if (!unwrapped) {
3621 return false;
3624 // Step 2.
3625 double utctime = unwrapped->UTCTime().toNumber();
3626 if (!std::isfinite(utctime)) {
3627 JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
3628 JSMSG_INVALID_DATE);
3629 return false;
3631 MOZ_ASSERT(IsInteger(utctime));
3633 auto instant = temporal::Instant::fromMilliseconds(int64_t(utctime));
3634 MOZ_ASSERT(temporal::IsValidEpochInstant(instant));
3636 // Step 3.
3637 auto* result = temporal::CreateTemporalInstant(cx, instant);
3638 if (!result) {
3639 return false;
3641 args.rval().setObject(*result);
3642 return true;
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),
3689 #endif
3690 #if JS_HAS_INTL_API
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),
3694 #else
3695 JS_FN("toLocaleString", date_toLocaleString, 0, 0),
3696 JS_FN("toLocaleDateString", date_toLocaleDateString, 0, 0),
3697 JS_FN("toLocaleTimeString", date_toLocaleTimeString, 0, 0),
3698 #endif
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),
3707 JS_FS_END};
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)) {
3714 return false;
3717 JSObject* obj = NewDateObjectMsec(cx, t, proto);
3718 if (!obj) {
3719 return false;
3722 args.rval().setObject(*obj);
3723 return true;
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());
3750 ESClass cls;
3751 if (!GetBuiltinClass(cx, obj, &cls)) {
3752 return false;
3755 if (cls == ESClass::Date) {
3756 RootedValue unboxed(cx);
3757 if (!Unbox(cx, obj, &unboxed)) {
3758 return false;
3761 return NewDateObject(cx, args, TimeClip(unboxed.toNumber()));
3765 if (!ToPrimitive(cx, args[0])) {
3766 return false;
3769 ClippedTime t;
3770 if (args[0].isString()) {
3771 JSLinearString* linearStr = args[0].toString()->ensureLinear(cx);
3772 if (!linearStr) {
3773 return false;
3776 if (!ParseDate(ForceUTC(cx->realm()), linearStr, &t, cx)) {
3777 t = ClippedTime::invalid();
3779 } else {
3780 double d;
3781 if (!ToNumber(cx, args[0], &d)) {
3782 return false;
3784 t = TimeClip(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);
3796 // Step 3.
3797 if (args.isConstructing()) {
3798 // Steps 3a-b.
3799 double y;
3800 if (!ToNumber(cx, args[0], &y)) {
3801 return false;
3804 // Steps 3c-d.
3805 double m;
3806 if (!ToNumber(cx, args[1], &m)) {
3807 return false;
3810 // Steps 3e-f.
3811 double dt;
3812 if (args.length() >= 3) {
3813 if (!ToNumber(cx, args[2], &dt)) {
3814 return false;
3816 } else {
3817 dt = 1;
3820 // Steps 3g-h.
3821 double h;
3822 if (args.length() >= 4) {
3823 if (!ToNumber(cx, args[3], &h)) {
3824 return false;
3826 } else {
3827 h = 0;
3830 // Steps 3i-j.
3831 double min;
3832 if (args.length() >= 5) {
3833 if (!ToNumber(cx, args[4], &min)) {
3834 return false;
3836 } else {
3837 min = 0;
3840 // Steps 3k-l.
3841 double s;
3842 if (args.length() >= 6) {
3843 if (!ToNumber(cx, args[5], &s)) {
3844 return false;
3846 } else {
3847 s = 0;
3850 // Steps 3m-n.
3851 double milli;
3852 if (args.length() >= 7) {
3853 if (!ToNumber(cx, args[6], &milli)) {
3854 return false;
3856 } else {
3857 milli = 0;
3860 // Step 3o.
3861 double yr = y;
3862 if (!std::isnan(y)) {
3863 double yint = ToInteger(y);
3864 if (0 <= yint && yint <= 99) {
3865 yr = 1900 + yint;
3869 // Step 3p.
3870 double finalDate = MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli));
3872 // Steps 3q-t.
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,
3905 &toUTCStringFun) &&
3906 NativeDefineDataProperty(cx, proto.as<NativeObject>(), toGMTStringId,
3907 toUTCStringFun, 0);
3910 static const ClassSpec DateObjectClassSpec = {
3911 GenericCreateConstructor<DateConstructor, 7, gc::AllocKind::FUNCTION>,
3912 GenericCreatePrototype<DateObject>,
3913 date_static_methods,
3914 nullptr,
3915 date_methods,
3916 nullptr,
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);
3931 if (!obj) {
3932 return nullptr;
3934 obj->setUTCTime(t);
3935 return obj;
3938 JS_PUBLIC_API JSObject* JS::NewDateObject(JSContext* cx, ClippedTime time) {
3939 AssertHeapIsIdle();
3940 CHECK_THREAD(cx);
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,
3946 int sec) {
3947 MOZ_ASSERT(mon < 12);
3948 double msec_time =
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,
3954 bool* isValid) {
3955 ESClass cls;
3956 if (!GetBuiltinClass(cx, obj, &cls)) {
3957 return false;
3960 if (cls != ESClass::Date) {
3961 *isValid = false;
3962 return true;
3965 RootedValue unboxed(cx);
3966 if (!Unbox(cx, obj, &unboxed)) {
3967 return false;
3970 *isValid = !std::isnan(unboxed.toNumber());
3971 return true;
3974 JS_PUBLIC_API JSObject* JS::NewDateObject(JSContext* cx, int year, int mon,
3975 int mday, int hour, int min,
3976 int sec) {
3977 AssertHeapIsIdle();
3978 CHECK_THREAD(cx);
3979 return js::NewDateObject(cx, year, mon, mday, hour, min, sec);
3982 JS_PUBLIC_API bool JS::ObjectIsDate(JSContext* cx, Handle<JSObject*> obj,
3983 bool* isDate) {
3984 cx->check(obj);
3986 ESClass cls;
3987 if (!GetBuiltinClass(cx, obj, &cls)) {
3988 return false;
3991 *isDate = cls == ESClass::Date;
3992 return true;
3995 JS_PUBLIC_API bool js::DateGetMsecSinceEpoch(JSContext* cx, HandleObject obj,
3996 double* msecsSinceEpoch) {
3997 ESClass cls;
3998 if (!GetBuiltinClass(cx, obj, &cls)) {
3999 return false;
4002 if (cls != ESClass::Date) {
4003 *msecsSinceEpoch = 0;
4004 return true;
4007 RootedValue unboxed(cx);
4008 if (!Unbox(cx, obj, &unboxed)) {
4009 return false;
4012 *msecsSinceEpoch = unboxed.toNumber();
4013 return true;
4016 JS_PUBLIC_API bool JS::IsISOStyleDate(JSContext* cx,
4017 const JS::Latin1Chars& str) {
4018 ClippedTime result;
4019 return ParseISOStyleDate(ForceUTC(cx->realm()), str.begin().get(),
4020 str.length(), &result);