Bug 1852740: add tests for the `fetchpriority` attribute in Link headers. r=necko...
[gecko.git] / js / src / jsdate.cpp
bloba53b6050b9e2d91926e7192e58e08d37267c7be4
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: set ts=8 sts=2 et sw=2 tw=80:
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
8 * JS date methods.
10 * "For example, OS/360 devotes 26 bytes of the permanently
11 * resident date-turnover routine to the proper handling of
12 * December 31 on leap years (when it is Day 366). That
13 * might have been left to the operator."
15 * Frederick Brooks, 'The Second-System Effect'.
18 #include "jsdate.h"
20 #include "mozilla/Atomics.h"
21 #include "mozilla/Casting.h"
22 #include "mozilla/FloatingPoint.h"
23 #include "mozilla/Sprintf.h"
24 #include "mozilla/TextUtils.h"
26 #include <algorithm>
27 #include <cstring>
28 #include <iterator>
29 #include <math.h>
30 #include <string.h>
32 #include "jsapi.h"
33 #include "jsfriendapi.h"
34 #include "jsnum.h"
35 #include "jstypes.h"
37 #ifdef JS_HAS_TEMPORAL_API
38 # include "builtin/temporal/Instant.h"
39 #endif
40 #include "js/CallAndConstruct.h" // JS::IsCallable
41 #include "js/Conversions.h"
42 #include "js/Date.h"
43 #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
44 #include "js/LocaleSensitive.h"
45 #include "js/Object.h" // JS::GetBuiltinClass
46 #include "js/PropertySpec.h"
47 #include "js/Wrapper.h"
48 #include "util/DifferentialTesting.h"
49 #include "util/StringBuffer.h"
50 #include "util/Text.h"
51 #include "vm/DateObject.h"
52 #include "vm/DateTime.h"
53 #include "vm/GlobalObject.h"
54 #include "vm/Interpreter.h"
55 #include "vm/JSContext.h"
56 #include "vm/JSObject.h"
57 #include "vm/StringType.h"
58 #include "vm/Time.h"
60 #include "vm/Compartment-inl.h" // For js::UnwrapAndTypeCheckThis
61 #include "vm/GeckoProfiler-inl.h"
62 #include "vm/JSObject-inl.h"
64 using namespace js;
66 using mozilla::Atomic;
67 using mozilla::BitwiseCast;
68 using mozilla::IsAsciiAlpha;
69 using mozilla::IsAsciiDigit;
70 using mozilla::IsAsciiLowercaseAlpha;
71 using mozilla::NumbersAreIdentical;
72 using mozilla::Relaxed;
74 using JS::AutoCheckCannotGC;
75 using JS::ClippedTime;
76 using JS::GenericNaN;
77 using JS::GetBuiltinClass;
78 using JS::TimeClip;
79 using JS::ToInteger;
81 // When this value is non-zero, we'll round the time by this resolution.
82 static Atomic<uint32_t, Relaxed> sResolutionUsec;
83 // This is not implemented yet, but we will use this to know to jitter the time
84 // in the JS shell
85 static Atomic<bool, Relaxed> sJitter;
86 // The callback we will use for the Gecko implementation of Timer
87 // Clamping/Jittering
88 static Atomic<JS::ReduceMicrosecondTimePrecisionCallback, Relaxed>
89 sReduceMicrosecondTimePrecisionCallback;
92 * The JS 'Date' object is patterned after the Java 'Date' object.
93 * Here is a script:
95 * today = new Date();
97 * print(today.toLocaleString());
99 * weekDay = today.getDay();
102 * These Java (and ECMA-262) methods are supported:
104 * UTC
105 * getDate (getUTCDate)
106 * getDay (getUTCDay)
107 * getHours (getUTCHours)
108 * getMinutes (getUTCMinutes)
109 * getMonth (getUTCMonth)
110 * getSeconds (getUTCSeconds)
111 * getMilliseconds (getUTCMilliseconds)
112 * getTime
113 * getTimezoneOffset
114 * getYear
115 * getFullYear (getUTCFullYear)
116 * parse
117 * setDate (setUTCDate)
118 * setHours (setUTCHours)
119 * setMinutes (setUTCMinutes)
120 * setMonth (setUTCMonth)
121 * setSeconds (setUTCSeconds)
122 * setMilliseconds (setUTCMilliseconds)
123 * setTime
124 * setYear (setFullYear, setUTCFullYear)
125 * toGMTString (toUTCString)
126 * toLocaleString
127 * toString
130 * These Java methods are not supported
132 * setDay
133 * before
134 * after
135 * equals
136 * hashCode
139 namespace {
141 class DateTimeHelper {
142 private:
143 #if JS_HAS_INTL_API
144 static double localTZA(DateTimeInfo::ForceUTC forceUTC, double t,
145 DateTimeInfo::TimeZoneOffset offset);
146 #else
147 static int equivalentYearForDST(int year);
148 static bool isRepresentableAsTime32(double t);
149 static double daylightSavingTA(DateTimeInfo::ForceUTC forceUTC, double t);
150 static double adjustTime(DateTimeInfo::ForceUTC forceUTC, double date);
151 static PRMJTime toPRMJTime(DateTimeInfo::ForceUTC forceUTC, double localTime,
152 double utcTime);
153 #endif
155 public:
156 static double localTime(DateTimeInfo::ForceUTC forceUTC, double t);
157 static double UTC(DateTimeInfo::ForceUTC forceUTC, double t);
158 static JSString* timeZoneComment(JSContext* cx,
159 DateTimeInfo::ForceUTC forceUTC,
160 const char* locale, double utcTime,
161 double localTime);
162 #if !JS_HAS_INTL_API
163 static size_t formatTime(DateTimeInfo::ForceUTC forceUTC, char* buf,
164 size_t buflen, const char* fmt, double utcTime,
165 double localTime);
166 #endif
169 } // namespace
171 static DateTimeInfo::ForceUTC ForceUTC(const Realm* realm) {
172 return realm->creationOptions().forceUTC() ? DateTimeInfo::ForceUTC::Yes
173 : DateTimeInfo::ForceUTC::No;
176 // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
177 // 5.2.5 Mathematical Operations
178 static inline double PositiveModulo(double dividend, double divisor) {
179 MOZ_ASSERT(divisor > 0);
180 MOZ_ASSERT(std::isfinite(divisor));
182 double result = fmod(dividend, divisor);
183 if (result < 0) {
184 result += divisor;
186 return result + (+0.0);
189 static inline double Day(double t) { return floor(t / msPerDay); }
191 static double TimeWithinDay(double t) { return PositiveModulo(t, msPerDay); }
193 /* ES5 15.9.1.3. */
194 static inline bool IsLeapYear(double year) {
195 MOZ_ASSERT(ToInteger(year) == year);
196 return fmod(year, 4) == 0 && (fmod(year, 100) != 0 || fmod(year, 400) == 0);
199 static inline double DayFromYear(double y) {
200 return 365 * (y - 1970) + floor((y - 1969) / 4.0) -
201 floor((y - 1901) / 100.0) + floor((y - 1601) / 400.0);
204 static inline double TimeFromYear(double y) {
205 return DayFromYear(y) * msPerDay;
208 namespace {
209 struct YearMonthDay {
210 int32_t year;
211 uint32_t month;
212 uint32_t day;
214 } // namespace
217 * This function returns the year, month and day corresponding to a given
218 * time value. The implementation closely follows (w.r.t. types and variable
219 * names) the algorithm shown in Figure 12 of [1].
221 * A key point of the algorithm is that it works on the so called
222 * Computational calendar where years run from March to February -- this
223 * largely avoids complications with leap years. The algorithm finds the
224 * date in the Computation calendar and then maps it to the Gregorian
225 * calendar.
227 * [1] Neri C, Schneider L., "Euclidean affine functions and their
228 * application to calendar algorithms."
229 * Softw Pract Exper. 2023;53(4):937-970. doi: 10.1002/spe.3172
230 * https://onlinelibrary.wiley.com/doi/full/10.1002/spe.3172
232 static YearMonthDay ToYearMonthDay(double t) {
233 MOZ_ASSERT(ToInteger(t) == t);
235 // Calendar cycles repeat every 400 years in the Gregorian calendar: a
236 // leap day is added every 4 years, removed every 100 years and added
237 // every 400 years. The number of days in 400 years is cycleInDays.
238 constexpr uint32_t cycleInYears = 400;
239 constexpr uint32_t cycleInDays = cycleInYears * 365 + (cycleInYears / 4) -
240 (cycleInYears / 100) + (cycleInYears / 400);
241 static_assert(cycleInDays == 146097, "Wrong calculation of cycleInDays.");
243 // The natural epoch for the Computational calendar is 0000/Mar/01 and
244 // there are rataDie1970Jan1 = 719468 days from this date to 1970/Jan/01,
245 // the epoch used by ES2024, 21.4.1.1.
246 constexpr uint32_t rataDie1970Jan1 = 719468;
248 constexpr uint32_t maxU32 = std::numeric_limits<uint32_t>::max();
250 // Let N_U be the number of days since the 1970/Jan/01. This function sets
251 // N = N_U + K, where K = rataDie1970Jan1 + s * cycleInDays and s is an
252 // integer number (to be chosen). Then, it evaluates 4 * N + 3 on uint32_t
253 // operands so that N must be positive and, to prevent overflow,
254 // 4 * N + 3 <= maxU32 <=> N <= (maxU32 - 3) / 4.
255 // Therefore, we must have 0 <= N_U + K <= (maxU32 - 3) / 4 or, in other
256 // words, N_U must be in [minDays, maxDays] = [-K, (maxU32 - 3) / 4 - K].
257 // Notice that this interval moves cycleInDays positions to the left when
258 // s is incremented. We chose s to get the interval's mid-point as close
259 // as possible to 0. For this, we wish to have:
260 // K ~= (maxU32 - 3) / 4 - K <=> 2 * K ~= (maxU32 - 3) / 4 <=>
261 // K ~= (maxU32 - 3) / 8 <=>
262 // rataDie1970Jan1 + s * cycleInDays ~= (maxU32 - 3) / 8 <=>
263 // s ~= ((maxU32 - 3) / 8 - rataDie1970Jan1) / cycleInDays ~= 3669.8.
264 // Therefore, we chose s = 3670. The shift and correction constants
265 // (see [1]) are then:
266 constexpr uint32_t s = 3670;
267 constexpr uint32_t K = rataDie1970Jan1 + s * cycleInDays;
268 constexpr uint32_t L = s * cycleInYears;
270 // [minDays, maxDays] correspond to a date range from -1'468'000/Mar/01 to
271 // 1'471'805/Jun/05.
272 constexpr int32_t minDays = -int32_t(K);
273 constexpr int32_t maxDays = (maxU32 - 3) / 4 - K;
274 static_assert(minDays == -536'895'458, "Wrong calculation of minDays or K.");
275 static_assert(maxDays == 536'846'365, "Wrong calculation of maxDays or K.");
277 // These are hard limits for the algorithm and far greater than the
278 // range [-8.64e15, 8.64e15] required by ES2024 21.4.1.1. Callers must
279 // ensure this function is not called out of the hard limits and,
280 // preferably, not outside the ES2024 limits.
281 constexpr int64_t minTime = minDays * int64_t(msPerDay);
282 [[maybe_unused]] constexpr int64_t maxTime = maxDays * int64_t(msPerDay);
283 MOZ_ASSERT(double(minTime) <= t && t <= double(maxTime));
284 const int64_t time = int64_t(t);
286 // Since time is the number of milliseconds since the epoch, 1970/Jan/01,
287 // one might expect N_U = time / uint64_t(msPerDay) is the number of days
288 // since epoch. There's a catch tough. Consider, for instance, half day
289 // before the epoch, that is, t = -0.5 * msPerDay. This falls on
290 // 1969/Dec/31 and should correspond to N_U = -1 but the above gives
291 // N_U = 0. Indeed, t / msPerDay = -0.5 but integer division truncates
292 // towards 0 (C++ [expr.mul]/4) and not towards -infinity as needed, so
293 // that time / uint64_t(msPerDay) = 0. To workaround this issue we perform
294 // the division on positive operands so that truncations towards 0 and
295 // -infinity are equivalent. For this, set u = time - minTime, which is
296 // positive as asserted above. Then, perform the division u / msPerDay and
297 // to the result add minTime / msPerDay = minDays to cancel the
298 // subtraction of minTime.
299 const uint64_t u = uint64_t(time - minTime);
300 const int32_t N_U = int32_t(u / uint64_t(msPerDay)) + minDays;
301 MOZ_ASSERT(minDays <= N_U && N_U <= maxDays);
303 const uint32_t N = uint32_t(N_U) + K;
305 // Some magic numbers have been explained above but, unfortunately,
306 // others with no precise interpretation do appear. They mostly come
307 // from numerical approximations of Euclidean affine functions (see [1])
308 // which are faster for the CPU to calculate. Unfortunately, no compiler
309 // can do these optimizations.
311 // Century C and year of the century N_C:
312 const uint32_t N_1 = 4 * N + 3;
313 const uint32_t C = N_1 / 146097;
314 const uint32_t N_C = N_1 % 146097 / 4;
316 // Year of the century Z and day of the year N_Y:
317 const uint32_t N_2 = 4 * N_C + 3;
318 const uint64_t P_2 = uint64_t(2939745) * N_2;
319 const uint32_t Z = uint32_t(P_2 / 4294967296);
320 const uint32_t N_Y = uint32_t(P_2 % 4294967296) / 2939745 / 4;
322 // Year Y:
323 const uint32_t Y = 100 * C + Z;
325 // Month M and day D.
326 // The expression for N_3 has been adapted to account for the difference
327 // between month numbers in ES5 15.9.1.4 (from 0 to 11) and [1] (from 1
328 // to 12). This is done by subtracting 65536 from the original
329 // expression so that M decreases by 1 and so does M_G further down.
330 const uint32_t N_3 = 2141 * N_Y + 132377; // 132377 = 197913 - 65536
331 const uint32_t M = N_3 / 65536;
332 const uint32_t D = N_3 % 65536 / 2141;
334 // Map from Computational to Gregorian calendar. Notice also the year
335 // correction and the type change and that Jan/01 is day 306 of the
336 // Computational calendar, cf. Table 1. [1]
337 constexpr uint32_t daysFromMar01ToJan01 = 306;
338 const uint32_t J = N_Y >= daysFromMar01ToJan01;
339 const int32_t Y_G = int32_t((Y - L) + J);
340 const uint32_t M_G = J ? M - 12 : M;
341 const uint32_t D_G = D + 1;
343 return {Y_G, M_G, D_G};
346 static double YearFromTime(double t) {
347 if (!std::isfinite(t)) {
348 return GenericNaN();
350 auto const year = ToYearMonthDay(t).year;
351 return double(year);
354 /* ES5 15.9.1.4. */
355 static double DayWithinYear(double t, double year) {
356 MOZ_ASSERT_IF(std::isfinite(t), YearFromTime(t) == year);
357 return Day(t) - DayFromYear(year);
360 static double MonthFromTime(double t) {
361 if (!std::isfinite(t)) {
362 return GenericNaN();
364 const auto month = ToYearMonthDay(t).month;
365 return double(month);
368 /* ES5 15.9.1.5. */
369 static double DateFromTime(double t) {
370 if (!std::isfinite(t)) {
371 return GenericNaN();
373 const auto day = ToYearMonthDay(t).day;
374 return double(day);
377 /* ES5 15.9.1.6. */
378 static int WeekDay(double t) {
380 * We can't assert TimeClip(t) == t because we call this function with
381 * local times, which can be offset outside TimeClip's permitted range.
383 MOZ_ASSERT(ToInteger(t) == t);
384 int result = (int(Day(t)) + 4) % 7;
385 if (result < 0) {
386 result += 7;
388 return result;
391 static inline int DayFromMonth(int month, bool isLeapYear) {
393 * The following array contains the day of year for the first day of
394 * each month, where index 0 is January, and day 0 is January 1.
396 static const int firstDayOfMonth[2][13] = {
397 {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
398 {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}};
400 MOZ_ASSERT(0 <= month && month <= 12);
401 return firstDayOfMonth[isLeapYear][month];
404 template <typename T>
405 static inline int DayFromMonth(T month, bool isLeapYear) = delete;
407 /* ES5 15.9.1.12 (out of order to accommodate DaylightSavingTA). */
408 static double MakeDay(double year, double month, double date) {
409 /* Step 1. */
410 if (!std::isfinite(year) || !std::isfinite(month) || !std::isfinite(date)) {
411 return GenericNaN();
414 /* Steps 2-4. */
415 double y = ToInteger(year);
416 double m = ToInteger(month);
417 double dt = ToInteger(date);
419 /* Step 5. */
420 double ym = y + floor(m / 12);
422 /* Step 6. */
423 int mn = int(PositiveModulo(m, 12));
425 /* Steps 7-8. */
426 bool leap = IsLeapYear(ym);
428 double yearday = floor(TimeFromYear(ym) / msPerDay);
429 double monthday = DayFromMonth(mn, leap);
431 return yearday + monthday + dt - 1;
434 /* ES5 15.9.1.13 (out of order to accommodate DaylightSavingTA). */
435 static inline double MakeDate(double day, double time) {
436 /* Step 1. */
437 if (!std::isfinite(day) || !std::isfinite(time)) {
438 return GenericNaN();
441 /* Step 2. */
442 return day * msPerDay + time;
445 JS_PUBLIC_API double JS::MakeDate(double year, unsigned month, unsigned day) {
446 MOZ_ASSERT(month <= 11);
447 MOZ_ASSERT(day >= 1 && day <= 31);
449 return ::MakeDate(MakeDay(year, month, day), 0);
452 JS_PUBLIC_API double JS::MakeDate(double year, unsigned month, unsigned day,
453 double time) {
454 MOZ_ASSERT(month <= 11);
455 MOZ_ASSERT(day >= 1 && day <= 31);
457 return ::MakeDate(MakeDay(year, month, day), time);
460 JS_PUBLIC_API double JS::YearFromTime(double time) {
461 const auto clipped = TimeClip(time);
462 if (!clipped.isValid()) {
463 return GenericNaN();
465 return ::YearFromTime(clipped.toDouble());
468 JS_PUBLIC_API double JS::MonthFromTime(double time) {
469 const auto clipped = TimeClip(time);
470 if (!clipped.isValid()) {
471 return GenericNaN();
473 return ::MonthFromTime(clipped.toDouble());
476 JS_PUBLIC_API double JS::DayFromTime(double time) {
477 const auto clipped = TimeClip(time);
478 if (!clipped.isValid()) {
479 return GenericNaN();
481 return DateFromTime(clipped.toDouble());
484 JS_PUBLIC_API double JS::DayFromYear(double year) {
485 return ::DayFromYear(year);
488 JS_PUBLIC_API double JS::DayWithinYear(double time, double year) {
489 const auto clipped = TimeClip(time);
490 if (!clipped.isValid()) {
491 return GenericNaN();
493 return ::DayWithinYear(clipped.toDouble(), year);
496 JS_PUBLIC_API void JS::SetReduceMicrosecondTimePrecisionCallback(
497 JS::ReduceMicrosecondTimePrecisionCallback callback) {
498 sReduceMicrosecondTimePrecisionCallback = callback;
501 JS_PUBLIC_API void JS::SetTimeResolutionUsec(uint32_t resolution, bool jitter) {
502 sResolutionUsec = resolution;
503 sJitter = jitter;
506 #if JS_HAS_INTL_API
507 // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
508 // 20.3.1.7 LocalTZA ( t, isUTC )
509 double DateTimeHelper::localTZA(DateTimeInfo::ForceUTC forceUTC, double t,
510 DateTimeInfo::TimeZoneOffset offset) {
511 MOZ_ASSERT(std::isfinite(t));
513 int64_t milliseconds = static_cast<int64_t>(t);
514 int32_t offsetMilliseconds =
515 DateTimeInfo::getOffsetMilliseconds(forceUTC, milliseconds, offset);
516 return static_cast<double>(offsetMilliseconds);
519 // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
520 // 20.3.1.8 LocalTime ( t )
521 double DateTimeHelper::localTime(DateTimeInfo::ForceUTC forceUTC, double t) {
522 if (!std::isfinite(t)) {
523 return GenericNaN();
526 MOZ_ASSERT(StartOfTime <= t && t <= EndOfTime);
527 return t + localTZA(forceUTC, t, DateTimeInfo::TimeZoneOffset::UTC);
530 // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
531 // 20.3.1.9 UTC ( t )
532 double DateTimeHelper::UTC(DateTimeInfo::ForceUTC forceUTC, double t) {
533 if (!std::isfinite(t)) {
534 return GenericNaN();
537 if (t < (StartOfTime - msPerDay) || t > (EndOfTime + msPerDay)) {
538 return GenericNaN();
541 return t - localTZA(forceUTC, t, DateTimeInfo::TimeZoneOffset::Local);
543 #else
545 * Find a year for which any given date will fall on the same weekday.
547 * This function should be used with caution when used other than
548 * for determining DST; it hasn't been proven not to produce an
549 * incorrect year for times near year boundaries.
551 int DateTimeHelper::equivalentYearForDST(int year) {
553 * Years and leap years on which Jan 1 is a Sunday, Monday, etc.
555 * yearStartingWith[0][i] is an example non-leap year where
556 * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
558 * yearStartingWith[1][i] is an example leap year where
559 * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
561 * Keep two different mappings, one for past years (< 1970), and a
562 * different one for future years (> 2037).
564 static const int pastYearStartingWith[2][7] = {
565 {1978, 1973, 1974, 1975, 1981, 1971, 1977},
566 {1984, 1996, 1980, 1992, 1976, 1988, 1972}};
567 static const int futureYearStartingWith[2][7] = {
568 {2034, 2035, 2030, 2031, 2037, 2027, 2033},
569 {2012, 2024, 2036, 2020, 2032, 2016, 2028}};
571 int day = int(DayFromYear(year) + 4) % 7;
572 if (day < 0) {
573 day += 7;
576 const auto& yearStartingWith =
577 year < 1970 ? pastYearStartingWith : futureYearStartingWith;
578 return yearStartingWith[IsLeapYear(year)][day];
581 // Return true if |t| is representable as a 32-bit time_t variable, that means
582 // the year is in [1970, 2038).
583 bool DateTimeHelper::isRepresentableAsTime32(double t) {
584 return 0.0 <= t && t < 2145916800000.0;
587 /* ES5 15.9.1.8. */
588 double DateTimeHelper::daylightSavingTA(DateTimeInfo::ForceUTC forceUTC,
589 double t) {
590 if (!std::isfinite(t)) {
591 return GenericNaN();
595 * If earlier than 1970 or after 2038, potentially beyond the ken of
596 * many OSes, map it to an equivalent year before asking.
598 if (!isRepresentableAsTime32(t)) {
599 int year = equivalentYearForDST(int(YearFromTime(t)));
600 double day = MakeDay(year, MonthFromTime(t), DateFromTime(t));
601 t = MakeDate(day, TimeWithinDay(t));
604 int64_t utcMilliseconds = static_cast<int64_t>(t);
605 int32_t offsetMilliseconds =
606 DateTimeInfo::getDSTOffsetMilliseconds(forceUTC, utcMilliseconds);
607 return static_cast<double>(offsetMilliseconds);
610 double DateTimeHelper::adjustTime(DateTimeInfo::ForceUTC forceUTC,
611 double date) {
612 double localTZA = DateTimeInfo::localTZA(forceUTC);
613 double t = daylightSavingTA(forceUTC, date) + localTZA;
614 t = (localTZA >= 0) ? fmod(t, msPerDay) : -fmod(msPerDay - t, msPerDay);
615 return t;
618 /* ES5 15.9.1.9. */
619 double DateTimeHelper::localTime(DateTimeInfo::ForceUTC forceUTC, double t) {
620 return t + adjustTime(forceUTC, t);
623 double DateTimeHelper::UTC(DateTimeInfo::ForceUTC forceUTC, double t) {
624 // Following the ES2017 specification creates undesirable results at DST
625 // transitions. For example when transitioning from PST to PDT,
626 // |new Date(2016,2,13,2,0,0).toTimeString()| returns the string value
627 // "01:00:00 GMT-0800 (PST)" instead of "03:00:00 GMT-0700 (PDT)". Follow
628 // V8 and subtract one hour before computing the offset.
629 // Spec bug: https://bugs.ecmascript.org/show_bug.cgi?id=4007
631 return t -
632 adjustTime(forceUTC, t - DateTimeInfo::localTZA(forceUTC) - msPerHour);
634 #endif /* JS_HAS_INTL_API */
636 static double LocalTime(DateTimeInfo::ForceUTC forceUTC, double t) {
637 return DateTimeHelper::localTime(forceUTC, t);
640 static double UTC(DateTimeInfo::ForceUTC forceUTC, double t) {
641 return DateTimeHelper::UTC(forceUTC, t);
644 /* ES5 15.9.1.10. */
645 static double HourFromTime(double t) {
646 return PositiveModulo(floor(t / msPerHour), HoursPerDay);
649 static double MinFromTime(double t) {
650 return PositiveModulo(floor(t / msPerMinute), MinutesPerHour);
653 static double SecFromTime(double t) {
654 return PositiveModulo(floor(t / msPerSecond), SecondsPerMinute);
657 static double msFromTime(double t) { return PositiveModulo(t, msPerSecond); }
659 /* ES5 15.9.1.11. */
660 static double MakeTime(double hour, double min, double sec, double ms) {
661 /* Step 1. */
662 if (!std::isfinite(hour) || !std::isfinite(min) || !std::isfinite(sec) ||
663 !std::isfinite(ms)) {
664 return GenericNaN();
667 /* Step 2. */
668 double h = ToInteger(hour);
670 /* Step 3. */
671 double m = ToInteger(min);
673 /* Step 4. */
674 double s = ToInteger(sec);
676 /* Step 5. */
677 double milli = ToInteger(ms);
679 /* Steps 6-7. */
680 return h * msPerHour + m * msPerMinute + s * msPerSecond + milli;
684 * end of ECMA 'support' functions
687 // ES2017 draft rev (TODO: Add git hash when PR 642 is merged.)
688 // 20.3.3.4
689 // Date.UTC(year [, month [, date [, hours [, minutes [, seconds [, ms]]]]]])
690 static bool date_UTC(JSContext* cx, unsigned argc, Value* vp) {
691 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date", "UTC");
692 CallArgs args = CallArgsFromVp(argc, vp);
694 // Step 1.
695 double y;
696 if (!ToNumber(cx, args.get(0), &y)) {
697 return false;
700 // Step 2.
701 double m;
702 if (args.length() >= 2) {
703 if (!ToNumber(cx, args[1], &m)) {
704 return false;
706 } else {
707 m = 0;
710 // Step 3.
711 double dt;
712 if (args.length() >= 3) {
713 if (!ToNumber(cx, args[2], &dt)) {
714 return false;
716 } else {
717 dt = 1;
720 // Step 4.
721 double h;
722 if (args.length() >= 4) {
723 if (!ToNumber(cx, args[3], &h)) {
724 return false;
726 } else {
727 h = 0;
730 // Step 5.
731 double min;
732 if (args.length() >= 5) {
733 if (!ToNumber(cx, args[4], &min)) {
734 return false;
736 } else {
737 min = 0;
740 // Step 6.
741 double s;
742 if (args.length() >= 6) {
743 if (!ToNumber(cx, args[5], &s)) {
744 return false;
746 } else {
747 s = 0;
750 // Step 7.
751 double milli;
752 if (args.length() >= 7) {
753 if (!ToNumber(cx, args[6], &milli)) {
754 return false;
756 } else {
757 milli = 0;
760 // Step 8.
761 double yr = y;
762 if (!std::isnan(y)) {
763 double yint = ToInteger(y);
764 if (0 <= yint && yint <= 99) {
765 yr = 1900 + yint;
769 // Step 9.
770 ClippedTime time =
771 TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)));
772 args.rval().set(TimeValue(time));
773 return true;
777 * Read and convert decimal digits from s[*i] into *result
778 * while *i < limit.
780 * Succeed if any digits are converted. Advance *i only
781 * as digits are consumed.
783 template <typename CharT>
784 static bool ParseDigits(size_t* result, const CharT* s, size_t* i,
785 size_t limit) {
786 size_t init = *i;
787 *result = 0;
788 while (*i < limit && ('0' <= s[*i] && s[*i] <= '9')) {
789 *result *= 10;
790 *result += (s[*i] - '0');
791 ++(*i);
793 return *i != init;
797 * Read and convert decimal digits to the right of a decimal point,
798 * representing a fractional integer, from s[*i] into *result
799 * while *i < limit.
801 * Succeed if any digits are converted. Advance *i only
802 * as digits are consumed.
804 template <typename CharT>
805 static bool ParseFractional(double* result, const CharT* s, size_t* i,
806 size_t limit) {
807 double factor = 0.1;
808 size_t init = *i;
809 *result = 0.0;
810 while (*i < limit && ('0' <= s[*i] && s[*i] <= '9')) {
811 *result += (s[*i] - '0') * factor;
812 factor *= 0.1;
813 ++(*i);
815 return *i != init;
819 * Read and convert exactly n decimal digits from s[*i]
820 * to s[min(*i+n,limit)] into *result.
822 * Succeed if exactly n digits are converted. Advance *i only
823 * on success.
825 template <typename CharT>
826 static bool ParseDigitsN(size_t n, size_t* result, const CharT* s, size_t* i,
827 size_t limit) {
828 size_t init = *i;
830 if (ParseDigits(result, s, i, std::min(limit, init + n))) {
831 return (*i - init) == n;
834 *i = init;
835 return false;
839 * Read and convert n or less decimal digits from s[*i]
840 * to s[min(*i+n,limit)] into *result.
842 * Succeed only if greater than zero but less than or equal to n digits are
843 * converted. Advance *i only on success.
845 template <typename CharT>
846 static bool ParseDigitsNOrLess(size_t n, size_t* result, const CharT* s,
847 size_t* i, size_t limit) {
848 size_t init = *i;
850 if (ParseDigits(result, s, i, std::min(limit, init + n))) {
851 return ((*i - init) > 0) && ((*i - init) <= n);
854 *i = init;
855 return false;
858 static int DaysInMonth(int year, int month) {
859 bool leap = IsLeapYear(year);
860 int result = int(DayFromMonth(month, leap) - DayFromMonth(month - 1, leap));
861 return result;
865 * Parse a string according to the formats specified in section 20.3.1.16
866 * of the ECMAScript standard. These formats are based upon a simplification
867 * of the ISO 8601 Extended Format. As per the spec omitted month and day
868 * values are defaulted to '01', omitted HH:mm:ss values are defaulted to '00'
869 * and an omitted sss field is defaulted to '000'.
871 * For cross compatibility we allow the following extensions.
873 * These are:
875 * Standalone time part:
876 * Any of the time formats below can be parsed without a date part.
877 * E.g. "T19:00:00Z" will parse successfully. The date part will then
878 * default to 1970-01-01.
880 * 'T' from the time part may be replaced with a space character:
881 * "1970-01-01 12:00:00Z" will parse successfully. Note that only a single
882 * space is permitted and this is not permitted in the standalone
883 * version above.
885 * One or more decimal digits for milliseconds:
886 * The specification requires exactly three decimal digits for
887 * the fractional part but we allow for one or more digits.
889 * Time zone specifier without ':':
890 * We allow the time zone to be specified without a ':' character.
891 * E.g. "T19:00:00+0700" is equivalent to "T19:00:00+07:00".
893 * One or two digits for months, days, hours, minutes and seconds:
894 * The specification requires exactly two decimal digits for the fields
895 * above. We allow for one or two decimal digits. I.e. "1970-1-1" is
896 * equivalent to "1970-01-01".
898 * Date part:
900 * Year:
901 * YYYY (eg 1997)
903 * Year and month:
904 * YYYY-MM (eg 1997-07)
906 * Complete date:
907 * YYYY-MM-DD (eg 1997-07-16)
909 * Time part:
911 * Hours and minutes:
912 * Thh:mmTZD (eg T19:20+01:00)
914 * Hours, minutes and seconds:
915 * Thh:mm:ssTZD (eg T19:20:30+01:00)
917 * Hours, minutes, seconds and a decimal fraction of a second:
918 * Thh:mm:ss.sTZD (eg T19:20:30.45+01:00)
920 * where:
922 * YYYY = four-digit year or six digit year as +YYYYYY or -YYYYYY
923 * MM = one or two-digit month (01=January, etc.)
924 * DD = one or two-digit day of month (01 through 31)
925 * hh = one or two digits of hour (00 through 23) (am/pm NOT allowed)
926 * mm = one or two digits of minute (00 through 59)
927 * ss = one or two digits of second (00 through 59)
928 * sss = one or more digits representing a decimal fraction of a second
929 * TZD = time zone designator (Z or +hh:mm or -hh:mm or missing for local)
931 template <typename CharT>
932 static bool ParseISOStyleDate(DateTimeInfo::ForceUTC forceUTC, const CharT* s,
933 size_t length, ClippedTime* result) {
934 size_t i = 0;
935 size_t pre = 0;
936 int tzMul = 1;
937 int dateMul = 1;
938 size_t year = 1970;
939 size_t month = 1;
940 size_t day = 1;
941 size_t hour = 0;
942 size_t min = 0;
943 size_t sec = 0;
944 double frac = 0;
945 bool isLocalTime = false;
946 size_t tzHour = 0;
947 size_t tzMin = 0;
948 bool isPermissive = false;
949 bool isStrict = false;
951 #define PEEK(ch) (i < length && s[i] == ch)
953 #define NEED(ch) \
954 if (i >= length || s[i] != ch) { \
955 return false; \
956 } else { \
957 ++i; \
960 #define DONE_DATE_UNLESS(ch) \
961 if (i >= length || s[i] != ch) { \
962 goto done_date; \
963 } else { \
964 ++i; \
967 #define DONE_UNLESS(ch) \
968 if (i >= length || s[i] != ch) { \
969 goto done; \
970 } else { \
971 ++i; \
974 #define NEED_NDIGITS(n, field) \
975 if (!ParseDigitsN(n, &field, s, &i, length)) { \
976 return false; \
979 #define NEED_NDIGITS_OR_LESS(n, field) \
980 pre = i; \
981 if (!ParseDigitsNOrLess(n, &field, s, &i, length)) { \
982 return false; \
984 if (i < pre + (n)) { \
985 if (isStrict) { \
986 return false; \
987 } else { \
988 isPermissive = true; \
992 if (PEEK('+') || PEEK('-')) {
993 if (PEEK('-')) {
994 dateMul = -1;
996 ++i;
997 NEED_NDIGITS(6, year);
999 // https://tc39.es/ecma262/#sec-expanded-years
1000 // -000000 is not a valid expanded year.
1001 if (year == 0 && dateMul == -1) {
1002 return false;
1004 } else {
1005 NEED_NDIGITS(4, year);
1007 DONE_DATE_UNLESS('-');
1008 NEED_NDIGITS_OR_LESS(2, month);
1009 DONE_DATE_UNLESS('-');
1010 NEED_NDIGITS_OR_LESS(2, day);
1012 done_date:
1013 if (PEEK('T')) {
1014 if (isPermissive) {
1015 // Require standard format "[+00]1970-01-01" if a time part marker "T"
1016 // exists
1017 return false;
1019 isStrict = true;
1020 i++;
1021 } else if (PEEK(' ')) {
1022 i++;
1023 } else {
1024 goto done;
1027 NEED_NDIGITS_OR_LESS(2, hour);
1028 NEED(':');
1029 NEED_NDIGITS_OR_LESS(2, min);
1031 if (PEEK(':')) {
1032 ++i;
1033 NEED_NDIGITS_OR_LESS(2, sec);
1034 if (PEEK('.')) {
1035 ++i;
1036 if (!ParseFractional(&frac, s, &i, length)) {
1037 return false;
1042 if (PEEK('Z')) {
1043 ++i;
1044 } else if (PEEK('+') || PEEK('-')) {
1045 if (PEEK('-')) {
1046 tzMul = -1;
1048 ++i;
1049 NEED_NDIGITS(2, tzHour);
1051 * Non-standard extension to the ISO date format:
1052 * allow two digits for the time zone offset.
1054 if (i >= length && !isStrict) {
1055 goto done;
1058 * Non-standard extension to the ISO date format (permitted by ES5):
1059 * allow "-0700" as a time zone offset, not just "-07:00".
1061 if (PEEK(':')) {
1062 ++i;
1064 NEED_NDIGITS(2, tzMin);
1065 } else {
1066 isLocalTime = true;
1069 done:
1070 if (year > 275943 // ceil(1e8/365) + 1970
1071 || (month == 0 || month > 12) ||
1072 (day == 0 || day > size_t(DaysInMonth(year, month))) || hour > 24 ||
1073 ((hour == 24) && (min > 0 || sec > 0 || frac > 0)) || min > 59 ||
1074 sec > 59 || tzHour > 23 || tzMin > 59) {
1075 return false;
1078 if (i != length) {
1079 return false;
1082 month -= 1; /* convert month to 0-based */
1084 double msec = MakeDate(MakeDay(dateMul * double(year), month, day),
1085 MakeTime(hour, min, sec, frac * 1000.0));
1087 if (isLocalTime) {
1088 msec = UTC(forceUTC, msec);
1089 } else {
1090 msec -= tzMul * (tzHour * msPerHour + tzMin * msPerMinute);
1093 *result = TimeClip(msec);
1094 return NumbersAreIdentical(msec, result->toDouble());
1096 #undef PEEK
1097 #undef NEED
1098 #undef DONE_UNLESS
1099 #undef NEED_NDIGITS
1100 #undef NEED_NDIGITS_OR_LESS
1103 int FixupNonFullYear(int year) {
1104 if (year < 50) {
1105 year += 2000;
1106 } else if (year >= 50 && year < 100) {
1107 year += 1900;
1109 return year;
1112 template <typename CharT>
1113 bool IsPrefixOfKeyword(const CharT* s, size_t len, const char* keyword) {
1114 while (len > 0 && *keyword) {
1115 MOZ_ASSERT(IsAsciiAlpha(*s));
1116 MOZ_ASSERT(IsAsciiLowercaseAlpha(*keyword));
1118 if (unicode::ToLowerCase(static_cast<Latin1Char>(*s)) != *keyword) {
1119 break;
1122 s++, keyword++;
1123 len--;
1126 return len == 0;
1129 static constexpr const char* const months_names[] = {
1130 "january", "february", "march", "april", "may", "june",
1131 "july", "august", "september", "october", "november", "december",
1134 // Try to parse the following date format:
1135 // dd-MMM-yyyy
1136 // dd-MMM-yy
1137 // yyyy-MMM-dd
1138 // yy-MMM-dd
1140 // Returns true and fills all out parameters when successfully parsed
1141 // dashed-date. Otherwise returns false and leaves out parameters untouched.
1142 template <typename CharT>
1143 static bool TryParseDashedDatePrefix(const CharT* s, size_t length,
1144 size_t* indexOut, int* yearOut,
1145 int* monOut, int* mdayOut) {
1146 size_t i = 0;
1148 size_t mday;
1149 if (!ParseDigitsNOrLess(4, &mday, s, &i, length)) {
1150 return false;
1152 size_t mdayDigits = i;
1154 if (i >= length || s[i] != '-') {
1155 return false;
1157 ++i;
1159 size_t start = i;
1160 for (; i < length; i++) {
1161 if (!IsAsciiAlpha(s[i])) {
1162 break;
1166 // The shortest month is "may".
1167 static constexpr size_t ShortestMonthNameLength = 3;
1168 if (i - start < ShortestMonthNameLength) {
1169 return false;
1172 size_t mon = 0;
1173 for (size_t m = 0; m < std::size(months_names); ++m) {
1174 // If the field isn't a prefix of the month (an exact match is *not*
1175 // required), try the next one.
1176 if (IsPrefixOfKeyword(s + start, i - start, months_names[m])) {
1177 // Use numeric value.
1178 mon = m + 1;
1179 break;
1182 if (mon == 0) {
1183 return false;
1186 if (i >= length || s[i] != '-') {
1187 return false;
1189 ++i;
1191 size_t pre = i;
1192 size_t year;
1193 if (!ParseDigitsNOrLess(4, &year, s, &i, length)) {
1194 return false;
1196 size_t yearDigits = i - pre;
1198 if (i < length && IsAsciiDigit(s[i])) {
1199 return false;
1202 // Swap the mday and year iff the year wasn't specified in full.
1203 if (mday > 31 && year <= 31 && yearDigits < 4) {
1204 std::swap(mday, year);
1205 std::swap(mdayDigits, yearDigits);
1208 if (mday > 31 || mdayDigits > 2) {
1209 return false;
1212 if (yearDigits < 4) {
1213 year = FixupNonFullYear(year);
1216 *indexOut = i;
1217 *yearOut = year;
1218 *monOut = mon;
1219 *mdayOut = mday;
1220 return true;
1223 struct CharsAndAction {
1224 const char* chars;
1225 int action;
1228 static constexpr CharsAndAction keywords[] = {
1229 // clang-format off
1230 // AM/PM
1231 { "am", -1 },
1232 { "pm", -2 },
1233 // Days of week.
1234 { "monday", 0 },
1235 { "tuesday", 0 },
1236 { "wednesday", 0 },
1237 { "thursday", 0 },
1238 { "friday", 0 },
1239 { "saturday", 0 },
1240 { "sunday", 0 },
1241 // Months.
1242 { "january", 1 },
1243 { "february", 2 },
1244 { "march", 3 },
1245 { "april", 4, },
1246 { "may", 5 },
1247 { "june", 6 },
1248 { "july", 7 },
1249 { "august", 8 },
1250 { "september", 9 },
1251 { "october", 10 },
1252 { "november", 11 },
1253 { "december", 12 },
1254 // Time zone abbreviations.
1255 { "gmt", 10000 + 0 },
1256 { "z", 10000 + 0 },
1257 { "ut", 10000 + 0 },
1258 { "utc", 10000 + 0 },
1259 { "est", 10000 + 5 * 60 },
1260 { "edt", 10000 + 4 * 60 },
1261 { "cst", 10000 + 6 * 60 },
1262 { "cdt", 10000 + 5 * 60 },
1263 { "mst", 10000 + 7 * 60 },
1264 { "mdt", 10000 + 6 * 60 },
1265 { "pst", 10000 + 8 * 60 },
1266 { "pdt", 10000 + 7 * 60 },
1267 // clang-format on
1270 template <size_t N>
1271 constexpr size_t MinKeywordLength(const CharsAndAction (&keywords)[N]) {
1272 size_t min = size_t(-1);
1273 for (const CharsAndAction& keyword : keywords) {
1274 min = std::min(min, std::char_traits<char>::length(keyword.chars));
1276 return min;
1279 template <typename CharT>
1280 static bool ParseDate(DateTimeInfo::ForceUTC forceUTC, const CharT* s,
1281 size_t length, ClippedTime* result) {
1282 if (ParseISOStyleDate(forceUTC, s, length, result)) {
1283 return true;
1286 if (length == 0) {
1287 return false;
1290 int year = -1;
1291 int mon = -1;
1292 int mday = -1;
1293 int hour = -1;
1294 int min = -1;
1295 int sec = -1;
1296 int tzOffset = -1;
1298 // One of '+', '-', ':', '/', or 0 (the default value).
1299 int prevc = 0;
1301 bool seenPlusMinus = false;
1302 bool seenMonthName = false;
1303 bool seenFullYear = false;
1304 bool negativeYear = false;
1306 size_t index = 0;
1308 // Try parsing the leading dashed-date.
1310 // If successfully parsed, index is updated to the end of the date part,
1311 // and year, mon, mday are set to the date.
1312 // Continue parsing optional time + tzOffset parts.
1314 // Otherwise, this is no-op.
1315 bool isDashedDate =
1316 TryParseDashedDatePrefix(s, length, &index, &year, &mon, &mday);
1318 while (index < length) {
1319 int c = s[index];
1320 index++;
1322 // Normalize U+202F (NARROW NO-BREAK SPACE). This character appears between
1323 // the AM/PM markers for |date.toLocaleString("en")|. We have to normalize
1324 // it for backward compatibility reasons.
1325 if (c == 0x202F) {
1326 c = ' ';
1329 // Spaces, ASCII control characters, periods, and commas are simply ignored.
1330 if (c <= ' ' || c == '.' || c == ',') {
1331 continue;
1334 // Parse delimiter characters. Save them to the side for future use.
1335 if (c == '/' || c == ':' || c == '+') {
1336 prevc = c;
1337 continue;
1340 // Dashes are delimiters if they're immediately followed by a number field.
1341 // If they're not followed by a number field, they're simply ignored.
1342 if (c == '-') {
1343 if (index < length && IsAsciiDigit(s[index])) {
1344 prevc = c;
1346 continue;
1349 // Skip over comments -- text inside matching parentheses. (Comments
1350 // themselves may contain comments as long as all the parentheses properly
1351 // match up. And apparently comments, including nested ones, may validly be
1352 // terminated by end of input...)
1353 if (c == '(') {
1354 int depth = 1;
1355 while (index < length) {
1356 c = s[index];
1357 index++;
1358 if (c == '(') {
1359 depth++;
1360 } else if (c == ')') {
1361 if (--depth <= 0) {
1362 break;
1366 continue;
1369 // Parse a number field.
1370 if (IsAsciiDigit(c)) {
1371 size_t partStart = index - 1;
1372 uint32_t u = c - '0';
1373 while (index < length) {
1374 c = s[index];
1375 if (!IsAsciiDigit(c)) {
1376 break;
1378 u = u * 10 + (c - '0');
1379 index++;
1381 size_t partLength = index - partStart;
1383 // See above for why we have to normalize U+202F.
1384 if (c == 0x202F) {
1385 c = ' ';
1388 int n = int(u);
1391 * Allow TZA before the year, so 'Wed Nov 05 21:49:11 GMT-0800 1997'
1392 * works.
1394 * Uses of seenPlusMinus allow ':' in TZA, so Java no-timezone style
1395 * of GMT+4:30 works.
1398 if (prevc == '-' && (tzOffset != 0 || seenPlusMinus) && partLength >= 4 &&
1399 year < 0) {
1400 // Parse as a negative, possibly zero-padded year if
1401 // 1. the preceding character is '-',
1402 // 2. the TZA is not 'GMT' (tested by |tzOffset != 0|),
1403 // 3. or a TZA was already parsed |seenPlusMinus == true|,
1404 // 4. the part length is at least 4 (to parse '-08' as a TZA),
1405 // 5. and we did not already parse a year |year < 0|.
1406 year = n;
1407 seenFullYear = true;
1408 negativeYear = true;
1409 } else if ((prevc == '+' || prevc == '-') /* && year>=0 */) {
1410 /* Make ':' case below change tzOffset. */
1411 seenPlusMinus = true;
1413 /* offset */
1414 if (n < 24 && partLength <= 2) {
1415 n = n * 60; /* EG. "GMT-3" */
1416 } else {
1417 n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */
1420 if (prevc == '+') /* plus means east of GMT */
1421 n = -n;
1423 // Reject if not preceded by 'GMT' or if a time zone offset
1424 // was already parsed.
1425 if (tzOffset != 0 && tzOffset != -1) {
1426 return false;
1429 tzOffset = n;
1430 } else if (prevc == '/' && mon >= 0 && mday >= 0 && year < 0) {
1431 if (c <= ' ' || c == ',' || c == '/' || index >= length) {
1432 year = n;
1433 } else {
1434 return false;
1436 } else if (c == ':') {
1437 if (hour < 0) {
1438 hour = /*byte*/ n;
1439 } else if (min < 0) {
1440 min = /*byte*/ n;
1441 } else {
1442 return false;
1444 } else if (c == '/') {
1446 * Until it is determined that mon is the actual month, keep
1447 * it as 1-based rather than 0-based.
1449 if (mon < 0) {
1450 mon = /*byte*/ n;
1451 } else if (mday < 0) {
1452 mday = /*byte*/ n;
1453 } else {
1454 return false;
1456 } else if (index < length && c != ',' && c > ' ' && c != '-' &&
1457 c != '(' &&
1458 // Allow zulu time e.g. "09/26/1995 16:00Z"
1459 !(hour != -1 && strchr("Zz", c)) &&
1460 // Allow '.' after day of month i.e. DD.Mon.YYYY/Mon.DD.YYYY,
1461 // or after year/month in YYYY/MM/DD
1462 (c != '.' || mday != -1) &&
1463 // Allow month or AM/PM directly after a number
1464 (!IsAsciiAlpha(c) ||
1465 (mon != -1 && !(strchr("AaPp", c) && index < length - 1 &&
1466 strchr("Mm", s[index + 1]))))) {
1467 return false;
1468 } else if (seenPlusMinus && n < 60) { /* handle GMT-3:30 */
1469 if (tzOffset < 0) {
1470 tzOffset -= n;
1471 } else {
1472 tzOffset += n;
1474 } else if (hour >= 0 && min < 0) {
1475 min = /*byte*/ n;
1476 } else if (prevc == ':' && min >= 0 && sec < 0) {
1477 sec = /*byte*/ n;
1478 } else if (mon < 0) {
1479 mon = /*byte*/ n;
1480 } else if (mon >= 0 && mday < 0) {
1481 mday = /*byte*/ n;
1482 } else if (mon >= 0 && mday >= 0 && year < 0) {
1483 year = n;
1484 seenFullYear = partLength >= 4;
1485 } else {
1486 return false;
1489 prevc = 0;
1490 continue;
1493 // Parse fields that are words: ASCII letters spelling out in English AM/PM,
1494 // day of week, month, or an extremely limited set of legacy time zone
1495 // abbreviations.
1496 if (IsAsciiAlpha(c)) {
1497 size_t start = index - 1;
1498 while (index < length) {
1499 c = s[index];
1500 if (!IsAsciiAlpha(c)) {
1501 break;
1503 index++;
1506 // There must be at least as many letters as in the shortest keyword.
1507 constexpr size_t MinLength = MinKeywordLength(keywords);
1508 if (index - start < MinLength) {
1509 return false;
1512 size_t k = std::size(keywords);
1513 while (k-- > 0) {
1514 const CharsAndAction& keyword = keywords[k];
1516 // If the field isn't a prefix of the keyword (an exact match is *not*
1517 // required), try the next one.
1518 if (!IsPrefixOfKeyword(s + start, index - start, keyword.chars)) {
1519 continue;
1522 int action = keyword.action;
1524 // Completely ignore days of the week, and don't derive any semantics
1525 // from them.
1526 if (action == 0) {
1527 break;
1530 // Perform action tests from smallest action values to largest.
1532 // Adjust a previously-specified hour for AM/PM accordingly (taking care
1533 // to treat 12:xx AM as 00:xx, 12:xx PM as 12:xx).
1534 if (action < 0) {
1535 MOZ_ASSERT(action == -1 || action == -2);
1536 if (hour > 12 || hour < 0) {
1537 return false;
1540 if (action == -1 && hour == 12) {
1541 hour = 0;
1542 } else if (action == -2 && hour != 12) {
1543 hour += 12;
1546 break;
1549 // Record a month if none has been seen before. (Note that some numbers
1550 // are initially treated as months; if a numeric field has already been
1551 // interpreted as a month, store that value to the actually appropriate
1552 // date component and set the month here.
1553 if (action <= 12) {
1554 if (seenMonthName) {
1555 return false;
1558 seenMonthName = true;
1560 if (mon < 0) {
1561 mon = action;
1562 } else if (mday < 0) {
1563 mday = mon;
1564 mon = action;
1565 } else if (year < 0) {
1566 if (mday > 0) {
1567 // If the date is of the form f l month, then when month is
1568 // reached we have f in mon and l in mday. In order to be
1569 // consistent with the f month l and month f l forms, we need to
1570 // swap so that f is in mday and l is in year.
1571 year = mday;
1572 mday = mon;
1573 } else {
1574 year = mon;
1576 mon = action;
1577 } else {
1578 return false;
1581 break;
1584 // Finally, record a time zone offset.
1585 MOZ_ASSERT(action >= 10000);
1586 tzOffset = action - 10000;
1587 break;
1590 if (k == size_t(-1)) {
1591 return false;
1594 prevc = 0;
1595 continue;
1598 // Any other character fails to parse.
1599 return false;
1602 if (year < 0 || mon < 0 || mday < 0) {
1603 return false;
1606 if (!isDashedDate) {
1607 // NOTE: TryParseDashedDatePrefix already handles the following fixup.
1610 * Case 1. The input string contains an English month name.
1611 * The form of the string can be month f l, or f month l, or
1612 * f l month which each evaluate to the same date.
1613 * If f and l are both greater than or equal to 100 the date
1614 * is invalid.
1616 * The year is taken to be either l, f if f > 31, or whichever
1617 * is set to zero.
1619 * Case 2. The input string is of the form "f/m/l" where f, m and l are
1620 * integers, e.g. 7/16/45. mon, mday and year values are adjusted
1621 * to achieve Chrome compatibility.
1623 * a. If 0 < f <= 12 and 0 < l <= 31, f/m/l is interpreted as
1624 * month/day/year.
1625 * b. If 31 < f and 0 < m <= 12 and 0 < l <= 31 f/m/l is
1626 * interpreted as year/month/day
1628 if (seenMonthName) {
1629 if (mday >= 100 && mon >= 100) {
1630 return false;
1633 if (year > 0 && (mday == 0 || mday > 31) && !seenFullYear) {
1634 int temp = year;
1635 year = mday;
1636 mday = temp;
1639 if (mday <= 0 || mday > 31) {
1640 return false;
1643 } else if (0 < mon && mon <= 12 && 0 < mday && mday <= 31) {
1644 /* (a) month/day/year */
1645 } else {
1646 /* (b) year/month/day */
1647 if (mon > 31 && mday <= 12 && year <= 31 && !seenFullYear) {
1648 int temp = year;
1649 year = mon;
1650 mon = mday;
1651 mday = temp;
1652 } else {
1653 return false;
1657 // If the year is greater than or equal to 50 and less than 100, it is
1658 // considered to be the number of years after 1900. If the year is less
1659 // than 50 it is considered to be the number of years after 2000,
1660 // otherwise it is considered to be the number of years after 0.
1661 if (!seenFullYear) {
1662 year = FixupNonFullYear(year);
1665 if (negativeYear) {
1666 year = -year;
1670 mon -= 1; /* convert month to 0-based */
1671 if (sec < 0) {
1672 sec = 0;
1674 if (min < 0) {
1675 min = 0;
1677 if (hour < 0) {
1678 hour = 0;
1681 double msec = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0));
1683 if (tzOffset == -1) { /* no time zone specified, have to use local */
1684 msec = UTC(forceUTC, msec);
1685 } else {
1686 msec += tzOffset * msPerMinute;
1689 *result = TimeClip(msec);
1690 return true;
1693 static bool ParseDate(DateTimeInfo::ForceUTC forceUTC, JSLinearString* s,
1694 ClippedTime* result) {
1695 AutoCheckCannotGC nogc;
1696 return s->hasLatin1Chars()
1697 ? ParseDate(forceUTC, s->latin1Chars(nogc), s->length(), result)
1698 : ParseDate(forceUTC, s->twoByteChars(nogc), s->length(), result);
1701 static bool date_parse(JSContext* cx, unsigned argc, Value* vp) {
1702 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date", "parse");
1703 CallArgs args = CallArgsFromVp(argc, vp);
1704 if (args.length() == 0) {
1705 args.rval().setNaN();
1706 return true;
1709 JSString* str = ToString<CanGC>(cx, args[0]);
1710 if (!str) {
1711 return false;
1714 JSLinearString* linearStr = str->ensureLinear(cx);
1715 if (!linearStr) {
1716 return false;
1719 ClippedTime result;
1720 if (!ParseDate(ForceUTC(cx->realm()), linearStr, &result)) {
1721 args.rval().setNaN();
1722 return true;
1725 args.rval().set(TimeValue(result));
1726 return true;
1729 static ClippedTime NowAsMillis(JSContext* cx) {
1730 if (js::SupportDifferentialTesting()) {
1731 return TimeClip(0);
1734 double now = PRMJ_Now();
1735 bool clampAndJitter = cx->realm()->behaviors().clampAndJitterTime();
1736 if (clampAndJitter && sReduceMicrosecondTimePrecisionCallback) {
1737 now = sReduceMicrosecondTimePrecisionCallback(now, cx);
1738 } else if (clampAndJitter && sResolutionUsec) {
1739 double clamped = floor(now / sResolutionUsec) * sResolutionUsec;
1741 if (sJitter) {
1742 // Calculate a random midpoint for jittering. In the browser, we are
1743 // adversarial: Web Content may try to calculate the midpoint themselves
1744 // and use that to bypass it's security. In the JS Shell, we are not
1745 // adversarial, we want to jitter the time to recreate the operating
1746 // environment, but we do not concern ourselves with trying to prevent an
1747 // attacker from calculating the midpoint themselves. So we use a very
1748 // simple, very fast CRC with a hardcoded seed.
1750 uint64_t midpoint = BitwiseCast<uint64_t>(clamped);
1751 midpoint ^= 0x0F00DD1E2BAD2DED; // XOR in a 'secret'
1752 // MurmurHash3 internal component from
1753 // https://searchfox.org/mozilla-central/rev/61d400da1c692453c2dc2c1cf37b616ce13dea5b/dom/canvas/MurmurHash3.cpp#85
1754 midpoint ^= midpoint >> 33;
1755 midpoint *= uint64_t{0xFF51AFD7ED558CCD};
1756 midpoint ^= midpoint >> 33;
1757 midpoint *= uint64_t{0xC4CEB9FE1A85EC53};
1758 midpoint ^= midpoint >> 33;
1759 midpoint %= sResolutionUsec;
1761 if (now > clamped + midpoint) { // We're jittering up to the next step
1762 now = clamped + sResolutionUsec;
1763 } else { // We're staying at the clamped value
1764 now = clamped;
1766 } else { // No jitter, only clamping
1767 now = clamped;
1771 return TimeClip(now / PRMJ_USEC_PER_MSEC);
1774 JS::ClippedTime js::DateNow(JSContext* cx) { return NowAsMillis(cx); }
1776 bool js::date_now(JSContext* cx, unsigned argc, Value* vp) {
1777 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date", "now");
1778 CallArgs args = CallArgsFromVp(argc, vp);
1779 args.rval().set(TimeValue(NowAsMillis(cx)));
1780 return true;
1783 DateTimeInfo::ForceUTC DateObject::forceUTC() const {
1784 return ForceUTC(realm());
1787 void DateObject::setUTCTime(ClippedTime t) {
1788 for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++) {
1789 setReservedSlot(ind, UndefinedValue());
1792 setFixedSlot(UTC_TIME_SLOT, TimeValue(t));
1795 void DateObject::setUTCTime(ClippedTime t, MutableHandleValue vp) {
1796 setUTCTime(t);
1797 vp.set(TimeValue(t));
1800 void DateObject::fillLocalTimeSlots() {
1801 const int32_t utcTZOffset =
1802 DateTimeInfo::utcToLocalStandardOffsetSeconds(forceUTC());
1804 /* Check if the cache is already populated. */
1805 if (!getReservedSlot(LOCAL_TIME_SLOT).isUndefined() &&
1806 getReservedSlot(UTC_TIME_ZONE_OFFSET_SLOT).toInt32() == utcTZOffset) {
1807 return;
1810 /* Remember time zone used to generate the local cache. */
1811 setReservedSlot(UTC_TIME_ZONE_OFFSET_SLOT, Int32Value(utcTZOffset));
1813 double utcTime = UTCTime().toNumber();
1815 if (!std::isfinite(utcTime)) {
1816 for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++) {
1817 setReservedSlot(ind, DoubleValue(utcTime));
1819 return;
1822 double localTime = LocalTime(forceUTC(), utcTime);
1824 setReservedSlot(LOCAL_TIME_SLOT, DoubleValue(localTime));
1826 const auto [year, month, day] = ToYearMonthDay(localTime);
1828 setReservedSlot(LOCAL_YEAR_SLOT, Int32Value(year));
1829 setReservedSlot(LOCAL_MONTH_SLOT, Int32Value(int32_t(month)));
1830 setReservedSlot(LOCAL_DATE_SLOT, Int32Value(int32_t(day)));
1832 int weekday = WeekDay(localTime);
1833 setReservedSlot(LOCAL_DAY_SLOT, Int32Value(weekday));
1835 double yearStartTime = TimeFromYear(year);
1836 uint64_t yearTime = uint64_t(localTime - yearStartTime);
1837 int32_t yearSeconds = int32_t(yearTime / 1000);
1838 setReservedSlot(LOCAL_SECONDS_INTO_YEAR_SLOT, Int32Value(yearSeconds));
1841 MOZ_ALWAYS_INLINE bool IsDate(HandleValue v) {
1842 return v.isObject() && v.toObject().is<DateObject>();
1846 * See ECMA 15.9.5.4 thru 15.9.5.23
1849 static bool date_getTime(JSContext* cx, unsigned argc, Value* vp) {
1850 CallArgs args = CallArgsFromVp(argc, vp);
1852 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getTime");
1853 if (!unwrapped) {
1854 return false;
1857 args.rval().set(unwrapped->UTCTime());
1858 return true;
1861 static bool date_getYear(JSContext* cx, unsigned argc, Value* vp) {
1862 CallArgs args = CallArgsFromVp(argc, vp);
1864 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getYear");
1865 if (!unwrapped) {
1866 return false;
1869 unwrapped->fillLocalTimeSlots();
1871 Value yearVal = unwrapped->localYear();
1872 if (yearVal.isInt32()) {
1873 /* Follow ECMA-262 to the letter, contrary to IE JScript. */
1874 int year = yearVal.toInt32() - 1900;
1875 args.rval().setInt32(year);
1876 } else {
1877 args.rval().set(yearVal);
1879 return true;
1882 static bool date_getFullYear(JSContext* cx, unsigned argc, Value* vp) {
1883 CallArgs args = CallArgsFromVp(argc, vp);
1885 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getFullYear");
1886 if (!unwrapped) {
1887 return false;
1890 unwrapped->fillLocalTimeSlots();
1891 args.rval().set(unwrapped->localYear());
1892 return true;
1895 static bool date_getUTCFullYear(JSContext* cx, unsigned argc, Value* vp) {
1896 CallArgs args = CallArgsFromVp(argc, vp);
1898 auto* unwrapped =
1899 UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCFullYear");
1900 if (!unwrapped) {
1901 return false;
1904 double result = unwrapped->UTCTime().toNumber();
1905 if (std::isfinite(result)) {
1906 result = YearFromTime(result);
1909 args.rval().setNumber(result);
1910 return true;
1913 static bool date_getMonth(JSContext* cx, unsigned argc, Value* vp) {
1914 CallArgs args = CallArgsFromVp(argc, vp);
1916 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getMonth");
1917 if (!unwrapped) {
1918 return false;
1921 unwrapped->fillLocalTimeSlots();
1922 args.rval().set(unwrapped->localMonth());
1923 return true;
1926 static bool date_getUTCMonth(JSContext* cx, unsigned argc, Value* vp) {
1927 CallArgs args = CallArgsFromVp(argc, vp);
1929 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCMonth");
1930 if (!unwrapped) {
1931 return false;
1934 double d = unwrapped->UTCTime().toNumber();
1935 args.rval().setNumber(MonthFromTime(d));
1936 return true;
1939 static bool date_getDate(JSContext* cx, unsigned argc, Value* vp) {
1940 CallArgs args = CallArgsFromVp(argc, vp);
1942 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getDate");
1943 if (!unwrapped) {
1944 return false;
1947 unwrapped->fillLocalTimeSlots();
1949 args.rval().set(unwrapped->localDate());
1950 return true;
1953 static bool date_getUTCDate(JSContext* cx, unsigned argc, Value* vp) {
1954 CallArgs args = CallArgsFromVp(argc, vp);
1956 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCDate");
1957 if (!unwrapped) {
1958 return false;
1961 double result = unwrapped->UTCTime().toNumber();
1962 if (std::isfinite(result)) {
1963 result = DateFromTime(result);
1966 args.rval().setNumber(result);
1967 return true;
1970 static bool date_getDay(JSContext* cx, unsigned argc, Value* vp) {
1971 CallArgs args = CallArgsFromVp(argc, vp);
1973 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getDay");
1974 if (!unwrapped) {
1975 return false;
1978 unwrapped->fillLocalTimeSlots();
1979 args.rval().set(unwrapped->localDay());
1980 return true;
1983 static bool date_getUTCDay(JSContext* cx, unsigned argc, Value* vp) {
1984 CallArgs args = CallArgsFromVp(argc, vp);
1986 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCDay");
1987 if (!unwrapped) {
1988 return false;
1991 double result = unwrapped->UTCTime().toNumber();
1992 if (std::isfinite(result)) {
1993 result = WeekDay(result);
1996 args.rval().setNumber(result);
1997 return true;
2000 static bool date_getHours(JSContext* cx, unsigned argc, Value* vp) {
2001 CallArgs args = CallArgsFromVp(argc, vp);
2003 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getHours");
2004 if (!unwrapped) {
2005 return false;
2008 unwrapped->fillLocalTimeSlots();
2010 // Note: localSecondsIntoYear is guaranteed to return an
2011 // int32 or NaN after the call to fillLocalTimeSlots.
2012 Value yearSeconds = unwrapped->localSecondsIntoYear();
2013 if (yearSeconds.isDouble()) {
2014 MOZ_ASSERT(std::isnan(yearSeconds.toDouble()));
2015 args.rval().set(yearSeconds);
2016 } else {
2017 args.rval().setInt32((yearSeconds.toInt32() / int(SecondsPerHour)) %
2018 int(HoursPerDay));
2020 return true;
2023 static bool date_getUTCHours(JSContext* cx, unsigned argc, Value* vp) {
2024 CallArgs args = CallArgsFromVp(argc, vp);
2026 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCHours");
2027 if (!unwrapped) {
2028 return false;
2031 double result = unwrapped->UTCTime().toNumber();
2032 if (std::isfinite(result)) {
2033 result = HourFromTime(result);
2036 args.rval().setNumber(result);
2037 return true;
2040 static bool date_getMinutes(JSContext* cx, unsigned argc, Value* vp) {
2041 CallArgs args = CallArgsFromVp(argc, vp);
2043 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getMinutes");
2044 if (!unwrapped) {
2045 return false;
2048 unwrapped->fillLocalTimeSlots();
2050 // Note: localSecondsIntoYear is guaranteed to return an
2051 // int32 or NaN after the call to fillLocalTimeSlots.
2052 Value yearSeconds = unwrapped->localSecondsIntoYear();
2053 if (yearSeconds.isDouble()) {
2054 MOZ_ASSERT(std::isnan(yearSeconds.toDouble()));
2055 args.rval().set(yearSeconds);
2056 } else {
2057 args.rval().setInt32((yearSeconds.toInt32() / int(SecondsPerMinute)) %
2058 int(MinutesPerHour));
2060 return true;
2063 static bool date_getUTCMinutes(JSContext* cx, unsigned argc, Value* vp) {
2064 CallArgs args = CallArgsFromVp(argc, vp);
2066 auto* unwrapped =
2067 UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCMinutes");
2068 if (!unwrapped) {
2069 return false;
2072 double result = unwrapped->UTCTime().toNumber();
2073 if (std::isfinite(result)) {
2074 result = MinFromTime(result);
2077 args.rval().setNumber(result);
2078 return true;
2081 static bool date_getSeconds(JSContext* cx, unsigned argc, Value* vp) {
2082 CallArgs args = CallArgsFromVp(argc, vp);
2084 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getSeconds");
2085 if (!unwrapped) {
2086 return false;
2089 unwrapped->fillLocalTimeSlots();
2091 // Note: localSecondsIntoYear is guaranteed to return an
2092 // int32 or NaN after the call to fillLocalTimeSlots.
2093 Value yearSeconds = unwrapped->localSecondsIntoYear();
2094 if (yearSeconds.isDouble()) {
2095 MOZ_ASSERT(std::isnan(yearSeconds.toDouble()));
2096 args.rval().set(yearSeconds);
2097 } else {
2098 args.rval().setInt32(yearSeconds.toInt32() % int(SecondsPerMinute));
2100 return true;
2103 static bool date_getUTCSeconds(JSContext* cx, unsigned argc, Value* vp) {
2104 CallArgs args = CallArgsFromVp(argc, vp);
2106 auto* unwrapped =
2107 UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCSeconds");
2108 if (!unwrapped) {
2109 return false;
2112 double result = unwrapped->UTCTime().toNumber();
2113 if (std::isfinite(result)) {
2114 result = SecFromTime(result);
2117 args.rval().setNumber(result);
2118 return true;
2122 * Date.getMilliseconds is mapped to getUTCMilliseconds. As long as no
2123 * supported time zone has a fractional-second component, the differences in
2124 * their specifications aren't observable.
2126 * The 'tz' database explicitly does not support fractional-second time zones.
2127 * For example the Netherlands observed Amsterdam Mean Time, estimated to be
2128 * UT +00:19:32.13, from 1909 to 1937, but in tzdata AMT is defined as exactly
2129 * UT +00:19:32.
2132 static bool getMilliseconds(JSContext* cx, unsigned argc, Value* vp,
2133 const char* methodName) {
2134 CallArgs args = CallArgsFromVp(argc, vp);
2136 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, methodName);
2137 if (!unwrapped) {
2138 return false;
2141 double result = unwrapped->UTCTime().toNumber();
2142 if (std::isfinite(result)) {
2143 result = msFromTime(result);
2146 args.rval().setNumber(result);
2147 return true;
2150 static bool date_getMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
2151 return getMilliseconds(cx, argc, vp, "getMilliseconds");
2154 static bool date_getUTCMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
2155 return getMilliseconds(cx, argc, vp, "getUTCMilliseconds");
2158 static bool date_getTimezoneOffset(JSContext* cx, unsigned argc, Value* vp) {
2159 CallArgs args = CallArgsFromVp(argc, vp);
2161 auto* unwrapped =
2162 UnwrapAndTypeCheckThis<DateObject>(cx, args, "getTimezoneOffset");
2163 if (!unwrapped) {
2164 return false;
2167 unwrapped->fillLocalTimeSlots();
2169 double utctime = unwrapped->UTCTime().toNumber();
2170 double localtime = unwrapped->localTime().toDouble();
2173 * Return the time zone offset in minutes for the current locale that is
2174 * appropriate for this time. This value would be a constant except for
2175 * daylight savings time.
2177 double result = (utctime - localtime) / msPerMinute;
2178 args.rval().setNumber(result);
2179 return true;
2182 static bool date_setTime(JSContext* cx, unsigned argc, Value* vp) {
2183 CallArgs args = CallArgsFromVp(argc, vp);
2185 Rooted<DateObject*> unwrapped(
2186 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setTime"));
2187 if (!unwrapped) {
2188 return false;
2191 if (args.length() == 0) {
2192 unwrapped->setUTCTime(ClippedTime::invalid(), args.rval());
2193 return true;
2196 double result;
2197 if (!ToNumber(cx, args[0], &result)) {
2198 return false;
2201 unwrapped->setUTCTime(TimeClip(result), args.rval());
2202 return true;
2205 static bool GetMsecsOrDefault(JSContext* cx, const CallArgs& args, unsigned i,
2206 double t, double* millis) {
2207 if (args.length() <= i) {
2208 *millis = msFromTime(t);
2209 return true;
2211 return ToNumber(cx, args[i], millis);
2214 static bool GetSecsOrDefault(JSContext* cx, const CallArgs& args, unsigned i,
2215 double t, double* sec) {
2216 if (args.length() <= i) {
2217 *sec = SecFromTime(t);
2218 return true;
2220 return ToNumber(cx, args[i], sec);
2223 static bool GetMinsOrDefault(JSContext* cx, const CallArgs& args, unsigned i,
2224 double t, double* mins) {
2225 if (args.length() <= i) {
2226 *mins = MinFromTime(t);
2227 return true;
2229 return ToNumber(cx, args[i], mins);
2232 /* ES6 20.3.4.23. */
2233 static bool date_setMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
2234 CallArgs args = CallArgsFromVp(argc, vp);
2236 // Step 1.
2237 Rooted<DateObject*> unwrapped(
2238 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setMilliseconds"));
2239 if (!unwrapped) {
2240 return false;
2242 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2244 // Step 2.
2245 double ms;
2246 if (!ToNumber(cx, args.get(0), &ms)) {
2247 return false;
2250 // Step 3.
2251 double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms);
2253 // Step 4.
2254 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), MakeDate(Day(t), time)));
2256 // Steps 5-6.
2257 unwrapped->setUTCTime(u, args.rval());
2258 return true;
2261 /* ES5 15.9.5.29. */
2262 static bool date_setUTCMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
2263 CallArgs args = CallArgsFromVp(argc, vp);
2265 Rooted<DateObject*> unwrapped(
2266 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCMilliseconds"));
2267 if (!unwrapped) {
2268 return false;
2271 /* Step 1. */
2272 double t = unwrapped->UTCTime().toNumber();
2274 /* Step 2. */
2275 double milli;
2276 if (!ToNumber(cx, args.get(0), &milli)) {
2277 return false;
2279 double time =
2280 MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli);
2282 /* Step 3. */
2283 ClippedTime v = TimeClip(MakeDate(Day(t), time));
2285 /* Steps 4-5. */
2286 unwrapped->setUTCTime(v, args.rval());
2287 return true;
2290 /* ES6 20.3.4.26. */
2291 static bool date_setSeconds(JSContext* cx, unsigned argc, Value* vp) {
2292 CallArgs args = CallArgsFromVp(argc, vp);
2294 Rooted<DateObject*> unwrapped(
2295 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setSeconds"));
2296 if (!unwrapped) {
2297 return false;
2300 // Steps 1-2.
2301 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2303 // Steps 3-4.
2304 double s;
2305 if (!ToNumber(cx, args.get(0), &s)) {
2306 return false;
2309 // Steps 5-6.
2310 double milli;
2311 if (!GetMsecsOrDefault(cx, args, 1, t, &milli)) {
2312 return false;
2315 // Step 7.
2316 double date =
2317 MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
2319 // Step 8.
2320 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), date));
2322 // Step 9.
2323 unwrapped->setUTCTime(u, args.rval());
2324 return true;
2327 /* ES5 15.9.5.32. */
2328 static bool date_setUTCSeconds(JSContext* cx, unsigned argc, Value* vp) {
2329 CallArgs args = CallArgsFromVp(argc, vp);
2331 Rooted<DateObject*> unwrapped(
2332 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCSeconds"));
2333 if (!unwrapped) {
2334 return false;
2337 /* Step 1. */
2338 double t = unwrapped->UTCTime().toNumber();
2340 /* Step 2. */
2341 double s;
2342 if (!ToNumber(cx, args.get(0), &s)) {
2343 return false;
2346 /* Step 3. */
2347 double milli;
2348 if (!GetMsecsOrDefault(cx, args, 1, t, &milli)) {
2349 return false;
2352 /* Step 4. */
2353 double date =
2354 MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
2356 /* Step 5. */
2357 ClippedTime v = TimeClip(date);
2359 /* Steps 6-7. */
2360 unwrapped->setUTCTime(v, args.rval());
2361 return true;
2364 /* ES6 20.3.4.24. */
2365 static bool date_setMinutes(JSContext* cx, unsigned argc, Value* vp) {
2366 CallArgs args = CallArgsFromVp(argc, vp);
2368 Rooted<DateObject*> unwrapped(
2369 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setMinutes"));
2370 if (!unwrapped) {
2371 return false;
2374 // Steps 1-2.
2375 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2377 // Steps 3-4.
2378 double m;
2379 if (!ToNumber(cx, args.get(0), &m)) {
2380 return false;
2383 // Steps 5-6.
2384 double s;
2385 if (!GetSecsOrDefault(cx, args, 1, t, &s)) {
2386 return false;
2389 // Steps 7-8.
2390 double milli;
2391 if (!GetMsecsOrDefault(cx, args, 2, t, &milli)) {
2392 return false;
2395 // Step 9.
2396 double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
2398 // Step 10.
2399 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), date));
2401 // Steps 11-12.
2402 unwrapped->setUTCTime(u, args.rval());
2403 return true;
2406 /* ES5 15.9.5.34. */
2407 static bool date_setUTCMinutes(JSContext* cx, unsigned argc, Value* vp) {
2408 CallArgs args = CallArgsFromVp(argc, vp);
2410 Rooted<DateObject*> unwrapped(
2411 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCMinutes"));
2412 if (!unwrapped) {
2413 return false;
2416 /* Step 1. */
2417 double t = unwrapped->UTCTime().toNumber();
2419 /* Step 2. */
2420 double m;
2421 if (!ToNumber(cx, args.get(0), &m)) {
2422 return false;
2425 /* Step 3. */
2426 double s;
2427 if (!GetSecsOrDefault(cx, args, 1, t, &s)) {
2428 return false;
2431 /* Step 4. */
2432 double milli;
2433 if (!GetMsecsOrDefault(cx, args, 2, t, &milli)) {
2434 return false;
2437 /* Step 5. */
2438 double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
2440 /* Step 6. */
2441 ClippedTime v = TimeClip(date);
2443 /* Steps 7-8. */
2444 unwrapped->setUTCTime(v, args.rval());
2445 return true;
2448 /* ES5 15.9.5.35. */
2449 static bool date_setHours(JSContext* cx, unsigned argc, Value* vp) {
2450 CallArgs args = CallArgsFromVp(argc, vp);
2452 Rooted<DateObject*> unwrapped(
2453 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setHours"));
2454 if (!unwrapped) {
2455 return false;
2458 // Steps 1-2.
2459 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2461 // Steps 3-4.
2462 double h;
2463 if (!ToNumber(cx, args.get(0), &h)) {
2464 return false;
2467 // Steps 5-6.
2468 double m;
2469 if (!GetMinsOrDefault(cx, args, 1, t, &m)) {
2470 return false;
2473 // Steps 7-8.
2474 double s;
2475 if (!GetSecsOrDefault(cx, args, 2, t, &s)) {
2476 return false;
2479 // Steps 9-10.
2480 double milli;
2481 if (!GetMsecsOrDefault(cx, args, 3, t, &milli)) {
2482 return false;
2485 // Step 11.
2486 double date = MakeDate(Day(t), MakeTime(h, m, s, milli));
2488 // Step 12.
2489 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), date));
2491 // Steps 13-14.
2492 unwrapped->setUTCTime(u, args.rval());
2493 return true;
2496 /* ES5 15.9.5.36. */
2497 static bool date_setUTCHours(JSContext* cx, unsigned argc, Value* vp) {
2498 CallArgs args = CallArgsFromVp(argc, vp);
2500 Rooted<DateObject*> unwrapped(
2501 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCHours"));
2502 if (!unwrapped) {
2503 return false;
2506 /* Step 1. */
2507 double t = unwrapped->UTCTime().toNumber();
2509 /* Step 2. */
2510 double h;
2511 if (!ToNumber(cx, args.get(0), &h)) {
2512 return false;
2515 /* Step 3. */
2516 double m;
2517 if (!GetMinsOrDefault(cx, args, 1, t, &m)) {
2518 return false;
2521 /* Step 4. */
2522 double s;
2523 if (!GetSecsOrDefault(cx, args, 2, t, &s)) {
2524 return false;
2527 /* Step 5. */
2528 double milli;
2529 if (!GetMsecsOrDefault(cx, args, 3, t, &milli)) {
2530 return false;
2533 /* Step 6. */
2534 double newDate = MakeDate(Day(t), MakeTime(h, m, s, milli));
2536 /* Step 7. */
2537 ClippedTime v = TimeClip(newDate);
2539 /* Steps 8-9. */
2540 unwrapped->setUTCTime(v, args.rval());
2541 return true;
2544 /* ES5 15.9.5.37. */
2545 static bool date_setDate(JSContext* cx, unsigned argc, Value* vp) {
2546 CallArgs args = CallArgsFromVp(argc, vp);
2548 Rooted<DateObject*> unwrapped(
2549 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setDate"));
2550 if (!unwrapped) {
2551 return false;
2554 /* Step 1. */
2555 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2557 /* Step 2. */
2558 double date;
2559 if (!ToNumber(cx, args.get(0), &date)) {
2560 return false;
2563 /* Step 3. */
2564 double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date),
2565 TimeWithinDay(t));
2567 /* Step 4. */
2568 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), newDate));
2570 /* Steps 5-6. */
2571 unwrapped->setUTCTime(u, args.rval());
2572 return true;
2575 static bool date_setUTCDate(JSContext* cx, unsigned argc, Value* vp) {
2576 CallArgs args = CallArgsFromVp(argc, vp);
2578 Rooted<DateObject*> unwrapped(
2579 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCDate"));
2580 if (!unwrapped) {
2581 return false;
2584 /* Step 1. */
2585 double t = unwrapped->UTCTime().toNumber();
2587 /* Step 2. */
2588 double date;
2589 if (!ToNumber(cx, args.get(0), &date)) {
2590 return false;
2593 /* Step 3. */
2594 double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date),
2595 TimeWithinDay(t));
2597 /* Step 4. */
2598 ClippedTime v = TimeClip(newDate);
2600 /* Steps 5-6. */
2601 unwrapped->setUTCTime(v, args.rval());
2602 return true;
2605 static bool GetDateOrDefault(JSContext* cx, const CallArgs& args, unsigned i,
2606 double t, double* date) {
2607 if (args.length() <= i) {
2608 *date = DateFromTime(t);
2609 return true;
2611 return ToNumber(cx, args[i], date);
2614 static bool GetMonthOrDefault(JSContext* cx, const CallArgs& args, unsigned i,
2615 double t, double* month) {
2616 if (args.length() <= i) {
2617 *month = MonthFromTime(t);
2618 return true;
2620 return ToNumber(cx, args[i], month);
2623 /* ES5 15.9.5.38. */
2624 static bool date_setMonth(JSContext* cx, unsigned argc, Value* vp) {
2625 CallArgs args = CallArgsFromVp(argc, vp);
2627 Rooted<DateObject*> unwrapped(
2628 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setMonth"));
2629 if (!unwrapped) {
2630 return false;
2633 /* Step 1. */
2634 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2636 /* Step 2. */
2637 double m;
2638 if (!ToNumber(cx, args.get(0), &m)) {
2639 return false;
2642 /* Step 3. */
2643 double date;
2644 if (!GetDateOrDefault(cx, args, 1, t, &date)) {
2645 return false;
2648 /* Step 4. */
2649 double newDate =
2650 MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t));
2652 /* Step 5. */
2653 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), newDate));
2655 /* Steps 6-7. */
2656 unwrapped->setUTCTime(u, args.rval());
2657 return true;
2660 /* ES5 15.9.5.39. */
2661 static bool date_setUTCMonth(JSContext* cx, unsigned argc, Value* vp) {
2662 CallArgs args = CallArgsFromVp(argc, vp);
2664 Rooted<DateObject*> unwrapped(
2665 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCMonth"));
2666 if (!unwrapped) {
2667 return false;
2670 /* Step 1. */
2671 double t = unwrapped->UTCTime().toNumber();
2673 /* Step 2. */
2674 double m;
2675 if (!ToNumber(cx, args.get(0), &m)) {
2676 return false;
2679 /* Step 3. */
2680 double date;
2681 if (!GetDateOrDefault(cx, args, 1, t, &date)) {
2682 return false;
2685 /* Step 4. */
2686 double newDate =
2687 MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t));
2689 /* Step 5. */
2690 ClippedTime v = TimeClip(newDate);
2692 /* Steps 6-7. */
2693 unwrapped->setUTCTime(v, args.rval());
2694 return true;
2697 static double ThisLocalTimeOrZero(DateTimeInfo::ForceUTC forceUTC,
2698 Handle<DateObject*> dateObj) {
2699 double t = dateObj->UTCTime().toNumber();
2700 if (std::isnan(t)) {
2701 return +0;
2703 return LocalTime(forceUTC, t);
2706 static double ThisUTCTimeOrZero(Handle<DateObject*> dateObj) {
2707 double t = dateObj->as<DateObject>().UTCTime().toNumber();
2708 return std::isnan(t) ? +0 : t;
2711 /* ES5 15.9.5.40. */
2712 static bool date_setFullYear(JSContext* cx, unsigned argc, Value* vp) {
2713 CallArgs args = CallArgsFromVp(argc, vp);
2715 Rooted<DateObject*> unwrapped(
2716 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setFullYear"));
2717 if (!unwrapped) {
2718 return false;
2721 /* Step 1. */
2722 double t = ThisLocalTimeOrZero(unwrapped->forceUTC(), unwrapped);
2724 /* Step 2. */
2725 double y;
2726 if (!ToNumber(cx, args.get(0), &y)) {
2727 return false;
2730 /* Step 3. */
2731 double m;
2732 if (!GetMonthOrDefault(cx, args, 1, t, &m)) {
2733 return false;
2736 /* Step 4. */
2737 double date;
2738 if (!GetDateOrDefault(cx, args, 2, t, &date)) {
2739 return false;
2742 /* Step 5. */
2743 double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t));
2745 /* Step 6. */
2746 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), newDate));
2748 /* Steps 7-8. */
2749 unwrapped->setUTCTime(u, args.rval());
2750 return true;
2753 /* ES5 15.9.5.41. */
2754 static bool date_setUTCFullYear(JSContext* cx, unsigned argc, Value* vp) {
2755 CallArgs args = CallArgsFromVp(argc, vp);
2757 Rooted<DateObject*> unwrapped(
2758 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCFullYear"));
2759 if (!unwrapped) {
2760 return false;
2763 /* Step 1. */
2764 double t = ThisUTCTimeOrZero(unwrapped);
2766 /* Step 2. */
2767 double y;
2768 if (!ToNumber(cx, args.get(0), &y)) {
2769 return false;
2772 /* Step 3. */
2773 double m;
2774 if (!GetMonthOrDefault(cx, args, 1, t, &m)) {
2775 return false;
2778 /* Step 4. */
2779 double date;
2780 if (!GetDateOrDefault(cx, args, 2, t, &date)) {
2781 return false;
2784 /* Step 5. */
2785 double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t));
2787 /* Step 6. */
2788 ClippedTime v = TimeClip(newDate);
2790 /* Steps 7-8. */
2791 unwrapped->setUTCTime(v, args.rval());
2792 return true;
2795 /* ES5 Annex B.2.5. */
2796 static bool date_setYear(JSContext* cx, unsigned argc, Value* vp) {
2797 CallArgs args = CallArgsFromVp(argc, vp);
2799 Rooted<DateObject*> unwrapped(
2800 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setYear"));
2801 if (!unwrapped) {
2802 return false;
2805 /* Step 1. */
2806 double t = ThisLocalTimeOrZero(unwrapped->forceUTC(), unwrapped);
2808 /* Step 2. */
2809 double y;
2810 if (!ToNumber(cx, args.get(0), &y)) {
2811 return false;
2814 /* Step 3. */
2815 if (std::isnan(y)) {
2816 unwrapped->setUTCTime(ClippedTime::invalid(), args.rval());
2817 return true;
2820 /* Step 4. */
2821 double yint = ToInteger(y);
2822 if (0 <= yint && yint <= 99) {
2823 yint += 1900;
2826 /* Step 5. */
2827 double day = MakeDay(yint, MonthFromTime(t), DateFromTime(t));
2829 /* Step 6. */
2830 double u = UTC(unwrapped->forceUTC(), MakeDate(day, TimeWithinDay(t)));
2832 /* Steps 7-8. */
2833 unwrapped->setUTCTime(TimeClip(u), args.rval());
2834 return true;
2837 /* constants for toString, toUTCString */
2838 static const char* const days[] = {"Sun", "Mon", "Tue", "Wed",
2839 "Thu", "Fri", "Sat"};
2840 static const char* const months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
2841 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
2843 /* ES5 B.2.6. */
2844 static bool date_toUTCString(JSContext* cx, unsigned argc, Value* vp) {
2845 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toUTCString");
2846 CallArgs args = CallArgsFromVp(argc, vp);
2848 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "toUTCString");
2849 if (!unwrapped) {
2850 return false;
2853 double utctime = unwrapped->UTCTime().toNumber();
2854 if (!std::isfinite(utctime)) {
2855 args.rval().setString(cx->names().Invalid_Date_);
2856 return true;
2859 char buf[100];
2860 SprintfLiteral(buf, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
2861 days[int(WeekDay(utctime))], int(DateFromTime(utctime)),
2862 months[int(MonthFromTime(utctime))],
2863 int(YearFromTime(utctime)), int(HourFromTime(utctime)),
2864 int(MinFromTime(utctime)), int(SecFromTime(utctime)));
2866 JSString* str = NewStringCopyZ<CanGC>(cx, buf);
2867 if (!str) {
2868 return false;
2871 args.rval().setString(str);
2872 return true;
2875 /* ES6 draft 2015-01-15 20.3.4.36. */
2876 static bool date_toISOString(JSContext* cx, unsigned argc, Value* vp) {
2877 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toISOString");
2878 CallArgs args = CallArgsFromVp(argc, vp);
2880 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "toISOString");
2881 if (!unwrapped) {
2882 return false;
2885 double utctime = unwrapped->UTCTime().toNumber();
2886 if (!std::isfinite(utctime)) {
2887 JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
2888 JSMSG_INVALID_DATE);
2889 return false;
2892 char buf[100];
2893 int year = int(YearFromTime(utctime));
2894 if (year < 0 || year > 9999) {
2895 SprintfLiteral(buf, "%+.6d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
2896 int(YearFromTime(utctime)), int(MonthFromTime(utctime)) + 1,
2897 int(DateFromTime(utctime)), int(HourFromTime(utctime)),
2898 int(MinFromTime(utctime)), int(SecFromTime(utctime)),
2899 int(msFromTime(utctime)));
2900 } else {
2901 SprintfLiteral(buf, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
2902 int(YearFromTime(utctime)), int(MonthFromTime(utctime)) + 1,
2903 int(DateFromTime(utctime)), int(HourFromTime(utctime)),
2904 int(MinFromTime(utctime)), int(SecFromTime(utctime)),
2905 int(msFromTime(utctime)));
2908 JSString* str = NewStringCopyZ<CanGC>(cx, buf);
2909 if (!str) {
2910 return false;
2912 args.rval().setString(str);
2913 return true;
2916 /* ES5 15.9.5.44. */
2917 static bool date_toJSON(JSContext* cx, unsigned argc, Value* vp) {
2918 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toJSON");
2919 CallArgs args = CallArgsFromVp(argc, vp);
2921 /* Step 1. */
2922 RootedObject obj(cx, ToObject(cx, args.thisv()));
2923 if (!obj) {
2924 return false;
2927 /* Step 2. */
2928 RootedValue tv(cx, ObjectValue(*obj));
2929 if (!ToPrimitive(cx, JSTYPE_NUMBER, &tv)) {
2930 return false;
2933 /* Step 3. */
2934 if (tv.isDouble() && !std::isfinite(tv.toDouble())) {
2935 args.rval().setNull();
2936 return true;
2939 /* Step 4. */
2940 RootedValue toISO(cx);
2941 if (!GetProperty(cx, obj, obj, cx->names().toISOString, &toISO)) {
2942 return false;
2945 /* Step 5. */
2946 if (!IsCallable(toISO)) {
2947 JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
2948 JSMSG_BAD_TOISOSTRING_PROP);
2949 return false;
2952 /* Step 6. */
2953 return Call(cx, toISO, obj, args.rval());
2956 #if JS_HAS_INTL_API
2957 JSString* DateTimeHelper::timeZoneComment(JSContext* cx,
2958 DateTimeInfo::ForceUTC forceUTC,
2959 const char* locale, double utcTime,
2960 double localTime) {
2961 if (!locale) {
2962 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
2963 JSMSG_DEFAULT_LOCALE_ERROR);
2964 return nullptr;
2967 char16_t tzbuf[100];
2968 tzbuf[0] = ' ';
2969 tzbuf[1] = '(';
2971 char16_t* timeZoneStart = tzbuf + 2;
2972 constexpr size_t remainingSpace =
2973 std::size(tzbuf) - 2 - 1; // for the trailing ')'
2975 int64_t utcMilliseconds = static_cast<int64_t>(utcTime);
2976 if (!DateTimeInfo::timeZoneDisplayName(
2977 forceUTC, timeZoneStart, remainingSpace, utcMilliseconds, locale)) {
2978 JS_ReportOutOfMemory(cx);
2979 return nullptr;
2982 // Reject if the result string is empty.
2983 size_t len = js_strlen(timeZoneStart);
2984 if (len == 0) {
2985 return cx->names().empty_;
2988 // Parenthesize the returned display name.
2989 timeZoneStart[len] = ')';
2991 return NewStringCopyN<CanGC>(cx, tzbuf, 2 + len + 1);
2993 #else
2994 /* Interface to PRMJTime date struct. */
2995 PRMJTime DateTimeHelper::toPRMJTime(DateTimeInfo::ForceUTC forceUTC,
2996 double localTime, double utcTime) {
2997 double year = YearFromTime(localTime);
2999 PRMJTime prtm;
3000 prtm.tm_usec = int32_t(msFromTime(localTime)) * 1000;
3001 prtm.tm_sec = int8_t(SecFromTime(localTime));
3002 prtm.tm_min = int8_t(MinFromTime(localTime));
3003 prtm.tm_hour = int8_t(HourFromTime(localTime));
3004 prtm.tm_mday = int8_t(DateFromTime(localTime));
3005 prtm.tm_mon = int8_t(MonthFromTime(localTime));
3006 prtm.tm_wday = int8_t(WeekDay(localTime));
3007 prtm.tm_year = year;
3008 prtm.tm_yday = int16_t(DayWithinYear(localTime, year));
3009 prtm.tm_isdst = (daylightSavingTA(forceUTC, utcTime) != 0);
3011 return prtm;
3014 size_t DateTimeHelper::formatTime(DateTimeInfo::ForceUTC forceUTC, char* buf,
3015 size_t buflen, const char* fmt,
3016 double utcTime, double localTime) {
3017 PRMJTime prtm = toPRMJTime(forceUTC, localTime, utcTime);
3019 // If an equivalent year was used to compute the date/time components, use
3020 // the same equivalent year to determine the time zone name and offset in
3021 // PRMJ_FormatTime(...).
3022 int timeZoneYear = isRepresentableAsTime32(utcTime)
3023 ? prtm.tm_year
3024 : equivalentYearForDST(prtm.tm_year);
3025 int offsetInSeconds = (int)floor((localTime - utcTime) / msPerSecond);
3027 return PRMJ_FormatTime(buf, buflen, fmt, &prtm, timeZoneYear,
3028 offsetInSeconds);
3031 JSString* DateTimeHelper::timeZoneComment(JSContext* cx,
3032 DateTimeInfo::ForceUTC forceUTC,
3033 const char* locale, double utcTime,
3034 double localTime) {
3035 char tzbuf[100];
3037 size_t tzlen =
3038 formatTime(forceUTC, tzbuf, sizeof tzbuf, " (%Z)", utcTime, localTime);
3039 if (tzlen != 0) {
3040 // Decide whether to use the resulting time zone string.
3042 // Reject it if it contains any non-ASCII or non-printable characters.
3043 // It's then likely in some other character encoding, and we probably
3044 // won't display it correctly.
3045 bool usetz = true;
3046 for (size_t i = 0; i < tzlen; i++) {
3047 char16_t c = tzbuf[i];
3048 if (!IsAsciiPrintable(c)) {
3049 usetz = false;
3050 break;
3054 // Also reject it if it's not parenthesized or if it's ' ()'.
3055 if (tzbuf[0] != ' ' || tzbuf[1] != '(' || tzbuf[2] == ')') {
3056 usetz = false;
3059 if (usetz) {
3060 return NewStringCopyN<CanGC>(cx, tzbuf, tzlen);
3064 return cx->names().empty_;
3066 #endif /* JS_HAS_INTL_API */
3068 enum class FormatSpec { DateTime, Date, Time };
3070 static bool FormatDate(JSContext* cx, DateTimeInfo::ForceUTC forceUTC,
3071 const char* locale, double utcTime, FormatSpec format,
3072 MutableHandleValue rval) {
3073 if (!std::isfinite(utcTime)) {
3074 rval.setString(cx->names().Invalid_Date_);
3075 return true;
3078 MOZ_ASSERT(NumbersAreIdentical(TimeClip(utcTime).toDouble(), utcTime));
3080 double localTime = LocalTime(forceUTC, utcTime);
3082 int offset = 0;
3083 RootedString timeZoneComment(cx);
3084 if (format == FormatSpec::DateTime || format == FormatSpec::Time) {
3085 // Offset from GMT in minutes. The offset includes daylight savings,
3086 // if it applies.
3087 int minutes = (int)trunc((localTime - utcTime) / msPerMinute);
3089 // Map 510 minutes to 0830 hours.
3090 offset = (minutes / 60) * 100 + minutes % 60;
3092 // Print as "Wed Nov 05 1997 19:38:03 GMT-0800 (PST)".
3094 // The TZA is printed as 'GMT-0800' rather than as 'PST' to avoid
3095 // operating-system dependence on strftime (which PRMJ_FormatTime
3096 // calls, for %Z only.) win32 prints PST as 'Pacific Standard Time.'
3097 // This way we always know what we're getting, and can parse it if
3098 // we produce it. The OS time zone string is included as a comment.
3100 // When ICU is used to retrieve the time zone string, the localized
3101 // 'long' name format from CLDR is used. For example when the default
3102 // locale is "en-US", PST is displayed as 'Pacific Standard Time', but
3103 // when it is "ru", 'Тихоокеанское стандартное время' is used. This
3104 // also means the time zone string may not fit into Latin-1.
3106 // Get a time zone string from the OS or ICU to include as a comment.
3107 timeZoneComment = DateTimeHelper::timeZoneComment(cx, forceUTC, locale,
3108 utcTime, localTime);
3109 if (!timeZoneComment) {
3110 return false;
3114 char buf[100];
3115 switch (format) {
3116 case FormatSpec::DateTime:
3117 /* Tue Oct 31 2000 09:41:40 GMT-0800 */
3118 SprintfLiteral(buf, "%s %s %.2d %.4d %.2d:%.2d:%.2d GMT%+.4d",
3119 days[int(WeekDay(localTime))],
3120 months[int(MonthFromTime(localTime))],
3121 int(DateFromTime(localTime)), int(YearFromTime(localTime)),
3122 int(HourFromTime(localTime)), int(MinFromTime(localTime)),
3123 int(SecFromTime(localTime)), offset);
3124 break;
3125 case FormatSpec::Date:
3126 /* Tue Oct 31 2000 */
3127 SprintfLiteral(buf, "%s %s %.2d %.4d", days[int(WeekDay(localTime))],
3128 months[int(MonthFromTime(localTime))],
3129 int(DateFromTime(localTime)),
3130 int(YearFromTime(localTime)));
3131 break;
3132 case FormatSpec::Time:
3133 /* 09:41:40 GMT-0800 */
3134 SprintfLiteral(buf, "%.2d:%.2d:%.2d GMT%+.4d",
3135 int(HourFromTime(localTime)), int(MinFromTime(localTime)),
3136 int(SecFromTime(localTime)), offset);
3137 break;
3140 RootedString str(cx, NewStringCopyZ<CanGC>(cx, buf));
3141 if (!str) {
3142 return false;
3145 // Append the time zone string if present.
3146 if (timeZoneComment && !timeZoneComment->empty()) {
3147 str = js::ConcatStrings<CanGC>(cx, str, timeZoneComment);
3148 if (!str) {
3149 return false;
3153 rval.setString(str);
3154 return true;
3157 #if !JS_HAS_INTL_API
3158 static bool ToLocaleFormatHelper(JSContext* cx, DateObject* unwrapped,
3159 const char* format, MutableHandleValue rval) {
3160 DateTimeInfo::ForceUTC forceUTC = unwrapped->forceUTC();
3161 const char* locale = unwrapped->realm()->getLocale();
3162 double utcTime = unwrapped->UTCTime().toNumber();
3164 char buf[100];
3165 if (!std::isfinite(utcTime)) {
3166 strcpy(buf, "InvalidDate");
3167 } else {
3168 double localTime = LocalTime(forceUTC, utcTime);
3170 /* Let PRMJTime format it. */
3171 size_t result_len = DateTimeHelper::formatTime(forceUTC, buf, sizeof buf,
3172 format, utcTime, localTime);
3174 /* If it failed, default to toString. */
3175 if (result_len == 0) {
3176 return FormatDate(cx, forceUTC, locale, utcTime, FormatSpec::DateTime,
3177 rval);
3180 /* Hacked check against undesired 2-digit year 00/00/00 form. */
3181 if (strcmp(format, "%x") == 0 && result_len >= 6 &&
3182 /* Format %x means use OS settings, which may have 2-digit yr, so
3183 hack end of 3/11/22 or 11.03.22 or 11Mar22 to use 4-digit yr...*/
3184 !IsAsciiDigit(buf[result_len - 3]) &&
3185 IsAsciiDigit(buf[result_len - 2]) &&
3186 IsAsciiDigit(buf[result_len - 1]) &&
3187 /* ...but not if starts with 4-digit year, like 2022/3/11. */
3188 !(IsAsciiDigit(buf[0]) && IsAsciiDigit(buf[1]) &&
3189 IsAsciiDigit(buf[2]) && IsAsciiDigit(buf[3]))) {
3190 int year = int(YearFromTime(localTime));
3191 snprintf(buf + (result_len - 2), (sizeof buf) - (result_len - 2), "%d",
3192 year);
3196 if (cx->runtime()->localeCallbacks &&
3197 cx->runtime()->localeCallbacks->localeToUnicode) {
3198 return cx->runtime()->localeCallbacks->localeToUnicode(cx, buf, rval);
3201 JSString* str = NewStringCopyZ<CanGC>(cx, buf);
3202 if (!str) {
3203 return false;
3205 rval.setString(str);
3206 return true;
3209 /* ES5 15.9.5.5. */
3210 static bool date_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
3211 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toLocaleString");
3212 CallArgs args = CallArgsFromVp(argc, vp);
3214 auto* unwrapped =
3215 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toLocaleString");
3216 if (!unwrapped) {
3217 return false;
3221 * Use '%#c' for windows, because '%c' is backward-compatible and non-y2k
3222 * with msvc; '%#c' requests that a full year be used in the result string.
3224 static const char format[] =
3225 # if defined(_WIN32)
3226 "%#c"
3227 # else
3228 "%c"
3229 # endif
3232 return ToLocaleFormatHelper(cx, unwrapped, format, args.rval());
3235 static bool date_toLocaleDateString(JSContext* cx, unsigned argc, Value* vp) {
3236 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype",
3237 "toLocaleDateString");
3238 CallArgs args = CallArgsFromVp(argc, vp);
3240 auto* unwrapped =
3241 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toLocaleDateString");
3242 if (!unwrapped) {
3243 return false;
3247 * Use '%#x' for windows, because '%x' is backward-compatible and non-y2k
3248 * with msvc; '%#x' requests that a full year be used in the result string.
3250 static const char format[] =
3251 # if defined(_WIN32)
3252 "%#x"
3253 # else
3254 "%x"
3255 # endif
3258 return ToLocaleFormatHelper(cx, unwrapped, format, args.rval());
3261 static bool date_toLocaleTimeString(JSContext* cx, unsigned argc, Value* vp) {
3262 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype",
3263 "toLocaleTimeString");
3264 CallArgs args = CallArgsFromVp(argc, vp);
3266 auto* unwrapped =
3267 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toLocaleTimeString");
3268 if (!unwrapped) {
3269 return false;
3272 return ToLocaleFormatHelper(cx, unwrapped, "%X", args.rval());
3274 #endif /* !JS_HAS_INTL_API */
3276 static bool date_toTimeString(JSContext* cx, unsigned argc, Value* vp) {
3277 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toTimeString");
3278 CallArgs args = CallArgsFromVp(argc, vp);
3280 auto* unwrapped =
3281 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toTimeString");
3282 if (!unwrapped) {
3283 return false;
3286 return FormatDate(cx, unwrapped->forceUTC(), unwrapped->realm()->getLocale(),
3287 unwrapped->UTCTime().toNumber(), FormatSpec::Time,
3288 args.rval());
3291 static bool date_toDateString(JSContext* cx, unsigned argc, Value* vp) {
3292 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toDateString");
3293 CallArgs args = CallArgsFromVp(argc, vp);
3295 auto* unwrapped =
3296 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toDateString");
3297 if (!unwrapped) {
3298 return false;
3301 return FormatDate(cx, unwrapped->forceUTC(), unwrapped->realm()->getLocale(),
3302 unwrapped->UTCTime().toNumber(), FormatSpec::Date,
3303 args.rval());
3306 static bool date_toSource(JSContext* cx, unsigned argc, Value* vp) {
3307 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toSource");
3308 CallArgs args = CallArgsFromVp(argc, vp);
3310 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "toSource");
3311 if (!unwrapped) {
3312 return false;
3315 JSStringBuilder sb(cx);
3316 if (!sb.append("(new Date(") ||
3317 !NumberValueToStringBuffer(unwrapped->UTCTime(), sb) ||
3318 !sb.append("))")) {
3319 return false;
3322 JSString* str = sb.finishString();
3323 if (!str) {
3324 return false;
3326 args.rval().setString(str);
3327 return true;
3330 bool date_toString(JSContext* cx, unsigned argc, Value* vp) {
3331 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toString");
3332 CallArgs args = CallArgsFromVp(argc, vp);
3334 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "toString");
3335 if (!unwrapped) {
3336 return false;
3339 return FormatDate(cx, unwrapped->forceUTC(), unwrapped->realm()->getLocale(),
3340 unwrapped->UTCTime().toNumber(), FormatSpec::DateTime,
3341 args.rval());
3344 bool js::date_valueOf(JSContext* cx, unsigned argc, Value* vp) {
3345 CallArgs args = CallArgsFromVp(argc, vp);
3347 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "valueOf");
3348 if (!unwrapped) {
3349 return false;
3352 args.rval().set(unwrapped->UTCTime());
3353 return true;
3356 // ES6 20.3.4.45 Date.prototype[@@toPrimitive]
3357 static bool date_toPrimitive(JSContext* cx, unsigned argc, Value* vp) {
3358 CallArgs args = CallArgsFromVp(argc, vp);
3360 // Steps 1-2.
3361 if (!args.thisv().isObject()) {
3362 ReportIncompatible(cx, args);
3363 return false;
3366 // Steps 3-5.
3367 JSType hint;
3368 if (!GetFirstArgumentAsTypeHint(cx, args, &hint)) {
3369 return false;
3371 if (hint == JSTYPE_UNDEFINED) {
3372 hint = JSTYPE_STRING;
3375 args.rval().set(args.thisv());
3376 RootedObject obj(cx, &args.thisv().toObject());
3377 return OrdinaryToPrimitive(cx, obj, hint, args.rval());
3380 #if JS_HAS_TEMPORAL_API
3382 * Date.prototype.toTemporalInstant ( )
3384 static bool date_toTemporalInstant(JSContext* cx, unsigned argc, Value* vp) {
3385 CallArgs args = CallArgsFromVp(argc, vp);
3387 // Step 1.
3388 auto* unwrapped =
3389 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toTemporalInstant");
3390 if (!unwrapped) {
3391 return false;
3394 // Step 2.
3395 double utctime = unwrapped->UTCTime().toNumber();
3396 if (!std::isfinite(utctime)) {
3397 JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
3398 JSMSG_INVALID_DATE);
3399 return false;
3401 MOZ_ASSERT(IsInteger(utctime));
3403 auto instant = temporal::Instant::fromMilliseconds(int64_t(utctime));
3404 MOZ_ASSERT(temporal::IsValidEpochInstant(instant));
3406 // Step 3.
3407 auto* result = temporal::CreateTemporalInstant(cx, instant);
3408 if (!result) {
3409 return false;
3411 args.rval().setObject(*result);
3412 return true;
3414 #endif /* JS_HAS_TEMPORAL_API */
3416 static const JSFunctionSpec date_static_methods[] = {
3417 JS_FN("UTC", date_UTC, 7, 0), JS_FN("parse", date_parse, 1, 0),
3418 JS_FN("now", date_now, 0, 0), JS_FS_END};
3420 static const JSFunctionSpec date_methods[] = {
3421 JS_FN("getTime", date_getTime, 0, 0),
3422 JS_FN("getTimezoneOffset", date_getTimezoneOffset, 0, 0),
3423 JS_FN("getYear", date_getYear, 0, 0),
3424 JS_FN("getFullYear", date_getFullYear, 0, 0),
3425 JS_FN("getUTCFullYear", date_getUTCFullYear, 0, 0),
3426 JS_FN("getMonth", date_getMonth, 0, 0),
3427 JS_FN("getUTCMonth", date_getUTCMonth, 0, 0),
3428 JS_FN("getDate", date_getDate, 0, 0),
3429 JS_FN("getUTCDate", date_getUTCDate, 0, 0),
3430 JS_FN("getDay", date_getDay, 0, 0),
3431 JS_FN("getUTCDay", date_getUTCDay, 0, 0),
3432 JS_FN("getHours", date_getHours, 0, 0),
3433 JS_FN("getUTCHours", date_getUTCHours, 0, 0),
3434 JS_FN("getMinutes", date_getMinutes, 0, 0),
3435 JS_FN("getUTCMinutes", date_getUTCMinutes, 0, 0),
3436 JS_FN("getSeconds", date_getSeconds, 0, 0),
3437 JS_FN("getUTCSeconds", date_getUTCSeconds, 0, 0),
3438 JS_FN("getMilliseconds", date_getMilliseconds, 0, 0),
3439 JS_FN("getUTCMilliseconds", date_getUTCMilliseconds, 0, 0),
3440 JS_FN("setTime", date_setTime, 1, 0),
3441 JS_FN("setYear", date_setYear, 1, 0),
3442 JS_FN("setFullYear", date_setFullYear, 3, 0),
3443 JS_FN("setUTCFullYear", date_setUTCFullYear, 3, 0),
3444 JS_FN("setMonth", date_setMonth, 2, 0),
3445 JS_FN("setUTCMonth", date_setUTCMonth, 2, 0),
3446 JS_FN("setDate", date_setDate, 1, 0),
3447 JS_FN("setUTCDate", date_setUTCDate, 1, 0),
3448 JS_FN("setHours", date_setHours, 4, 0),
3449 JS_FN("setUTCHours", date_setUTCHours, 4, 0),
3450 JS_FN("setMinutes", date_setMinutes, 3, 0),
3451 JS_FN("setUTCMinutes", date_setUTCMinutes, 3, 0),
3452 JS_FN("setSeconds", date_setSeconds, 2, 0),
3453 JS_FN("setUTCSeconds", date_setUTCSeconds, 2, 0),
3454 JS_FN("setMilliseconds", date_setMilliseconds, 1, 0),
3455 JS_FN("setUTCMilliseconds", date_setUTCMilliseconds, 1, 0),
3456 JS_FN("toUTCString", date_toUTCString, 0, 0),
3457 #if JS_HAS_TEMPORAL_API
3458 JS_FN("toTemporalInstant", date_toTemporalInstant, 0, 0),
3459 #endif
3460 #if JS_HAS_INTL_API
3461 JS_SELF_HOSTED_FN("toLocaleString", "Date_toLocaleString", 0, 0),
3462 JS_SELF_HOSTED_FN("toLocaleDateString", "Date_toLocaleDateString", 0, 0),
3463 JS_SELF_HOSTED_FN("toLocaleTimeString", "Date_toLocaleTimeString", 0, 0),
3464 #else
3465 JS_FN("toLocaleString", date_toLocaleString, 0, 0),
3466 JS_FN("toLocaleDateString", date_toLocaleDateString, 0, 0),
3467 JS_FN("toLocaleTimeString", date_toLocaleTimeString, 0, 0),
3468 #endif
3469 JS_FN("toDateString", date_toDateString, 0, 0),
3470 JS_FN("toTimeString", date_toTimeString, 0, 0),
3471 JS_FN("toISOString", date_toISOString, 0, 0),
3472 JS_FN("toJSON", date_toJSON, 1, 0),
3473 JS_FN("toSource", date_toSource, 0, 0),
3474 JS_FN("toString", date_toString, 0, 0),
3475 JS_FN("valueOf", date_valueOf, 0, 0),
3476 JS_SYM_FN(toPrimitive, date_toPrimitive, 1, JSPROP_READONLY),
3477 JS_FS_END};
3479 static bool NewDateObject(JSContext* cx, const CallArgs& args, ClippedTime t) {
3480 MOZ_ASSERT(args.isConstructing());
3482 RootedObject proto(cx);
3483 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Date, &proto)) {
3484 return false;
3487 JSObject* obj = NewDateObjectMsec(cx, t, proto);
3488 if (!obj) {
3489 return false;
3492 args.rval().setObject(*obj);
3493 return true;
3496 static bool ToDateString(JSContext* cx, const CallArgs& args, ClippedTime t) {
3497 return FormatDate(cx, ForceUTC(cx->realm()), cx->realm()->getLocale(),
3498 t.toDouble(), FormatSpec::DateTime, args.rval());
3501 static bool DateNoArguments(JSContext* cx, const CallArgs& args) {
3502 MOZ_ASSERT(args.length() == 0);
3504 ClippedTime now = NowAsMillis(cx);
3506 if (args.isConstructing()) {
3507 return NewDateObject(cx, args, now);
3510 return ToDateString(cx, args, now);
3513 static bool DateOneArgument(JSContext* cx, const CallArgs& args) {
3514 MOZ_ASSERT(args.length() == 1);
3516 if (args.isConstructing()) {
3517 if (args[0].isObject()) {
3518 RootedObject obj(cx, &args[0].toObject());
3520 ESClass cls;
3521 if (!GetBuiltinClass(cx, obj, &cls)) {
3522 return false;
3525 if (cls == ESClass::Date) {
3526 RootedValue unboxed(cx);
3527 if (!Unbox(cx, obj, &unboxed)) {
3528 return false;
3531 return NewDateObject(cx, args, TimeClip(unboxed.toNumber()));
3535 if (!ToPrimitive(cx, args[0])) {
3536 return false;
3539 ClippedTime t;
3540 if (args[0].isString()) {
3541 JSLinearString* linearStr = args[0].toString()->ensureLinear(cx);
3542 if (!linearStr) {
3543 return false;
3546 if (!ParseDate(ForceUTC(cx->realm()), linearStr, &t)) {
3547 t = ClippedTime::invalid();
3549 } else {
3550 double d;
3551 if (!ToNumber(cx, args[0], &d)) {
3552 return false;
3554 t = TimeClip(d);
3557 return NewDateObject(cx, args, t);
3560 return ToDateString(cx, args, NowAsMillis(cx));
3563 static bool DateMultipleArguments(JSContext* cx, const CallArgs& args) {
3564 MOZ_ASSERT(args.length() >= 2);
3566 // Step 3.
3567 if (args.isConstructing()) {
3568 // Steps 3a-b.
3569 double y;
3570 if (!ToNumber(cx, args[0], &y)) {
3571 return false;
3574 // Steps 3c-d.
3575 double m;
3576 if (!ToNumber(cx, args[1], &m)) {
3577 return false;
3580 // Steps 3e-f.
3581 double dt;
3582 if (args.length() >= 3) {
3583 if (!ToNumber(cx, args[2], &dt)) {
3584 return false;
3586 } else {
3587 dt = 1;
3590 // Steps 3g-h.
3591 double h;
3592 if (args.length() >= 4) {
3593 if (!ToNumber(cx, args[3], &h)) {
3594 return false;
3596 } else {
3597 h = 0;
3600 // Steps 3i-j.
3601 double min;
3602 if (args.length() >= 5) {
3603 if (!ToNumber(cx, args[4], &min)) {
3604 return false;
3606 } else {
3607 min = 0;
3610 // Steps 3k-l.
3611 double s;
3612 if (args.length() >= 6) {
3613 if (!ToNumber(cx, args[5], &s)) {
3614 return false;
3616 } else {
3617 s = 0;
3620 // Steps 3m-n.
3621 double milli;
3622 if (args.length() >= 7) {
3623 if (!ToNumber(cx, args[6], &milli)) {
3624 return false;
3626 } else {
3627 milli = 0;
3630 // Step 3o.
3631 double yr = y;
3632 if (!std::isnan(y)) {
3633 double yint = ToInteger(y);
3634 if (0 <= yint && yint <= 99) {
3635 yr = 1900 + yint;
3639 // Step 3p.
3640 double finalDate = MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli));
3642 // Steps 3q-t.
3643 return NewDateObject(cx, args,
3644 TimeClip(UTC(ForceUTC(cx->realm()), finalDate)));
3647 return ToDateString(cx, args, NowAsMillis(cx));
3650 static bool DateConstructor(JSContext* cx, unsigned argc, Value* vp) {
3651 AutoJSConstructorProfilerEntry pseudoFrame(cx, "Date");
3652 CallArgs args = CallArgsFromVp(argc, vp);
3654 if (args.length() == 0) {
3655 return DateNoArguments(cx, args);
3658 if (args.length() == 1) {
3659 return DateOneArgument(cx, args);
3662 return DateMultipleArguments(cx, args);
3665 static bool FinishDateClassInit(JSContext* cx, HandleObject ctor,
3666 HandleObject proto) {
3668 * Date.prototype.toGMTString has the same initial value as
3669 * Date.prototype.toUTCString.
3671 RootedValue toUTCStringFun(cx);
3672 RootedId toUTCStringId(cx, NameToId(cx->names().toUTCString));
3673 RootedId toGMTStringId(cx, NameToId(cx->names().toGMTString));
3674 return NativeGetProperty(cx, proto.as<NativeObject>(), toUTCStringId,
3675 &toUTCStringFun) &&
3676 NativeDefineDataProperty(cx, proto.as<NativeObject>(), toGMTStringId,
3677 toUTCStringFun, 0);
3680 static const ClassSpec DateObjectClassSpec = {
3681 GenericCreateConstructor<DateConstructor, 7, gc::AllocKind::FUNCTION>,
3682 GenericCreatePrototype<DateObject>,
3683 date_static_methods,
3684 nullptr,
3685 date_methods,
3686 nullptr,
3687 FinishDateClassInit};
3689 const JSClass DateObject::class_ = {"Date",
3690 JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
3691 JSCLASS_HAS_CACHED_PROTO(JSProto_Date),
3692 JS_NULL_CLASS_OPS, &DateObjectClassSpec};
3694 const JSClass DateObject::protoClass_ = {
3695 "Date.prototype", JSCLASS_HAS_CACHED_PROTO(JSProto_Date), JS_NULL_CLASS_OPS,
3696 &DateObjectClassSpec};
3698 JSObject* js::NewDateObjectMsec(JSContext* cx, ClippedTime t,
3699 HandleObject proto /* = nullptr */) {
3700 DateObject* obj = NewObjectWithClassProto<DateObject>(cx, proto);
3701 if (!obj) {
3702 return nullptr;
3704 obj->setUTCTime(t);
3705 return obj;
3708 JS_PUBLIC_API JSObject* JS::NewDateObject(JSContext* cx, ClippedTime time) {
3709 AssertHeapIsIdle();
3710 CHECK_THREAD(cx);
3711 return NewDateObjectMsec(cx, time);
3714 JS_PUBLIC_API JSObject* js::NewDateObject(JSContext* cx, int year, int mon,
3715 int mday, int hour, int min,
3716 int sec) {
3717 MOZ_ASSERT(mon < 12);
3718 double msec_time =
3719 MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0.0));
3720 return NewDateObjectMsec(cx, TimeClip(UTC(ForceUTC(cx->realm()), msec_time)));
3723 JS_PUBLIC_API bool js::DateIsValid(JSContext* cx, HandleObject obj,
3724 bool* isValid) {
3725 ESClass cls;
3726 if (!GetBuiltinClass(cx, obj, &cls)) {
3727 return false;
3730 if (cls != ESClass::Date) {
3731 *isValid = false;
3732 return true;
3735 RootedValue unboxed(cx);
3736 if (!Unbox(cx, obj, &unboxed)) {
3737 return false;
3740 *isValid = !std::isnan(unboxed.toNumber());
3741 return true;
3744 JS_PUBLIC_API JSObject* JS::NewDateObject(JSContext* cx, int year, int mon,
3745 int mday, int hour, int min,
3746 int sec) {
3747 AssertHeapIsIdle();
3748 CHECK_THREAD(cx);
3749 return js::NewDateObject(cx, year, mon, mday, hour, min, sec);
3752 JS_PUBLIC_API bool JS::ObjectIsDate(JSContext* cx, Handle<JSObject*> obj,
3753 bool* isDate) {
3754 cx->check(obj);
3756 ESClass cls;
3757 if (!GetBuiltinClass(cx, obj, &cls)) {
3758 return false;
3761 *isDate = cls == ESClass::Date;
3762 return true;
3765 JS_PUBLIC_API bool js::DateGetMsecSinceEpoch(JSContext* cx, HandleObject obj,
3766 double* msecsSinceEpoch) {
3767 ESClass cls;
3768 if (!GetBuiltinClass(cx, obj, &cls)) {
3769 return false;
3772 if (cls != ESClass::Date) {
3773 *msecsSinceEpoch = 0;
3774 return true;
3777 RootedValue unboxed(cx);
3778 if (!Unbox(cx, obj, &unboxed)) {
3779 return false;
3782 *msecsSinceEpoch = unboxed.toNumber();
3783 return true;