Backed out changeset 1e582a0e5593 (bug 1852921) for causing build bustages
[gecko.git] / js / src / jsdate.cpp
blob88b8ced7eaae8290ab7c0436d9eda7a75fc3bd90
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 <iterator>
28 #include <math.h>
29 #include <string.h>
31 #include "jsapi.h"
32 #include "jsfriendapi.h"
33 #include "jsnum.h"
34 #include "jstypes.h"
36 #ifdef JS_HAS_TEMPORAL_API
37 # include "builtin/temporal/Instant.h"
38 #endif
39 #include "js/CallAndConstruct.h" // JS::IsCallable
40 #include "js/Conversions.h"
41 #include "js/Date.h"
42 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
43 #include "js/LocaleSensitive.h"
44 #include "js/Object.h" // JS::GetBuiltinClass
45 #include "js/PropertySpec.h"
46 #include "js/Wrapper.h"
47 #include "util/DifferentialTesting.h"
48 #include "util/StringBuffer.h"
49 #include "util/Text.h"
50 #include "vm/DateObject.h"
51 #include "vm/DateTime.h"
52 #include "vm/GlobalObject.h"
53 #include "vm/Interpreter.h"
54 #include "vm/JSContext.h"
55 #include "vm/JSObject.h"
56 #include "vm/StringType.h"
57 #include "vm/Time.h"
59 #include "vm/Compartment-inl.h" // For js::UnwrapAndTypeCheckThis
60 #include "vm/GeckoProfiler-inl.h"
61 #include "vm/JSObject-inl.h"
63 using namespace js;
65 using mozilla::Atomic;
66 using mozilla::BitwiseCast;
67 using mozilla::IsAsciiAlpha;
68 using mozilla::IsAsciiDigit;
69 using mozilla::IsAsciiLowercaseAlpha;
70 using mozilla::NumbersAreIdentical;
71 using mozilla::Relaxed;
73 using JS::AutoCheckCannotGC;
74 using JS::ClippedTime;
75 using JS::GenericNaN;
76 using JS::GetBuiltinClass;
77 using JS::TimeClip;
78 using JS::ToInteger;
80 // When this value is non-zero, we'll round the time by this resolution.
81 static Atomic<uint32_t, Relaxed> sResolutionUsec;
82 // This is not implemented yet, but we will use this to know to jitter the time
83 // in the JS shell
84 static Atomic<bool, Relaxed> sJitter;
85 // The callback we will use for the Gecko implementation of Timer
86 // Clamping/Jittering
87 static Atomic<JS::ReduceMicrosecondTimePrecisionCallback, Relaxed>
88 sReduceMicrosecondTimePrecisionCallback;
91 * The JS 'Date' object is patterned after the Java 'Date' object.
92 * Here is a script:
94 * today = new Date();
96 * print(today.toLocaleString());
98 * weekDay = today.getDay();
101 * These Java (and ECMA-262) methods are supported:
103 * UTC
104 * getDate (getUTCDate)
105 * getDay (getUTCDay)
106 * getHours (getUTCHours)
107 * getMinutes (getUTCMinutes)
108 * getMonth (getUTCMonth)
109 * getSeconds (getUTCSeconds)
110 * getMilliseconds (getUTCMilliseconds)
111 * getTime
112 * getTimezoneOffset
113 * getYear
114 * getFullYear (getUTCFullYear)
115 * parse
116 * setDate (setUTCDate)
117 * setHours (setUTCHours)
118 * setMinutes (setUTCMinutes)
119 * setMonth (setUTCMonth)
120 * setSeconds (setUTCSeconds)
121 * setMilliseconds (setUTCMilliseconds)
122 * setTime
123 * setYear (setFullYear, setUTCFullYear)
124 * toGMTString (toUTCString)
125 * toLocaleString
126 * toString
129 * These Java methods are not supported
131 * setDay
132 * before
133 * after
134 * equals
135 * hashCode
138 namespace {
140 class DateTimeHelper {
141 private:
142 #if JS_HAS_INTL_API
143 static double localTZA(DateTimeInfo::ForceUTC forceUTC, double t,
144 DateTimeInfo::TimeZoneOffset offset);
145 #else
146 static int equivalentYearForDST(int year);
147 static bool isRepresentableAsTime32(double t);
148 static double daylightSavingTA(DateTimeInfo::ForceUTC forceUTC, double t);
149 static double adjustTime(DateTimeInfo::ForceUTC forceUTC, double date);
150 static PRMJTime toPRMJTime(DateTimeInfo::ForceUTC forceUTC, double localTime,
151 double utcTime);
152 #endif
154 public:
155 static double localTime(DateTimeInfo::ForceUTC forceUTC, double t);
156 static double UTC(DateTimeInfo::ForceUTC forceUTC, double t);
157 static JSString* timeZoneComment(JSContext* cx,
158 DateTimeInfo::ForceUTC forceUTC,
159 const char* locale, double utcTime,
160 double localTime);
161 #if !JS_HAS_INTL_API
162 static size_t formatTime(DateTimeInfo::ForceUTC forceUTC, char* buf,
163 size_t buflen, const char* fmt, double utcTime,
164 double localTime);
165 #endif
168 } // namespace
170 static DateTimeInfo::ForceUTC ForceUTC(const Realm* realm) {
171 return realm->creationOptions().forceUTC() ? DateTimeInfo::ForceUTC::Yes
172 : DateTimeInfo::ForceUTC::No;
175 // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
176 // 5.2.5 Mathematical Operations
177 static inline double PositiveModulo(double dividend, double divisor) {
178 MOZ_ASSERT(divisor > 0);
179 MOZ_ASSERT(std::isfinite(divisor));
181 double result = fmod(dividend, divisor);
182 if (result < 0) {
183 result += divisor;
185 return result + (+0.0);
188 static inline double Day(double t) { return floor(t / msPerDay); }
190 static double TimeWithinDay(double t) { return PositiveModulo(t, msPerDay); }
192 /* ES5 15.9.1.3. */
193 static inline bool IsLeapYear(double year) {
194 MOZ_ASSERT(ToInteger(year) == year);
195 return fmod(year, 4) == 0 && (fmod(year, 100) != 0 || fmod(year, 400) == 0);
198 static inline double DayFromYear(double y) {
199 return 365 * (y - 1970) + floor((y - 1969) / 4.0) -
200 floor((y - 1901) / 100.0) + floor((y - 1601) / 400.0);
203 static inline double TimeFromYear(double y) {
204 return DayFromYear(y) * msPerDay;
207 namespace {
208 struct YearMonthDay {
209 int32_t year;
210 uint32_t month;
211 uint32_t day;
213 } // namespace
216 * This function returns the year, month and day corresponding to a given
217 * time value. The implementation closely follows (w.r.t. types and variable
218 * names) the algorithm shown in Figure 12 of [1].
220 * A key point of the algorithm is that it works on the so called
221 * Computational calendar where years run from March to February -- this
222 * largely avoids complications with leap years. The algorithm finds the
223 * date in the Computation calendar and then maps it to the Gregorian
224 * calendar.
226 * [1] Neri C, Schneider L., "Euclidean affine functions and their
227 * application to calendar algorithms."
228 * Softw Pract Exper. 2023;53(4):937-970. doi: 10.1002/spe.3172
229 * https://onlinelibrary.wiley.com/doi/full/10.1002/spe.3172
231 static YearMonthDay ToYearMonthDay(double t) {
232 MOZ_ASSERT(ToInteger(t) == t);
234 // Calendar cycles repeat every 400 years in the Gregorian calendar: a
235 // leap day is added every 4 years, removed every 100 years and added
236 // every 400 years. The number of days in 400 years is cycleInDays.
237 constexpr uint32_t cycleInYears = 400;
238 constexpr uint32_t cycleInDays = cycleInYears * 365 + (cycleInYears / 4) -
239 (cycleInYears / 100) + (cycleInYears / 400);
240 static_assert(cycleInDays == 146097, "Wrong calculation of cycleInDays.");
242 // The natural epoch for the Computational calendar is 0000/Mar/01 and
243 // there are rataDie1970Jan1 = 719468 days from this date to 1970/Jan/01,
244 // the epoch used by ES2024, 21.4.1.1.
245 constexpr uint32_t rataDie1970Jan1 = 719468;
247 constexpr uint32_t maxU32 = std::numeric_limits<uint32_t>::max();
249 // Let N_U be the number of days since the 1970/Jan/01. This function sets
250 // N = N_U + K, where K = rataDie1970Jan1 + s * cycleInDays and s is an
251 // integer number (to be chosen). Then, it evaluates 4 * N + 3 on uint32_t
252 // operands so that N must be positive and, to prevent overflow,
253 // 4 * N + 3 <= maxU32 <=> N <= (maxU32 - 3) / 4.
254 // Therefore, we must have 0 <= N_U + K <= (maxU32 - 3) / 4 or, in other
255 // words, N_U must be in [minDays, maxDays] = [-K, (maxU32 - 3) / 4 - K].
256 // Notice that this interval moves cycleInDays positions to the left when
257 // s is incremented. We chose s to get the interval's mid-point as close
258 // as possible to 0. For this, we wish to have:
259 // K ~= (maxU32 - 3) / 4 - K <=> 2 * K ~= (maxU32 - 3) / 4 <=>
260 // K ~= (maxU32 - 3) / 8 <=>
261 // rataDie1970Jan1 + s * cycleInDays ~= (maxU32 - 3) / 8 <=>
262 // s ~= ((maxU32 - 3) / 8 - rataDie1970Jan1) / cycleInDays ~= 3669.8.
263 // Therefore, we chose s = 3670. The shift and correction constants
264 // (see [1]) are then:
265 constexpr uint32_t s = 3670;
266 constexpr uint32_t K = rataDie1970Jan1 + s * cycleInDays;
267 constexpr uint32_t L = s * cycleInYears;
269 // [minDays, maxDays] correspond to a date range from -1'468'000/Mar/01 to
270 // 1'471'805/Jun/05.
271 constexpr int32_t minDays = -int32_t(K);
272 constexpr int32_t maxDays = (maxU32 - 3) / 4 - K;
273 static_assert(minDays == -536'895'458, "Wrong calculation of minDays or K.");
274 static_assert(maxDays == 536'846'365, "Wrong calculation of maxDays or K.");
276 // These are hard limits for the algorithm and far greater than the
277 // range [-8.64e15, 8.64e15] required by ES2024 21.4.1.1. Callers must
278 // ensure this function is not called out of the hard limits and,
279 // preferably, not outside the ES2024 limits.
280 constexpr int64_t minTime = minDays * int64_t(msPerDay);
281 [[maybe_unused]] constexpr int64_t maxTime = maxDays * int64_t(msPerDay);
282 MOZ_ASSERT(double(minTime) <= t && t <= double(maxTime));
283 const int64_t time = int64_t(t);
285 // Since time is the number of milliseconds since the epoch, 1970/Jan/01,
286 // one might expect N_U = time / uint64_t(msPerDay) is the number of days
287 // since epoch. There's a catch tough. Consider, for instance, half day
288 // before the epoch, that is, t = -0.5 * msPerDay. This falls on
289 // 1969/Dec/31 and should correspond to N_U = -1 but the above gives
290 // N_U = 0. Indeed, t / msPerDay = -0.5 but integer division truncates
291 // towards 0 (C++ [expr.mul]/4) and not towards -infinity as needed, so
292 // that time / uint64_t(msPerDay) = 0. To workaround this issue we perform
293 // the division on positive operands so that truncations towards 0 and
294 // -infinity are equivalent. For this, set u = time - minTime, which is
295 // positive as asserted above. Then, perform the division u / msPerDay and
296 // to the result add minTime / msPerDay = minDays to cancel the
297 // subtraction of minTime.
298 const uint64_t u = uint64_t(time - minTime);
299 const int32_t N_U = int32_t(u / uint64_t(msPerDay)) + minDays;
300 MOZ_ASSERT(minDays <= N_U && N_U <= maxDays);
302 const uint32_t N = uint32_t(N_U) + K;
304 // Some magic numbers have been explained above but, unfortunately,
305 // others with no precise interpretation do appear. They mostly come
306 // from numerical approximations of Euclidean affine functions (see [1])
307 // which are faster for the CPU to calculate. Unfortunately, no compiler
308 // can do these optimizations.
310 // Century C and year of the century N_C:
311 const uint32_t N_1 = 4 * N + 3;
312 const uint32_t C = N_1 / 146097;
313 const uint32_t N_C = N_1 % 146097 / 4;
315 // Year of the century Z and day of the year N_Y:
316 const uint32_t N_2 = 4 * N_C + 3;
317 const uint64_t P_2 = uint64_t(2939745) * N_2;
318 const uint32_t Z = uint32_t(P_2 / 4294967296);
319 const uint32_t N_Y = uint32_t(P_2 % 4294967296) / 2939745 / 4;
321 // Year Y:
322 const uint32_t Y = 100 * C + Z;
324 // Month M and day D.
325 // The expression for N_3 has been adapted to account for the difference
326 // between month numbers in ES5 15.9.1.4 (from 0 to 11) and [1] (from 1
327 // to 12). This is done by subtracting 65536 from the original
328 // expression so that M decreases by 1 and so does M_G further down.
329 const uint32_t N_3 = 2141 * N_Y + 132377; // 132377 = 197913 - 65536
330 const uint32_t M = N_3 / 65536;
331 const uint32_t D = N_3 % 65536 / 2141;
333 // Map from Computational to Gregorian calendar. Notice also the year
334 // correction and the type change and that Jan/01 is day 306 of the
335 // Computational calendar, cf. Table 1. [1]
336 constexpr uint32_t daysFromMar01ToJan01 = 306;
337 const uint32_t J = N_Y >= daysFromMar01ToJan01;
338 const int32_t Y_G = int32_t((Y - L) + J);
339 const uint32_t M_G = J ? M - 12 : M;
340 const uint32_t D_G = D + 1;
342 return {Y_G, M_G, D_G};
345 static double YearFromTime(double t) {
346 if (!std::isfinite(t)) {
347 return GenericNaN();
349 auto const year = ToYearMonthDay(t).year;
350 return double(year);
353 /* ES5 15.9.1.4. */
354 static double DayWithinYear(double t, double year) {
355 MOZ_ASSERT_IF(std::isfinite(t), YearFromTime(t) == year);
356 return Day(t) - DayFromYear(year);
359 static double MonthFromTime(double t) {
360 if (!std::isfinite(t)) {
361 return GenericNaN();
363 const auto month = ToYearMonthDay(t).month;
364 return double(month);
367 /* ES5 15.9.1.5. */
368 static double DateFromTime(double t) {
369 if (!std::isfinite(t)) {
370 return GenericNaN();
372 const auto day = ToYearMonthDay(t).day;
373 return double(day);
376 /* ES5 15.9.1.6. */
377 static int WeekDay(double t) {
379 * We can't assert TimeClip(t) == t because we call this function with
380 * local times, which can be offset outside TimeClip's permitted range.
382 MOZ_ASSERT(ToInteger(t) == t);
383 int result = (int(Day(t)) + 4) % 7;
384 if (result < 0) {
385 result += 7;
387 return result;
390 static inline int DayFromMonth(int month, bool isLeapYear) {
392 * The following array contains the day of year for the first day of
393 * each month, where index 0 is January, and day 0 is January 1.
395 static const int firstDayOfMonth[2][13] = {
396 {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
397 {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}};
399 MOZ_ASSERT(0 <= month && month <= 12);
400 return firstDayOfMonth[isLeapYear][month];
403 template <typename T>
404 static inline int DayFromMonth(T month, bool isLeapYear) = delete;
406 /* ES5 15.9.1.12 (out of order to accommodate DaylightSavingTA). */
407 static double MakeDay(double year, double month, double date) {
408 /* Step 1. */
409 if (!std::isfinite(year) || !std::isfinite(month) || !std::isfinite(date)) {
410 return GenericNaN();
413 /* Steps 2-4. */
414 double y = ToInteger(year);
415 double m = ToInteger(month);
416 double dt = ToInteger(date);
418 /* Step 5. */
419 double ym = y + floor(m / 12);
421 /* Step 6. */
422 int mn = int(PositiveModulo(m, 12));
424 /* Steps 7-8. */
425 bool leap = IsLeapYear(ym);
427 double yearday = floor(TimeFromYear(ym) / msPerDay);
428 double monthday = DayFromMonth(mn, leap);
430 return yearday + monthday + dt - 1;
433 /* ES5 15.9.1.13 (out of order to accommodate DaylightSavingTA). */
434 static inline double MakeDate(double day, double time) {
435 /* Step 1. */
436 if (!std::isfinite(day) || !std::isfinite(time)) {
437 return GenericNaN();
440 /* Step 2. */
441 return day * msPerDay + time;
444 JS_PUBLIC_API double JS::MakeDate(double year, unsigned month, unsigned day) {
445 MOZ_ASSERT(month <= 11);
446 MOZ_ASSERT(day >= 1 && day <= 31);
448 return ::MakeDate(MakeDay(year, month, day), 0);
451 JS_PUBLIC_API double JS::MakeDate(double year, unsigned month, unsigned day,
452 double time) {
453 MOZ_ASSERT(month <= 11);
454 MOZ_ASSERT(day >= 1 && day <= 31);
456 return ::MakeDate(MakeDay(year, month, day), time);
459 JS_PUBLIC_API double JS::YearFromTime(double time) {
460 const auto clipped = TimeClip(time);
461 if (!clipped.isValid()) {
462 return GenericNaN();
464 return ::YearFromTime(clipped.toDouble());
467 JS_PUBLIC_API double JS::MonthFromTime(double time) {
468 const auto clipped = TimeClip(time);
469 if (!clipped.isValid()) {
470 return GenericNaN();
472 return ::MonthFromTime(clipped.toDouble());
475 JS_PUBLIC_API double JS::DayFromTime(double time) {
476 const auto clipped = TimeClip(time);
477 if (!clipped.isValid()) {
478 return GenericNaN();
480 return DateFromTime(clipped.toDouble());
483 JS_PUBLIC_API double JS::DayFromYear(double year) {
484 return ::DayFromYear(year);
487 JS_PUBLIC_API double JS::DayWithinYear(double time, double year) {
488 const auto clipped = TimeClip(time);
489 if (!clipped.isValid()) {
490 return GenericNaN();
492 return ::DayWithinYear(clipped.toDouble(), year);
495 JS_PUBLIC_API void JS::SetReduceMicrosecondTimePrecisionCallback(
496 JS::ReduceMicrosecondTimePrecisionCallback callback) {
497 sReduceMicrosecondTimePrecisionCallback = callback;
500 JS_PUBLIC_API void JS::SetTimeResolutionUsec(uint32_t resolution, bool jitter) {
501 sResolutionUsec = resolution;
502 sJitter = jitter;
505 #if JS_HAS_INTL_API
506 // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
507 // 20.3.1.7 LocalTZA ( t, isUTC )
508 double DateTimeHelper::localTZA(DateTimeInfo::ForceUTC forceUTC, double t,
509 DateTimeInfo::TimeZoneOffset offset) {
510 MOZ_ASSERT(std::isfinite(t));
512 int64_t milliseconds = static_cast<int64_t>(t);
513 int32_t offsetMilliseconds =
514 DateTimeInfo::getOffsetMilliseconds(forceUTC, milliseconds, offset);
515 return static_cast<double>(offsetMilliseconds);
518 // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
519 // 20.3.1.8 LocalTime ( t )
520 double DateTimeHelper::localTime(DateTimeInfo::ForceUTC forceUTC, double t) {
521 if (!std::isfinite(t)) {
522 return GenericNaN();
525 MOZ_ASSERT(StartOfTime <= t && t <= EndOfTime);
526 return t + localTZA(forceUTC, t, DateTimeInfo::TimeZoneOffset::UTC);
529 // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
530 // 20.3.1.9 UTC ( t )
531 double DateTimeHelper::UTC(DateTimeInfo::ForceUTC forceUTC, double t) {
532 if (!std::isfinite(t)) {
533 return GenericNaN();
536 if (t < (StartOfTime - msPerDay) || t > (EndOfTime + msPerDay)) {
537 return GenericNaN();
540 return t - localTZA(forceUTC, t, DateTimeInfo::TimeZoneOffset::Local);
542 #else
544 * Find a year for which any given date will fall on the same weekday.
546 * This function should be used with caution when used other than
547 * for determining DST; it hasn't been proven not to produce an
548 * incorrect year for times near year boundaries.
550 int DateTimeHelper::equivalentYearForDST(int year) {
552 * Years and leap years on which Jan 1 is a Sunday, Monday, etc.
554 * yearStartingWith[0][i] is an example non-leap year where
555 * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
557 * yearStartingWith[1][i] is an example leap year where
558 * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
560 * Keep two different mappings, one for past years (< 1970), and a
561 * different one for future years (> 2037).
563 static const int pastYearStartingWith[2][7] = {
564 {1978, 1973, 1974, 1975, 1981, 1971, 1977},
565 {1984, 1996, 1980, 1992, 1976, 1988, 1972}};
566 static const int futureYearStartingWith[2][7] = {
567 {2034, 2035, 2030, 2031, 2037, 2027, 2033},
568 {2012, 2024, 2036, 2020, 2032, 2016, 2028}};
570 int day = int(DayFromYear(year) + 4) % 7;
571 if (day < 0) {
572 day += 7;
575 const auto& yearStartingWith =
576 year < 1970 ? pastYearStartingWith : futureYearStartingWith;
577 return yearStartingWith[IsLeapYear(year)][day];
580 // Return true if |t| is representable as a 32-bit time_t variable, that means
581 // the year is in [1970, 2038).
582 bool DateTimeHelper::isRepresentableAsTime32(double t) {
583 return 0.0 <= t && t < 2145916800000.0;
586 /* ES5 15.9.1.8. */
587 double DateTimeHelper::daylightSavingTA(DateTimeInfo::ForceUTC forceUTC,
588 double t) {
589 if (!std::isfinite(t)) {
590 return GenericNaN();
594 * If earlier than 1970 or after 2038, potentially beyond the ken of
595 * many OSes, map it to an equivalent year before asking.
597 if (!isRepresentableAsTime32(t)) {
598 int year = equivalentYearForDST(int(YearFromTime(t)));
599 double day = MakeDay(year, MonthFromTime(t), DateFromTime(t));
600 t = MakeDate(day, TimeWithinDay(t));
603 int64_t utcMilliseconds = static_cast<int64_t>(t);
604 int32_t offsetMilliseconds =
605 DateTimeInfo::getDSTOffsetMilliseconds(forceUTC, utcMilliseconds);
606 return static_cast<double>(offsetMilliseconds);
609 double DateTimeHelper::adjustTime(DateTimeInfo::ForceUTC forceUTC,
610 double date) {
611 double localTZA = DateTimeInfo::localTZA(forceUTC);
612 double t = daylightSavingTA(forceUTC, date) + localTZA;
613 t = (localTZA >= 0) ? fmod(t, msPerDay) : -fmod(msPerDay - t, msPerDay);
614 return t;
617 /* ES5 15.9.1.9. */
618 double DateTimeHelper::localTime(DateTimeInfo::ForceUTC forceUTC, double t) {
619 return t + adjustTime(forceUTC, t);
622 double DateTimeHelper::UTC(DateTimeInfo::ForceUTC forceUTC, double t) {
623 // Following the ES2017 specification creates undesirable results at DST
624 // transitions. For example when transitioning from PST to PDT,
625 // |new Date(2016,2,13,2,0,0).toTimeString()| returns the string value
626 // "01:00:00 GMT-0800 (PST)" instead of "03:00:00 GMT-0700 (PDT)". Follow
627 // V8 and subtract one hour before computing the offset.
628 // Spec bug: https://bugs.ecmascript.org/show_bug.cgi?id=4007
630 return t -
631 adjustTime(forceUTC, t - DateTimeInfo::localTZA(forceUTC) - msPerHour);
633 #endif /* JS_HAS_INTL_API */
635 static double LocalTime(DateTimeInfo::ForceUTC forceUTC, double t) {
636 return DateTimeHelper::localTime(forceUTC, t);
639 static double UTC(DateTimeInfo::ForceUTC forceUTC, double t) {
640 return DateTimeHelper::UTC(forceUTC, t);
643 /* ES5 15.9.1.10. */
644 static double HourFromTime(double t) {
645 return PositiveModulo(floor(t / msPerHour), HoursPerDay);
648 static double MinFromTime(double t) {
649 return PositiveModulo(floor(t / msPerMinute), MinutesPerHour);
652 static double SecFromTime(double t) {
653 return PositiveModulo(floor(t / msPerSecond), SecondsPerMinute);
656 static double msFromTime(double t) { return PositiveModulo(t, msPerSecond); }
658 /* ES5 15.9.1.11. */
659 static double MakeTime(double hour, double min, double sec, double ms) {
660 /* Step 1. */
661 if (!std::isfinite(hour) || !std::isfinite(min) || !std::isfinite(sec) ||
662 !std::isfinite(ms)) {
663 return GenericNaN();
666 /* Step 2. */
667 double h = ToInteger(hour);
669 /* Step 3. */
670 double m = ToInteger(min);
672 /* Step 4. */
673 double s = ToInteger(sec);
675 /* Step 5. */
676 double milli = ToInteger(ms);
678 /* Steps 6-7. */
679 return h * msPerHour + m * msPerMinute + s * msPerSecond + milli;
683 * end of ECMA 'support' functions
686 // ES2017 draft rev (TODO: Add git hash when PR 642 is merged.)
687 // 20.3.3.4
688 // Date.UTC(year [, month [, date [, hours [, minutes [, seconds [, ms]]]]]])
689 static bool date_UTC(JSContext* cx, unsigned argc, Value* vp) {
690 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date", "UTC");
691 CallArgs args = CallArgsFromVp(argc, vp);
693 // Step 1.
694 double y;
695 if (!ToNumber(cx, args.get(0), &y)) {
696 return false;
699 // Step 2.
700 double m;
701 if (args.length() >= 2) {
702 if (!ToNumber(cx, args[1], &m)) {
703 return false;
705 } else {
706 m = 0;
709 // Step 3.
710 double dt;
711 if (args.length() >= 3) {
712 if (!ToNumber(cx, args[2], &dt)) {
713 return false;
715 } else {
716 dt = 1;
719 // Step 4.
720 double h;
721 if (args.length() >= 4) {
722 if (!ToNumber(cx, args[3], &h)) {
723 return false;
725 } else {
726 h = 0;
729 // Step 5.
730 double min;
731 if (args.length() >= 5) {
732 if (!ToNumber(cx, args[4], &min)) {
733 return false;
735 } else {
736 min = 0;
739 // Step 6.
740 double s;
741 if (args.length() >= 6) {
742 if (!ToNumber(cx, args[5], &s)) {
743 return false;
745 } else {
746 s = 0;
749 // Step 7.
750 double milli;
751 if (args.length() >= 7) {
752 if (!ToNumber(cx, args[6], &milli)) {
753 return false;
755 } else {
756 milli = 0;
759 // Step 8.
760 double yr = y;
761 if (!std::isnan(y)) {
762 double yint = ToInteger(y);
763 if (0 <= yint && yint <= 99) {
764 yr = 1900 + yint;
768 // Step 9.
769 ClippedTime time =
770 TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)));
771 args.rval().set(TimeValue(time));
772 return true;
776 * Read and convert decimal digits from s[*i] into *result
777 * while *i < limit.
779 * Succeed if any digits are converted. Advance *i only
780 * as digits are consumed.
782 template <typename CharT>
783 static bool ParseDigits(size_t* result, const CharT* s, size_t* i,
784 size_t limit) {
785 size_t init = *i;
786 *result = 0;
787 while (*i < limit && ('0' <= s[*i] && s[*i] <= '9')) {
788 *result *= 10;
789 *result += (s[*i] - '0');
790 ++(*i);
792 return *i != init;
796 * Read and convert decimal digits to the right of a decimal point,
797 * representing a fractional integer, from s[*i] into *result
798 * while *i < limit.
800 * Succeed if any digits are converted. Advance *i only
801 * as digits are consumed.
803 template <typename CharT>
804 static bool ParseFractional(double* result, const CharT* s, size_t* i,
805 size_t limit) {
806 double factor = 0.1;
807 size_t init = *i;
808 *result = 0.0;
809 while (*i < limit && ('0' <= s[*i] && s[*i] <= '9')) {
810 *result += (s[*i] - '0') * factor;
811 factor *= 0.1;
812 ++(*i);
814 return *i != init;
818 * Read and convert exactly n decimal digits from s[*i]
819 * to s[min(*i+n,limit)] into *result.
821 * Succeed if exactly n digits are converted. Advance *i only
822 * on success.
824 template <typename CharT>
825 static bool ParseDigitsN(size_t n, size_t* result, const CharT* s, size_t* i,
826 size_t limit) {
827 size_t init = *i;
829 if (ParseDigits(result, s, i, std::min(limit, init + n))) {
830 return (*i - init) == n;
833 *i = init;
834 return false;
838 * Read and convert n or less decimal digits from s[*i]
839 * to s[min(*i+n,limit)] into *result.
841 * Succeed only if greater than zero but less than or equal to n digits are
842 * converted. Advance *i only on success.
844 template <typename CharT>
845 static bool ParseDigitsNOrLess(size_t n, size_t* result, const CharT* s,
846 size_t* i, size_t limit) {
847 size_t init = *i;
849 if (ParseDigits(result, s, i, std::min(limit, init + n))) {
850 return ((*i - init) > 0) && ((*i - init) <= n);
853 *i = init;
854 return false;
857 static int DaysInMonth(int year, int month) {
858 bool leap = IsLeapYear(year);
859 int result = int(DayFromMonth(month, leap) - DayFromMonth(month - 1, leap));
860 return result;
864 * Parse a string according to the formats specified in section 20.3.1.16
865 * of the ECMAScript standard. These formats are based upon a simplification
866 * of the ISO 8601 Extended Format. As per the spec omitted month and day
867 * values are defaulted to '01', omitted HH:mm:ss values are defaulted to '00'
868 * and an omitted sss field is defaulted to '000'.
870 * For cross compatibility we allow the following extensions.
872 * These are:
874 * Standalone time part:
875 * Any of the time formats below can be parsed without a date part.
876 * E.g. "T19:00:00Z" will parse successfully. The date part will then
877 * default to 1970-01-01.
879 * 'T' from the time part may be replaced with a space character:
880 * "1970-01-01 12:00:00Z" will parse successfully. Note that only a single
881 * space is permitted and this is not permitted in the standalone
882 * version above.
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 * One or two digits for months, days, hours, minutes and seconds:
893 * The specification requires exactly two decimal digits for the fields
894 * above. We allow for one or two decimal digits. I.e. "1970-1-1" is
895 * equivalent to "1970-01-01".
897 * Date part:
899 * Year:
900 * YYYY (eg 1997)
902 * Year and month:
903 * YYYY-MM (eg 1997-07)
905 * Complete date:
906 * YYYY-MM-DD (eg 1997-07-16)
908 * Time part:
910 * Hours and minutes:
911 * Thh:mmTZD (eg T19:20+01:00)
913 * Hours, minutes and seconds:
914 * Thh:mm:ssTZD (eg T19:20:30+01:00)
916 * Hours, minutes, seconds and a decimal fraction of a second:
917 * Thh:mm:ss.sTZD (eg T19:20:30.45+01:00)
919 * where:
921 * YYYY = four-digit year or six digit year as +YYYYYY or -YYYYYY
922 * MM = one or two-digit month (01=January, etc.)
923 * DD = one or two-digit day of month (01 through 31)
924 * hh = one or two digits of hour (00 through 23) (am/pm NOT allowed)
925 * mm = one or two digits of minute (00 through 59)
926 * ss = one or two digits of second (00 through 59)
927 * sss = one or more digits representing a decimal fraction of a second
928 * TZD = time zone designator (Z or +hh:mm or -hh:mm or missing for local)
930 template <typename CharT>
931 static bool ParseISOStyleDate(DateTimeInfo::ForceUTC forceUTC, const CharT* s,
932 size_t length, ClippedTime* result) {
933 size_t i = 0;
934 size_t pre = 0;
935 int tzMul = 1;
936 int dateMul = 1;
937 size_t year = 1970;
938 size_t month = 1;
939 size_t day = 1;
940 size_t hour = 0;
941 size_t min = 0;
942 size_t sec = 0;
943 double frac = 0;
944 bool isLocalTime = false;
945 size_t tzHour = 0;
946 size_t tzMin = 0;
947 bool isPermissive = false;
948 bool isStrict = false;
950 #define PEEK(ch) (i < length && s[i] == ch)
952 #define NEED(ch) \
953 if (i >= length || s[i] != ch) { \
954 return false; \
955 } else { \
956 ++i; \
959 #define DONE_DATE_UNLESS(ch) \
960 if (i >= length || s[i] != ch) { \
961 goto done_date; \
962 } else { \
963 ++i; \
966 #define DONE_UNLESS(ch) \
967 if (i >= length || s[i] != ch) { \
968 goto done; \
969 } else { \
970 ++i; \
973 #define NEED_NDIGITS(n, field) \
974 if (!ParseDigitsN(n, &field, s, &i, length)) { \
975 return false; \
978 #define NEED_NDIGITS_OR_LESS(n, field) \
979 pre = i; \
980 if (!ParseDigitsNOrLess(n, &field, s, &i, length)) { \
981 return false; \
983 if (i < pre + (n)) { \
984 if (isStrict) { \
985 return false; \
986 } else { \
987 isPermissive = true; \
991 if (PEEK('+') || PEEK('-')) {
992 if (PEEK('-')) {
993 dateMul = -1;
995 ++i;
996 NEED_NDIGITS(6, year);
998 // https://tc39.es/ecma262/#sec-expanded-years
999 // -000000 is not a valid expanded year.
1000 if (year == 0 && dateMul == -1) {
1001 return false;
1003 } else {
1004 NEED_NDIGITS(4, year);
1006 DONE_DATE_UNLESS('-');
1007 NEED_NDIGITS_OR_LESS(2, month);
1008 DONE_DATE_UNLESS('-');
1009 NEED_NDIGITS_OR_LESS(2, day);
1011 done_date:
1012 if (PEEK('T')) {
1013 if (isPermissive) {
1014 // Require standard format "[+00]1970-01-01" if a time part marker "T"
1015 // exists
1016 return false;
1018 isStrict = true;
1019 i++;
1020 } else if (PEEK(' ')) {
1021 i++;
1022 } else {
1023 goto done;
1026 NEED_NDIGITS_OR_LESS(2, hour);
1027 NEED(':');
1028 NEED_NDIGITS_OR_LESS(2, min);
1030 if (PEEK(':')) {
1031 ++i;
1032 NEED_NDIGITS_OR_LESS(2, sec);
1033 if (PEEK('.')) {
1034 ++i;
1035 if (!ParseFractional(&frac, s, &i, length)) {
1036 return false;
1041 if (PEEK('Z')) {
1042 ++i;
1043 } else if (PEEK('+') || PEEK('-')) {
1044 if (PEEK('-')) {
1045 tzMul = -1;
1047 ++i;
1048 NEED_NDIGITS(2, tzHour);
1050 * Non-standard extension to the ISO date format:
1051 * allow two digits for the time zone offset.
1053 if (i >= length && !isStrict) {
1054 goto done;
1057 * Non-standard extension to the ISO date format (permitted by ES5):
1058 * allow "-0700" as a time zone offset, not just "-07:00".
1060 if (PEEK(':')) {
1061 ++i;
1063 NEED_NDIGITS(2, tzMin);
1064 } else {
1065 isLocalTime = true;
1068 done:
1069 if (year > 275943 // ceil(1e8/365) + 1970
1070 || (month == 0 || month > 12) ||
1071 (day == 0 || day > size_t(DaysInMonth(year, month))) || hour > 24 ||
1072 ((hour == 24) && (min > 0 || sec > 0 || frac > 0)) || min > 59 ||
1073 sec > 59 || tzHour > 23 || tzMin > 59) {
1074 return false;
1077 if (i != length) {
1078 return false;
1081 month -= 1; /* convert month to 0-based */
1083 double msec = MakeDate(MakeDay(dateMul * double(year), month, day),
1084 MakeTime(hour, min, sec, frac * 1000.0));
1086 if (isLocalTime) {
1087 msec = UTC(forceUTC, msec);
1088 } else {
1089 msec -= tzMul * (tzHour * msPerHour + tzMin * msPerMinute);
1092 *result = TimeClip(msec);
1093 return NumbersAreIdentical(msec, result->toDouble());
1095 #undef PEEK
1096 #undef NEED
1097 #undef DONE_UNLESS
1098 #undef NEED_NDIGITS
1099 #undef NEED_NDIGITS_OR_LESS
1102 int FixupNonFullYear(int year) {
1103 if (year < 50) {
1104 year += 2000;
1105 } else if (year >= 50 && year < 100) {
1106 year += 1900;
1108 return year;
1111 template <typename CharT>
1112 bool IsPrefixOfKeyword(const CharT* s, size_t len, const char* keyword) {
1113 while (len > 0 && *keyword) {
1114 MOZ_ASSERT(IsAsciiAlpha(*s));
1115 MOZ_ASSERT(IsAsciiLowercaseAlpha(*keyword));
1117 if (unicode::ToLowerCase(static_cast<Latin1Char>(*s)) != *keyword) {
1118 break;
1121 s++, keyword++;
1122 len--;
1125 return len == 0;
1128 static constexpr const char* const months_names[] = {
1129 "january", "february", "march", "april", "may", "june",
1130 "july", "august", "september", "october", "november", "december",
1133 // Try to parse the following date format:
1134 // dd-MMM-yyyy
1135 // dd-MMM-yy
1136 // yyyy-MMM-dd
1137 // yy-MMM-dd
1139 // Returns true and fills all out parameters when successfully parsed
1140 // dashed-date. Otherwise returns false and leaves out parameters untouched.
1141 template <typename CharT>
1142 static bool TryParseDashedDatePrefix(const CharT* s, size_t length,
1143 size_t* indexOut, int* yearOut,
1144 int* monOut, int* mdayOut) {
1145 size_t i = 0;
1147 size_t mday;
1148 if (!ParseDigitsNOrLess(4, &mday, s, &i, length)) {
1149 return false;
1151 size_t mdayDigits = i;
1153 if (i >= length || s[i] != '-') {
1154 return false;
1156 ++i;
1158 size_t start = i;
1159 for (; i < length; i++) {
1160 if (!IsAsciiAlpha(s[i])) {
1161 break;
1165 // The shortest month is "may".
1166 static constexpr size_t ShortestMonthNameLength = 3;
1167 if (i - start < ShortestMonthNameLength) {
1168 return false;
1171 size_t mon = 0;
1172 for (size_t m = 0; m < std::size(months_names); ++m) {
1173 // If the field isn't a prefix of the month (an exact match is *not*
1174 // required), try the next one.
1175 if (IsPrefixOfKeyword(s + start, i - start, months_names[m])) {
1176 // Use numeric value.
1177 mon = m + 1;
1178 break;
1181 if (mon == 0) {
1182 return false;
1185 if (i >= length || s[i] != '-') {
1186 return false;
1188 ++i;
1190 size_t pre = i;
1191 size_t year;
1192 if (!ParseDigitsNOrLess(4, &year, s, &i, length)) {
1193 return false;
1195 size_t yearDigits = i - pre;
1197 if (i < length && IsAsciiDigit(s[i])) {
1198 return false;
1201 // Swap the mday and year iff the year wasn't specified in full.
1202 if (mday > 31 && year <= 31 && yearDigits < 4) {
1203 std::swap(mday, year);
1204 std::swap(mdayDigits, yearDigits);
1207 if (mday > 31 || mdayDigits > 2) {
1208 return false;
1211 if (yearDigits < 4) {
1212 year = FixupNonFullYear(year);
1215 *indexOut = i;
1216 *yearOut = year;
1217 *monOut = mon;
1218 *mdayOut = mday;
1219 return true;
1222 struct CharsAndAction {
1223 const char* chars;
1224 int action;
1227 static constexpr CharsAndAction keywords[] = {
1228 // clang-format off
1229 // AM/PM
1230 { "am", -1 },
1231 { "pm", -2 },
1232 // Days of week.
1233 { "monday", 0 },
1234 { "tuesday", 0 },
1235 { "wednesday", 0 },
1236 { "thursday", 0 },
1237 { "friday", 0 },
1238 { "saturday", 0 },
1239 { "sunday", 0 },
1240 // Months.
1241 { "january", 1 },
1242 { "february", 2 },
1243 { "march", 3 },
1244 { "april", 4, },
1245 { "may", 5 },
1246 { "june", 6 },
1247 { "july", 7 },
1248 { "august", 8 },
1249 { "september", 9 },
1250 { "october", 10 },
1251 { "november", 11 },
1252 { "december", 12 },
1253 // Time zone abbreviations.
1254 { "gmt", 10000 + 0 },
1255 { "ut", 10000 + 0 },
1256 { "utc", 10000 + 0 },
1257 { "est", 10000 + 5 * 60 },
1258 { "edt", 10000 + 4 * 60 },
1259 { "cst", 10000 + 6 * 60 },
1260 { "cdt", 10000 + 5 * 60 },
1261 { "mst", 10000 + 7 * 60 },
1262 { "mdt", 10000 + 6 * 60 },
1263 { "pst", 10000 + 8 * 60 },
1264 { "pdt", 10000 + 7 * 60 },
1265 // clang-format on
1268 template <size_t N>
1269 constexpr size_t MinKeywordLength(const CharsAndAction (&keywords)[N]) {
1270 size_t min = size_t(-1);
1271 for (const CharsAndAction& keyword : keywords) {
1272 min = std::min(min, std::char_traits<char>::length(keyword.chars));
1274 return min;
1277 template <typename CharT>
1278 static bool ParseDate(DateTimeInfo::ForceUTC forceUTC, const CharT* s,
1279 size_t length, ClippedTime* result) {
1280 if (ParseISOStyleDate(forceUTC, s, length, result)) {
1281 return true;
1284 if (length == 0) {
1285 return false;
1288 int year = -1;
1289 int mon = -1;
1290 int mday = -1;
1291 int hour = -1;
1292 int min = -1;
1293 int sec = -1;
1294 int tzOffset = -1;
1296 // One of '+', '-', ':', '/', or 0 (the default value).
1297 int prevc = 0;
1299 bool seenPlusMinus = false;
1300 bool seenMonthName = false;
1301 bool seenFullYear = false;
1302 bool negativeYear = false;
1304 size_t index = 0;
1306 // Try parsing the leading dashed-date.
1308 // If successfully parsed, index is updated to the end of the date part,
1309 // and year, mon, mday are set to the date.
1310 // Continue parsing optional time + tzOffset parts.
1312 // Otherwise, this is no-op.
1313 bool isDashedDate =
1314 TryParseDashedDatePrefix(s, length, &index, &year, &mon, &mday);
1316 while (index < length) {
1317 int c = s[index];
1318 index++;
1320 // Normalize U+202F (NARROW NO-BREAK SPACE). This character appears between
1321 // the AM/PM markers for |date.toLocaleString("en")|. We have to normalize
1322 // it for backward compatibility reasons.
1323 if (c == 0x202F) {
1324 c = ' ';
1327 // Spaces, ASCII control characters, periods, and commas are simply ignored.
1328 if (c <= ' ' || c == '.' || c == ',') {
1329 continue;
1332 // Parse delimiter characters. Save them to the side for future use.
1333 if (c == '/' || c == ':' || c == '+') {
1334 prevc = c;
1335 continue;
1338 // Dashes are delimiters if they're immediately followed by a number field.
1339 // If they're not followed by a number field, they're simply ignored.
1340 if (c == '-') {
1341 if (index < length && IsAsciiDigit(s[index])) {
1342 prevc = c;
1344 continue;
1347 // Skip over comments -- text inside matching parentheses. (Comments
1348 // themselves may contain comments as long as all the parentheses properly
1349 // match up. And apparently comments, including nested ones, may validly be
1350 // terminated by end of input...)
1351 if (c == '(') {
1352 int depth = 1;
1353 while (index < length) {
1354 c = s[index];
1355 index++;
1356 if (c == '(') {
1357 depth++;
1358 } else if (c == ')') {
1359 if (--depth <= 0) {
1360 break;
1364 continue;
1367 // Parse a number field.
1368 if (IsAsciiDigit(c)) {
1369 size_t partStart = index - 1;
1370 uint32_t u = c - '0';
1371 while (index < length) {
1372 c = s[index];
1373 if (!IsAsciiDigit(c)) {
1374 break;
1376 u = u * 10 + (c - '0');
1377 index++;
1379 size_t partLength = index - partStart;
1381 // See above for why we have to normalize U+202F.
1382 if (c == 0x202F) {
1383 c = ' ';
1386 int n = int(u);
1389 * Allow TZA before the year, so 'Wed Nov 05 21:49:11 GMT-0800 1997'
1390 * works.
1392 * Uses of seenPlusMinus allow ':' in TZA, so Java no-timezone style
1393 * of GMT+4:30 works.
1396 if (prevc == '-' && (tzOffset != 0 || seenPlusMinus) && partLength >= 4 &&
1397 year < 0) {
1398 // Parse as a negative, possibly zero-padded year if
1399 // 1. the preceding character is '-',
1400 // 2. the TZA is not 'GMT' (tested by |tzOffset != 0|),
1401 // 3. or a TZA was already parsed |seenPlusMinus == true|,
1402 // 4. the part length is at least 4 (to parse '-08' as a TZA),
1403 // 5. and we did not already parse a year |year < 0|.
1404 year = n;
1405 seenFullYear = true;
1406 negativeYear = true;
1407 } else if ((prevc == '+' || prevc == '-') /* && year>=0 */) {
1408 /* Make ':' case below change tzOffset. */
1409 seenPlusMinus = true;
1411 /* offset */
1412 if (n < 24 && partLength <= 2) {
1413 n = n * 60; /* EG. "GMT-3" */
1414 } else {
1415 n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */
1418 if (prevc == '+') /* plus means east of GMT */
1419 n = -n;
1421 // Reject if not preceded by 'GMT' or if a time zone offset
1422 // was already parsed.
1423 if (tzOffset != 0 && tzOffset != -1) {
1424 return false;
1427 tzOffset = n;
1428 } else if (prevc == '/' && mon >= 0 && mday >= 0 && year < 0) {
1429 if (c <= ' ' || c == ',' || c == '/' || index >= length) {
1430 year = n;
1431 } else {
1432 return false;
1434 } else if (c == ':') {
1435 if (hour < 0) {
1436 hour = /*byte*/ n;
1437 } else if (min < 0) {
1438 min = /*byte*/ n;
1439 } else {
1440 return false;
1442 } else if (c == '/') {
1444 * Until it is determined that mon is the actual month, keep
1445 * it as 1-based rather than 0-based.
1447 if (mon < 0) {
1448 mon = /*byte*/ n;
1449 } else if (mday < 0) {
1450 mday = /*byte*/ n;
1451 } else {
1452 return false;
1454 } else if (index < length && c != ',' && c > ' ' && c != '-' &&
1455 c != '(') {
1456 return false;
1457 } else if (seenPlusMinus && n < 60) { /* handle GMT-3:30 */
1458 if (tzOffset < 0) {
1459 tzOffset -= n;
1460 } else {
1461 tzOffset += n;
1463 } else if (hour >= 0 && min < 0) {
1464 min = /*byte*/ n;
1465 } else if (prevc == ':' && min >= 0 && sec < 0) {
1466 sec = /*byte*/ n;
1467 } else if (mon < 0) {
1468 mon = /*byte*/ n;
1469 } else if (mon >= 0 && mday < 0) {
1470 mday = /*byte*/ n;
1471 } else if (mon >= 0 && mday >= 0 && year < 0) {
1472 year = n;
1473 seenFullYear = partLength >= 4;
1474 } else {
1475 return false;
1478 prevc = 0;
1479 continue;
1482 // Parse fields that are words: ASCII letters spelling out in English AM/PM,
1483 // day of week, month, or an extremely limited set of legacy time zone
1484 // abbreviations.
1485 if (IsAsciiAlpha(c)) {
1486 size_t start = index - 1;
1487 while (index < length) {
1488 c = s[index];
1489 if (!IsAsciiAlpha(c)) {
1490 break;
1492 index++;
1495 // There must be at least as many letters as in the shortest keyword.
1496 constexpr size_t MinLength = MinKeywordLength(keywords);
1497 if (index - start < MinLength) {
1498 return false;
1501 size_t k = std::size(keywords);
1502 while (k-- > 0) {
1503 const CharsAndAction& keyword = keywords[k];
1505 // If the field isn't a prefix of the keyword (an exact match is *not*
1506 // required), try the next one.
1507 if (!IsPrefixOfKeyword(s + start, index - start, keyword.chars)) {
1508 continue;
1511 int action = keyword.action;
1513 // Completely ignore days of the week, and don't derive any semantics
1514 // from them.
1515 if (action == 0) {
1516 break;
1519 // Perform action tests from smallest action values to largest.
1521 // Adjust a previously-specified hour for AM/PM accordingly (taking care
1522 // to treat 12:xx AM as 00:xx, 12:xx PM as 12:xx).
1523 if (action < 0) {
1524 MOZ_ASSERT(action == -1 || action == -2);
1525 if (hour > 12 || hour < 0) {
1526 return false;
1529 if (action == -1 && hour == 12) {
1530 hour = 0;
1531 } else if (action == -2 && hour != 12) {
1532 hour += 12;
1535 break;
1538 // Record a month if none has been seen before. (Note that some numbers
1539 // are initially treated as months; if a numeric field has already been
1540 // interpreted as a month, store that value to the actually appropriate
1541 // date component and set the month here.
1542 if (action <= 12) {
1543 if (seenMonthName) {
1544 return false;
1547 seenMonthName = true;
1549 if (mon < 0) {
1550 mon = action;
1551 } else if (mday < 0) {
1552 mday = mon;
1553 mon = action;
1554 } else if (year < 0) {
1555 if (mday > 0) {
1556 // If the date is of the form f l month, then when month is
1557 // reached we have f in mon and l in mday. In order to be
1558 // consistent with the f month l and month f l forms, we need to
1559 // swap so that f is in mday and l is in year.
1560 year = mday;
1561 mday = mon;
1562 } else {
1563 year = mon;
1565 mon = action;
1566 } else {
1567 return false;
1570 break;
1573 // Finally, record a time zone offset.
1574 MOZ_ASSERT(action >= 10000);
1575 tzOffset = action - 10000;
1576 break;
1579 if (k == size_t(-1)) {
1580 return false;
1583 prevc = 0;
1584 continue;
1587 // Any other character fails to parse.
1588 return false;
1591 if (year < 0 || mon < 0 || mday < 0) {
1592 return false;
1595 if (!isDashedDate) {
1596 // NOTE: TryParseDashedDatePrefix already handles the following fixup.
1599 * Case 1. The input string contains an English month name.
1600 * The form of the string can be month f l, or f month l, or
1601 * f l month which each evaluate to the same date.
1602 * If f and l are both greater than or equal to 100 the date
1603 * is invalid.
1605 * The year is taken to be either l, f if f > 31, or whichever
1606 * is set to zero.
1608 * Case 2. The input string is of the form "f/m/l" where f, m and l are
1609 * integers, e.g. 7/16/45. mon, mday and year values are adjusted
1610 * to achieve Chrome compatibility.
1612 * a. If 0 < f <= 12 and 0 < l <= 31, f/m/l is interpreted as
1613 * month/day/year.
1614 * b. If 31 < f and 0 < m <= 12 and 0 < l <= 31 f/m/l is
1615 * interpreted as year/month/day
1617 if (seenMonthName) {
1618 if (mday >= 100 && mon >= 100) {
1619 return false;
1622 if (year > 0 && (mday == 0 || mday > 31) && !seenFullYear) {
1623 int temp = year;
1624 year = mday;
1625 mday = temp;
1628 if (mday <= 0 || mday > 31) {
1629 return false;
1632 } else if (0 < mon && mon <= 12 && 0 < mday && mday <= 31) {
1633 /* (a) month/day/year */
1634 } else {
1635 /* (b) year/month/day */
1636 if (mon > 31 && mday <= 12 && year <= 31 && !seenFullYear) {
1637 int temp = year;
1638 year = mon;
1639 mon = mday;
1640 mday = temp;
1641 } else {
1642 return false;
1646 // If the year is greater than or equal to 50 and less than 100, it is
1647 // considered to be the number of years after 1900. If the year is less
1648 // than 50 it is considered to be the number of years after 2000,
1649 // otherwise it is considered to be the number of years after 0.
1650 if (!seenFullYear) {
1651 year = FixupNonFullYear(year);
1654 if (negativeYear) {
1655 year = -year;
1659 mon -= 1; /* convert month to 0-based */
1660 if (sec < 0) {
1661 sec = 0;
1663 if (min < 0) {
1664 min = 0;
1666 if (hour < 0) {
1667 hour = 0;
1670 double msec = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0));
1672 if (tzOffset == -1) { /* no time zone specified, have to use local */
1673 msec = UTC(forceUTC, msec);
1674 } else {
1675 msec += tzOffset * msPerMinute;
1678 *result = TimeClip(msec);
1679 return true;
1682 static bool ParseDate(DateTimeInfo::ForceUTC forceUTC, JSLinearString* s,
1683 ClippedTime* result) {
1684 AutoCheckCannotGC nogc;
1685 return s->hasLatin1Chars()
1686 ? ParseDate(forceUTC, s->latin1Chars(nogc), s->length(), result)
1687 : ParseDate(forceUTC, s->twoByteChars(nogc), s->length(), result);
1690 static bool date_parse(JSContext* cx, unsigned argc, Value* vp) {
1691 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date", "parse");
1692 CallArgs args = CallArgsFromVp(argc, vp);
1693 if (args.length() == 0) {
1694 args.rval().setNaN();
1695 return true;
1698 JSString* str = ToString<CanGC>(cx, args[0]);
1699 if (!str) {
1700 return false;
1703 JSLinearString* linearStr = str->ensureLinear(cx);
1704 if (!linearStr) {
1705 return false;
1708 ClippedTime result;
1709 if (!ParseDate(ForceUTC(cx->realm()), linearStr, &result)) {
1710 args.rval().setNaN();
1711 return true;
1714 args.rval().set(TimeValue(result));
1715 return true;
1718 static ClippedTime NowAsMillis(JSContext* cx) {
1719 if (js::SupportDifferentialTesting()) {
1720 return TimeClip(0);
1723 double now = PRMJ_Now();
1724 bool clampAndJitter = cx->realm()->behaviors().clampAndJitterTime();
1725 if (clampAndJitter && sReduceMicrosecondTimePrecisionCallback) {
1726 now = sReduceMicrosecondTimePrecisionCallback(now, cx);
1727 } else if (clampAndJitter && sResolutionUsec) {
1728 double clamped = floor(now / sResolutionUsec) * sResolutionUsec;
1730 if (sJitter) {
1731 // Calculate a random midpoint for jittering. In the browser, we are
1732 // adversarial: Web Content may try to calculate the midpoint themselves
1733 // and use that to bypass it's security. In the JS Shell, we are not
1734 // adversarial, we want to jitter the time to recreate the operating
1735 // environment, but we do not concern ourselves with trying to prevent an
1736 // attacker from calculating the midpoint themselves. So we use a very
1737 // simple, very fast CRC with a hardcoded seed.
1739 uint64_t midpoint = BitwiseCast<uint64_t>(clamped);
1740 midpoint ^= 0x0F00DD1E2BAD2DED; // XOR in a 'secret'
1741 // MurmurHash3 internal component from
1742 // https://searchfox.org/mozilla-central/rev/61d400da1c692453c2dc2c1cf37b616ce13dea5b/dom/canvas/MurmurHash3.cpp#85
1743 midpoint ^= midpoint >> 33;
1744 midpoint *= uint64_t{0xFF51AFD7ED558CCD};
1745 midpoint ^= midpoint >> 33;
1746 midpoint *= uint64_t{0xC4CEB9FE1A85EC53};
1747 midpoint ^= midpoint >> 33;
1748 midpoint %= sResolutionUsec;
1750 if (now > clamped + midpoint) { // We're jittering up to the next step
1751 now = clamped + sResolutionUsec;
1752 } else { // We're staying at the clamped value
1753 now = clamped;
1755 } else { // No jitter, only clamping
1756 now = clamped;
1760 return TimeClip(now / PRMJ_USEC_PER_MSEC);
1763 JS::ClippedTime js::DateNow(JSContext* cx) { return NowAsMillis(cx); }
1765 bool js::date_now(JSContext* cx, unsigned argc, Value* vp) {
1766 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date", "now");
1767 CallArgs args = CallArgsFromVp(argc, vp);
1768 args.rval().set(TimeValue(NowAsMillis(cx)));
1769 return true;
1772 DateTimeInfo::ForceUTC DateObject::forceUTC() const {
1773 return ForceUTC(realm());
1776 void DateObject::setUTCTime(ClippedTime t) {
1777 for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++) {
1778 setReservedSlot(ind, UndefinedValue());
1781 setFixedSlot(UTC_TIME_SLOT, TimeValue(t));
1784 void DateObject::setUTCTime(ClippedTime t, MutableHandleValue vp) {
1785 setUTCTime(t);
1786 vp.set(TimeValue(t));
1789 void DateObject::fillLocalTimeSlots() {
1790 const int32_t utcTZOffset =
1791 DateTimeInfo::utcToLocalStandardOffsetSeconds(forceUTC());
1793 /* Check if the cache is already populated. */
1794 if (!getReservedSlot(LOCAL_TIME_SLOT).isUndefined() &&
1795 getReservedSlot(UTC_TIME_ZONE_OFFSET_SLOT).toInt32() == utcTZOffset) {
1796 return;
1799 /* Remember time zone used to generate the local cache. */
1800 setReservedSlot(UTC_TIME_ZONE_OFFSET_SLOT, Int32Value(utcTZOffset));
1802 double utcTime = UTCTime().toNumber();
1804 if (!std::isfinite(utcTime)) {
1805 for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++) {
1806 setReservedSlot(ind, DoubleValue(utcTime));
1808 return;
1811 double localTime = LocalTime(forceUTC(), utcTime);
1813 setReservedSlot(LOCAL_TIME_SLOT, DoubleValue(localTime));
1815 const auto [year, month, day] = ToYearMonthDay(localTime);
1817 setReservedSlot(LOCAL_YEAR_SLOT, Int32Value(year));
1818 setReservedSlot(LOCAL_MONTH_SLOT, Int32Value(int32_t(month)));
1819 setReservedSlot(LOCAL_DATE_SLOT, Int32Value(int32_t(day)));
1821 int weekday = WeekDay(localTime);
1822 setReservedSlot(LOCAL_DAY_SLOT, Int32Value(weekday));
1824 double yearStartTime = TimeFromYear(year);
1825 uint64_t yearTime = uint64_t(localTime - yearStartTime);
1826 int32_t yearSeconds = int32_t(yearTime / 1000);
1827 setReservedSlot(LOCAL_SECONDS_INTO_YEAR_SLOT, Int32Value(yearSeconds));
1830 MOZ_ALWAYS_INLINE bool IsDate(HandleValue v) {
1831 return v.isObject() && v.toObject().is<DateObject>();
1835 * See ECMA 15.9.5.4 thru 15.9.5.23
1838 static bool date_getTime(JSContext* cx, unsigned argc, Value* vp) {
1839 CallArgs args = CallArgsFromVp(argc, vp);
1841 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getTime");
1842 if (!unwrapped) {
1843 return false;
1846 args.rval().set(unwrapped->UTCTime());
1847 return true;
1850 static bool date_getYear(JSContext* cx, unsigned argc, Value* vp) {
1851 CallArgs args = CallArgsFromVp(argc, vp);
1853 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getYear");
1854 if (!unwrapped) {
1855 return false;
1858 unwrapped->fillLocalTimeSlots();
1860 Value yearVal = unwrapped->localYear();
1861 if (yearVal.isInt32()) {
1862 /* Follow ECMA-262 to the letter, contrary to IE JScript. */
1863 int year = yearVal.toInt32() - 1900;
1864 args.rval().setInt32(year);
1865 } else {
1866 args.rval().set(yearVal);
1868 return true;
1871 static bool date_getFullYear(JSContext* cx, unsigned argc, Value* vp) {
1872 CallArgs args = CallArgsFromVp(argc, vp);
1874 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getFullYear");
1875 if (!unwrapped) {
1876 return false;
1879 unwrapped->fillLocalTimeSlots();
1880 args.rval().set(unwrapped->localYear());
1881 return true;
1884 static bool date_getUTCFullYear(JSContext* cx, unsigned argc, Value* vp) {
1885 CallArgs args = CallArgsFromVp(argc, vp);
1887 auto* unwrapped =
1888 UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCFullYear");
1889 if (!unwrapped) {
1890 return false;
1893 double result = unwrapped->UTCTime().toNumber();
1894 if (std::isfinite(result)) {
1895 result = YearFromTime(result);
1898 args.rval().setNumber(result);
1899 return true;
1902 static bool date_getMonth(JSContext* cx, unsigned argc, Value* vp) {
1903 CallArgs args = CallArgsFromVp(argc, vp);
1905 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getMonth");
1906 if (!unwrapped) {
1907 return false;
1910 unwrapped->fillLocalTimeSlots();
1911 args.rval().set(unwrapped->localMonth());
1912 return true;
1915 static bool date_getUTCMonth(JSContext* cx, unsigned argc, Value* vp) {
1916 CallArgs args = CallArgsFromVp(argc, vp);
1918 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCMonth");
1919 if (!unwrapped) {
1920 return false;
1923 double d = unwrapped->UTCTime().toNumber();
1924 args.rval().setNumber(MonthFromTime(d));
1925 return true;
1928 static bool date_getDate(JSContext* cx, unsigned argc, Value* vp) {
1929 CallArgs args = CallArgsFromVp(argc, vp);
1931 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getDate");
1932 if (!unwrapped) {
1933 return false;
1936 unwrapped->fillLocalTimeSlots();
1938 args.rval().set(unwrapped->localDate());
1939 return true;
1942 static bool date_getUTCDate(JSContext* cx, unsigned argc, Value* vp) {
1943 CallArgs args = CallArgsFromVp(argc, vp);
1945 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCDate");
1946 if (!unwrapped) {
1947 return false;
1950 double result = unwrapped->UTCTime().toNumber();
1951 if (std::isfinite(result)) {
1952 result = DateFromTime(result);
1955 args.rval().setNumber(result);
1956 return true;
1959 static bool date_getDay(JSContext* cx, unsigned argc, Value* vp) {
1960 CallArgs args = CallArgsFromVp(argc, vp);
1962 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getDay");
1963 if (!unwrapped) {
1964 return false;
1967 unwrapped->fillLocalTimeSlots();
1968 args.rval().set(unwrapped->localDay());
1969 return true;
1972 static bool date_getUTCDay(JSContext* cx, unsigned argc, Value* vp) {
1973 CallArgs args = CallArgsFromVp(argc, vp);
1975 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCDay");
1976 if (!unwrapped) {
1977 return false;
1980 double result = unwrapped->UTCTime().toNumber();
1981 if (std::isfinite(result)) {
1982 result = WeekDay(result);
1985 args.rval().setNumber(result);
1986 return true;
1989 static bool date_getHours(JSContext* cx, unsigned argc, Value* vp) {
1990 CallArgs args = CallArgsFromVp(argc, vp);
1992 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getHours");
1993 if (!unwrapped) {
1994 return false;
1997 unwrapped->fillLocalTimeSlots();
1999 // Note: localSecondsIntoYear is guaranteed to return an
2000 // int32 or NaN after the call to fillLocalTimeSlots.
2001 Value yearSeconds = unwrapped->localSecondsIntoYear();
2002 if (yearSeconds.isDouble()) {
2003 MOZ_ASSERT(std::isnan(yearSeconds.toDouble()));
2004 args.rval().set(yearSeconds);
2005 } else {
2006 args.rval().setInt32((yearSeconds.toInt32() / int(SecondsPerHour)) %
2007 int(HoursPerDay));
2009 return true;
2012 static bool date_getUTCHours(JSContext* cx, unsigned argc, Value* vp) {
2013 CallArgs args = CallArgsFromVp(argc, vp);
2015 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCHours");
2016 if (!unwrapped) {
2017 return false;
2020 double result = unwrapped->UTCTime().toNumber();
2021 if (std::isfinite(result)) {
2022 result = HourFromTime(result);
2025 args.rval().setNumber(result);
2026 return true;
2029 static bool date_getMinutes(JSContext* cx, unsigned argc, Value* vp) {
2030 CallArgs args = CallArgsFromVp(argc, vp);
2032 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getMinutes");
2033 if (!unwrapped) {
2034 return false;
2037 unwrapped->fillLocalTimeSlots();
2039 // Note: localSecondsIntoYear is guaranteed to return an
2040 // int32 or NaN after the call to fillLocalTimeSlots.
2041 Value yearSeconds = unwrapped->localSecondsIntoYear();
2042 if (yearSeconds.isDouble()) {
2043 MOZ_ASSERT(std::isnan(yearSeconds.toDouble()));
2044 args.rval().set(yearSeconds);
2045 } else {
2046 args.rval().setInt32((yearSeconds.toInt32() / int(SecondsPerMinute)) %
2047 int(MinutesPerHour));
2049 return true;
2052 static bool date_getUTCMinutes(JSContext* cx, unsigned argc, Value* vp) {
2053 CallArgs args = CallArgsFromVp(argc, vp);
2055 auto* unwrapped =
2056 UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCMinutes");
2057 if (!unwrapped) {
2058 return false;
2061 double result = unwrapped->UTCTime().toNumber();
2062 if (std::isfinite(result)) {
2063 result = MinFromTime(result);
2066 args.rval().setNumber(result);
2067 return true;
2070 static bool date_getSeconds(JSContext* cx, unsigned argc, Value* vp) {
2071 CallArgs args = CallArgsFromVp(argc, vp);
2073 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getSeconds");
2074 if (!unwrapped) {
2075 return false;
2078 unwrapped->fillLocalTimeSlots();
2080 // Note: localSecondsIntoYear is guaranteed to return an
2081 // int32 or NaN after the call to fillLocalTimeSlots.
2082 Value yearSeconds = unwrapped->localSecondsIntoYear();
2083 if (yearSeconds.isDouble()) {
2084 MOZ_ASSERT(std::isnan(yearSeconds.toDouble()));
2085 args.rval().set(yearSeconds);
2086 } else {
2087 args.rval().setInt32(yearSeconds.toInt32() % int(SecondsPerMinute));
2089 return true;
2092 static bool date_getUTCSeconds(JSContext* cx, unsigned argc, Value* vp) {
2093 CallArgs args = CallArgsFromVp(argc, vp);
2095 auto* unwrapped =
2096 UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCSeconds");
2097 if (!unwrapped) {
2098 return false;
2101 double result = unwrapped->UTCTime().toNumber();
2102 if (std::isfinite(result)) {
2103 result = SecFromTime(result);
2106 args.rval().setNumber(result);
2107 return true;
2111 * Date.getMilliseconds is mapped to getUTCMilliseconds. As long as no
2112 * supported time zone has a fractional-second component, the differences in
2113 * their specifications aren't observable.
2115 * The 'tz' database explicitly does not support fractional-second time zones.
2116 * For example the Netherlands observed Amsterdam Mean Time, estimated to be
2117 * UT +00:19:32.13, from 1909 to 1937, but in tzdata AMT is defined as exactly
2118 * UT +00:19:32.
2121 static bool getMilliseconds(JSContext* cx, unsigned argc, Value* vp,
2122 const char* methodName) {
2123 CallArgs args = CallArgsFromVp(argc, vp);
2125 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, methodName);
2126 if (!unwrapped) {
2127 return false;
2130 double result = unwrapped->UTCTime().toNumber();
2131 if (std::isfinite(result)) {
2132 result = msFromTime(result);
2135 args.rval().setNumber(result);
2136 return true;
2139 static bool date_getMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
2140 return getMilliseconds(cx, argc, vp, "getMilliseconds");
2143 static bool date_getUTCMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
2144 return getMilliseconds(cx, argc, vp, "getUTCMilliseconds");
2147 static bool date_getTimezoneOffset(JSContext* cx, unsigned argc, Value* vp) {
2148 CallArgs args = CallArgsFromVp(argc, vp);
2150 auto* unwrapped =
2151 UnwrapAndTypeCheckThis<DateObject>(cx, args, "getTimezoneOffset");
2152 if (!unwrapped) {
2153 return false;
2156 unwrapped->fillLocalTimeSlots();
2158 double utctime = unwrapped->UTCTime().toNumber();
2159 double localtime = unwrapped->localTime().toDouble();
2162 * Return the time zone offset in minutes for the current locale that is
2163 * appropriate for this time. This value would be a constant except for
2164 * daylight savings time.
2166 double result = (utctime - localtime) / msPerMinute;
2167 args.rval().setNumber(result);
2168 return true;
2171 static bool date_setTime(JSContext* cx, unsigned argc, Value* vp) {
2172 CallArgs args = CallArgsFromVp(argc, vp);
2174 Rooted<DateObject*> unwrapped(
2175 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setTime"));
2176 if (!unwrapped) {
2177 return false;
2180 if (args.length() == 0) {
2181 unwrapped->setUTCTime(ClippedTime::invalid(), args.rval());
2182 return true;
2185 double result;
2186 if (!ToNumber(cx, args[0], &result)) {
2187 return false;
2190 unwrapped->setUTCTime(TimeClip(result), args.rval());
2191 return true;
2194 static bool GetMsecsOrDefault(JSContext* cx, const CallArgs& args, unsigned i,
2195 double t, double* millis) {
2196 if (args.length() <= i) {
2197 *millis = msFromTime(t);
2198 return true;
2200 return ToNumber(cx, args[i], millis);
2203 static bool GetSecsOrDefault(JSContext* cx, const CallArgs& args, unsigned i,
2204 double t, double* sec) {
2205 if (args.length() <= i) {
2206 *sec = SecFromTime(t);
2207 return true;
2209 return ToNumber(cx, args[i], sec);
2212 static bool GetMinsOrDefault(JSContext* cx, const CallArgs& args, unsigned i,
2213 double t, double* mins) {
2214 if (args.length() <= i) {
2215 *mins = MinFromTime(t);
2216 return true;
2218 return ToNumber(cx, args[i], mins);
2221 /* ES6 20.3.4.23. */
2222 static bool date_setMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
2223 CallArgs args = CallArgsFromVp(argc, vp);
2225 // Step 1.
2226 Rooted<DateObject*> unwrapped(
2227 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setMilliseconds"));
2228 if (!unwrapped) {
2229 return false;
2231 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2233 // Step 2.
2234 double ms;
2235 if (!ToNumber(cx, args.get(0), &ms)) {
2236 return false;
2239 // Step 3.
2240 double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms);
2242 // Step 4.
2243 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), MakeDate(Day(t), time)));
2245 // Steps 5-6.
2246 unwrapped->setUTCTime(u, args.rval());
2247 return true;
2250 /* ES5 15.9.5.29. */
2251 static bool date_setUTCMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
2252 CallArgs args = CallArgsFromVp(argc, vp);
2254 Rooted<DateObject*> unwrapped(
2255 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCMilliseconds"));
2256 if (!unwrapped) {
2257 return false;
2260 /* Step 1. */
2261 double t = unwrapped->UTCTime().toNumber();
2263 /* Step 2. */
2264 double milli;
2265 if (!ToNumber(cx, args.get(0), &milli)) {
2266 return false;
2268 double time =
2269 MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli);
2271 /* Step 3. */
2272 ClippedTime v = TimeClip(MakeDate(Day(t), time));
2274 /* Steps 4-5. */
2275 unwrapped->setUTCTime(v, args.rval());
2276 return true;
2279 /* ES6 20.3.4.26. */
2280 static bool date_setSeconds(JSContext* cx, unsigned argc, Value* vp) {
2281 CallArgs args = CallArgsFromVp(argc, vp);
2283 Rooted<DateObject*> unwrapped(
2284 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setSeconds"));
2285 if (!unwrapped) {
2286 return false;
2289 // Steps 1-2.
2290 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2292 // Steps 3-4.
2293 double s;
2294 if (!ToNumber(cx, args.get(0), &s)) {
2295 return false;
2298 // Steps 5-6.
2299 double milli;
2300 if (!GetMsecsOrDefault(cx, args, 1, t, &milli)) {
2301 return false;
2304 // Step 7.
2305 double date =
2306 MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
2308 // Step 8.
2309 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), date));
2311 // Step 9.
2312 unwrapped->setUTCTime(u, args.rval());
2313 return true;
2316 /* ES5 15.9.5.32. */
2317 static bool date_setUTCSeconds(JSContext* cx, unsigned argc, Value* vp) {
2318 CallArgs args = CallArgsFromVp(argc, vp);
2320 Rooted<DateObject*> unwrapped(
2321 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCSeconds"));
2322 if (!unwrapped) {
2323 return false;
2326 /* Step 1. */
2327 double t = unwrapped->UTCTime().toNumber();
2329 /* Step 2. */
2330 double s;
2331 if (!ToNumber(cx, args.get(0), &s)) {
2332 return false;
2335 /* Step 3. */
2336 double milli;
2337 if (!GetMsecsOrDefault(cx, args, 1, t, &milli)) {
2338 return false;
2341 /* Step 4. */
2342 double date =
2343 MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
2345 /* Step 5. */
2346 ClippedTime v = TimeClip(date);
2348 /* Steps 6-7. */
2349 unwrapped->setUTCTime(v, args.rval());
2350 return true;
2353 /* ES6 20.3.4.24. */
2354 static bool date_setMinutes(JSContext* cx, unsigned argc, Value* vp) {
2355 CallArgs args = CallArgsFromVp(argc, vp);
2357 Rooted<DateObject*> unwrapped(
2358 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setMinutes"));
2359 if (!unwrapped) {
2360 return false;
2363 // Steps 1-2.
2364 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2366 // Steps 3-4.
2367 double m;
2368 if (!ToNumber(cx, args.get(0), &m)) {
2369 return false;
2372 // Steps 5-6.
2373 double s;
2374 if (!GetSecsOrDefault(cx, args, 1, t, &s)) {
2375 return false;
2378 // Steps 7-8.
2379 double milli;
2380 if (!GetMsecsOrDefault(cx, args, 2, t, &milli)) {
2381 return false;
2384 // Step 9.
2385 double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
2387 // Step 10.
2388 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), date));
2390 // Steps 11-12.
2391 unwrapped->setUTCTime(u, args.rval());
2392 return true;
2395 /* ES5 15.9.5.34. */
2396 static bool date_setUTCMinutes(JSContext* cx, unsigned argc, Value* vp) {
2397 CallArgs args = CallArgsFromVp(argc, vp);
2399 Rooted<DateObject*> unwrapped(
2400 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCMinutes"));
2401 if (!unwrapped) {
2402 return false;
2405 /* Step 1. */
2406 double t = unwrapped->UTCTime().toNumber();
2408 /* Step 2. */
2409 double m;
2410 if (!ToNumber(cx, args.get(0), &m)) {
2411 return false;
2414 /* Step 3. */
2415 double s;
2416 if (!GetSecsOrDefault(cx, args, 1, t, &s)) {
2417 return false;
2420 /* Step 4. */
2421 double milli;
2422 if (!GetMsecsOrDefault(cx, args, 2, t, &milli)) {
2423 return false;
2426 /* Step 5. */
2427 double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
2429 /* Step 6. */
2430 ClippedTime v = TimeClip(date);
2432 /* Steps 7-8. */
2433 unwrapped->setUTCTime(v, args.rval());
2434 return true;
2437 /* ES5 15.9.5.35. */
2438 static bool date_setHours(JSContext* cx, unsigned argc, Value* vp) {
2439 CallArgs args = CallArgsFromVp(argc, vp);
2441 Rooted<DateObject*> unwrapped(
2442 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setHours"));
2443 if (!unwrapped) {
2444 return false;
2447 // Steps 1-2.
2448 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2450 // Steps 3-4.
2451 double h;
2452 if (!ToNumber(cx, args.get(0), &h)) {
2453 return false;
2456 // Steps 5-6.
2457 double m;
2458 if (!GetMinsOrDefault(cx, args, 1, t, &m)) {
2459 return false;
2462 // Steps 7-8.
2463 double s;
2464 if (!GetSecsOrDefault(cx, args, 2, t, &s)) {
2465 return false;
2468 // Steps 9-10.
2469 double milli;
2470 if (!GetMsecsOrDefault(cx, args, 3, t, &milli)) {
2471 return false;
2474 // Step 11.
2475 double date = MakeDate(Day(t), MakeTime(h, m, s, milli));
2477 // Step 12.
2478 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), date));
2480 // Steps 13-14.
2481 unwrapped->setUTCTime(u, args.rval());
2482 return true;
2485 /* ES5 15.9.5.36. */
2486 static bool date_setUTCHours(JSContext* cx, unsigned argc, Value* vp) {
2487 CallArgs args = CallArgsFromVp(argc, vp);
2489 Rooted<DateObject*> unwrapped(
2490 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCHours"));
2491 if (!unwrapped) {
2492 return false;
2495 /* Step 1. */
2496 double t = unwrapped->UTCTime().toNumber();
2498 /* Step 2. */
2499 double h;
2500 if (!ToNumber(cx, args.get(0), &h)) {
2501 return false;
2504 /* Step 3. */
2505 double m;
2506 if (!GetMinsOrDefault(cx, args, 1, t, &m)) {
2507 return false;
2510 /* Step 4. */
2511 double s;
2512 if (!GetSecsOrDefault(cx, args, 2, t, &s)) {
2513 return false;
2516 /* Step 5. */
2517 double milli;
2518 if (!GetMsecsOrDefault(cx, args, 3, t, &milli)) {
2519 return false;
2522 /* Step 6. */
2523 double newDate = MakeDate(Day(t), MakeTime(h, m, s, milli));
2525 /* Step 7. */
2526 ClippedTime v = TimeClip(newDate);
2528 /* Steps 8-9. */
2529 unwrapped->setUTCTime(v, args.rval());
2530 return true;
2533 /* ES5 15.9.5.37. */
2534 static bool date_setDate(JSContext* cx, unsigned argc, Value* vp) {
2535 CallArgs args = CallArgsFromVp(argc, vp);
2537 Rooted<DateObject*> unwrapped(
2538 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setDate"));
2539 if (!unwrapped) {
2540 return false;
2543 /* Step 1. */
2544 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2546 /* Step 2. */
2547 double date;
2548 if (!ToNumber(cx, args.get(0), &date)) {
2549 return false;
2552 /* Step 3. */
2553 double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date),
2554 TimeWithinDay(t));
2556 /* Step 4. */
2557 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), newDate));
2559 /* Steps 5-6. */
2560 unwrapped->setUTCTime(u, args.rval());
2561 return true;
2564 static bool date_setUTCDate(JSContext* cx, unsigned argc, Value* vp) {
2565 CallArgs args = CallArgsFromVp(argc, vp);
2567 Rooted<DateObject*> unwrapped(
2568 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCDate"));
2569 if (!unwrapped) {
2570 return false;
2573 /* Step 1. */
2574 double t = unwrapped->UTCTime().toNumber();
2576 /* Step 2. */
2577 double date;
2578 if (!ToNumber(cx, args.get(0), &date)) {
2579 return false;
2582 /* Step 3. */
2583 double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date),
2584 TimeWithinDay(t));
2586 /* Step 4. */
2587 ClippedTime v = TimeClip(newDate);
2589 /* Steps 5-6. */
2590 unwrapped->setUTCTime(v, args.rval());
2591 return true;
2594 static bool GetDateOrDefault(JSContext* cx, const CallArgs& args, unsigned i,
2595 double t, double* date) {
2596 if (args.length() <= i) {
2597 *date = DateFromTime(t);
2598 return true;
2600 return ToNumber(cx, args[i], date);
2603 static bool GetMonthOrDefault(JSContext* cx, const CallArgs& args, unsigned i,
2604 double t, double* month) {
2605 if (args.length() <= i) {
2606 *month = MonthFromTime(t);
2607 return true;
2609 return ToNumber(cx, args[i], month);
2612 /* ES5 15.9.5.38. */
2613 static bool date_setMonth(JSContext* cx, unsigned argc, Value* vp) {
2614 CallArgs args = CallArgsFromVp(argc, vp);
2616 Rooted<DateObject*> unwrapped(
2617 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setMonth"));
2618 if (!unwrapped) {
2619 return false;
2622 /* Step 1. */
2623 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2625 /* Step 2. */
2626 double m;
2627 if (!ToNumber(cx, args.get(0), &m)) {
2628 return false;
2631 /* Step 3. */
2632 double date;
2633 if (!GetDateOrDefault(cx, args, 1, t, &date)) {
2634 return false;
2637 /* Step 4. */
2638 double newDate =
2639 MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t));
2641 /* Step 5. */
2642 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), newDate));
2644 /* Steps 6-7. */
2645 unwrapped->setUTCTime(u, args.rval());
2646 return true;
2649 /* ES5 15.9.5.39. */
2650 static bool date_setUTCMonth(JSContext* cx, unsigned argc, Value* vp) {
2651 CallArgs args = CallArgsFromVp(argc, vp);
2653 Rooted<DateObject*> unwrapped(
2654 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCMonth"));
2655 if (!unwrapped) {
2656 return false;
2659 /* Step 1. */
2660 double t = unwrapped->UTCTime().toNumber();
2662 /* Step 2. */
2663 double m;
2664 if (!ToNumber(cx, args.get(0), &m)) {
2665 return false;
2668 /* Step 3. */
2669 double date;
2670 if (!GetDateOrDefault(cx, args, 1, t, &date)) {
2671 return false;
2674 /* Step 4. */
2675 double newDate =
2676 MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t));
2678 /* Step 5. */
2679 ClippedTime v = TimeClip(newDate);
2681 /* Steps 6-7. */
2682 unwrapped->setUTCTime(v, args.rval());
2683 return true;
2686 static double ThisLocalTimeOrZero(DateTimeInfo::ForceUTC forceUTC,
2687 Handle<DateObject*> dateObj) {
2688 double t = dateObj->UTCTime().toNumber();
2689 if (std::isnan(t)) {
2690 return +0;
2692 return LocalTime(forceUTC, t);
2695 static double ThisUTCTimeOrZero(Handle<DateObject*> dateObj) {
2696 double t = dateObj->as<DateObject>().UTCTime().toNumber();
2697 return std::isnan(t) ? +0 : t;
2700 /* ES5 15.9.5.40. */
2701 static bool date_setFullYear(JSContext* cx, unsigned argc, Value* vp) {
2702 CallArgs args = CallArgsFromVp(argc, vp);
2704 Rooted<DateObject*> unwrapped(
2705 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setFullYear"));
2706 if (!unwrapped) {
2707 return false;
2710 /* Step 1. */
2711 double t = ThisLocalTimeOrZero(unwrapped->forceUTC(), unwrapped);
2713 /* Step 2. */
2714 double y;
2715 if (!ToNumber(cx, args.get(0), &y)) {
2716 return false;
2719 /* Step 3. */
2720 double m;
2721 if (!GetMonthOrDefault(cx, args, 1, t, &m)) {
2722 return false;
2725 /* Step 4. */
2726 double date;
2727 if (!GetDateOrDefault(cx, args, 2, t, &date)) {
2728 return false;
2731 /* Step 5. */
2732 double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t));
2734 /* Step 6. */
2735 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), newDate));
2737 /* Steps 7-8. */
2738 unwrapped->setUTCTime(u, args.rval());
2739 return true;
2742 /* ES5 15.9.5.41. */
2743 static bool date_setUTCFullYear(JSContext* cx, unsigned argc, Value* vp) {
2744 CallArgs args = CallArgsFromVp(argc, vp);
2746 Rooted<DateObject*> unwrapped(
2747 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCFullYear"));
2748 if (!unwrapped) {
2749 return false;
2752 /* Step 1. */
2753 double t = ThisUTCTimeOrZero(unwrapped);
2755 /* Step 2. */
2756 double y;
2757 if (!ToNumber(cx, args.get(0), &y)) {
2758 return false;
2761 /* Step 3. */
2762 double m;
2763 if (!GetMonthOrDefault(cx, args, 1, t, &m)) {
2764 return false;
2767 /* Step 4. */
2768 double date;
2769 if (!GetDateOrDefault(cx, args, 2, t, &date)) {
2770 return false;
2773 /* Step 5. */
2774 double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t));
2776 /* Step 6. */
2777 ClippedTime v = TimeClip(newDate);
2779 /* Steps 7-8. */
2780 unwrapped->setUTCTime(v, args.rval());
2781 return true;
2784 /* ES5 Annex B.2.5. */
2785 static bool date_setYear(JSContext* cx, unsigned argc, Value* vp) {
2786 CallArgs args = CallArgsFromVp(argc, vp);
2788 Rooted<DateObject*> unwrapped(
2789 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setYear"));
2790 if (!unwrapped) {
2791 return false;
2794 /* Step 1. */
2795 double t = ThisLocalTimeOrZero(unwrapped->forceUTC(), unwrapped);
2797 /* Step 2. */
2798 double y;
2799 if (!ToNumber(cx, args.get(0), &y)) {
2800 return false;
2803 /* Step 3. */
2804 if (std::isnan(y)) {
2805 unwrapped->setUTCTime(ClippedTime::invalid(), args.rval());
2806 return true;
2809 /* Step 4. */
2810 double yint = ToInteger(y);
2811 if (0 <= yint && yint <= 99) {
2812 yint += 1900;
2815 /* Step 5. */
2816 double day = MakeDay(yint, MonthFromTime(t), DateFromTime(t));
2818 /* Step 6. */
2819 double u = UTC(unwrapped->forceUTC(), MakeDate(day, TimeWithinDay(t)));
2821 /* Steps 7-8. */
2822 unwrapped->setUTCTime(TimeClip(u), args.rval());
2823 return true;
2826 /* constants for toString, toUTCString */
2827 static const char* const days[] = {"Sun", "Mon", "Tue", "Wed",
2828 "Thu", "Fri", "Sat"};
2829 static const char* const months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
2830 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
2832 /* ES5 B.2.6. */
2833 static bool date_toUTCString(JSContext* cx, unsigned argc, Value* vp) {
2834 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toUTCString");
2835 CallArgs args = CallArgsFromVp(argc, vp);
2837 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "toUTCString");
2838 if (!unwrapped) {
2839 return false;
2842 double utctime = unwrapped->UTCTime().toNumber();
2843 if (!std::isfinite(utctime)) {
2844 args.rval().setString(cx->names().Invalid_Date_);
2845 return true;
2848 char buf[100];
2849 SprintfLiteral(buf, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
2850 days[int(WeekDay(utctime))], int(DateFromTime(utctime)),
2851 months[int(MonthFromTime(utctime))],
2852 int(YearFromTime(utctime)), int(HourFromTime(utctime)),
2853 int(MinFromTime(utctime)), int(SecFromTime(utctime)));
2855 JSString* str = NewStringCopyZ<CanGC>(cx, buf);
2856 if (!str) {
2857 return false;
2860 args.rval().setString(str);
2861 return true;
2864 /* ES6 draft 2015-01-15 20.3.4.36. */
2865 static bool date_toISOString(JSContext* cx, unsigned argc, Value* vp) {
2866 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toISOString");
2867 CallArgs args = CallArgsFromVp(argc, vp);
2869 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "toISOString");
2870 if (!unwrapped) {
2871 return false;
2874 double utctime = unwrapped->UTCTime().toNumber();
2875 if (!std::isfinite(utctime)) {
2876 JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
2877 JSMSG_INVALID_DATE);
2878 return false;
2881 char buf[100];
2882 int year = int(YearFromTime(utctime));
2883 if (year < 0 || year > 9999) {
2884 SprintfLiteral(buf, "%+.6d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
2885 int(YearFromTime(utctime)), int(MonthFromTime(utctime)) + 1,
2886 int(DateFromTime(utctime)), int(HourFromTime(utctime)),
2887 int(MinFromTime(utctime)), int(SecFromTime(utctime)),
2888 int(msFromTime(utctime)));
2889 } else {
2890 SprintfLiteral(buf, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
2891 int(YearFromTime(utctime)), int(MonthFromTime(utctime)) + 1,
2892 int(DateFromTime(utctime)), int(HourFromTime(utctime)),
2893 int(MinFromTime(utctime)), int(SecFromTime(utctime)),
2894 int(msFromTime(utctime)));
2897 JSString* str = NewStringCopyZ<CanGC>(cx, buf);
2898 if (!str) {
2899 return false;
2901 args.rval().setString(str);
2902 return true;
2905 /* ES5 15.9.5.44. */
2906 static bool date_toJSON(JSContext* cx, unsigned argc, Value* vp) {
2907 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toJSON");
2908 CallArgs args = CallArgsFromVp(argc, vp);
2910 /* Step 1. */
2911 RootedObject obj(cx, ToObject(cx, args.thisv()));
2912 if (!obj) {
2913 return false;
2916 /* Step 2. */
2917 RootedValue tv(cx, ObjectValue(*obj));
2918 if (!ToPrimitive(cx, JSTYPE_NUMBER, &tv)) {
2919 return false;
2922 /* Step 3. */
2923 if (tv.isDouble() && !std::isfinite(tv.toDouble())) {
2924 args.rval().setNull();
2925 return true;
2928 /* Step 4. */
2929 RootedValue toISO(cx);
2930 if (!GetProperty(cx, obj, obj, cx->names().toISOString, &toISO)) {
2931 return false;
2934 /* Step 5. */
2935 if (!IsCallable(toISO)) {
2936 JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
2937 JSMSG_BAD_TOISOSTRING_PROP);
2938 return false;
2941 /* Step 6. */
2942 return Call(cx, toISO, obj, args.rval());
2945 #if JS_HAS_INTL_API
2946 JSString* DateTimeHelper::timeZoneComment(JSContext* cx,
2947 DateTimeInfo::ForceUTC forceUTC,
2948 const char* locale, double utcTime,
2949 double localTime) {
2950 if (!locale) {
2951 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2952 JSMSG_DEFAULT_LOCALE_ERROR);
2953 return nullptr;
2956 char16_t tzbuf[100];
2957 tzbuf[0] = ' ';
2958 tzbuf[1] = '(';
2960 char16_t* timeZoneStart = tzbuf + 2;
2961 constexpr size_t remainingSpace =
2962 std::size(tzbuf) - 2 - 1; // for the trailing ')'
2964 int64_t utcMilliseconds = static_cast<int64_t>(utcTime);
2965 if (!DateTimeInfo::timeZoneDisplayName(
2966 forceUTC, timeZoneStart, remainingSpace, utcMilliseconds, locale)) {
2967 JS_ReportOutOfMemory(cx);
2968 return nullptr;
2971 // Reject if the result string is empty.
2972 size_t len = js_strlen(timeZoneStart);
2973 if (len == 0) {
2974 return cx->names().empty_;
2977 // Parenthesize the returned display name.
2978 timeZoneStart[len] = ')';
2980 return NewStringCopyN<CanGC>(cx, tzbuf, 2 + len + 1);
2982 #else
2983 /* Interface to PRMJTime date struct. */
2984 PRMJTime DateTimeHelper::toPRMJTime(DateTimeInfo::ForceUTC forceUTC,
2985 double localTime, double utcTime) {
2986 double year = YearFromTime(localTime);
2988 PRMJTime prtm;
2989 prtm.tm_usec = int32_t(msFromTime(localTime)) * 1000;
2990 prtm.tm_sec = int8_t(SecFromTime(localTime));
2991 prtm.tm_min = int8_t(MinFromTime(localTime));
2992 prtm.tm_hour = int8_t(HourFromTime(localTime));
2993 prtm.tm_mday = int8_t(DateFromTime(localTime));
2994 prtm.tm_mon = int8_t(MonthFromTime(localTime));
2995 prtm.tm_wday = int8_t(WeekDay(localTime));
2996 prtm.tm_year = year;
2997 prtm.tm_yday = int16_t(DayWithinYear(localTime, year));
2998 prtm.tm_isdst = (daylightSavingTA(forceUTC, utcTime) != 0);
3000 return prtm;
3003 size_t DateTimeHelper::formatTime(DateTimeInfo::ForceUTC forceUTC, char* buf,
3004 size_t buflen, const char* fmt,
3005 double utcTime, double localTime) {
3006 PRMJTime prtm = toPRMJTime(forceUTC, localTime, utcTime);
3008 // If an equivalent year was used to compute the date/time components, use
3009 // the same equivalent year to determine the time zone name and offset in
3010 // PRMJ_FormatTime(...).
3011 int timeZoneYear = isRepresentableAsTime32(utcTime)
3012 ? prtm.tm_year
3013 : equivalentYearForDST(prtm.tm_year);
3014 int offsetInSeconds = (int)floor((localTime - utcTime) / msPerSecond);
3016 return PRMJ_FormatTime(buf, buflen, fmt, &prtm, timeZoneYear,
3017 offsetInSeconds);
3020 JSString* DateTimeHelper::timeZoneComment(JSContext* cx,
3021 DateTimeInfo::ForceUTC forceUTC,
3022 const char* locale, double utcTime,
3023 double localTime) {
3024 char tzbuf[100];
3026 size_t tzlen =
3027 formatTime(forceUTC, tzbuf, sizeof tzbuf, " (%Z)", utcTime, localTime);
3028 if (tzlen != 0) {
3029 // Decide whether to use the resulting time zone string.
3031 // Reject it if it contains any non-ASCII or non-printable characters.
3032 // It's then likely in some other character encoding, and we probably
3033 // won't display it correctly.
3034 bool usetz = true;
3035 for (size_t i = 0; i < tzlen; i++) {
3036 char16_t c = tzbuf[i];
3037 if (!IsAsciiPrintable(c)) {
3038 usetz = false;
3039 break;
3043 // Also reject it if it's not parenthesized or if it's ' ()'.
3044 if (tzbuf[0] != ' ' || tzbuf[1] != '(' || tzbuf[2] == ')') {
3045 usetz = false;
3048 if (usetz) {
3049 return NewStringCopyN<CanGC>(cx, tzbuf, tzlen);
3053 return cx->names().empty_;
3055 #endif /* JS_HAS_INTL_API */
3057 enum class FormatSpec { DateTime, Date, Time };
3059 static bool FormatDate(JSContext* cx, DateTimeInfo::ForceUTC forceUTC,
3060 const char* locale, double utcTime, FormatSpec format,
3061 MutableHandleValue rval) {
3062 if (!std::isfinite(utcTime)) {
3063 rval.setString(cx->names().Invalid_Date_);
3064 return true;
3067 MOZ_ASSERT(NumbersAreIdentical(TimeClip(utcTime).toDouble(), utcTime));
3069 double localTime = LocalTime(forceUTC, utcTime);
3071 int offset = 0;
3072 RootedString timeZoneComment(cx);
3073 if (format == FormatSpec::DateTime || format == FormatSpec::Time) {
3074 // Offset from GMT in minutes. The offset includes daylight savings,
3075 // if it applies.
3076 int minutes = (int)trunc((localTime - utcTime) / msPerMinute);
3078 // Map 510 minutes to 0830 hours.
3079 offset = (minutes / 60) * 100 + minutes % 60;
3081 // Print as "Wed Nov 05 1997 19:38:03 GMT-0800 (PST)".
3083 // The TZA is printed as 'GMT-0800' rather than as 'PST' to avoid
3084 // operating-system dependence on strftime (which PRMJ_FormatTime
3085 // calls, for %Z only.) win32 prints PST as 'Pacific Standard Time.'
3086 // This way we always know what we're getting, and can parse it if
3087 // we produce it. The OS time zone string is included as a comment.
3089 // When ICU is used to retrieve the time zone string, the localized
3090 // 'long' name format from CLDR is used. For example when the default
3091 // locale is "en-US", PST is displayed as 'Pacific Standard Time', but
3092 // when it is "ru", 'Тихоокеанское стандартное время' is used. This
3093 // also means the time zone string may not fit into Latin-1.
3095 // Get a time zone string from the OS or ICU to include as a comment.
3096 timeZoneComment = DateTimeHelper::timeZoneComment(cx, forceUTC, locale,
3097 utcTime, localTime);
3098 if (!timeZoneComment) {
3099 return false;
3103 char buf[100];
3104 switch (format) {
3105 case FormatSpec::DateTime:
3106 /* Tue Oct 31 2000 09:41:40 GMT-0800 */
3107 SprintfLiteral(buf, "%s %s %.2d %.4d %.2d:%.2d:%.2d GMT%+.4d",
3108 days[int(WeekDay(localTime))],
3109 months[int(MonthFromTime(localTime))],
3110 int(DateFromTime(localTime)), int(YearFromTime(localTime)),
3111 int(HourFromTime(localTime)), int(MinFromTime(localTime)),
3112 int(SecFromTime(localTime)), offset);
3113 break;
3114 case FormatSpec::Date:
3115 /* Tue Oct 31 2000 */
3116 SprintfLiteral(buf, "%s %s %.2d %.4d", days[int(WeekDay(localTime))],
3117 months[int(MonthFromTime(localTime))],
3118 int(DateFromTime(localTime)),
3119 int(YearFromTime(localTime)));
3120 break;
3121 case FormatSpec::Time:
3122 /* 09:41:40 GMT-0800 */
3123 SprintfLiteral(buf, "%.2d:%.2d:%.2d GMT%+.4d",
3124 int(HourFromTime(localTime)), int(MinFromTime(localTime)),
3125 int(SecFromTime(localTime)), offset);
3126 break;
3129 RootedString str(cx, NewStringCopyZ<CanGC>(cx, buf));
3130 if (!str) {
3131 return false;
3134 // Append the time zone string if present.
3135 if (timeZoneComment && !timeZoneComment->empty()) {
3136 str = js::ConcatStrings<CanGC>(cx, str, timeZoneComment);
3137 if (!str) {
3138 return false;
3142 rval.setString(str);
3143 return true;
3146 #if !JS_HAS_INTL_API
3147 static bool ToLocaleFormatHelper(JSContext* cx, DateObject* unwrapped,
3148 const char* format, MutableHandleValue rval) {
3149 DateTimeInfo::ForceUTC forceUTC = unwrapped->forceUTC();
3150 const char* locale = unwrapped->realm()->getLocale();
3151 double utcTime = unwrapped->UTCTime().toNumber();
3153 char buf[100];
3154 if (!std::isfinite(utcTime)) {
3155 strcpy(buf, "InvalidDate");
3156 } else {
3157 double localTime = LocalTime(forceUTC, utcTime);
3159 /* Let PRMJTime format it. */
3160 size_t result_len = DateTimeHelper::formatTime(forceUTC, buf, sizeof buf,
3161 format, utcTime, localTime);
3163 /* If it failed, default to toString. */
3164 if (result_len == 0) {
3165 return FormatDate(cx, forceUTC, locale, utcTime, FormatSpec::DateTime,
3166 rval);
3169 /* Hacked check against undesired 2-digit year 00/00/00 form. */
3170 if (strcmp(format, "%x") == 0 && result_len >= 6 &&
3171 /* Format %x means use OS settings, which may have 2-digit yr, so
3172 hack end of 3/11/22 or 11.03.22 or 11Mar22 to use 4-digit yr...*/
3173 !IsAsciiDigit(buf[result_len - 3]) &&
3174 IsAsciiDigit(buf[result_len - 2]) &&
3175 IsAsciiDigit(buf[result_len - 1]) &&
3176 /* ...but not if starts with 4-digit year, like 2022/3/11. */
3177 !(IsAsciiDigit(buf[0]) && IsAsciiDigit(buf[1]) &&
3178 IsAsciiDigit(buf[2]) && IsAsciiDigit(buf[3]))) {
3179 int year = int(YearFromTime(localTime));
3180 snprintf(buf + (result_len - 2), (sizeof buf) - (result_len - 2), "%d",
3181 year);
3185 if (cx->runtime()->localeCallbacks &&
3186 cx->runtime()->localeCallbacks->localeToUnicode) {
3187 return cx->runtime()->localeCallbacks->localeToUnicode(cx, buf, rval);
3190 JSString* str = NewStringCopyZ<CanGC>(cx, buf);
3191 if (!str) {
3192 return false;
3194 rval.setString(str);
3195 return true;
3198 /* ES5 15.9.5.5. */
3199 static bool date_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
3200 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toLocaleString");
3201 CallArgs args = CallArgsFromVp(argc, vp);
3203 auto* unwrapped =
3204 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toLocaleString");
3205 if (!unwrapped) {
3206 return false;
3210 * Use '%#c' for windows, because '%c' is backward-compatible and non-y2k
3211 * with msvc; '%#c' requests that a full year be used in the result string.
3213 static const char format[] =
3214 # if defined(_WIN32)
3215 "%#c"
3216 # else
3217 "%c"
3218 # endif
3221 return ToLocaleFormatHelper(cx, unwrapped, format, args.rval());
3224 static bool date_toLocaleDateString(JSContext* cx, unsigned argc, Value* vp) {
3225 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype",
3226 "toLocaleDateString");
3227 CallArgs args = CallArgsFromVp(argc, vp);
3229 auto* unwrapped =
3230 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toLocaleDateString");
3231 if (!unwrapped) {
3232 return false;
3236 * Use '%#x' for windows, because '%x' is backward-compatible and non-y2k
3237 * with msvc; '%#x' requests that a full year be used in the result string.
3239 static const char format[] =
3240 # if defined(_WIN32)
3241 "%#x"
3242 # else
3243 "%x"
3244 # endif
3247 return ToLocaleFormatHelper(cx, unwrapped, format, args.rval());
3250 static bool date_toLocaleTimeString(JSContext* cx, unsigned argc, Value* vp) {
3251 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype",
3252 "toLocaleTimeString");
3253 CallArgs args = CallArgsFromVp(argc, vp);
3255 auto* unwrapped =
3256 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toLocaleTimeString");
3257 if (!unwrapped) {
3258 return false;
3261 return ToLocaleFormatHelper(cx, unwrapped, "%X", args.rval());
3263 #endif /* !JS_HAS_INTL_API */
3265 static bool date_toTimeString(JSContext* cx, unsigned argc, Value* vp) {
3266 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toTimeString");
3267 CallArgs args = CallArgsFromVp(argc, vp);
3269 auto* unwrapped =
3270 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toTimeString");
3271 if (!unwrapped) {
3272 return false;
3275 return FormatDate(cx, unwrapped->forceUTC(), unwrapped->realm()->getLocale(),
3276 unwrapped->UTCTime().toNumber(), FormatSpec::Time,
3277 args.rval());
3280 static bool date_toDateString(JSContext* cx, unsigned argc, Value* vp) {
3281 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toDateString");
3282 CallArgs args = CallArgsFromVp(argc, vp);
3284 auto* unwrapped =
3285 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toDateString");
3286 if (!unwrapped) {
3287 return false;
3290 return FormatDate(cx, unwrapped->forceUTC(), unwrapped->realm()->getLocale(),
3291 unwrapped->UTCTime().toNumber(), FormatSpec::Date,
3292 args.rval());
3295 static bool date_toSource(JSContext* cx, unsigned argc, Value* vp) {
3296 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toSource");
3297 CallArgs args = CallArgsFromVp(argc, vp);
3299 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "toSource");
3300 if (!unwrapped) {
3301 return false;
3304 JSStringBuilder sb(cx);
3305 if (!sb.append("(new Date(") ||
3306 !NumberValueToStringBuffer(unwrapped->UTCTime(), sb) ||
3307 !sb.append("))")) {
3308 return false;
3311 JSString* str = sb.finishString();
3312 if (!str) {
3313 return false;
3315 args.rval().setString(str);
3316 return true;
3319 bool date_toString(JSContext* cx, unsigned argc, Value* vp) {
3320 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toString");
3321 CallArgs args = CallArgsFromVp(argc, vp);
3323 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "toString");
3324 if (!unwrapped) {
3325 return false;
3328 return FormatDate(cx, unwrapped->forceUTC(), unwrapped->realm()->getLocale(),
3329 unwrapped->UTCTime().toNumber(), FormatSpec::DateTime,
3330 args.rval());
3333 bool js::date_valueOf(JSContext* cx, unsigned argc, Value* vp) {
3334 CallArgs args = CallArgsFromVp(argc, vp);
3336 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "valueOf");
3337 if (!unwrapped) {
3338 return false;
3341 args.rval().set(unwrapped->UTCTime());
3342 return true;
3345 // ES6 20.3.4.45 Date.prototype[@@toPrimitive]
3346 static bool date_toPrimitive(JSContext* cx, unsigned argc, Value* vp) {
3347 CallArgs args = CallArgsFromVp(argc, vp);
3349 // Steps 1-2.
3350 if (!args.thisv().isObject()) {
3351 ReportIncompatible(cx, args);
3352 return false;
3355 // Steps 3-5.
3356 JSType hint;
3357 if (!GetFirstArgumentAsTypeHint(cx, args, &hint)) {
3358 return false;
3360 if (hint == JSTYPE_UNDEFINED) {
3361 hint = JSTYPE_STRING;
3364 args.rval().set(args.thisv());
3365 RootedObject obj(cx, &args.thisv().toObject());
3366 return OrdinaryToPrimitive(cx, obj, hint, args.rval());
3369 #if JS_HAS_TEMPORAL_API
3371 * Date.prototype.toTemporalInstant ( )
3373 static bool date_toTemporalInstant(JSContext* cx, unsigned argc, Value* vp) {
3374 CallArgs args = CallArgsFromVp(argc, vp);
3376 // Step 1.
3377 auto* unwrapped =
3378 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toTemporalInstant");
3379 if (!unwrapped) {
3380 return false;
3383 // Step 2.
3384 double utctime = unwrapped->UTCTime().toNumber();
3385 if (!std::isfinite(utctime)) {
3386 JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
3387 JSMSG_INVALID_DATE);
3388 return false;
3390 MOZ_ASSERT(IsInteger(utctime));
3392 auto instant = temporal::Instant::fromMilliseconds(int64_t(utctime));
3393 MOZ_ASSERT(temporal::IsValidEpochInstant(instant));
3395 // Step 3.
3396 auto* result = temporal::CreateTemporalInstant(cx, instant);
3397 if (!result) {
3398 return false;
3400 args.rval().setObject(*result);
3401 return true;
3403 #endif /* JS_HAS_TEMPORAL_API */
3405 static const JSFunctionSpec date_static_methods[] = {
3406 JS_FN("UTC", date_UTC, 7, 0), JS_FN("parse", date_parse, 1, 0),
3407 JS_FN("now", date_now, 0, 0), JS_FS_END};
3409 static const JSFunctionSpec date_methods[] = {
3410 JS_FN("getTime", date_getTime, 0, 0),
3411 JS_FN("getTimezoneOffset", date_getTimezoneOffset, 0, 0),
3412 JS_FN("getYear", date_getYear, 0, 0),
3413 JS_FN("getFullYear", date_getFullYear, 0, 0),
3414 JS_FN("getUTCFullYear", date_getUTCFullYear, 0, 0),
3415 JS_FN("getMonth", date_getMonth, 0, 0),
3416 JS_FN("getUTCMonth", date_getUTCMonth, 0, 0),
3417 JS_FN("getDate", date_getDate, 0, 0),
3418 JS_FN("getUTCDate", date_getUTCDate, 0, 0),
3419 JS_FN("getDay", date_getDay, 0, 0),
3420 JS_FN("getUTCDay", date_getUTCDay, 0, 0),
3421 JS_FN("getHours", date_getHours, 0, 0),
3422 JS_FN("getUTCHours", date_getUTCHours, 0, 0),
3423 JS_FN("getMinutes", date_getMinutes, 0, 0),
3424 JS_FN("getUTCMinutes", date_getUTCMinutes, 0, 0),
3425 JS_FN("getSeconds", date_getSeconds, 0, 0),
3426 JS_FN("getUTCSeconds", date_getUTCSeconds, 0, 0),
3427 JS_FN("getMilliseconds", date_getMilliseconds, 0, 0),
3428 JS_FN("getUTCMilliseconds", date_getUTCMilliseconds, 0, 0),
3429 JS_FN("setTime", date_setTime, 1, 0),
3430 JS_FN("setYear", date_setYear, 1, 0),
3431 JS_FN("setFullYear", date_setFullYear, 3, 0),
3432 JS_FN("setUTCFullYear", date_setUTCFullYear, 3, 0),
3433 JS_FN("setMonth", date_setMonth, 2, 0),
3434 JS_FN("setUTCMonth", date_setUTCMonth, 2, 0),
3435 JS_FN("setDate", date_setDate, 1, 0),
3436 JS_FN("setUTCDate", date_setUTCDate, 1, 0),
3437 JS_FN("setHours", date_setHours, 4, 0),
3438 JS_FN("setUTCHours", date_setUTCHours, 4, 0),
3439 JS_FN("setMinutes", date_setMinutes, 3, 0),
3440 JS_FN("setUTCMinutes", date_setUTCMinutes, 3, 0),
3441 JS_FN("setSeconds", date_setSeconds, 2, 0),
3442 JS_FN("setUTCSeconds", date_setUTCSeconds, 2, 0),
3443 JS_FN("setMilliseconds", date_setMilliseconds, 1, 0),
3444 JS_FN("setUTCMilliseconds", date_setUTCMilliseconds, 1, 0),
3445 JS_FN("toUTCString", date_toUTCString, 0, 0),
3446 #if JS_HAS_TEMPORAL_API
3447 JS_FN("toTemporalInstant", date_toTemporalInstant, 0, 0),
3448 #endif
3449 #if JS_HAS_INTL_API
3450 JS_SELF_HOSTED_FN("toLocaleString", "Date_toLocaleString", 0, 0),
3451 JS_SELF_HOSTED_FN("toLocaleDateString", "Date_toLocaleDateString", 0, 0),
3452 JS_SELF_HOSTED_FN("toLocaleTimeString", "Date_toLocaleTimeString", 0, 0),
3453 #else
3454 JS_FN("toLocaleString", date_toLocaleString, 0, 0),
3455 JS_FN("toLocaleDateString", date_toLocaleDateString, 0, 0),
3456 JS_FN("toLocaleTimeString", date_toLocaleTimeString, 0, 0),
3457 #endif
3458 JS_FN("toDateString", date_toDateString, 0, 0),
3459 JS_FN("toTimeString", date_toTimeString, 0, 0),
3460 JS_FN("toISOString", date_toISOString, 0, 0),
3461 JS_FN("toJSON", date_toJSON, 1, 0),
3462 JS_FN("toSource", date_toSource, 0, 0),
3463 JS_FN("toString", date_toString, 0, 0),
3464 JS_FN("valueOf", date_valueOf, 0, 0),
3465 JS_SYM_FN(toPrimitive, date_toPrimitive, 1, JSPROP_READONLY),
3466 JS_FS_END};
3468 static bool NewDateObject(JSContext* cx, const CallArgs& args, ClippedTime t) {
3469 MOZ_ASSERT(args.isConstructing());
3471 RootedObject proto(cx);
3472 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Date, &proto)) {
3473 return false;
3476 JSObject* obj = NewDateObjectMsec(cx, t, proto);
3477 if (!obj) {
3478 return false;
3481 args.rval().setObject(*obj);
3482 return true;
3485 static bool ToDateString(JSContext* cx, const CallArgs& args, ClippedTime t) {
3486 return FormatDate(cx, ForceUTC(cx->realm()), cx->realm()->getLocale(),
3487 t.toDouble(), FormatSpec::DateTime, args.rval());
3490 static bool DateNoArguments(JSContext* cx, const CallArgs& args) {
3491 MOZ_ASSERT(args.length() == 0);
3493 ClippedTime now = NowAsMillis(cx);
3495 if (args.isConstructing()) {
3496 return NewDateObject(cx, args, now);
3499 return ToDateString(cx, args, now);
3502 static bool DateOneArgument(JSContext* cx, const CallArgs& args) {
3503 MOZ_ASSERT(args.length() == 1);
3505 if (args.isConstructing()) {
3506 if (args[0].isObject()) {
3507 RootedObject obj(cx, &args[0].toObject());
3509 ESClass cls;
3510 if (!GetBuiltinClass(cx, obj, &cls)) {
3511 return false;
3514 if (cls == ESClass::Date) {
3515 RootedValue unboxed(cx);
3516 if (!Unbox(cx, obj, &unboxed)) {
3517 return false;
3520 return NewDateObject(cx, args, TimeClip(unboxed.toNumber()));
3524 if (!ToPrimitive(cx, args[0])) {
3525 return false;
3528 ClippedTime t;
3529 if (args[0].isString()) {
3530 JSLinearString* linearStr = args[0].toString()->ensureLinear(cx);
3531 if (!linearStr) {
3532 return false;
3535 if (!ParseDate(ForceUTC(cx->realm()), linearStr, &t)) {
3536 t = ClippedTime::invalid();
3538 } else {
3539 double d;
3540 if (!ToNumber(cx, args[0], &d)) {
3541 return false;
3543 t = TimeClip(d);
3546 return NewDateObject(cx, args, t);
3549 return ToDateString(cx, args, NowAsMillis(cx));
3552 static bool DateMultipleArguments(JSContext* cx, const CallArgs& args) {
3553 MOZ_ASSERT(args.length() >= 2);
3555 // Step 3.
3556 if (args.isConstructing()) {
3557 // Steps 3a-b.
3558 double y;
3559 if (!ToNumber(cx, args[0], &y)) {
3560 return false;
3563 // Steps 3c-d.
3564 double m;
3565 if (!ToNumber(cx, args[1], &m)) {
3566 return false;
3569 // Steps 3e-f.
3570 double dt;
3571 if (args.length() >= 3) {
3572 if (!ToNumber(cx, args[2], &dt)) {
3573 return false;
3575 } else {
3576 dt = 1;
3579 // Steps 3g-h.
3580 double h;
3581 if (args.length() >= 4) {
3582 if (!ToNumber(cx, args[3], &h)) {
3583 return false;
3585 } else {
3586 h = 0;
3589 // Steps 3i-j.
3590 double min;
3591 if (args.length() >= 5) {
3592 if (!ToNumber(cx, args[4], &min)) {
3593 return false;
3595 } else {
3596 min = 0;
3599 // Steps 3k-l.
3600 double s;
3601 if (args.length() >= 6) {
3602 if (!ToNumber(cx, args[5], &s)) {
3603 return false;
3605 } else {
3606 s = 0;
3609 // Steps 3m-n.
3610 double milli;
3611 if (args.length() >= 7) {
3612 if (!ToNumber(cx, args[6], &milli)) {
3613 return false;
3615 } else {
3616 milli = 0;
3619 // Step 3o.
3620 double yr = y;
3621 if (!std::isnan(y)) {
3622 double yint = ToInteger(y);
3623 if (0 <= yint && yint <= 99) {
3624 yr = 1900 + yint;
3628 // Step 3p.
3629 double finalDate = MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli));
3631 // Steps 3q-t.
3632 return NewDateObject(cx, args,
3633 TimeClip(UTC(ForceUTC(cx->realm()), finalDate)));
3636 return ToDateString(cx, args, NowAsMillis(cx));
3639 static bool DateConstructor(JSContext* cx, unsigned argc, Value* vp) {
3640 AutoJSConstructorProfilerEntry pseudoFrame(cx, "Date");
3641 CallArgs args = CallArgsFromVp(argc, vp);
3643 if (args.length() == 0) {
3644 return DateNoArguments(cx, args);
3647 if (args.length() == 1) {
3648 return DateOneArgument(cx, args);
3651 return DateMultipleArguments(cx, args);
3654 static bool FinishDateClassInit(JSContext* cx, HandleObject ctor,
3655 HandleObject proto) {
3657 * Date.prototype.toGMTString has the same initial value as
3658 * Date.prototype.toUTCString.
3660 RootedValue toUTCStringFun(cx);
3661 RootedId toUTCStringId(cx, NameToId(cx->names().toUTCString));
3662 RootedId toGMTStringId(cx, NameToId(cx->names().toGMTString));
3663 return NativeGetProperty(cx, proto.as<NativeObject>(), toUTCStringId,
3664 &toUTCStringFun) &&
3665 NativeDefineDataProperty(cx, proto.as<NativeObject>(), toGMTStringId,
3666 toUTCStringFun, 0);
3669 static const ClassSpec DateObjectClassSpec = {
3670 GenericCreateConstructor<DateConstructor, 7, gc::AllocKind::FUNCTION>,
3671 GenericCreatePrototype<DateObject>,
3672 date_static_methods,
3673 nullptr,
3674 date_methods,
3675 nullptr,
3676 FinishDateClassInit};
3678 const JSClass DateObject::class_ = {"Date",
3679 JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
3680 JSCLASS_HAS_CACHED_PROTO(JSProto_Date),
3681 JS_NULL_CLASS_OPS, &DateObjectClassSpec};
3683 const JSClass DateObject::protoClass_ = {
3684 "Date.prototype", JSCLASS_HAS_CACHED_PROTO(JSProto_Date), JS_NULL_CLASS_OPS,
3685 &DateObjectClassSpec};
3687 JSObject* js::NewDateObjectMsec(JSContext* cx, ClippedTime t,
3688 HandleObject proto /* = nullptr */) {
3689 DateObject* obj = NewObjectWithClassProto<DateObject>(cx, proto);
3690 if (!obj) {
3691 return nullptr;
3693 obj->setUTCTime(t);
3694 return obj;
3697 JS_PUBLIC_API JSObject* JS::NewDateObject(JSContext* cx, ClippedTime time) {
3698 AssertHeapIsIdle();
3699 CHECK_THREAD(cx);
3700 return NewDateObjectMsec(cx, time);
3703 JS_PUBLIC_API JSObject* js::NewDateObject(JSContext* cx, int year, int mon,
3704 int mday, int hour, int min,
3705 int sec) {
3706 MOZ_ASSERT(mon < 12);
3707 double msec_time =
3708 MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0.0));
3709 return NewDateObjectMsec(cx, TimeClip(UTC(ForceUTC(cx->realm()), msec_time)));
3712 JS_PUBLIC_API bool js::DateIsValid(JSContext* cx, HandleObject obj,
3713 bool* isValid) {
3714 ESClass cls;
3715 if (!GetBuiltinClass(cx, obj, &cls)) {
3716 return false;
3719 if (cls != ESClass::Date) {
3720 *isValid = false;
3721 return true;
3724 RootedValue unboxed(cx);
3725 if (!Unbox(cx, obj, &unboxed)) {
3726 return false;
3729 *isValid = !std::isnan(unboxed.toNumber());
3730 return true;
3733 JS_PUBLIC_API JSObject* JS::NewDateObject(JSContext* cx, int year, int mon,
3734 int mday, int hour, int min,
3735 int sec) {
3736 AssertHeapIsIdle();
3737 CHECK_THREAD(cx);
3738 return js::NewDateObject(cx, year, mon, mday, hour, min, sec);
3741 JS_PUBLIC_API bool JS::ObjectIsDate(JSContext* cx, Handle<JSObject*> obj,
3742 bool* isDate) {
3743 cx->check(obj);
3745 ESClass cls;
3746 if (!GetBuiltinClass(cx, obj, &cls)) {
3747 return false;
3750 *isDate = cls == ESClass::Date;
3751 return true;
3754 JS_PUBLIC_API bool js::DateGetMsecSinceEpoch(JSContext* cx, HandleObject obj,
3755 double* msecsSinceEpoch) {
3756 ESClass cls;
3757 if (!GetBuiltinClass(cx, obj, &cls)) {
3758 return false;
3761 if (cls != ESClass::Date) {
3762 *msecsSinceEpoch = 0;
3763 return true;
3766 RootedValue unboxed(cx);
3767 if (!Unbox(cx, obj, &unboxed)) {
3768 return false;
3771 *msecsSinceEpoch = unboxed.toNumber();
3772 return true;