Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / js / src / jsdate.cpp
blob7040213f5670dc9d683da94a6078c98bd874d531
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 JS::ReduceMicrosecondTimePrecisionCallback
502 JS::GetReduceMicrosecondTimePrecisionCallback() {
503 return sReduceMicrosecondTimePrecisionCallback;
506 JS_PUBLIC_API void JS::SetTimeResolutionUsec(uint32_t resolution, bool jitter) {
507 sResolutionUsec = resolution;
508 sJitter = jitter;
511 #if JS_HAS_INTL_API
512 // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
513 // 20.3.1.7 LocalTZA ( t, isUTC )
514 double DateTimeHelper::localTZA(DateTimeInfo::ForceUTC forceUTC, double t,
515 DateTimeInfo::TimeZoneOffset offset) {
516 MOZ_ASSERT(std::isfinite(t));
518 int64_t milliseconds = static_cast<int64_t>(t);
519 int32_t offsetMilliseconds =
520 DateTimeInfo::getOffsetMilliseconds(forceUTC, milliseconds, offset);
521 return static_cast<double>(offsetMilliseconds);
524 // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
525 // 20.3.1.8 LocalTime ( t )
526 double DateTimeHelper::localTime(DateTimeInfo::ForceUTC forceUTC, double t) {
527 if (!std::isfinite(t)) {
528 return GenericNaN();
531 MOZ_ASSERT(StartOfTime <= t && t <= EndOfTime);
532 return t + localTZA(forceUTC, t, DateTimeInfo::TimeZoneOffset::UTC);
535 // ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
536 // 20.3.1.9 UTC ( t )
537 double DateTimeHelper::UTC(DateTimeInfo::ForceUTC forceUTC, double t) {
538 if (!std::isfinite(t)) {
539 return GenericNaN();
542 if (t < (StartOfTime - msPerDay) || t > (EndOfTime + msPerDay)) {
543 return GenericNaN();
546 return t - localTZA(forceUTC, t, DateTimeInfo::TimeZoneOffset::Local);
548 #else
550 * Find a year for which any given date will fall on the same weekday.
552 * This function should be used with caution when used other than
553 * for determining DST; it hasn't been proven not to produce an
554 * incorrect year for times near year boundaries.
556 int DateTimeHelper::equivalentYearForDST(int year) {
558 * Years and leap years on which Jan 1 is a Sunday, Monday, etc.
560 * yearStartingWith[0][i] is an example non-leap year where
561 * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
563 * yearStartingWith[1][i] is an example leap year where
564 * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
566 * Keep two different mappings, one for past years (< 1970), and a
567 * different one for future years (> 2037).
569 static const int pastYearStartingWith[2][7] = {
570 {1978, 1973, 1974, 1975, 1981, 1971, 1977},
571 {1984, 1996, 1980, 1992, 1976, 1988, 1972}};
572 static const int futureYearStartingWith[2][7] = {
573 {2034, 2035, 2030, 2031, 2037, 2027, 2033},
574 {2012, 2024, 2036, 2020, 2032, 2016, 2028}};
576 int day = int(::DayFromYear(year) + 4) % 7;
577 if (day < 0) {
578 day += 7;
581 const auto& yearStartingWith =
582 year < 1970 ? pastYearStartingWith : futureYearStartingWith;
583 return yearStartingWith[IsLeapYear(year)][day];
586 // Return true if |t| is representable as a 32-bit time_t variable, that means
587 // the year is in [1970, 2038).
588 bool DateTimeHelper::isRepresentableAsTime32(double t) {
589 return 0.0 <= t && t < 2145916800000.0;
592 /* ES5 15.9.1.8. */
593 double DateTimeHelper::daylightSavingTA(DateTimeInfo::ForceUTC forceUTC,
594 double t) {
595 if (!std::isfinite(t)) {
596 return GenericNaN();
600 * If earlier than 1970 or after 2038, potentially beyond the ken of
601 * many OSes, map it to an equivalent year before asking.
603 if (!isRepresentableAsTime32(t)) {
604 int year = equivalentYearForDST(int(::YearFromTime(t)));
605 double day = MakeDay(year, ::MonthFromTime(t), DateFromTime(t));
606 t = MakeDate(day, TimeWithinDay(t));
609 int64_t utcMilliseconds = static_cast<int64_t>(t);
610 int32_t offsetMilliseconds =
611 DateTimeInfo::getDSTOffsetMilliseconds(forceUTC, utcMilliseconds);
612 return static_cast<double>(offsetMilliseconds);
615 double DateTimeHelper::adjustTime(DateTimeInfo::ForceUTC forceUTC,
616 double date) {
617 double localTZA = DateTimeInfo::localTZA(forceUTC);
618 double t = daylightSavingTA(forceUTC, date) + localTZA;
619 t = (localTZA >= 0) ? fmod(t, msPerDay) : -fmod(msPerDay - t, msPerDay);
620 return t;
623 /* ES5 15.9.1.9. */
624 double DateTimeHelper::localTime(DateTimeInfo::ForceUTC forceUTC, double t) {
625 return t + adjustTime(forceUTC, t);
628 double DateTimeHelper::UTC(DateTimeInfo::ForceUTC forceUTC, double t) {
629 // Following the ES2017 specification creates undesirable results at DST
630 // transitions. For example when transitioning from PST to PDT,
631 // |new Date(2016,2,13,2,0,0).toTimeString()| returns the string value
632 // "01:00:00 GMT-0800 (PST)" instead of "03:00:00 GMT-0700 (PDT)". Follow
633 // V8 and subtract one hour before computing the offset.
634 // Spec bug: https://bugs.ecmascript.org/show_bug.cgi?id=4007
636 return t -
637 adjustTime(forceUTC, t - DateTimeInfo::localTZA(forceUTC) - msPerHour);
639 #endif /* JS_HAS_INTL_API */
641 static double LocalTime(DateTimeInfo::ForceUTC forceUTC, double t) {
642 return DateTimeHelper::localTime(forceUTC, t);
645 static double UTC(DateTimeInfo::ForceUTC forceUTC, double t) {
646 return DateTimeHelper::UTC(forceUTC, t);
649 /* ES5 15.9.1.10. */
650 static double HourFromTime(double t) {
651 return PositiveModulo(floor(t / msPerHour), HoursPerDay);
654 static double MinFromTime(double t) {
655 return PositiveModulo(floor(t / msPerMinute), MinutesPerHour);
658 static double SecFromTime(double t) {
659 return PositiveModulo(floor(t / msPerSecond), SecondsPerMinute);
662 static double msFromTime(double t) { return PositiveModulo(t, msPerSecond); }
664 /* ES5 15.9.1.11. */
665 static double MakeTime(double hour, double min, double sec, double ms) {
666 /* Step 1. */
667 if (!std::isfinite(hour) || !std::isfinite(min) || !std::isfinite(sec) ||
668 !std::isfinite(ms)) {
669 return GenericNaN();
672 /* Step 2. */
673 double h = ToInteger(hour);
675 /* Step 3. */
676 double m = ToInteger(min);
678 /* Step 4. */
679 double s = ToInteger(sec);
681 /* Step 5. */
682 double milli = ToInteger(ms);
684 /* Steps 6-7. */
685 return h * msPerHour + m * msPerMinute + s * msPerSecond + milli;
689 * end of ECMA 'support' functions
692 // ES2017 draft rev (TODO: Add git hash when PR 642 is merged.)
693 // 20.3.3.4
694 // Date.UTC(year [, month [, date [, hours [, minutes [, seconds [, ms]]]]]])
695 static bool date_UTC(JSContext* cx, unsigned argc, Value* vp) {
696 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date", "UTC");
697 CallArgs args = CallArgsFromVp(argc, vp);
699 // Step 1.
700 double y;
701 if (!ToNumber(cx, args.get(0), &y)) {
702 return false;
705 // Step 2.
706 double m;
707 if (args.length() >= 2) {
708 if (!ToNumber(cx, args[1], &m)) {
709 return false;
711 } else {
712 m = 0;
715 // Step 3.
716 double dt;
717 if (args.length() >= 3) {
718 if (!ToNumber(cx, args[2], &dt)) {
719 return false;
721 } else {
722 dt = 1;
725 // Step 4.
726 double h;
727 if (args.length() >= 4) {
728 if (!ToNumber(cx, args[3], &h)) {
729 return false;
731 } else {
732 h = 0;
735 // Step 5.
736 double min;
737 if (args.length() >= 5) {
738 if (!ToNumber(cx, args[4], &min)) {
739 return false;
741 } else {
742 min = 0;
745 // Step 6.
746 double s;
747 if (args.length() >= 6) {
748 if (!ToNumber(cx, args[5], &s)) {
749 return false;
751 } else {
752 s = 0;
755 // Step 7.
756 double milli;
757 if (args.length() >= 7) {
758 if (!ToNumber(cx, args[6], &milli)) {
759 return false;
761 } else {
762 milli = 0;
765 // Step 8.
766 double yr = y;
767 if (!std::isnan(y)) {
768 double yint = ToInteger(y);
769 if (0 <= yint && yint <= 99) {
770 yr = 1900 + yint;
774 // Step 9.
775 ClippedTime time =
776 TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)));
777 args.rval().set(TimeValue(time));
778 return true;
782 * Read and convert decimal digits from s[*i] into *result
783 * while *i < limit.
785 * Succeed if any digits are converted. Advance *i only
786 * as digits are consumed.
788 template <typename CharT>
789 static bool ParseDigits(size_t* result, const CharT* s, size_t* i,
790 size_t limit) {
791 size_t init = *i;
792 *result = 0;
793 while (*i < limit && ('0' <= s[*i] && s[*i] <= '9')) {
794 *result *= 10;
795 *result += (s[*i] - '0');
796 ++(*i);
798 return *i != init;
802 * Read and convert decimal digits to the right of a decimal point,
803 * representing a fractional integer, from s[*i] into *result
804 * while *i < limit, up to 3 digits. Consumes any digits beyond 3
805 * without affecting the result.
807 * Succeed if any digits are converted. Advance *i only
808 * as digits are consumed.
810 template <typename CharT>
811 static bool ParseFractional(int* result, const CharT* s, size_t* i,
812 size_t limit) {
813 int factor = 100;
814 size_t init = *i;
815 *result = 0;
816 for (; *i < limit && ('0' <= s[*i] && s[*i] <= '9'); ++(*i)) {
817 if (*i - init >= 3) {
818 // If we're past 3 digits, do nothing with it, but continue to
819 // consume the remainder of the digits
820 continue;
822 *result += (s[*i] - '0') * factor;
823 factor /= 10;
825 return *i != init;
829 * Read and convert exactly n decimal digits from s[*i]
830 * to s[min(*i+n,limit)] into *result.
832 * Succeed if exactly n digits are converted. Advance *i only
833 * on success.
835 template <typename CharT>
836 static bool ParseDigitsN(size_t n, size_t* result, const CharT* s, size_t* i,
837 size_t limit) {
838 size_t init = *i;
840 if (ParseDigits(result, s, i, std::min(limit, init + n))) {
841 return (*i - init) == n;
844 *i = init;
845 return false;
849 * Read and convert n or less decimal digits from s[*i]
850 * to s[min(*i+n,limit)] into *result.
852 * Succeed only if greater than zero but less than or equal to n digits are
853 * converted. Advance *i only on success.
855 template <typename CharT>
856 static bool ParseDigitsNOrLess(size_t n, size_t* result, const CharT* s,
857 size_t* i, size_t limit) {
858 size_t init = *i;
860 if (ParseDigits(result, s, i, std::min(limit, init + n))) {
861 return ((*i - init) > 0) && ((*i - init) <= n);
864 *i = init;
865 return false;
869 * Parse a string according to the formats specified in the standard:
871 * https://tc39.es/ecma262/#sec-date-time-string-format
872 * https://tc39.es/ecma262/#sec-expanded-years
874 * These formats are based upon a simplification of the ISO 8601 Extended
875 * Format. As per the spec omitted month and day values are defaulted to '01',
876 * omitted HH:mm:ss values are defaulted to '00' and an omitted sss field is
877 * defaulted to '000'.
879 * For cross compatibility we allow the following extensions.
881 * These are:
883 * One or more decimal digits for milliseconds:
884 * The specification requires exactly three decimal digits for
885 * the fractional part but we allow for one or more digits.
887 * Time zone specifier without ':':
888 * We allow the time zone to be specified without a ':' character.
889 * E.g. "T19:00:00+0700" is equivalent to "T19:00:00+07:00".
891 * Date part:
893 * Year:
894 * YYYY (eg 1997)
896 * Year and month:
897 * YYYY-MM (eg 1997-07)
899 * Complete date:
900 * YYYY-MM-DD (eg 1997-07-16)
902 * Time part:
904 * Hours and minutes:
905 * Thh:mmTZD (eg T19:20+01:00)
907 * Hours, minutes and seconds:
908 * Thh:mm:ssTZD (eg T19:20:30+01:00)
910 * Hours, minutes, seconds and a decimal fraction of a second:
911 * Thh:mm:ss.sssTZD (eg T19:20:30.45+01:00)
913 * where:
915 * YYYY = four-digit year or six digit year as +YYYYYY or -YYYYYY
916 * MM = two-digit month (01=January, etc.)
917 * DD = two-digit day of month (01 through 31)
918 * hh = two digits of hour (00 through 24) (am/pm NOT allowed)
919 * mm = two digits of minute (00 through 59)
920 * ss = two digits of second (00 through 59)
921 * sss = one or more digits representing a decimal fraction of a second
922 * TZD = time zone designator (Z or +hh:mm or -hh:mm or missing for local)
924 template <typename CharT>
925 static bool ParseISOStyleDate(DateTimeInfo::ForceUTC forceUTC, const CharT* s,
926 size_t length, ClippedTime* result) {
927 size_t i = 0;
928 int tzMul = 1;
929 int dateMul = 1;
930 size_t year = 1970;
931 size_t month = 1;
932 size_t day = 1;
933 size_t hour = 0;
934 size_t min = 0;
935 size_t sec = 0;
936 int msec = 0;
937 bool isLocalTime = false;
938 size_t tzHour = 0;
939 size_t tzMin = 0;
941 #define PEEK(ch) (i < length && s[i] == ch)
943 #define NEED(ch) \
944 if (i >= length || s[i] != ch) { \
945 return false; \
946 } else { \
947 ++i; \
950 #define DONE_DATE_UNLESS(ch) \
951 if (i >= length || s[i] != ch) { \
952 goto done_date; \
953 } else { \
954 ++i; \
957 #define DONE_UNLESS(ch) \
958 if (i >= length || s[i] != ch) { \
959 goto done; \
960 } else { \
961 ++i; \
964 #define NEED_NDIGITS(n, field) \
965 if (!ParseDigitsN(n, &field, s, &i, length)) { \
966 return false; \
969 if (PEEK('+') || PEEK('-')) {
970 if (PEEK('-')) {
971 dateMul = -1;
973 ++i;
974 NEED_NDIGITS(6, year);
976 // https://tc39.es/ecma262/#sec-expanded-years
977 // -000000 is not a valid expanded year.
978 if (year == 0 && dateMul == -1) {
979 return false;
981 } else {
982 NEED_NDIGITS(4, year);
984 DONE_DATE_UNLESS('-');
985 NEED_NDIGITS(2, month);
986 DONE_DATE_UNLESS('-');
987 NEED_NDIGITS(2, day);
989 done_date:
990 if (PEEK('T')) {
991 ++i;
992 } else {
993 goto done;
996 NEED_NDIGITS(2, hour);
997 NEED(':');
998 NEED_NDIGITS(2, min);
1000 if (PEEK(':')) {
1001 ++i;
1002 NEED_NDIGITS(2, sec);
1003 if (PEEK('.')) {
1004 ++i;
1005 if (!ParseFractional(&msec, s, &i, length)) {
1006 return false;
1011 if (PEEK('Z')) {
1012 ++i;
1013 } else if (PEEK('+') || PEEK('-')) {
1014 if (PEEK('-')) {
1015 tzMul = -1;
1017 ++i;
1018 NEED_NDIGITS(2, tzHour);
1020 * Non-standard extension to the ISO date format (permitted by ES5):
1021 * allow "-0700" as a time zone offset, not just "-07:00".
1023 if (PEEK(':')) {
1024 ++i;
1026 NEED_NDIGITS(2, tzMin);
1027 } else {
1028 isLocalTime = true;
1031 done:
1032 if (year > 275943 // ceil(1e8/365) + 1970
1033 || month == 0 || month > 12 || day == 0 || day > 31 || hour > 24 ||
1034 (hour == 24 && (min > 0 || sec > 0 || msec > 0)) || min > 59 ||
1035 sec > 59 || tzHour > 23 || tzMin > 59) {
1036 return false;
1039 if (i != length) {
1040 return false;
1043 month -= 1; /* convert month to 0-based */
1045 double date = MakeDate(MakeDay(dateMul * double(year), month, day),
1046 MakeTime(hour, min, sec, msec));
1048 if (isLocalTime) {
1049 date = UTC(forceUTC, date);
1050 } else {
1051 date -= tzMul * (tzHour * msPerHour + tzMin * msPerMinute);
1054 *result = TimeClip(date);
1055 return NumbersAreIdentical(date, result->toDouble());
1057 #undef PEEK
1058 #undef NEED
1059 #undef DONE_UNLESS
1060 #undef NEED_NDIGITS
1063 int FixupNonFullYear(int year) {
1064 if (year < 50) {
1065 year += 2000;
1066 } else if (year >= 50 && year < 100) {
1067 year += 1900;
1069 return year;
1072 template <typename CharT>
1073 bool MatchesKeyword(const CharT* s, size_t len, const char* keyword) {
1074 while (len > 0) {
1075 MOZ_ASSERT(IsAsciiAlpha(*s));
1076 MOZ_ASSERT(IsAsciiLowercaseAlpha(*keyword) || *keyword == '\0');
1078 if (unicode::ToLowerCase(static_cast<Latin1Char>(*s)) != *keyword) {
1079 return false;
1082 ++s, ++keyword;
1083 --len;
1086 return *keyword == '\0';
1089 static constexpr const char* const month_prefixes[] = {
1090 "jan", "feb", "mar", "apr", "may", "jun",
1091 "jul", "aug", "sep", "oct", "nov", "dec",
1095 * Given a string s of length >= 3, checks if it begins,
1096 * case-insensitive, with the given lower case prefix.
1098 template <typename CharT>
1099 bool StartsWithMonthPrefix(const CharT* s, const char* prefix) {
1100 MOZ_ASSERT(strlen(prefix) == 3);
1102 for (size_t i = 0; i < 3; ++i) {
1103 MOZ_ASSERT(IsAsciiAlpha(*s));
1104 MOZ_ASSERT(IsAsciiLowercaseAlpha(*prefix));
1106 if (unicode::ToLowerCase(static_cast<Latin1Char>(*s)) != *prefix) {
1107 return false;
1110 ++s, ++prefix;
1113 return true;
1116 template <typename CharT>
1117 bool IsMonthName(const CharT* s, size_t len, int* mon) {
1118 // Month abbreviations < 3 chars are not accepted.
1119 if (len < 3) {
1120 return false;
1123 for (size_t m = 0; m < std::size(month_prefixes); ++m) {
1124 if (StartsWithMonthPrefix(s, month_prefixes[m])) {
1125 // Use numeric value.
1126 *mon = m + 1;
1127 return true;
1131 return false;
1135 * Try to parse the following date formats:
1136 * dd-MMM-yyyy
1137 * dd-MMM-yy
1138 * MMM-dd-yyyy
1139 * MMM-dd-yy
1140 * yyyy-MMM-dd
1141 * yy-MMM-dd
1143 * Returns true and fills all out parameters when successfully parsed
1144 * dashed-date. Otherwise returns false and leaves out parameters untouched.
1146 template <typename CharT>
1147 static bool TryParseDashedDatePrefix(const CharT* s, size_t length,
1148 size_t* indexOut, int* yearOut,
1149 int* monOut, int* mdayOut) {
1150 size_t i = *indexOut;
1152 size_t pre = i;
1153 size_t mday;
1154 if (!ParseDigitsNOrLess(6, &mday, s, &i, length)) {
1155 return false;
1157 size_t mdayDigits = i - pre;
1159 if (i >= length || s[i] != '-') {
1160 return false;
1162 ++i;
1164 int mon = 0;
1165 if (*monOut == -1) {
1166 // If month wasn't already set by ParseDate, it must be in the middle of
1167 // this format, let's look for it
1168 size_t start = i;
1169 for (; i < length; i++) {
1170 if (!IsAsciiAlpha(s[i])) {
1171 break;
1175 if (!IsMonthName(s + start, i - start, &mon)) {
1176 return false;
1179 if (i >= length || s[i] != '-') {
1180 return false;
1182 ++i;
1185 pre = i;
1186 size_t year;
1187 if (!ParseDigitsNOrLess(6, &year, s, &i, length)) {
1188 return false;
1190 size_t yearDigits = i - pre;
1192 if (i < length && IsAsciiDigit(s[i])) {
1193 return false;
1196 // Swap the mday and year if the year wasn't specified in full.
1197 if (mday > 31 && year <= 31 && yearDigits < 4) {
1198 std::swap(mday, year);
1199 std::swap(mdayDigits, yearDigits);
1202 if (mday > 31 || mdayDigits > 2) {
1203 return false;
1206 if (yearDigits < 4) {
1207 year = FixupNonFullYear(year);
1210 *indexOut = i;
1211 *yearOut = year;
1212 if (*monOut == -1) {
1213 *monOut = mon;
1215 *mdayOut = mday;
1216 return true;
1220 * Try to parse dates in the style of YYYY-MM-DD which do not conform to
1221 * the formal standard from ParseISOStyleDate. This includes cases such as
1223 * - Year does not have 4 digits
1224 * - Month or mday has 1 digit
1225 * - Space in between date and time, rather than a 'T'
1227 * Regarding the last case, this function only parses out the date, returning
1228 * to ParseDate to finish parsing the time and timezone, if present.
1230 * Returns true and fills all out parameters when successfully parsed
1231 * dashed-date. Otherwise returns false and leaves out parameters untouched.
1233 template <typename CharT>
1234 static bool TryParseDashedNumericDatePrefix(const CharT* s, size_t length,
1235 size_t* indexOut, int* yearOut,
1236 int* monOut, int* mdayOut) {
1237 size_t i = *indexOut;
1239 size_t first;
1240 if (!ParseDigitsNOrLess(6, &first, s, &i, length)) {
1241 return false;
1244 if (i >= length || s[i] != '-') {
1245 return false;
1247 ++i;
1249 size_t second;
1250 if (!ParseDigitsNOrLess(2, &second, s, &i, length)) {
1251 return false;
1254 if (i >= length || s[i] != '-') {
1255 return false;
1257 ++i;
1259 size_t third;
1260 if (!ParseDigitsNOrLess(6, &third, s, &i, length)) {
1261 return false;
1264 int year;
1265 int mon = -1;
1266 int mday = -1;
1268 // 1 or 2 digits for the first number is tricky; 1-12 means it's a month, 0 or
1269 // >31 means it's a year, and 13-31 is invalid due to ambiguity.
1270 if (first >= 1 && first <= 12) {
1271 mon = first;
1272 } else if (first == 0 || first > 31) {
1273 year = first;
1274 } else {
1275 return false;
1278 if (mon < 0) {
1279 // If month hasn't been set yet, it's definitely the 2nd number
1280 mon = second;
1281 } else {
1282 // If it has, the next number is the mday
1283 mday = second;
1286 if (mday < 0) {
1287 // The third number is probably the mday...
1288 mday = third;
1289 } else {
1290 // But otherwise, it's the year
1291 year = third;
1294 if (mon < 1 || mon > 12 || mday < 1 || mday > 31) {
1295 return false;
1298 if (year < 100) {
1299 year = FixupNonFullYear(year);
1302 *indexOut = i;
1303 *yearOut = year;
1304 *monOut = mon;
1305 *mdayOut = mday;
1306 return true;
1309 struct CharsAndAction {
1310 const char* chars;
1311 int action;
1314 static constexpr CharsAndAction keywords[] = {
1315 // clang-format off
1316 // AM/PM
1317 { "am", -1 },
1318 { "pm", -2 },
1319 // Time zone abbreviations.
1320 { "gmt", 10000 + 0 },
1321 { "z", 10000 + 0 },
1322 { "ut", 10000 + 0 },
1323 { "utc", 10000 + 0 },
1324 { "est", 10000 + 5 * 60 },
1325 { "edt", 10000 + 4 * 60 },
1326 { "cst", 10000 + 6 * 60 },
1327 { "cdt", 10000 + 5 * 60 },
1328 { "mst", 10000 + 7 * 60 },
1329 { "mdt", 10000 + 6 * 60 },
1330 { "pst", 10000 + 8 * 60 },
1331 { "pdt", 10000 + 7 * 60 },
1332 // clang-format on
1335 template <size_t N>
1336 constexpr size_t MinKeywordLength(const CharsAndAction (&keywords)[N]) {
1337 size_t min = size_t(-1);
1338 for (const CharsAndAction& keyword : keywords) {
1339 min = std::min(min, std::char_traits<char>::length(keyword.chars));
1341 return min;
1344 template <typename CharT>
1345 static bool ParseDate(DateTimeInfo::ForceUTC forceUTC, const CharT* s,
1346 size_t length, ClippedTime* result) {
1347 if (length == 0) {
1348 return false;
1351 if (ParseISOStyleDate(forceUTC, s, length, result)) {
1352 return true;
1355 size_t index = 0;
1356 int mon = -1;
1357 bool seenMonthName = false;
1359 // Before we begin, we need to scrub any words from the beginning of the
1360 // string up to the first number, recording the month if we encounter it
1361 for (; index < length; index++) {
1362 int c = s[index];
1364 if (strchr(" ,.-/", c)) {
1365 continue;
1367 if (!IsAsciiAlpha(c)) {
1368 break;
1371 size_t start = index;
1372 index++;
1373 for (; index < length; index++) {
1374 if (!IsAsciiAlpha(s[index])) {
1375 break;
1379 if (index >= length) {
1380 return false;
1383 if (IsMonthName(s + start, index - start, &mon)) {
1384 seenMonthName = true;
1385 // If the next digit is a number, we need to break so it
1386 // gets parsed as mday
1387 if (IsAsciiDigit(s[index])) {
1388 break;
1390 } else {
1391 // Reject numbers directly after letters e.g. foo2
1392 if (IsAsciiDigit(s[index]) && IsAsciiAlpha(s[index - 1])) {
1393 return false;
1398 int year = -1;
1399 int mday = -1;
1400 int hour = -1;
1401 int min = -1;
1402 int sec = -1;
1403 int msec = 0;
1404 int tzOffset = -1;
1406 // One of '+', '-', ':', '/', or 0 (the default value).
1407 int prevc = 0;
1409 bool seenPlusMinus = false;
1410 bool seenFullYear = false;
1411 bool negativeYear = false;
1412 // Includes "GMT", "UTC", "UT", and "Z" timezone keywords
1413 bool seenGmtAbbr = false;
1415 // Try parsing the leading dashed-date.
1417 // If successfully parsed, index is updated to the end of the date part,
1418 // and year, mon, mday are set to the date.
1419 // Continue parsing optional time + tzOffset parts.
1421 // Otherwise, this is no-op.
1422 bool isDashedDate =
1423 TryParseDashedDatePrefix(s, length, &index, &year, &mon, &mday) ||
1424 TryParseDashedNumericDatePrefix(s, length, &index, &year, &mon, &mday);
1426 if (isDashedDate && index < length && strchr("T:+", s[index])) {
1427 return false;
1430 while (index < length) {
1431 int c = s[index];
1432 index++;
1434 // Normalize U+202F (NARROW NO-BREAK SPACE). This character appears between
1435 // the AM/PM markers for |date.toLocaleString("en")|. We have to normalize
1436 // it for backward compatibility reasons.
1437 if (c == 0x202F) {
1438 c = ' ';
1441 if ((c == '+' || c == '-') &&
1442 // Reject + or - after timezone (still allowing for negative year)
1443 ((seenPlusMinus && year != -1) ||
1444 // Reject timezones like "1995-09-26 -04:30" (if the - is right up
1445 // against the previous number, it will get parsed as a time,
1446 // see the other comment below)
1447 (year != -1 && hour == -1 && !seenGmtAbbr &&
1448 !IsAsciiDigit(s[index - 2])))) {
1449 return false;
1452 // Spaces, ASCII control characters, periods, and commas are simply ignored.
1453 if (c <= ' ' || c == '.' || c == ',') {
1454 continue;
1457 // Parse delimiter characters. Save them to the side for future use.
1458 if (c == '/' || c == ':' || c == '+') {
1459 prevc = c;
1460 continue;
1463 // Dashes are delimiters if they're immediately followed by a number field.
1464 // If they're not followed by a number field, they're simply ignored.
1465 if (c == '-') {
1466 if (index < length && IsAsciiDigit(s[index])) {
1467 prevc = c;
1469 continue;
1472 // Skip over comments -- text inside matching parentheses. (Comments
1473 // themselves may contain comments as long as all the parentheses properly
1474 // match up. And apparently comments, including nested ones, may validly be
1475 // terminated by end of input...)
1476 if (c == '(') {
1477 int depth = 1;
1478 while (index < length) {
1479 c = s[index];
1480 index++;
1481 if (c == '(') {
1482 depth++;
1483 } else if (c == ')') {
1484 if (--depth <= 0) {
1485 break;
1489 continue;
1492 // Parse a number field.
1493 if (IsAsciiDigit(c)) {
1494 size_t partStart = index - 1;
1495 uint32_t u = c - '0';
1496 while (index < length) {
1497 c = s[index];
1498 if (!IsAsciiDigit(c)) {
1499 break;
1501 u = u * 10 + (c - '0');
1502 index++;
1504 size_t partLength = index - partStart;
1506 // See above for why we have to normalize U+202F.
1507 if (c == 0x202F) {
1508 c = ' ';
1511 int n = int(u);
1514 * Allow TZA before the year, so 'Wed Nov 05 21:49:11 GMT-0800 1997'
1515 * works.
1517 * Uses of seenPlusMinus allow ':' in TZA, so Java no-timezone style
1518 * of GMT+4:30 works.
1521 if (prevc == '-' && (tzOffset != 0 || seenPlusMinus) && partLength >= 4 &&
1522 year < 0) {
1523 // Parse as a negative, possibly zero-padded year if
1524 // 1. the preceding character is '-',
1525 // 2. the TZA is not 'GMT' (tested by |tzOffset != 0|),
1526 // 3. or a TZA was already parsed |seenPlusMinus == true|,
1527 // 4. the part length is at least 4 (to parse '-08' as a TZA),
1528 // 5. and we did not already parse a year |year < 0|.
1529 year = n;
1530 seenFullYear = true;
1531 negativeYear = true;
1532 } else if ((prevc == '+' || prevc == '-') &&
1533 // "1995-09-26-04:30" needs to be parsed as a time,
1534 // not a time zone
1535 (seenGmtAbbr || hour != -1)) {
1536 /* Make ':' case below change tzOffset. */
1537 seenPlusMinus = true;
1539 /* offset */
1540 if (n < 24 && partLength <= 2) {
1541 n = n * 60; /* EG. "GMT-3" */
1542 } else {
1543 n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */
1546 if (prevc == '+') /* plus means east of GMT */
1547 n = -n;
1549 // Reject if not preceded by 'GMT' or if a time zone offset
1550 // was already parsed.
1551 if (tzOffset != 0 && tzOffset != -1) {
1552 return false;
1555 tzOffset = n;
1556 } else if (prevc == '/' && mon >= 0 && mday >= 0 && year < 0) {
1557 if (c <= ' ' || c == ',' || c == '/' || index >= length) {
1558 year = n;
1559 } else {
1560 return false;
1562 } else if (c == ':') {
1563 if (hour < 0) {
1564 hour = /*byte*/ n;
1565 } else if (min < 0) {
1566 min = /*byte*/ n;
1567 } else {
1568 return false;
1570 } else if (c == '/') {
1572 * Until it is determined that mon is the actual month, keep
1573 * it as 1-based rather than 0-based.
1575 if (mon < 0) {
1576 mon = /*byte*/ n;
1577 } else if (mday < 0) {
1578 mday = /*byte*/ n;
1579 } else {
1580 return false;
1582 } else if (index < length && c != ',' && c > ' ' && c != '-' &&
1583 c != '(' &&
1584 // Allow '.' as a delimiter until seconds have been parsed
1585 // (this allows the decimal for milliseconds)
1586 (c != '.' || sec != -1) &&
1587 // Allow zulu time e.g. "09/26/1995 16:00Z", or
1588 // '+' directly after time e.g. 00:00+0500
1589 !(hour != -1 && strchr("Zz+", c)) &&
1590 // Allow month or AM/PM directly after a number
1591 (!IsAsciiAlpha(c) ||
1592 (mon != -1 && !(strchr("AaPp", c) && index < length - 1 &&
1593 strchr("Mm", s[index + 1]))))) {
1594 return false;
1595 } else if (seenPlusMinus && n < 60) { /* handle GMT-3:30 */
1596 if (tzOffset < 0) {
1597 tzOffset -= n;
1598 } else {
1599 tzOffset += n;
1601 } else if (hour >= 0 && min < 0) {
1602 min = /*byte*/ n;
1603 } else if (prevc == ':' && min >= 0 && sec < 0) {
1604 sec = /*byte*/ n;
1605 if (c == '.') {
1606 index++;
1607 if (!ParseFractional(&msec, s, &index, length)) {
1608 return false;
1611 } else if (mon < 0) {
1612 mon = /*byte*/ n;
1613 } else if (mon >= 0 && mday < 0) {
1614 mday = /*byte*/ n;
1615 } else if (mon >= 0 && mday >= 0 && year < 0) {
1616 year = n;
1617 seenFullYear = partLength >= 4;
1618 } else {
1619 return false;
1622 prevc = 0;
1623 continue;
1626 // Parse fields that are words: ASCII letters spelling out in English AM/PM,
1627 // day of week, month, or an extremely limited set of legacy time zone
1628 // abbreviations.
1629 if (IsAsciiAlpha(c)) {
1630 size_t start = index - 1;
1631 while (index < length) {
1632 c = s[index];
1633 if (!IsAsciiAlpha(c)) {
1634 break;
1636 index++;
1639 // There must be at least as many letters as in the shortest keyword.
1640 constexpr size_t MinLength = MinKeywordLength(keywords);
1641 if (index - start < MinLength) {
1642 return false;
1645 // Record a month if it is a month name. Note that some numbers are
1646 // initially treated as months; if a numeric field has already been
1647 // interpreted as a month, store that value to the actually appropriate
1648 // date component and set the month here.
1649 int tryMonth;
1650 if (IsMonthName(s + start, index - start, &tryMonth)) {
1651 if (seenMonthName) {
1652 // Overwrite the previous month name
1653 mon = tryMonth;
1654 prevc = 0;
1655 continue;
1658 seenMonthName = true;
1660 if (mon < 0) {
1661 mon = tryMonth;
1662 } else if (mday < 0) {
1663 mday = mon;
1664 mon = tryMonth;
1665 } else if (year < 0) {
1666 if (mday > 0) {
1667 // If the date is of the form f l month, then when month is
1668 // reached we have f in mon and l in mday. In order to be
1669 // consistent with the f month l and month f l forms, we need to
1670 // swap so that f is in mday and l is in year.
1671 year = mday;
1672 mday = mon;
1673 } else {
1674 year = mon;
1676 mon = tryMonth;
1677 } else {
1678 return false;
1681 prevc = 0;
1682 continue;
1685 size_t k = std::size(keywords);
1686 while (k-- > 0) {
1687 const CharsAndAction& keyword = keywords[k];
1689 // If the field doesn't match the keyword, try the next one.
1690 if (!MatchesKeyword(s + start, index - start, keyword.chars)) {
1691 continue;
1694 int action = keyword.action;
1696 if (action == 10000) {
1697 seenGmtAbbr = true;
1700 // Perform action tests from smallest action values to largest.
1702 // Adjust a previously-specified hour for AM/PM accordingly (taking care
1703 // to treat 12:xx AM as 00:xx, 12:xx PM as 12:xx).
1704 if (action < 0) {
1705 MOZ_ASSERT(action == -1 || action == -2);
1706 if (hour > 12 || hour < 0) {
1707 return false;
1710 if (action == -1 && hour == 12) {
1711 hour = 0;
1712 } else if (action == -2 && hour != 12) {
1713 hour += 12;
1716 break;
1719 // Finally, record a time zone offset.
1720 MOZ_ASSERT(action >= 10000);
1721 tzOffset = action - 10000;
1722 break;
1725 if (k == size_t(-1)) {
1726 return false;
1729 prevc = 0;
1730 continue;
1733 // Any other character fails to parse.
1734 return false;
1737 // Handle cases where the input is a single number. Single numbers >= 1000
1738 // are handled by the spec (ParseISOStyleDate), so we don't need to account
1739 // for that here.
1740 if (mon != -1 && year < 0 && mday < 0) {
1741 // Reject 13-31 for Chrome parity
1742 if (mon >= 13 && mon <= 31) {
1743 return false;
1746 mday = 1;
1747 if (mon >= 1 && mon <= 12) {
1748 // 1-12 is parsed as a month with the year defaulted to 2001
1749 // (again, for Chrome parity)
1750 year = 2001;
1751 } else {
1752 year = FixupNonFullYear(mon);
1753 mon = 1;
1757 if (year < 0 || mon < 0 || mday < 0) {
1758 return false;
1761 if (!isDashedDate) {
1762 // NOTE: TryParseDashedDatePrefix already handles the following fixup.
1765 * Case 1. The input string contains an English month name.
1766 * The form of the string can be month f l, or f month l, or
1767 * f l month which each evaluate to the same date.
1768 * If f and l are both greater than or equal to 100 the date
1769 * is invalid.
1771 * The year is taken to be either l, f if f > 31, or whichever
1772 * is set to zero.
1774 * Case 2. The input string is of the form "f/m/l" where f, m and l are
1775 * integers, e.g. 7/16/45. mon, mday and year values are adjusted
1776 * to achieve Chrome compatibility.
1778 * a. If 0 < f <= 12 and 0 < l <= 31, f/m/l is interpreted as
1779 * month/day/year.
1780 * b. If 31 < f and 0 < m <= 12 and 0 < l <= 31 f/m/l is
1781 * interpreted as year/month/day
1783 if (seenMonthName) {
1784 if (mday >= 100 && mon >= 100) {
1785 return false;
1788 if (year > 0 && (mday == 0 || mday > 31) && !seenFullYear) {
1789 int temp = year;
1790 year = mday;
1791 mday = temp;
1794 if (mday <= 0 || mday > 31) {
1795 return false;
1798 } else if (0 < mon && mon <= 12 && 0 < mday && mday <= 31) {
1799 /* (a) month/day/year */
1800 } else {
1801 /* (b) year/month/day */
1802 if (mon > 31 && mday <= 12 && year <= 31 && !seenFullYear) {
1803 int temp = year;
1804 year = mon;
1805 mon = mday;
1806 mday = temp;
1807 } else {
1808 return false;
1812 // If the year is greater than or equal to 50 and less than 100, it is
1813 // considered to be the number of years after 1900. If the year is less
1814 // than 50 it is considered to be the number of years after 2000,
1815 // otherwise it is considered to be the number of years after 0.
1816 if (!seenFullYear) {
1817 year = FixupNonFullYear(year);
1820 if (negativeYear) {
1821 year = -year;
1825 mon -= 1; /* convert month to 0-based */
1826 if (sec < 0) {
1827 sec = 0;
1829 if (min < 0) {
1830 min = 0;
1832 if (hour < 0) {
1833 hour = 0;
1836 double date =
1837 MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, msec));
1839 if (tzOffset == -1) { /* no time zone specified, have to use local */
1840 date = UTC(forceUTC, date);
1841 } else {
1842 date += tzOffset * msPerMinute;
1845 *result = TimeClip(date);
1846 return true;
1849 static bool ParseDate(DateTimeInfo::ForceUTC forceUTC, JSLinearString* s,
1850 ClippedTime* result) {
1851 AutoCheckCannotGC nogc;
1852 return s->hasLatin1Chars()
1853 ? ParseDate(forceUTC, s->latin1Chars(nogc), s->length(), result)
1854 : ParseDate(forceUTC, s->twoByteChars(nogc), s->length(), result);
1857 static bool date_parse(JSContext* cx, unsigned argc, Value* vp) {
1858 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date", "parse");
1859 CallArgs args = CallArgsFromVp(argc, vp);
1860 if (args.length() == 0) {
1861 args.rval().setNaN();
1862 return true;
1865 JSString* str = ToString<CanGC>(cx, args[0]);
1866 if (!str) {
1867 return false;
1870 JSLinearString* linearStr = str->ensureLinear(cx);
1871 if (!linearStr) {
1872 return false;
1875 ClippedTime result;
1876 if (!ParseDate(ForceUTC(cx->realm()), linearStr, &result)) {
1877 args.rval().setNaN();
1878 return true;
1881 args.rval().set(TimeValue(result));
1882 return true;
1885 static ClippedTime NowAsMillis(JSContext* cx) {
1886 if (js::SupportDifferentialTesting()) {
1887 return TimeClip(0);
1890 double now = PRMJ_Now();
1891 bool clampAndJitter = cx->realm()->behaviors().clampAndJitterTime();
1892 if (clampAndJitter && sReduceMicrosecondTimePrecisionCallback) {
1893 now = sReduceMicrosecondTimePrecisionCallback(
1894 now, cx->realm()->behaviors().reduceTimerPrecisionCallerType().value(),
1895 cx);
1896 } else if (clampAndJitter && sResolutionUsec) {
1897 double clamped = floor(now / sResolutionUsec) * sResolutionUsec;
1899 if (sJitter) {
1900 // Calculate a random midpoint for jittering. In the browser, we are
1901 // adversarial: Web Content may try to calculate the midpoint themselves
1902 // and use that to bypass it's security. In the JS Shell, we are not
1903 // adversarial, we want to jitter the time to recreate the operating
1904 // environment, but we do not concern ourselves with trying to prevent an
1905 // attacker from calculating the midpoint themselves. So we use a very
1906 // simple, very fast CRC with a hardcoded seed.
1908 uint64_t midpoint = BitwiseCast<uint64_t>(clamped);
1909 midpoint ^= 0x0F00DD1E2BAD2DED; // XOR in a 'secret'
1910 // MurmurHash3 internal component from
1911 // https://searchfox.org/mozilla-central/rev/61d400da1c692453c2dc2c1cf37b616ce13dea5b/dom/canvas/MurmurHash3.cpp#85
1912 midpoint ^= midpoint >> 33;
1913 midpoint *= uint64_t{0xFF51AFD7ED558CCD};
1914 midpoint ^= midpoint >> 33;
1915 midpoint *= uint64_t{0xC4CEB9FE1A85EC53};
1916 midpoint ^= midpoint >> 33;
1917 midpoint %= sResolutionUsec;
1919 if (now > clamped + midpoint) { // We're jittering up to the next step
1920 now = clamped + sResolutionUsec;
1921 } else { // We're staying at the clamped value
1922 now = clamped;
1924 } else { // No jitter, only clamping
1925 now = clamped;
1929 return TimeClip(now / PRMJ_USEC_PER_MSEC);
1932 JS::ClippedTime js::DateNow(JSContext* cx) { return NowAsMillis(cx); }
1934 bool js::date_now(JSContext* cx, unsigned argc, Value* vp) {
1935 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date", "now");
1936 CallArgs args = CallArgsFromVp(argc, vp);
1937 args.rval().set(TimeValue(NowAsMillis(cx)));
1938 return true;
1941 DateTimeInfo::ForceUTC DateObject::forceUTC() const {
1942 return ForceUTC(realm());
1945 void DateObject::setUTCTime(ClippedTime t) {
1946 for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++) {
1947 setReservedSlot(ind, UndefinedValue());
1950 setFixedSlot(UTC_TIME_SLOT, TimeValue(t));
1953 void DateObject::setUTCTime(ClippedTime t, MutableHandleValue vp) {
1954 setUTCTime(t);
1955 vp.set(TimeValue(t));
1958 void DateObject::fillLocalTimeSlots() {
1959 const int32_t utcTZOffset =
1960 DateTimeInfo::utcToLocalStandardOffsetSeconds(forceUTC());
1962 /* Check if the cache is already populated. */
1963 if (!getReservedSlot(LOCAL_TIME_SLOT).isUndefined() &&
1964 getReservedSlot(UTC_TIME_ZONE_OFFSET_SLOT).toInt32() == utcTZOffset) {
1965 return;
1968 /* Remember time zone used to generate the local cache. */
1969 setReservedSlot(UTC_TIME_ZONE_OFFSET_SLOT, Int32Value(utcTZOffset));
1971 double utcTime = UTCTime().toNumber();
1973 if (!std::isfinite(utcTime)) {
1974 for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++) {
1975 setReservedSlot(ind, DoubleValue(utcTime));
1977 return;
1980 double localTime = LocalTime(forceUTC(), utcTime);
1982 setReservedSlot(LOCAL_TIME_SLOT, DoubleValue(localTime));
1984 const auto [year, month, day] = ToYearMonthDay(localTime);
1986 setReservedSlot(LOCAL_YEAR_SLOT, Int32Value(year));
1987 setReservedSlot(LOCAL_MONTH_SLOT, Int32Value(int32_t(month)));
1988 setReservedSlot(LOCAL_DATE_SLOT, Int32Value(int32_t(day)));
1990 int weekday = WeekDay(localTime);
1991 setReservedSlot(LOCAL_DAY_SLOT, Int32Value(weekday));
1993 double yearStartTime = TimeFromYear(year);
1994 uint64_t yearTime = uint64_t(localTime - yearStartTime);
1995 int32_t yearSeconds = int32_t(yearTime / 1000);
1996 setReservedSlot(LOCAL_SECONDS_INTO_YEAR_SLOT, Int32Value(yearSeconds));
1999 MOZ_ALWAYS_INLINE bool IsDate(HandleValue v) {
2000 return v.isObject() && v.toObject().is<DateObject>();
2004 * See ECMA 15.9.5.4 thru 15.9.5.23
2007 static bool date_getTime(JSContext* cx, unsigned argc, Value* vp) {
2008 CallArgs args = CallArgsFromVp(argc, vp);
2010 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getTime");
2011 if (!unwrapped) {
2012 return false;
2015 args.rval().set(unwrapped->UTCTime());
2016 return true;
2019 static bool date_getYear(JSContext* cx, unsigned argc, Value* vp) {
2020 CallArgs args = CallArgsFromVp(argc, vp);
2022 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getYear");
2023 if (!unwrapped) {
2024 return false;
2027 unwrapped->fillLocalTimeSlots();
2029 Value yearVal = unwrapped->localYear();
2030 if (yearVal.isInt32()) {
2031 /* Follow ECMA-262 to the letter, contrary to IE JScript. */
2032 int year = yearVal.toInt32() - 1900;
2033 args.rval().setInt32(year);
2034 } else {
2035 args.rval().set(yearVal);
2037 return true;
2040 static bool date_getFullYear(JSContext* cx, unsigned argc, Value* vp) {
2041 CallArgs args = CallArgsFromVp(argc, vp);
2043 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getFullYear");
2044 if (!unwrapped) {
2045 return false;
2048 unwrapped->fillLocalTimeSlots();
2049 args.rval().set(unwrapped->localYear());
2050 return true;
2053 static bool date_getUTCFullYear(JSContext* cx, unsigned argc, Value* vp) {
2054 CallArgs args = CallArgsFromVp(argc, vp);
2056 auto* unwrapped =
2057 UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCFullYear");
2058 if (!unwrapped) {
2059 return false;
2062 double result = unwrapped->UTCTime().toNumber();
2063 if (std::isfinite(result)) {
2064 result = ::YearFromTime(result);
2067 args.rval().setNumber(result);
2068 return true;
2071 static bool date_getMonth(JSContext* cx, unsigned argc, Value* vp) {
2072 CallArgs args = CallArgsFromVp(argc, vp);
2074 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getMonth");
2075 if (!unwrapped) {
2076 return false;
2079 unwrapped->fillLocalTimeSlots();
2080 args.rval().set(unwrapped->localMonth());
2081 return true;
2084 static bool date_getUTCMonth(JSContext* cx, unsigned argc, Value* vp) {
2085 CallArgs args = CallArgsFromVp(argc, vp);
2087 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCMonth");
2088 if (!unwrapped) {
2089 return false;
2092 double d = unwrapped->UTCTime().toNumber();
2093 args.rval().setNumber(::MonthFromTime(d));
2094 return true;
2097 static bool date_getDate(JSContext* cx, unsigned argc, Value* vp) {
2098 CallArgs args = CallArgsFromVp(argc, vp);
2100 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getDate");
2101 if (!unwrapped) {
2102 return false;
2105 unwrapped->fillLocalTimeSlots();
2107 args.rval().set(unwrapped->localDate());
2108 return true;
2111 static bool date_getUTCDate(JSContext* cx, unsigned argc, Value* vp) {
2112 CallArgs args = CallArgsFromVp(argc, vp);
2114 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCDate");
2115 if (!unwrapped) {
2116 return false;
2119 double result = unwrapped->UTCTime().toNumber();
2120 if (std::isfinite(result)) {
2121 result = DateFromTime(result);
2124 args.rval().setNumber(result);
2125 return true;
2128 static bool date_getDay(JSContext* cx, unsigned argc, Value* vp) {
2129 CallArgs args = CallArgsFromVp(argc, vp);
2131 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getDay");
2132 if (!unwrapped) {
2133 return false;
2136 unwrapped->fillLocalTimeSlots();
2137 args.rval().set(unwrapped->localDay());
2138 return true;
2141 static bool date_getUTCDay(JSContext* cx, unsigned argc, Value* vp) {
2142 CallArgs args = CallArgsFromVp(argc, vp);
2144 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCDay");
2145 if (!unwrapped) {
2146 return false;
2149 double result = unwrapped->UTCTime().toNumber();
2150 if (std::isfinite(result)) {
2151 result = WeekDay(result);
2154 args.rval().setNumber(result);
2155 return true;
2158 static bool date_getHours(JSContext* cx, unsigned argc, Value* vp) {
2159 CallArgs args = CallArgsFromVp(argc, vp);
2161 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getHours");
2162 if (!unwrapped) {
2163 return false;
2166 unwrapped->fillLocalTimeSlots();
2168 // Note: localSecondsIntoYear is guaranteed to return an
2169 // int32 or NaN after the call to fillLocalTimeSlots.
2170 Value yearSeconds = unwrapped->localSecondsIntoYear();
2171 if (yearSeconds.isDouble()) {
2172 MOZ_ASSERT(std::isnan(yearSeconds.toDouble()));
2173 args.rval().set(yearSeconds);
2174 } else {
2175 args.rval().setInt32((yearSeconds.toInt32() / int(SecondsPerHour)) %
2176 int(HoursPerDay));
2178 return true;
2181 static bool date_getUTCHours(JSContext* cx, unsigned argc, Value* vp) {
2182 CallArgs args = CallArgsFromVp(argc, vp);
2184 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCHours");
2185 if (!unwrapped) {
2186 return false;
2189 double result = unwrapped->UTCTime().toNumber();
2190 if (std::isfinite(result)) {
2191 result = HourFromTime(result);
2194 args.rval().setNumber(result);
2195 return true;
2198 static bool date_getMinutes(JSContext* cx, unsigned argc, Value* vp) {
2199 CallArgs args = CallArgsFromVp(argc, vp);
2201 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getMinutes");
2202 if (!unwrapped) {
2203 return false;
2206 unwrapped->fillLocalTimeSlots();
2208 // Note: localSecondsIntoYear is guaranteed to return an
2209 // int32 or NaN after the call to fillLocalTimeSlots.
2210 Value yearSeconds = unwrapped->localSecondsIntoYear();
2211 if (yearSeconds.isDouble()) {
2212 MOZ_ASSERT(std::isnan(yearSeconds.toDouble()));
2213 args.rval().set(yearSeconds);
2214 } else {
2215 args.rval().setInt32((yearSeconds.toInt32() / int(SecondsPerMinute)) %
2216 int(MinutesPerHour));
2218 return true;
2221 static bool date_getUTCMinutes(JSContext* cx, unsigned argc, Value* vp) {
2222 CallArgs args = CallArgsFromVp(argc, vp);
2224 auto* unwrapped =
2225 UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCMinutes");
2226 if (!unwrapped) {
2227 return false;
2230 double result = unwrapped->UTCTime().toNumber();
2231 if (std::isfinite(result)) {
2232 result = MinFromTime(result);
2235 args.rval().setNumber(result);
2236 return true;
2239 static bool date_getSeconds(JSContext* cx, unsigned argc, Value* vp) {
2240 CallArgs args = CallArgsFromVp(argc, vp);
2242 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getSeconds");
2243 if (!unwrapped) {
2244 return false;
2247 unwrapped->fillLocalTimeSlots();
2249 // Note: localSecondsIntoYear is guaranteed to return an
2250 // int32 or NaN after the call to fillLocalTimeSlots.
2251 Value yearSeconds = unwrapped->localSecondsIntoYear();
2252 if (yearSeconds.isDouble()) {
2253 MOZ_ASSERT(std::isnan(yearSeconds.toDouble()));
2254 args.rval().set(yearSeconds);
2255 } else {
2256 args.rval().setInt32(yearSeconds.toInt32() % int(SecondsPerMinute));
2258 return true;
2261 static bool date_getUTCSeconds(JSContext* cx, unsigned argc, Value* vp) {
2262 CallArgs args = CallArgsFromVp(argc, vp);
2264 auto* unwrapped =
2265 UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCSeconds");
2266 if (!unwrapped) {
2267 return false;
2270 double result = unwrapped->UTCTime().toNumber();
2271 if (std::isfinite(result)) {
2272 result = SecFromTime(result);
2275 args.rval().setNumber(result);
2276 return true;
2280 * Date.getMilliseconds is mapped to getUTCMilliseconds. As long as no
2281 * supported time zone has a fractional-second component, the differences in
2282 * their specifications aren't observable.
2284 * The 'tz' database explicitly does not support fractional-second time zones.
2285 * For example the Netherlands observed Amsterdam Mean Time, estimated to be
2286 * UT +00:19:32.13, from 1909 to 1937, but in tzdata AMT is defined as exactly
2287 * UT +00:19:32.
2290 static bool getMilliseconds(JSContext* cx, unsigned argc, Value* vp,
2291 const char* methodName) {
2292 CallArgs args = CallArgsFromVp(argc, vp);
2294 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, methodName);
2295 if (!unwrapped) {
2296 return false;
2299 double result = unwrapped->UTCTime().toNumber();
2300 if (std::isfinite(result)) {
2301 result = msFromTime(result);
2304 args.rval().setNumber(result);
2305 return true;
2308 static bool date_getMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
2309 return getMilliseconds(cx, argc, vp, "getMilliseconds");
2312 static bool date_getUTCMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
2313 return getMilliseconds(cx, argc, vp, "getUTCMilliseconds");
2316 static bool date_getTimezoneOffset(JSContext* cx, unsigned argc, Value* vp) {
2317 CallArgs args = CallArgsFromVp(argc, vp);
2319 auto* unwrapped =
2320 UnwrapAndTypeCheckThis<DateObject>(cx, args, "getTimezoneOffset");
2321 if (!unwrapped) {
2322 return false;
2325 unwrapped->fillLocalTimeSlots();
2327 double utctime = unwrapped->UTCTime().toNumber();
2328 double localtime = unwrapped->localTime().toDouble();
2331 * Return the time zone offset in minutes for the current locale that is
2332 * appropriate for this time. This value would be a constant except for
2333 * daylight savings time.
2335 double result = (utctime - localtime) / msPerMinute;
2336 args.rval().setNumber(result);
2337 return true;
2340 static bool date_setTime(JSContext* cx, unsigned argc, Value* vp) {
2341 CallArgs args = CallArgsFromVp(argc, vp);
2343 Rooted<DateObject*> unwrapped(
2344 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setTime"));
2345 if (!unwrapped) {
2346 return false;
2349 if (args.length() == 0) {
2350 unwrapped->setUTCTime(ClippedTime::invalid(), args.rval());
2351 return true;
2354 double result;
2355 if (!ToNumber(cx, args[0], &result)) {
2356 return false;
2359 unwrapped->setUTCTime(TimeClip(result), args.rval());
2360 return true;
2363 static bool GetMsecsOrDefault(JSContext* cx, const CallArgs& args, unsigned i,
2364 double t, double* millis) {
2365 if (args.length() <= i) {
2366 *millis = msFromTime(t);
2367 return true;
2369 return ToNumber(cx, args[i], millis);
2372 static bool GetSecsOrDefault(JSContext* cx, const CallArgs& args, unsigned i,
2373 double t, double* sec) {
2374 if (args.length() <= i) {
2375 *sec = SecFromTime(t);
2376 return true;
2378 return ToNumber(cx, args[i], sec);
2381 static bool GetMinsOrDefault(JSContext* cx, const CallArgs& args, unsigned i,
2382 double t, double* mins) {
2383 if (args.length() <= i) {
2384 *mins = MinFromTime(t);
2385 return true;
2387 return ToNumber(cx, args[i], mins);
2390 /* ES6 20.3.4.23. */
2391 static bool date_setMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
2392 CallArgs args = CallArgsFromVp(argc, vp);
2394 // Step 1.
2395 Rooted<DateObject*> unwrapped(
2396 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setMilliseconds"));
2397 if (!unwrapped) {
2398 return false;
2400 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2402 // Step 2.
2403 double ms;
2404 if (!ToNumber(cx, args.get(0), &ms)) {
2405 return false;
2408 // Step 3.
2409 double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms);
2411 // Step 4.
2412 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), MakeDate(Day(t), time)));
2414 // Steps 5-6.
2415 unwrapped->setUTCTime(u, args.rval());
2416 return true;
2419 /* ES5 15.9.5.29. */
2420 static bool date_setUTCMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
2421 CallArgs args = CallArgsFromVp(argc, vp);
2423 Rooted<DateObject*> unwrapped(
2424 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCMilliseconds"));
2425 if (!unwrapped) {
2426 return false;
2429 /* Step 1. */
2430 double t = unwrapped->UTCTime().toNumber();
2432 /* Step 2. */
2433 double milli;
2434 if (!ToNumber(cx, args.get(0), &milli)) {
2435 return false;
2437 double time =
2438 MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli);
2440 /* Step 3. */
2441 ClippedTime v = TimeClip(MakeDate(Day(t), time));
2443 /* Steps 4-5. */
2444 unwrapped->setUTCTime(v, args.rval());
2445 return true;
2448 /* ES6 20.3.4.26. */
2449 static bool date_setSeconds(JSContext* cx, unsigned argc, Value* vp) {
2450 CallArgs args = CallArgsFromVp(argc, vp);
2452 Rooted<DateObject*> unwrapped(
2453 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setSeconds"));
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 s;
2463 if (!ToNumber(cx, args.get(0), &s)) {
2464 return false;
2467 // Steps 5-6.
2468 double milli;
2469 if (!GetMsecsOrDefault(cx, args, 1, t, &milli)) {
2470 return false;
2473 // Step 7.
2474 double date =
2475 MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
2477 // Step 8.
2478 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), date));
2480 // Step 9.
2481 unwrapped->setUTCTime(u, args.rval());
2482 return true;
2485 /* ES5 15.9.5.32. */
2486 static bool date_setUTCSeconds(JSContext* cx, unsigned argc, Value* vp) {
2487 CallArgs args = CallArgsFromVp(argc, vp);
2489 Rooted<DateObject*> unwrapped(
2490 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCSeconds"));
2491 if (!unwrapped) {
2492 return false;
2495 /* Step 1. */
2496 double t = unwrapped->UTCTime().toNumber();
2498 /* Step 2. */
2499 double s;
2500 if (!ToNumber(cx, args.get(0), &s)) {
2501 return false;
2504 /* Step 3. */
2505 double milli;
2506 if (!GetMsecsOrDefault(cx, args, 1, t, &milli)) {
2507 return false;
2510 /* Step 4. */
2511 double date =
2512 MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli));
2514 /* Step 5. */
2515 ClippedTime v = TimeClip(date);
2517 /* Steps 6-7. */
2518 unwrapped->setUTCTime(v, args.rval());
2519 return true;
2522 /* ES6 20.3.4.24. */
2523 static bool date_setMinutes(JSContext* cx, unsigned argc, Value* vp) {
2524 CallArgs args = CallArgsFromVp(argc, vp);
2526 Rooted<DateObject*> unwrapped(
2527 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setMinutes"));
2528 if (!unwrapped) {
2529 return false;
2532 // Steps 1-2.
2533 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2535 // Steps 3-4.
2536 double m;
2537 if (!ToNumber(cx, args.get(0), &m)) {
2538 return false;
2541 // Steps 5-6.
2542 double s;
2543 if (!GetSecsOrDefault(cx, args, 1, t, &s)) {
2544 return false;
2547 // Steps 7-8.
2548 double milli;
2549 if (!GetMsecsOrDefault(cx, args, 2, t, &milli)) {
2550 return false;
2553 // Step 9.
2554 double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
2556 // Step 10.
2557 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), date));
2559 // Steps 11-12.
2560 unwrapped->setUTCTime(u, args.rval());
2561 return true;
2564 /* ES5 15.9.5.34. */
2565 static bool date_setUTCMinutes(JSContext* cx, unsigned argc, Value* vp) {
2566 CallArgs args = CallArgsFromVp(argc, vp);
2568 Rooted<DateObject*> unwrapped(
2569 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCMinutes"));
2570 if (!unwrapped) {
2571 return false;
2574 /* Step 1. */
2575 double t = unwrapped->UTCTime().toNumber();
2577 /* Step 2. */
2578 double m;
2579 if (!ToNumber(cx, args.get(0), &m)) {
2580 return false;
2583 /* Step 3. */
2584 double s;
2585 if (!GetSecsOrDefault(cx, args, 1, t, &s)) {
2586 return false;
2589 /* Step 4. */
2590 double milli;
2591 if (!GetMsecsOrDefault(cx, args, 2, t, &milli)) {
2592 return false;
2595 /* Step 5. */
2596 double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli));
2598 /* Step 6. */
2599 ClippedTime v = TimeClip(date);
2601 /* Steps 7-8. */
2602 unwrapped->setUTCTime(v, args.rval());
2603 return true;
2606 /* ES5 15.9.5.35. */
2607 static bool date_setHours(JSContext* cx, unsigned argc, Value* vp) {
2608 CallArgs args = CallArgsFromVp(argc, vp);
2610 Rooted<DateObject*> unwrapped(
2611 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setHours"));
2612 if (!unwrapped) {
2613 return false;
2616 // Steps 1-2.
2617 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2619 // Steps 3-4.
2620 double h;
2621 if (!ToNumber(cx, args.get(0), &h)) {
2622 return false;
2625 // Steps 5-6.
2626 double m;
2627 if (!GetMinsOrDefault(cx, args, 1, t, &m)) {
2628 return false;
2631 // Steps 7-8.
2632 double s;
2633 if (!GetSecsOrDefault(cx, args, 2, t, &s)) {
2634 return false;
2637 // Steps 9-10.
2638 double milli;
2639 if (!GetMsecsOrDefault(cx, args, 3, t, &milli)) {
2640 return false;
2643 // Step 11.
2644 double date = MakeDate(Day(t), MakeTime(h, m, s, milli));
2646 // Step 12.
2647 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), date));
2649 // Steps 13-14.
2650 unwrapped->setUTCTime(u, args.rval());
2651 return true;
2654 /* ES5 15.9.5.36. */
2655 static bool date_setUTCHours(JSContext* cx, unsigned argc, Value* vp) {
2656 CallArgs args = CallArgsFromVp(argc, vp);
2658 Rooted<DateObject*> unwrapped(
2659 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCHours"));
2660 if (!unwrapped) {
2661 return false;
2664 /* Step 1. */
2665 double t = unwrapped->UTCTime().toNumber();
2667 /* Step 2. */
2668 double h;
2669 if (!ToNumber(cx, args.get(0), &h)) {
2670 return false;
2673 /* Step 3. */
2674 double m;
2675 if (!GetMinsOrDefault(cx, args, 1, t, &m)) {
2676 return false;
2679 /* Step 4. */
2680 double s;
2681 if (!GetSecsOrDefault(cx, args, 2, t, &s)) {
2682 return false;
2685 /* Step 5. */
2686 double milli;
2687 if (!GetMsecsOrDefault(cx, args, 3, t, &milli)) {
2688 return false;
2691 /* Step 6. */
2692 double newDate = MakeDate(Day(t), MakeTime(h, m, s, milli));
2694 /* Step 7. */
2695 ClippedTime v = TimeClip(newDate);
2697 /* Steps 8-9. */
2698 unwrapped->setUTCTime(v, args.rval());
2699 return true;
2702 /* ES5 15.9.5.37. */
2703 static bool date_setDate(JSContext* cx, unsigned argc, Value* vp) {
2704 CallArgs args = CallArgsFromVp(argc, vp);
2706 Rooted<DateObject*> unwrapped(
2707 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setDate"));
2708 if (!unwrapped) {
2709 return false;
2712 /* Step 1. */
2713 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2715 /* Step 2. */
2716 double date;
2717 if (!ToNumber(cx, args.get(0), &date)) {
2718 return false;
2721 /* Step 3. */
2722 double newDate = MakeDate(
2723 MakeDay(::YearFromTime(t), ::MonthFromTime(t), date), TimeWithinDay(t));
2725 /* Step 4. */
2726 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), newDate));
2728 /* Steps 5-6. */
2729 unwrapped->setUTCTime(u, args.rval());
2730 return true;
2733 static bool date_setUTCDate(JSContext* cx, unsigned argc, Value* vp) {
2734 CallArgs args = CallArgsFromVp(argc, vp);
2736 Rooted<DateObject*> unwrapped(
2737 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCDate"));
2738 if (!unwrapped) {
2739 return false;
2742 /* Step 1. */
2743 double t = unwrapped->UTCTime().toNumber();
2745 /* Step 2. */
2746 double date;
2747 if (!ToNumber(cx, args.get(0), &date)) {
2748 return false;
2751 /* Step 3. */
2752 double newDate = MakeDate(
2753 MakeDay(::YearFromTime(t), ::MonthFromTime(t), date), TimeWithinDay(t));
2755 /* Step 4. */
2756 ClippedTime v = TimeClip(newDate);
2758 /* Steps 5-6. */
2759 unwrapped->setUTCTime(v, args.rval());
2760 return true;
2763 static bool GetDateOrDefault(JSContext* cx, const CallArgs& args, unsigned i,
2764 double t, double* date) {
2765 if (args.length() <= i) {
2766 *date = DateFromTime(t);
2767 return true;
2769 return ToNumber(cx, args[i], date);
2772 static bool GetMonthOrDefault(JSContext* cx, const CallArgs& args, unsigned i,
2773 double t, double* month) {
2774 if (args.length() <= i) {
2775 *month = ::MonthFromTime(t);
2776 return true;
2778 return ToNumber(cx, args[i], month);
2781 /* ES5 15.9.5.38. */
2782 static bool date_setMonth(JSContext* cx, unsigned argc, Value* vp) {
2783 CallArgs args = CallArgsFromVp(argc, vp);
2785 Rooted<DateObject*> unwrapped(
2786 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setMonth"));
2787 if (!unwrapped) {
2788 return false;
2791 /* Step 1. */
2792 double t = LocalTime(unwrapped->forceUTC(), unwrapped->UTCTime().toNumber());
2794 /* Step 2. */
2795 double m;
2796 if (!ToNumber(cx, args.get(0), &m)) {
2797 return false;
2800 /* Step 3. */
2801 double date;
2802 if (!GetDateOrDefault(cx, args, 1, t, &date)) {
2803 return false;
2806 /* Step 4. */
2807 double newDate =
2808 MakeDate(MakeDay(::YearFromTime(t), m, date), TimeWithinDay(t));
2810 /* Step 5. */
2811 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), newDate));
2813 /* Steps 6-7. */
2814 unwrapped->setUTCTime(u, args.rval());
2815 return true;
2818 /* ES5 15.9.5.39. */
2819 static bool date_setUTCMonth(JSContext* cx, unsigned argc, Value* vp) {
2820 CallArgs args = CallArgsFromVp(argc, vp);
2822 Rooted<DateObject*> unwrapped(
2823 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCMonth"));
2824 if (!unwrapped) {
2825 return false;
2828 /* Step 1. */
2829 double t = unwrapped->UTCTime().toNumber();
2831 /* Step 2. */
2832 double m;
2833 if (!ToNumber(cx, args.get(0), &m)) {
2834 return false;
2837 /* Step 3. */
2838 double date;
2839 if (!GetDateOrDefault(cx, args, 1, t, &date)) {
2840 return false;
2843 /* Step 4. */
2844 double newDate =
2845 MakeDate(MakeDay(::YearFromTime(t), m, date), TimeWithinDay(t));
2847 /* Step 5. */
2848 ClippedTime v = TimeClip(newDate);
2850 /* Steps 6-7. */
2851 unwrapped->setUTCTime(v, args.rval());
2852 return true;
2855 static double ThisLocalTimeOrZero(DateTimeInfo::ForceUTC forceUTC,
2856 Handle<DateObject*> dateObj) {
2857 double t = dateObj->UTCTime().toNumber();
2858 if (std::isnan(t)) {
2859 return +0;
2861 return LocalTime(forceUTC, t);
2864 static double ThisUTCTimeOrZero(Handle<DateObject*> dateObj) {
2865 double t = dateObj->as<DateObject>().UTCTime().toNumber();
2866 return std::isnan(t) ? +0 : t;
2869 /* ES5 15.9.5.40. */
2870 static bool date_setFullYear(JSContext* cx, unsigned argc, Value* vp) {
2871 CallArgs args = CallArgsFromVp(argc, vp);
2873 Rooted<DateObject*> unwrapped(
2874 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setFullYear"));
2875 if (!unwrapped) {
2876 return false;
2879 /* Step 1. */
2880 double t = ThisLocalTimeOrZero(unwrapped->forceUTC(), unwrapped);
2882 /* Step 2. */
2883 double y;
2884 if (!ToNumber(cx, args.get(0), &y)) {
2885 return false;
2888 /* Step 3. */
2889 double m;
2890 if (!GetMonthOrDefault(cx, args, 1, t, &m)) {
2891 return false;
2894 /* Step 4. */
2895 double date;
2896 if (!GetDateOrDefault(cx, args, 2, t, &date)) {
2897 return false;
2900 /* Step 5. */
2901 double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t));
2903 /* Step 6. */
2904 ClippedTime u = TimeClip(UTC(unwrapped->forceUTC(), newDate));
2906 /* Steps 7-8. */
2907 unwrapped->setUTCTime(u, args.rval());
2908 return true;
2911 /* ES5 15.9.5.41. */
2912 static bool date_setUTCFullYear(JSContext* cx, unsigned argc, Value* vp) {
2913 CallArgs args = CallArgsFromVp(argc, vp);
2915 Rooted<DateObject*> unwrapped(
2916 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCFullYear"));
2917 if (!unwrapped) {
2918 return false;
2921 /* Step 1. */
2922 double t = ThisUTCTimeOrZero(unwrapped);
2924 /* Step 2. */
2925 double y;
2926 if (!ToNumber(cx, args.get(0), &y)) {
2927 return false;
2930 /* Step 3. */
2931 double m;
2932 if (!GetMonthOrDefault(cx, args, 1, t, &m)) {
2933 return false;
2936 /* Step 4. */
2937 double date;
2938 if (!GetDateOrDefault(cx, args, 2, t, &date)) {
2939 return false;
2942 /* Step 5. */
2943 double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t));
2945 /* Step 6. */
2946 ClippedTime v = TimeClip(newDate);
2948 /* Steps 7-8. */
2949 unwrapped->setUTCTime(v, args.rval());
2950 return true;
2953 /* ES5 Annex B.2.5. */
2954 static bool date_setYear(JSContext* cx, unsigned argc, Value* vp) {
2955 CallArgs args = CallArgsFromVp(argc, vp);
2957 Rooted<DateObject*> unwrapped(
2958 cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setYear"));
2959 if (!unwrapped) {
2960 return false;
2963 /* Step 1. */
2964 double t = ThisLocalTimeOrZero(unwrapped->forceUTC(), unwrapped);
2966 /* Step 2. */
2967 double y;
2968 if (!ToNumber(cx, args.get(0), &y)) {
2969 return false;
2972 /* Step 3. */
2973 if (std::isnan(y)) {
2974 unwrapped->setUTCTime(ClippedTime::invalid(), args.rval());
2975 return true;
2978 /* Step 4. */
2979 double yint = ToInteger(y);
2980 if (0 <= yint && yint <= 99) {
2981 yint += 1900;
2984 /* Step 5. */
2985 double day = MakeDay(yint, ::MonthFromTime(t), DateFromTime(t));
2987 /* Step 6. */
2988 double u = UTC(unwrapped->forceUTC(), MakeDate(day, TimeWithinDay(t)));
2990 /* Steps 7-8. */
2991 unwrapped->setUTCTime(TimeClip(u), args.rval());
2992 return true;
2995 /* constants for toString, toUTCString */
2996 static const char* const days[] = {"Sun", "Mon", "Tue", "Wed",
2997 "Thu", "Fri", "Sat"};
2998 static const char* const months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
2999 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
3001 /* ES5 B.2.6. */
3002 static bool date_toUTCString(JSContext* cx, unsigned argc, Value* vp) {
3003 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toUTCString");
3004 CallArgs args = CallArgsFromVp(argc, vp);
3006 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "toUTCString");
3007 if (!unwrapped) {
3008 return false;
3011 double utctime = unwrapped->UTCTime().toNumber();
3012 if (!std::isfinite(utctime)) {
3013 args.rval().setString(cx->names().Invalid_Date_);
3014 return true;
3017 char buf[100];
3018 SprintfLiteral(buf, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
3019 days[int(WeekDay(utctime))], int(DateFromTime(utctime)),
3020 months[int(::MonthFromTime(utctime))],
3021 int(::YearFromTime(utctime)), int(HourFromTime(utctime)),
3022 int(MinFromTime(utctime)), int(SecFromTime(utctime)));
3024 JSString* str = NewStringCopyZ<CanGC>(cx, buf);
3025 if (!str) {
3026 return false;
3029 args.rval().setString(str);
3030 return true;
3033 /* ES6 draft 2015-01-15 20.3.4.36. */
3034 static bool date_toISOString(JSContext* cx, unsigned argc, Value* vp) {
3035 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toISOString");
3036 CallArgs args = CallArgsFromVp(argc, vp);
3038 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "toISOString");
3039 if (!unwrapped) {
3040 return false;
3043 double utctime = unwrapped->UTCTime().toNumber();
3044 if (!std::isfinite(utctime)) {
3045 JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
3046 JSMSG_INVALID_DATE);
3047 return false;
3050 char buf[100];
3051 int year = int(::YearFromTime(utctime));
3052 if (year < 0 || year > 9999) {
3053 SprintfLiteral(buf, "%+.6d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
3054 int(::YearFromTime(utctime)),
3055 int(::MonthFromTime(utctime)) + 1,
3056 int(DateFromTime(utctime)), int(HourFromTime(utctime)),
3057 int(MinFromTime(utctime)), int(SecFromTime(utctime)),
3058 int(msFromTime(utctime)));
3059 } else {
3060 SprintfLiteral(buf, "%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3dZ",
3061 int(::YearFromTime(utctime)),
3062 int(::MonthFromTime(utctime)) + 1,
3063 int(DateFromTime(utctime)), int(HourFromTime(utctime)),
3064 int(MinFromTime(utctime)), int(SecFromTime(utctime)),
3065 int(msFromTime(utctime)));
3068 JSString* str = NewStringCopyZ<CanGC>(cx, buf);
3069 if (!str) {
3070 return false;
3072 args.rval().setString(str);
3073 return true;
3076 /* ES5 15.9.5.44. */
3077 static bool date_toJSON(JSContext* cx, unsigned argc, Value* vp) {
3078 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toJSON");
3079 CallArgs args = CallArgsFromVp(argc, vp);
3081 /* Step 1. */
3082 RootedObject obj(cx, ToObject(cx, args.thisv()));
3083 if (!obj) {
3084 return false;
3087 /* Step 2. */
3088 RootedValue tv(cx, ObjectValue(*obj));
3089 if (!ToPrimitive(cx, JSTYPE_NUMBER, &tv)) {
3090 return false;
3093 /* Step 3. */
3094 if (tv.isDouble() && !std::isfinite(tv.toDouble())) {
3095 args.rval().setNull();
3096 return true;
3099 /* Step 4. */
3100 RootedValue toISO(cx);
3101 if (!GetProperty(cx, obj, obj, cx->names().toISOString, &toISO)) {
3102 return false;
3105 /* Step 5. */
3106 if (!IsCallable(toISO)) {
3107 JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
3108 JSMSG_BAD_TOISOSTRING_PROP);
3109 return false;
3112 /* Step 6. */
3113 return Call(cx, toISO, obj, args.rval());
3116 #if JS_HAS_INTL_API
3117 JSString* DateTimeHelper::timeZoneComment(JSContext* cx,
3118 DateTimeInfo::ForceUTC forceUTC,
3119 const char* locale, double utcTime,
3120 double localTime) {
3121 char16_t tzbuf[100];
3122 tzbuf[0] = ' ';
3123 tzbuf[1] = '(';
3125 char16_t* timeZoneStart = tzbuf + 2;
3126 constexpr size_t remainingSpace =
3127 std::size(tzbuf) - 2 - 1; // for the trailing ')'
3129 int64_t utcMilliseconds = static_cast<int64_t>(utcTime);
3130 if (!DateTimeInfo::timeZoneDisplayName(
3131 forceUTC, timeZoneStart, remainingSpace, utcMilliseconds, locale)) {
3132 JS_ReportOutOfMemory(cx);
3133 return nullptr;
3136 // Reject if the result string is empty.
3137 size_t len = js_strlen(timeZoneStart);
3138 if (len == 0) {
3139 return cx->names().empty_;
3142 // Parenthesize the returned display name.
3143 timeZoneStart[len] = ')';
3145 return NewStringCopyN<CanGC>(cx, tzbuf, 2 + len + 1);
3147 #else
3148 /* Interface to PRMJTime date struct. */
3149 PRMJTime DateTimeHelper::toPRMJTime(DateTimeInfo::ForceUTC forceUTC,
3150 double localTime, double utcTime) {
3151 double year = ::YearFromTime(localTime);
3153 PRMJTime prtm;
3154 prtm.tm_usec = int32_t(msFromTime(localTime)) * 1000;
3155 prtm.tm_sec = int8_t(SecFromTime(localTime));
3156 prtm.tm_min = int8_t(MinFromTime(localTime));
3157 prtm.tm_hour = int8_t(HourFromTime(localTime));
3158 prtm.tm_mday = int8_t(DateFromTime(localTime));
3159 prtm.tm_mon = int8_t(::MonthFromTime(localTime));
3160 prtm.tm_wday = int8_t(WeekDay(localTime));
3161 prtm.tm_year = year;
3162 prtm.tm_yday = int16_t(::DayWithinYear(localTime, year));
3163 prtm.tm_isdst = (daylightSavingTA(forceUTC, utcTime) != 0);
3165 return prtm;
3168 size_t DateTimeHelper::formatTime(DateTimeInfo::ForceUTC forceUTC, char* buf,
3169 size_t buflen, const char* fmt,
3170 double utcTime, double localTime) {
3171 PRMJTime prtm = toPRMJTime(forceUTC, localTime, utcTime);
3173 // If an equivalent year was used to compute the date/time components, use
3174 // the same equivalent year to determine the time zone name and offset in
3175 // PRMJ_FormatTime(...).
3176 int timeZoneYear = isRepresentableAsTime32(utcTime)
3177 ? prtm.tm_year
3178 : equivalentYearForDST(prtm.tm_year);
3179 int offsetInSeconds = (int)floor((localTime - utcTime) / msPerSecond);
3181 return PRMJ_FormatTime(buf, buflen, fmt, &prtm, timeZoneYear,
3182 offsetInSeconds);
3185 JSString* DateTimeHelper::timeZoneComment(JSContext* cx,
3186 DateTimeInfo::ForceUTC forceUTC,
3187 const char* locale, double utcTime,
3188 double localTime) {
3189 char tzbuf[100];
3191 size_t tzlen =
3192 formatTime(forceUTC, tzbuf, sizeof tzbuf, " (%Z)", utcTime, localTime);
3193 if (tzlen != 0) {
3194 // Decide whether to use the resulting time zone string.
3196 // Reject it if it contains any non-ASCII or non-printable characters.
3197 // It's then likely in some other character encoding, and we probably
3198 // won't display it correctly.
3199 bool usetz = true;
3200 for (size_t i = 0; i < tzlen; i++) {
3201 char16_t c = tzbuf[i];
3202 if (!IsAsciiPrintable(c)) {
3203 usetz = false;
3204 break;
3208 // Also reject it if it's not parenthesized or if it's ' ()'.
3209 if (tzbuf[0] != ' ' || tzbuf[1] != '(' || tzbuf[2] == ')') {
3210 usetz = false;
3213 if (usetz) {
3214 return NewStringCopyN<CanGC>(cx, tzbuf, tzlen);
3218 return cx->names().empty_;
3220 #endif /* JS_HAS_INTL_API */
3222 enum class FormatSpec { DateTime, Date, Time };
3224 static bool FormatDate(JSContext* cx, DateTimeInfo::ForceUTC forceUTC,
3225 const char* locale, double utcTime, FormatSpec format,
3226 MutableHandleValue rval) {
3227 if (!std::isfinite(utcTime)) {
3228 rval.setString(cx->names().Invalid_Date_);
3229 return true;
3232 MOZ_ASSERT(NumbersAreIdentical(TimeClip(utcTime).toDouble(), utcTime));
3234 double localTime = LocalTime(forceUTC, utcTime);
3236 int offset = 0;
3237 RootedString timeZoneComment(cx);
3238 if (format == FormatSpec::DateTime || format == FormatSpec::Time) {
3239 // Offset from GMT in minutes. The offset includes daylight savings,
3240 // if it applies.
3241 int minutes = (int)trunc((localTime - utcTime) / msPerMinute);
3243 // Map 510 minutes to 0830 hours.
3244 offset = (minutes / 60) * 100 + minutes % 60;
3246 // Print as "Wed Nov 05 1997 19:38:03 GMT-0800 (PST)".
3248 // The TZA is printed as 'GMT-0800' rather than as 'PST' to avoid
3249 // operating-system dependence on strftime (which PRMJ_FormatTime
3250 // calls, for %Z only.) win32 prints PST as 'Pacific Standard Time.'
3251 // This way we always know what we're getting, and can parse it if
3252 // we produce it. The OS time zone string is included as a comment.
3254 // When ICU is used to retrieve the time zone string, the localized
3255 // 'long' name format from CLDR is used. For example when the default
3256 // locale is "en-US", PST is displayed as 'Pacific Standard Time', but
3257 // when it is "ru", 'Тихоокеанское стандартное время' is used. This
3258 // also means the time zone string may not fit into Latin-1.
3260 // Get a time zone string from the OS or ICU to include as a comment.
3261 timeZoneComment = DateTimeHelper::timeZoneComment(cx, forceUTC, locale,
3262 utcTime, localTime);
3263 if (!timeZoneComment) {
3264 return false;
3268 char buf[100];
3269 switch (format) {
3270 case FormatSpec::DateTime:
3271 /* Tue Oct 31 2000 09:41:40 GMT-0800 */
3272 SprintfLiteral(
3273 buf, "%s %s %.2d %.4d %.2d:%.2d:%.2d GMT%+.4d",
3274 days[int(WeekDay(localTime))],
3275 months[int(::MonthFromTime(localTime))], int(DateFromTime(localTime)),
3276 int(::YearFromTime(localTime)), int(HourFromTime(localTime)),
3277 int(MinFromTime(localTime)), int(SecFromTime(localTime)), offset);
3278 break;
3279 case FormatSpec::Date:
3280 /* Tue Oct 31 2000 */
3281 SprintfLiteral(buf, "%s %s %.2d %.4d", days[int(WeekDay(localTime))],
3282 months[int(::MonthFromTime(localTime))],
3283 int(DateFromTime(localTime)),
3284 int(::YearFromTime(localTime)));
3285 break;
3286 case FormatSpec::Time:
3287 /* 09:41:40 GMT-0800 */
3288 SprintfLiteral(buf, "%.2d:%.2d:%.2d GMT%+.4d",
3289 int(HourFromTime(localTime)), int(MinFromTime(localTime)),
3290 int(SecFromTime(localTime)), offset);
3291 break;
3294 RootedString str(cx, NewStringCopyZ<CanGC>(cx, buf));
3295 if (!str) {
3296 return false;
3299 // Append the time zone string if present.
3300 if (timeZoneComment && !timeZoneComment->empty()) {
3301 str = js::ConcatStrings<CanGC>(cx, str, timeZoneComment);
3302 if (!str) {
3303 return false;
3307 rval.setString(str);
3308 return true;
3311 #if !JS_HAS_INTL_API
3312 static bool ToLocaleFormatHelper(JSContext* cx, DateObject* unwrapped,
3313 const char* format, MutableHandleValue rval) {
3314 DateTimeInfo::ForceUTC forceUTC = unwrapped->forceUTC();
3315 double utcTime = unwrapped->UTCTime().toNumber();
3317 const char* locale = unwrapped->realm()->getLocale();
3318 if (!locale) {
3319 return false;
3322 char buf[100];
3323 if (!std::isfinite(utcTime)) {
3324 strcpy(buf, "InvalidDate");
3325 } else {
3326 double localTime = LocalTime(forceUTC, utcTime);
3328 /* Let PRMJTime format it. */
3329 size_t result_len = DateTimeHelper::formatTime(forceUTC, buf, sizeof buf,
3330 format, utcTime, localTime);
3332 /* If it failed, default to toString. */
3333 if (result_len == 0) {
3334 return FormatDate(cx, forceUTC, locale, utcTime, FormatSpec::DateTime,
3335 rval);
3338 /* Hacked check against undesired 2-digit year 00/00/00 form. */
3339 if (strcmp(format, "%x") == 0 && result_len >= 6 &&
3340 /* Format %x means use OS settings, which may have 2-digit yr, so
3341 hack end of 3/11/22 or 11.03.22 or 11Mar22 to use 4-digit yr...*/
3342 !IsAsciiDigit(buf[result_len - 3]) &&
3343 IsAsciiDigit(buf[result_len - 2]) &&
3344 IsAsciiDigit(buf[result_len - 1]) &&
3345 /* ...but not if starts with 4-digit year, like 2022/3/11. */
3346 !(IsAsciiDigit(buf[0]) && IsAsciiDigit(buf[1]) &&
3347 IsAsciiDigit(buf[2]) && IsAsciiDigit(buf[3]))) {
3348 int year = int(::YearFromTime(localTime));
3349 snprintf(buf + (result_len - 2), (sizeof buf) - (result_len - 2), "%d",
3350 year);
3354 if (cx->runtime()->localeCallbacks &&
3355 cx->runtime()->localeCallbacks->localeToUnicode) {
3356 return cx->runtime()->localeCallbacks->localeToUnicode(cx, buf, rval);
3359 JSString* str = NewStringCopyZ<CanGC>(cx, buf);
3360 if (!str) {
3361 return false;
3363 rval.setString(str);
3364 return true;
3367 /* ES5 15.9.5.5. */
3368 static bool date_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
3369 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toLocaleString");
3370 CallArgs args = CallArgsFromVp(argc, vp);
3372 auto* unwrapped =
3373 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toLocaleString");
3374 if (!unwrapped) {
3375 return false;
3379 * Use '%#c' for windows, because '%c' is backward-compatible and non-y2k
3380 * with msvc; '%#c' requests that a full year be used in the result string.
3382 static const char format[] =
3383 # if defined(_WIN32)
3384 "%#c"
3385 # else
3386 "%c"
3387 # endif
3390 return ToLocaleFormatHelper(cx, unwrapped, format, args.rval());
3393 static bool date_toLocaleDateString(JSContext* cx, unsigned argc, Value* vp) {
3394 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype",
3395 "toLocaleDateString");
3396 CallArgs args = CallArgsFromVp(argc, vp);
3398 auto* unwrapped =
3399 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toLocaleDateString");
3400 if (!unwrapped) {
3401 return false;
3405 * Use '%#x' for windows, because '%x' is backward-compatible and non-y2k
3406 * with msvc; '%#x' requests that a full year be used in the result string.
3408 static const char format[] =
3409 # if defined(_WIN32)
3410 "%#x"
3411 # else
3412 "%x"
3413 # endif
3416 return ToLocaleFormatHelper(cx, unwrapped, format, args.rval());
3419 static bool date_toLocaleTimeString(JSContext* cx, unsigned argc, Value* vp) {
3420 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype",
3421 "toLocaleTimeString");
3422 CallArgs args = CallArgsFromVp(argc, vp);
3424 auto* unwrapped =
3425 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toLocaleTimeString");
3426 if (!unwrapped) {
3427 return false;
3430 return ToLocaleFormatHelper(cx, unwrapped, "%X", args.rval());
3432 #endif /* !JS_HAS_INTL_API */
3434 static bool date_toTimeString(JSContext* cx, unsigned argc, Value* vp) {
3435 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toTimeString");
3436 CallArgs args = CallArgsFromVp(argc, vp);
3438 auto* unwrapped =
3439 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toTimeString");
3440 if (!unwrapped) {
3441 return false;
3444 const char* locale = unwrapped->realm()->getLocale();
3445 if (!locale) {
3446 return false;
3448 return FormatDate(cx, unwrapped->forceUTC(), locale,
3449 unwrapped->UTCTime().toNumber(), FormatSpec::Time,
3450 args.rval());
3453 static bool date_toDateString(JSContext* cx, unsigned argc, Value* vp) {
3454 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toDateString");
3455 CallArgs args = CallArgsFromVp(argc, vp);
3457 auto* unwrapped =
3458 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toDateString");
3459 if (!unwrapped) {
3460 return false;
3463 const char* locale = unwrapped->realm()->getLocale();
3464 if (!locale) {
3465 return false;
3467 return FormatDate(cx, unwrapped->forceUTC(), locale,
3468 unwrapped->UTCTime().toNumber(), FormatSpec::Date,
3469 args.rval());
3472 static bool date_toSource(JSContext* cx, unsigned argc, Value* vp) {
3473 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toSource");
3474 CallArgs args = CallArgsFromVp(argc, vp);
3476 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "toSource");
3477 if (!unwrapped) {
3478 return false;
3481 JSStringBuilder sb(cx);
3482 if (!sb.append("(new Date(") ||
3483 !NumberValueToStringBuffer(unwrapped->UTCTime(), sb) ||
3484 !sb.append("))")) {
3485 return false;
3488 JSString* str = sb.finishString();
3489 if (!str) {
3490 return false;
3492 args.rval().setString(str);
3493 return true;
3496 bool date_toString(JSContext* cx, unsigned argc, Value* vp) {
3497 AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toString");
3498 CallArgs args = CallArgsFromVp(argc, vp);
3500 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "toString");
3501 if (!unwrapped) {
3502 return false;
3505 const char* locale = unwrapped->realm()->getLocale();
3506 if (!locale) {
3507 return false;
3509 return FormatDate(cx, unwrapped->forceUTC(), locale,
3510 unwrapped->UTCTime().toNumber(), FormatSpec::DateTime,
3511 args.rval());
3514 bool js::date_valueOf(JSContext* cx, unsigned argc, Value* vp) {
3515 CallArgs args = CallArgsFromVp(argc, vp);
3517 auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "valueOf");
3518 if (!unwrapped) {
3519 return false;
3522 args.rval().set(unwrapped->UTCTime());
3523 return true;
3526 // ES6 20.3.4.45 Date.prototype[@@toPrimitive]
3527 static bool date_toPrimitive(JSContext* cx, unsigned argc, Value* vp) {
3528 CallArgs args = CallArgsFromVp(argc, vp);
3530 // Steps 1-2.
3531 if (!args.thisv().isObject()) {
3532 ReportIncompatible(cx, args);
3533 return false;
3536 // Steps 3-5.
3537 JSType hint;
3538 if (!GetFirstArgumentAsTypeHint(cx, args, &hint)) {
3539 return false;
3541 if (hint == JSTYPE_UNDEFINED) {
3542 hint = JSTYPE_STRING;
3545 args.rval().set(args.thisv());
3546 RootedObject obj(cx, &args.thisv().toObject());
3547 return OrdinaryToPrimitive(cx, obj, hint, args.rval());
3550 #if JS_HAS_TEMPORAL_API
3552 * Date.prototype.toTemporalInstant ( )
3554 static bool date_toTemporalInstant(JSContext* cx, unsigned argc, Value* vp) {
3555 CallArgs args = CallArgsFromVp(argc, vp);
3557 // Step 1.
3558 auto* unwrapped =
3559 UnwrapAndTypeCheckThis<DateObject>(cx, args, "toTemporalInstant");
3560 if (!unwrapped) {
3561 return false;
3564 // Step 2.
3565 double utctime = unwrapped->UTCTime().toNumber();
3566 if (!std::isfinite(utctime)) {
3567 JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
3568 JSMSG_INVALID_DATE);
3569 return false;
3571 MOZ_ASSERT(IsInteger(utctime));
3573 auto instant = temporal::Instant::fromMilliseconds(int64_t(utctime));
3574 MOZ_ASSERT(temporal::IsValidEpochInstant(instant));
3576 // Step 3.
3577 auto* result = temporal::CreateTemporalInstant(cx, instant);
3578 if (!result) {
3579 return false;
3581 args.rval().setObject(*result);
3582 return true;
3584 #endif /* JS_HAS_TEMPORAL_API */
3586 static const JSFunctionSpec date_static_methods[] = {
3587 JS_FN("UTC", date_UTC, 7, 0), JS_FN("parse", date_parse, 1, 0),
3588 JS_FN("now", date_now, 0, 0), JS_FS_END};
3590 static const JSFunctionSpec date_methods[] = {
3591 JS_FN("getTime", date_getTime, 0, 0),
3592 JS_FN("getTimezoneOffset", date_getTimezoneOffset, 0, 0),
3593 JS_FN("getYear", date_getYear, 0, 0),
3594 JS_FN("getFullYear", date_getFullYear, 0, 0),
3595 JS_FN("getUTCFullYear", date_getUTCFullYear, 0, 0),
3596 JS_FN("getMonth", date_getMonth, 0, 0),
3597 JS_FN("getUTCMonth", date_getUTCMonth, 0, 0),
3598 JS_FN("getDate", date_getDate, 0, 0),
3599 JS_FN("getUTCDate", date_getUTCDate, 0, 0),
3600 JS_FN("getDay", date_getDay, 0, 0),
3601 JS_FN("getUTCDay", date_getUTCDay, 0, 0),
3602 JS_FN("getHours", date_getHours, 0, 0),
3603 JS_FN("getUTCHours", date_getUTCHours, 0, 0),
3604 JS_FN("getMinutes", date_getMinutes, 0, 0),
3605 JS_FN("getUTCMinutes", date_getUTCMinutes, 0, 0),
3606 JS_FN("getSeconds", date_getSeconds, 0, 0),
3607 JS_FN("getUTCSeconds", date_getUTCSeconds, 0, 0),
3608 JS_FN("getMilliseconds", date_getMilliseconds, 0, 0),
3609 JS_FN("getUTCMilliseconds", date_getUTCMilliseconds, 0, 0),
3610 JS_FN("setTime", date_setTime, 1, 0),
3611 JS_FN("setYear", date_setYear, 1, 0),
3612 JS_FN("setFullYear", date_setFullYear, 3, 0),
3613 JS_FN("setUTCFullYear", date_setUTCFullYear, 3, 0),
3614 JS_FN("setMonth", date_setMonth, 2, 0),
3615 JS_FN("setUTCMonth", date_setUTCMonth, 2, 0),
3616 JS_FN("setDate", date_setDate, 1, 0),
3617 JS_FN("setUTCDate", date_setUTCDate, 1, 0),
3618 JS_FN("setHours", date_setHours, 4, 0),
3619 JS_FN("setUTCHours", date_setUTCHours, 4, 0),
3620 JS_FN("setMinutes", date_setMinutes, 3, 0),
3621 JS_FN("setUTCMinutes", date_setUTCMinutes, 3, 0),
3622 JS_FN("setSeconds", date_setSeconds, 2, 0),
3623 JS_FN("setUTCSeconds", date_setUTCSeconds, 2, 0),
3624 JS_FN("setMilliseconds", date_setMilliseconds, 1, 0),
3625 JS_FN("setUTCMilliseconds", date_setUTCMilliseconds, 1, 0),
3626 JS_FN("toUTCString", date_toUTCString, 0, 0),
3627 #if JS_HAS_TEMPORAL_API
3628 JS_FN("toTemporalInstant", date_toTemporalInstant, 0, 0),
3629 #endif
3630 #if JS_HAS_INTL_API
3631 JS_SELF_HOSTED_FN("toLocaleString", "Date_toLocaleString", 0, 0),
3632 JS_SELF_HOSTED_FN("toLocaleDateString", "Date_toLocaleDateString", 0, 0),
3633 JS_SELF_HOSTED_FN("toLocaleTimeString", "Date_toLocaleTimeString", 0, 0),
3634 #else
3635 JS_FN("toLocaleString", date_toLocaleString, 0, 0),
3636 JS_FN("toLocaleDateString", date_toLocaleDateString, 0, 0),
3637 JS_FN("toLocaleTimeString", date_toLocaleTimeString, 0, 0),
3638 #endif
3639 JS_FN("toDateString", date_toDateString, 0, 0),
3640 JS_FN("toTimeString", date_toTimeString, 0, 0),
3641 JS_FN("toISOString", date_toISOString, 0, 0),
3642 JS_FN("toJSON", date_toJSON, 1, 0),
3643 JS_FN("toSource", date_toSource, 0, 0),
3644 JS_FN("toString", date_toString, 0, 0),
3645 JS_FN("valueOf", date_valueOf, 0, 0),
3646 JS_SYM_FN(toPrimitive, date_toPrimitive, 1, JSPROP_READONLY),
3647 JS_FS_END};
3649 static bool NewDateObject(JSContext* cx, const CallArgs& args, ClippedTime t) {
3650 MOZ_ASSERT(args.isConstructing());
3652 RootedObject proto(cx);
3653 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Date, &proto)) {
3654 return false;
3657 JSObject* obj = NewDateObjectMsec(cx, t, proto);
3658 if (!obj) {
3659 return false;
3662 args.rval().setObject(*obj);
3663 return true;
3666 static bool ToDateString(JSContext* cx, const CallArgs& args, ClippedTime t) {
3667 const char* locale = cx->realm()->getLocale();
3668 if (!locale) {
3669 return false;
3671 return FormatDate(cx, ForceUTC(cx->realm()), locale,
3672 t.toDouble(), FormatSpec::DateTime, args.rval());
3675 static bool DateNoArguments(JSContext* cx, const CallArgs& args) {
3676 MOZ_ASSERT(args.length() == 0);
3678 ClippedTime now = NowAsMillis(cx);
3680 if (args.isConstructing()) {
3681 return NewDateObject(cx, args, now);
3684 return ToDateString(cx, args, now);
3687 static bool DateOneArgument(JSContext* cx, const CallArgs& args) {
3688 MOZ_ASSERT(args.length() == 1);
3690 if (args.isConstructing()) {
3691 if (args[0].isObject()) {
3692 RootedObject obj(cx, &args[0].toObject());
3694 ESClass cls;
3695 if (!GetBuiltinClass(cx, obj, &cls)) {
3696 return false;
3699 if (cls == ESClass::Date) {
3700 RootedValue unboxed(cx);
3701 if (!Unbox(cx, obj, &unboxed)) {
3702 return false;
3705 return NewDateObject(cx, args, TimeClip(unboxed.toNumber()));
3709 if (!ToPrimitive(cx, args[0])) {
3710 return false;
3713 ClippedTime t;
3714 if (args[0].isString()) {
3715 JSLinearString* linearStr = args[0].toString()->ensureLinear(cx);
3716 if (!linearStr) {
3717 return false;
3720 if (!ParseDate(ForceUTC(cx->realm()), linearStr, &t)) {
3721 t = ClippedTime::invalid();
3723 } else {
3724 double d;
3725 if (!ToNumber(cx, args[0], &d)) {
3726 return false;
3728 t = TimeClip(d);
3731 return NewDateObject(cx, args, t);
3734 return ToDateString(cx, args, NowAsMillis(cx));
3737 static bool DateMultipleArguments(JSContext* cx, const CallArgs& args) {
3738 MOZ_ASSERT(args.length() >= 2);
3740 // Step 3.
3741 if (args.isConstructing()) {
3742 // Steps 3a-b.
3743 double y;
3744 if (!ToNumber(cx, args[0], &y)) {
3745 return false;
3748 // Steps 3c-d.
3749 double m;
3750 if (!ToNumber(cx, args[1], &m)) {
3751 return false;
3754 // Steps 3e-f.
3755 double dt;
3756 if (args.length() >= 3) {
3757 if (!ToNumber(cx, args[2], &dt)) {
3758 return false;
3760 } else {
3761 dt = 1;
3764 // Steps 3g-h.
3765 double h;
3766 if (args.length() >= 4) {
3767 if (!ToNumber(cx, args[3], &h)) {
3768 return false;
3770 } else {
3771 h = 0;
3774 // Steps 3i-j.
3775 double min;
3776 if (args.length() >= 5) {
3777 if (!ToNumber(cx, args[4], &min)) {
3778 return false;
3780 } else {
3781 min = 0;
3784 // Steps 3k-l.
3785 double s;
3786 if (args.length() >= 6) {
3787 if (!ToNumber(cx, args[5], &s)) {
3788 return false;
3790 } else {
3791 s = 0;
3794 // Steps 3m-n.
3795 double milli;
3796 if (args.length() >= 7) {
3797 if (!ToNumber(cx, args[6], &milli)) {
3798 return false;
3800 } else {
3801 milli = 0;
3804 // Step 3o.
3805 double yr = y;
3806 if (!std::isnan(y)) {
3807 double yint = ToInteger(y);
3808 if (0 <= yint && yint <= 99) {
3809 yr = 1900 + yint;
3813 // Step 3p.
3814 double finalDate = MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli));
3816 // Steps 3q-t.
3817 return NewDateObject(cx, args,
3818 TimeClip(UTC(ForceUTC(cx->realm()), finalDate)));
3821 return ToDateString(cx, args, NowAsMillis(cx));
3824 static bool DateConstructor(JSContext* cx, unsigned argc, Value* vp) {
3825 AutoJSConstructorProfilerEntry pseudoFrame(cx, "Date");
3826 CallArgs args = CallArgsFromVp(argc, vp);
3828 if (args.length() == 0) {
3829 return DateNoArguments(cx, args);
3832 if (args.length() == 1) {
3833 return DateOneArgument(cx, args);
3836 return DateMultipleArguments(cx, args);
3839 static bool FinishDateClassInit(JSContext* cx, HandleObject ctor,
3840 HandleObject proto) {
3842 * Date.prototype.toGMTString has the same initial value as
3843 * Date.prototype.toUTCString.
3845 RootedValue toUTCStringFun(cx);
3846 RootedId toUTCStringId(cx, NameToId(cx->names().toUTCString));
3847 RootedId toGMTStringId(cx, NameToId(cx->names().toGMTString));
3848 return NativeGetProperty(cx, proto.as<NativeObject>(), toUTCStringId,
3849 &toUTCStringFun) &&
3850 NativeDefineDataProperty(cx, proto.as<NativeObject>(), toGMTStringId,
3851 toUTCStringFun, 0);
3854 static const ClassSpec DateObjectClassSpec = {
3855 GenericCreateConstructor<DateConstructor, 7, gc::AllocKind::FUNCTION>,
3856 GenericCreatePrototype<DateObject>,
3857 date_static_methods,
3858 nullptr,
3859 date_methods,
3860 nullptr,
3861 FinishDateClassInit};
3863 const JSClass DateObject::class_ = {"Date",
3864 JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
3865 JSCLASS_HAS_CACHED_PROTO(JSProto_Date),
3866 JS_NULL_CLASS_OPS, &DateObjectClassSpec};
3868 const JSClass DateObject::protoClass_ = {
3869 "Date.prototype", JSCLASS_HAS_CACHED_PROTO(JSProto_Date), JS_NULL_CLASS_OPS,
3870 &DateObjectClassSpec};
3872 JSObject* js::NewDateObjectMsec(JSContext* cx, ClippedTime t,
3873 HandleObject proto /* = nullptr */) {
3874 DateObject* obj = NewObjectWithClassProto<DateObject>(cx, proto);
3875 if (!obj) {
3876 return nullptr;
3878 obj->setUTCTime(t);
3879 return obj;
3882 JS_PUBLIC_API JSObject* JS::NewDateObject(JSContext* cx, ClippedTime time) {
3883 AssertHeapIsIdle();
3884 CHECK_THREAD(cx);
3885 return NewDateObjectMsec(cx, time);
3888 JS_PUBLIC_API JSObject* js::NewDateObject(JSContext* cx, int year, int mon,
3889 int mday, int hour, int min,
3890 int sec) {
3891 MOZ_ASSERT(mon < 12);
3892 double msec_time =
3893 MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0.0));
3894 return NewDateObjectMsec(cx, TimeClip(UTC(ForceUTC(cx->realm()), msec_time)));
3897 JS_PUBLIC_API bool js::DateIsValid(JSContext* cx, HandleObject obj,
3898 bool* isValid) {
3899 ESClass cls;
3900 if (!GetBuiltinClass(cx, obj, &cls)) {
3901 return false;
3904 if (cls != ESClass::Date) {
3905 *isValid = false;
3906 return true;
3909 RootedValue unboxed(cx);
3910 if (!Unbox(cx, obj, &unboxed)) {
3911 return false;
3914 *isValid = !std::isnan(unboxed.toNumber());
3915 return true;
3918 JS_PUBLIC_API JSObject* JS::NewDateObject(JSContext* cx, int year, int mon,
3919 int mday, int hour, int min,
3920 int sec) {
3921 AssertHeapIsIdle();
3922 CHECK_THREAD(cx);
3923 return js::NewDateObject(cx, year, mon, mday, hour, min, sec);
3926 JS_PUBLIC_API bool JS::ObjectIsDate(JSContext* cx, Handle<JSObject*> obj,
3927 bool* isDate) {
3928 cx->check(obj);
3930 ESClass cls;
3931 if (!GetBuiltinClass(cx, obj, &cls)) {
3932 return false;
3935 *isDate = cls == ESClass::Date;
3936 return true;
3939 JS_PUBLIC_API bool js::DateGetMsecSinceEpoch(JSContext* cx, HandleObject obj,
3940 double* msecsSinceEpoch) {
3941 ESClass cls;
3942 if (!GetBuiltinClass(cx, obj, &cls)) {
3943 return false;
3946 if (cls != ESClass::Date) {
3947 *msecsSinceEpoch = 0;
3948 return true;
3951 RootedValue unboxed(cx);
3952 if (!Unbox(cx, obj, &unboxed)) {
3953 return false;
3956 *msecsSinceEpoch = unboxed.toNumber();
3957 return true;
3960 JS_PUBLIC_API bool JS::IsISOStyleDate(JSContext* cx,
3961 const JS::Latin1Chars& str) {
3962 ClippedTime result;
3963 return ParseISOStyleDate(ForceUTC(cx->realm()), str.begin().get(),
3964 str.length(), &result);