tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

jsdate.cpp (130008B)


      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/. */
      6 
      7 /*
      8 * JS date methods.
      9 *
     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."
     14 *
     15 * Frederick Brooks, 'The Second-System Effect'.
     16 */
     17 
     18 #include "jsdate.h"
     19 
     20 #include "mozilla/Atomics.h"
     21 #include "mozilla/Casting.h"
     22 #include "mozilla/FloatingPoint.h"
     23 #include "mozilla/MathAlgorithms.h"
     24 #include "mozilla/TextUtils.h"
     25 
     26 #include <algorithm>
     27 #include <cstring>
     28 #include <math.h>
     29 #include <string.h>
     30 
     31 #include "jsapi.h"
     32 #include "jsfriendapi.h"
     33 #include "jsnum.h"
     34 #include "jstypes.h"
     35 
     36 #if JS_HAS_INTL_API
     37 #  include "builtin/intl/DateTimeFormat.h"
     38 #  include "builtin/intl/GlobalIntlData.h"
     39 #endif
     40 #ifdef JS_HAS_INTL_API
     41 #  include "builtin/temporal/Instant.h"
     42 #endif
     43 #include "jit/InlinableNatives.h"
     44 #include "js/CallAndConstruct.h"  // JS::IsCallable
     45 #include "js/Conversions.h"
     46 #include "js/Date.h"
     47 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
     48 #include "js/LocaleSensitive.h"
     49 #include "js/Object.h"  // JS::GetBuiltinClass
     50 #include "js/PropertySpec.h"
     51 #include "js/Wrapper.h"
     52 #include "util/DifferentialTesting.h"
     53 #include "util/StringBuilder.h"
     54 #include "util/Text.h"
     55 #include "vm/DateObject.h"
     56 #include "vm/DateTime.h"
     57 #include "vm/GlobalObject.h"
     58 #include "vm/Interpreter.h"
     59 #include "vm/JSContext.h"
     60 #include "vm/JSObject.h"
     61 #include "vm/StringType.h"
     62 #include "vm/Time.h"
     63 
     64 #include "vm/Compartment-inl.h"  // For js::UnwrapAndTypeCheckThis
     65 #include "vm/GeckoProfiler-inl.h"
     66 #include "vm/JSObject-inl.h"
     67 
     68 using namespace js;
     69 
     70 using mozilla::Atomic;
     71 using mozilla::BitwiseCast;
     72 using mozilla::IsAsciiAlpha;
     73 using mozilla::IsAsciiDigit;
     74 using mozilla::IsAsciiLowercaseAlpha;
     75 using mozilla::NumbersAreIdentical;
     76 using mozilla::Relaxed;
     77 
     78 using JS::AutoCheckCannotGC;
     79 using JS::ClippedTime;
     80 using JS::GenericNaN;
     81 using JS::GetBuiltinClass;
     82 using JS::TimeClip;
     83 using JS::ToInteger;
     84 
     85 // When this value is non-zero, we'll round the time by this resolution.
     86 static Atomic<uint32_t, Relaxed> sResolutionUsec;
     87 // This is not implemented yet, but we will use this to know to jitter the time
     88 // in the JS shell
     89 static Atomic<bool, Relaxed> sJitter;
     90 // The callback we will use for the Gecko implementation of Timer
     91 // Clamping/Jittering
     92 static Atomic<JS::ReduceMicrosecondTimePrecisionCallback, Relaxed>
     93    sReduceMicrosecondTimePrecisionCallback;
     94 
     95 namespace {
     96 
     97 class DateTimeHelper {
     98 private:
     99 #if !JS_HAS_INTL_API
    100  static int equivalentYearForDST(int year);
    101  static bool isRepresentableAsTime32(int64_t t);
    102  static int32_t daylightSavingTA(int64_t t);
    103  static int32_t adjustTime(int64_t date);
    104  static PRMJTime toPRMJTime(int64_t localTime, int64_t utcTime);
    105 #endif
    106 
    107 public:
    108  static int32_t getTimeZoneOffset(DateTimeInfo* dtInfo,
    109                                   int64_t epochMilliseconds,
    110                                   DateTimeInfo::TimeZoneOffset offset);
    111 
    112  static JSString* timeZoneComment(JSContext* cx, DateTimeInfo* dtInfo,
    113                                   const char* locale, int64_t utcTime,
    114                                   int64_t localTime);
    115 #if !JS_HAS_INTL_API
    116  static size_t formatTime(char* buf, size_t buflen, const char* fmt,
    117                           int64_t utcTime, int64_t localTime);
    118 #endif
    119 };
    120 
    121 }  // namespace
    122 
    123 /**
    124 * 5.2.5 Mathematical Operations
    125 *
    126 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    127 */
    128 static inline double PositiveModulo(double dividend, double divisor) {
    129  MOZ_ASSERT(divisor > 0);
    130  MOZ_ASSERT(std::isfinite(divisor));
    131 
    132  double result = fmod(dividend, divisor);
    133  if (result < 0) {
    134    result += divisor;
    135  }
    136  return result + (+0.0);
    137 }
    138 
    139 template <typename T>
    140 static inline std::enable_if_t<std::is_integral_v<T>, int32_t> PositiveModulo(
    141    T dividend, int32_t divisor) {
    142  MOZ_ASSERT(divisor > 0);
    143 
    144  int32_t result = dividend % divisor;
    145  if (result < 0) {
    146    result += divisor;
    147  }
    148  return result;
    149 }
    150 
    151 template <typename T>
    152 static constexpr T FloorDiv(T dividend, int32_t divisor) {
    153  MOZ_ASSERT(divisor > 0);
    154 
    155  T quotient = dividend / divisor;
    156  T remainder = dividend % divisor;
    157  if (remainder < 0) {
    158    quotient -= 1;
    159  }
    160  return quotient;
    161 }
    162 
    163 #ifdef DEBUG
    164 /**
    165 * 21.4.1.1 Time Values and Time Range
    166 *
    167 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    168 */
    169 static inline bool IsTimeValue(double t) {
    170  if (std::isnan(t)) {
    171    return true;
    172  }
    173  return IsInteger(t) && StartOfTime <= t && t <= EndOfTime;
    174 }
    175 #endif
    176 
    177 /**
    178 * Time value with local time zone offset applied.
    179 */
    180 static inline bool IsLocalTimeValue(double t) {
    181  if (std::isnan(t)) {
    182    return true;
    183  }
    184  return IsInteger(t) && (StartOfTime - msPerDay) < t &&
    185         t < (EndOfTime + msPerDay);
    186 }
    187 
    188 /**
    189 * 21.4.1.3 Day ( t )
    190 *
    191 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    192 */
    193 static inline int32_t Day(int64_t t) {
    194  MOZ_ASSERT(IsLocalTimeValue(t));
    195  return int32_t(FloorDiv(t, msPerDay));
    196 }
    197 
    198 /**
    199 * 21.4.1.4 TimeWithinDay ( t )
    200 *
    201 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    202 */
    203 static int32_t TimeWithinDay(int64_t t) {
    204  MOZ_ASSERT(IsLocalTimeValue(t));
    205  return PositiveModulo(t, msPerDay);
    206 }
    207 
    208 /**
    209 * 21.4.1.5 DaysInYear ( y )
    210 * 21.4.1.10 InLeapYear ( t )
    211 *
    212 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    213 */
    214 static inline bool IsLeapYear(double year) {
    215  MOZ_ASSERT(IsInteger(year));
    216  return fmod(year, 4) == 0 && (fmod(year, 100) != 0 || fmod(year, 400) == 0);
    217 }
    218 
    219 static constexpr bool IsLeapYear(int32_t year) {
    220  MOZ_ASSERT(mozilla::Abs(year) <= 2'000'000);
    221 
    222  // From: https://www.youtube.com/watch?v=0s9F4QWAl-E&t=1790s
    223  int32_t d = (year % 100 != 0) ? 4 : 16;
    224  return (year & (d - 1)) == 0;
    225 }
    226 
    227 /**
    228 * 21.4.1.6 DayFromYear ( y )
    229 *
    230 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    231 */
    232 static inline double DayFromYear(double y) {
    233  // Steps 1-7.
    234  return 365 * (y - 1970) + floor((y - 1969) / 4.0) -
    235         floor((y - 1901) / 100.0) + floor((y - 1601) / 400.0);
    236 }
    237 
    238 static constexpr int32_t DayFromYear(int32_t y) {
    239  MOZ_ASSERT(mozilla::Abs(y) <= 2'000'000);
    240 
    241  // Steps 1-7.
    242  return 365 * (y - 1970) + FloorDiv((y - 1969), 4) -
    243         FloorDiv((y - 1901), 100) + FloorDiv((y - 1601), 400);
    244 }
    245 
    246 /**
    247 * 21.4.1.7 TimeFromYear ( y )
    248 *
    249 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    250 */
    251 static inline double TimeFromYear(double y) {
    252  return ::DayFromYear(y) * msPerDay;
    253 }
    254 
    255 static inline int64_t TimeFromYear(int32_t y) {
    256  return ::DayFromYear(y) * int64_t(msPerDay);
    257 }
    258 
    259 /*
    260 * This function returns the year, month and day corresponding to a given
    261 * time value. The implementation closely follows (w.r.t. types and variable
    262 * names) the algorithm shown in Figure 12 of [1].
    263 *
    264 * A key point of the algorithm is that it works on the so called
    265 * Computational calendar where years run from March to February -- this
    266 * largely avoids complications with leap years. The algorithm finds the
    267 * date in the Computational calendar and then maps it to the Gregorian
    268 * calendar.
    269 *
    270 * [1] Neri C, Schneider L., "Euclidean affine functions and their
    271 * application to calendar algorithms."
    272 * Softw Pract Exper. 2023;53(4):937-970. doi: 10.1002/spe.3172
    273 * https://onlinelibrary.wiley.com/doi/full/10.1002/spe.3172
    274 */
    275 YearMonthDay js::ToYearMonthDay(int64_t time) {
    276  // Calendar cycles repeat every 400 years in the Gregorian calendar: a
    277  // leap day is added every 4 years, removed every 100 years and added
    278  // every 400 years. The number of days in 400 years is cycleInDays.
    279  constexpr uint32_t cycleInYears = 400;
    280  constexpr uint32_t cycleInDays = cycleInYears * 365 + (cycleInYears / 4) -
    281                                   (cycleInYears / 100) + (cycleInYears / 400);
    282  static_assert(cycleInDays == 146097, "Wrong calculation of cycleInDays.");
    283 
    284  // The natural epoch for the Computational calendar is 0000/Mar/01 and
    285  // there are rataDie1970Jan1 = 719468 days from this date to 1970/Jan/01,
    286  // the epoch used by ES2024, 21.4.1.1.
    287  constexpr uint32_t rataDie1970Jan1 = 719468;
    288 
    289  constexpr uint32_t maxU32 = std::numeric_limits<uint32_t>::max();
    290 
    291  // Let N_U be the number of days since the 1970/Jan/01. This function sets
    292  // N = N_U + K, where K = rataDie1970Jan1 + s * cycleInDays and s is an
    293  // integer number (to be chosen). Then, it evaluates 4 * N + 3 on uint32_t
    294  // operands so that N must be positive and, to prevent overflow,
    295  //   4 * N + 3 <= maxU32 <=> N <= (maxU32 - 3) / 4.
    296  // Therefore, we must have  0 <= N_U + K <= (maxU32 - 3) / 4 or, in other
    297  // words, N_U must be in [minDays, maxDays] = [-K, (maxU32 - 3) / 4 - K].
    298  // Notice that this interval moves cycleInDays positions to the left when
    299  // s is incremented. We chose s to get the interval's mid-point as close
    300  // as possible to 0. For this, we wish to have:
    301  //   K ~= (maxU32 - 3) / 4 - K <=> 2 * K ~= (maxU32 - 3) / 4 <=>
    302  //   K ~= (maxU32 - 3) / 8 <=>
    303  //   rataDie1970Jan1 + s * cycleInDays ~= (maxU32 - 3) / 8 <=>
    304  //   s ~= ((maxU32 - 3) / 8 - rataDie1970Jan1) / cycleInDays ~= 3669.8.
    305  // Therefore, we chose s = 3670. The shift and correction constants
    306  // (see [1]) are then:
    307  constexpr uint32_t s = 3670;
    308  constexpr uint32_t K = rataDie1970Jan1 + s * cycleInDays;
    309  constexpr uint32_t L = s * cycleInYears;
    310 
    311  // [minDays, maxDays] correspond to a date range from -1'468'000/Mar/01 to
    312  // 1'471'805/Jun/05.
    313  constexpr int32_t minDays = -int32_t(K);
    314  constexpr int32_t maxDays = (maxU32 - 3) / 4 - K;
    315  static_assert(minDays == -536'895'458, "Wrong calculation of minDays or K.");
    316  static_assert(maxDays == 536'846'365, "Wrong calculation of maxDays or K.");
    317 
    318  // These are hard limits for the algorithm and far greater than the
    319  // range [-8.64e15, 8.64e15] required by ES2024 21.4.1.1. Callers must
    320  // ensure this function is not called out of the hard limits and,
    321  // preferably, not outside the ES2024 limits.
    322  constexpr int64_t minTime = minDays * int64_t(msPerDay);
    323  [[maybe_unused]] constexpr int64_t maxTime = maxDays * int64_t(msPerDay);
    324  MOZ_ASSERT(minTime <= time && time <= maxTime);
    325 
    326  // Since time is the number of milliseconds since the epoch, 1970/Jan/01,
    327  // one might expect N_U = time / uint64_t(msPerDay) is the number of days
    328  // since epoch. There's a catch tough. Consider, for instance, half day
    329  // before the epoch, that is, t = -0.5 * msPerDay. This falls on
    330  // 1969/Dec/31 and should correspond to N_U = -1 but the above gives
    331  // N_U = 0. Indeed, t / msPerDay = -0.5 but integer division truncates
    332  // towards 0 (C++ [expr.mul]/4) and not towards -infinity as needed, so
    333  // that time / uint64_t(msPerDay) = 0. To workaround this issue we perform
    334  // the division on positive operands so that truncations towards 0 and
    335  // -infinity are equivalent. For this, set u = time - minTime, which is
    336  // positive as asserted above. Then, perform the division u / msPerDay and
    337  // to the result add minTime / msPerDay = minDays to cancel the
    338  // subtraction of minTime.
    339  const uint64_t u = uint64_t(time - minTime);
    340  const int32_t N_U = int32_t(u / uint64_t(msPerDay)) + minDays;
    341  MOZ_ASSERT(minDays <= N_U && N_U <= maxDays);
    342 
    343  const uint32_t N = uint32_t(N_U) + K;
    344 
    345  // Some magic numbers have been explained above but, unfortunately,
    346  // others with no precise interpretation do appear. They mostly come
    347  // from numerical approximations of Euclidean affine functions (see [1])
    348  // which are faster for the CPU to calculate. Unfortunately, no compiler
    349  // can do these optimizations.
    350 
    351  // Century C and year of the century N_C:
    352  const uint32_t N_1 = 4 * N + 3;
    353  const uint32_t C = N_1 / 146097;
    354  const uint32_t N_C = N_1 % 146097 / 4;
    355 
    356  // Year of the century Z and day of the year N_Y:
    357  const uint32_t N_2 = 4 * N_C + 3;
    358  const uint64_t P_2 = uint64_t(2939745) * N_2;
    359  const uint32_t Z = uint32_t(P_2 / 4294967296);
    360  const uint32_t N_Y = uint32_t(P_2 % 4294967296) / 2939745 / 4;
    361 
    362  // Year Y:
    363  const uint32_t Y = 100 * C + Z;
    364 
    365  // Month M and day D.
    366  // The expression for N_3 has been adapted to account for the difference
    367  // between month numbers in ES5 15.9.1.4 (from 0 to 11) and [1] (from 1
    368  // to 12). This is done by subtracting 65536 from the original
    369  // expression so that M decreases by 1 and so does M_G further down.
    370  const uint32_t N_3 = 2141 * N_Y + 132377;  // 132377 = 197913 - 65536
    371  const uint32_t M = N_3 / 65536;
    372  const uint32_t D = N_3 % 65536 / 2141;
    373 
    374  // Map from Computational to Gregorian calendar. Notice also the year
    375  // correction and the type change and that Jan/01 is day 306 of the
    376  // Computational calendar, cf. Table 1. [1]
    377  constexpr uint32_t daysFromMar01ToJan01 = 306;
    378  const uint32_t J = N_Y >= daysFromMar01ToJan01;
    379  const int32_t Y_G = int32_t((Y - L) + J);
    380  const int32_t M_G = int32_t(J ? M - 12 : M);
    381  const int32_t D_G = int32_t(D + 1);
    382 
    383  return {Y_G, M_G, D_G};
    384 }
    385 
    386 /**
    387 * 21.4.1.8 YearFromTime ( t )
    388 *
    389 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    390 */
    391 static int32_t YearFromTime(int64_t t) {
    392  MOZ_ASSERT(IsLocalTimeValue(t));
    393  return ToYearMonthDay(t).year;
    394 }
    395 
    396 /**
    397 * 21.4.1.9 DayWithinYear ( t )
    398 *
    399 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    400 */
    401 static double DayWithinYear(int64_t t, double year) {
    402  MOZ_ASSERT(::YearFromTime(t) == year);
    403  return Day(t) - ::DayFromYear(year);
    404 }
    405 
    406 /**
    407 * 21.4.1.11 MonthFromTime ( t )
    408 *
    409 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    410 */
    411 static int32_t MonthFromTime(int64_t t) {
    412  MOZ_ASSERT(IsLocalTimeValue(t));
    413  return ToYearMonthDay(t).month;
    414 }
    415 
    416 /**
    417 * 21.4.1.12 DateFromTime ( t )
    418 *
    419 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    420 */
    421 static int32_t DateFromTime(int64_t t) {
    422  MOZ_ASSERT(IsLocalTimeValue(t));
    423  return ToYearMonthDay(t).day;
    424 }
    425 
    426 /**
    427 * 21.4.1.13 WeekDay ( t )
    428 *
    429 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    430 */
    431 static int32_t WeekDay(int64_t t) {
    432  MOZ_ASSERT(IsLocalTimeValue(t));
    433 
    434  int32_t result = (Day(t) + 4) % 7;
    435  if (result < 0) {
    436    result += 7;
    437  }
    438  return result;
    439 }
    440 
    441 static inline int DayFromMonth(int month, bool isLeapYear) {
    442  /*
    443   * The following array contains the day of year for the first day of
    444   * each month, where index 0 is January, and day 0 is January 1.
    445   */
    446  static const int firstDayOfMonth[2][13] = {
    447      {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
    448      {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}};
    449 
    450  MOZ_ASSERT(0 <= month && month <= 12);
    451  return firstDayOfMonth[isLeapYear][month];
    452 }
    453 
    454 template <typename T>
    455 static inline int DayFromMonth(T month, bool isLeapYear) = delete;
    456 
    457 /**
    458 * 21.4.1.28 MakeDay ( year, month, date )
    459 *
    460 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    461 */
    462 static double MakeDay(double year, double month, double date) {
    463  // Step 1.
    464  if (!std::isfinite(year) || !std::isfinite(month) || !std::isfinite(date)) {
    465    return GenericNaN();
    466  }
    467 
    468  // Steps 2-4.
    469  double y = ToInteger(year);
    470  double m = ToInteger(month);
    471  double dt = ToInteger(date);
    472 
    473  static constexpr int32_t maxYears = 1'000'000;
    474  static constexpr int32_t maxMonths = 1'000'000 * 12;
    475  static constexpr int32_t maxDate = 100'000'000;
    476 
    477  // Use integer math if possible, because it avoids some notoriously slow
    478  // functions like `fmod`.
    479  if (MOZ_LIKELY(std::abs(y) <= maxYears && std::abs(m) <= maxMonths &&
    480                 std::abs(dt) <= maxDate)) {
    481    int32_t year = mozilla::AssertedCast<int32_t>(y);
    482    int32_t month = mozilla::AssertedCast<int32_t>(m);
    483    int32_t date = mozilla::AssertedCast<int32_t>(dt);
    484 
    485    static_assert(maxMonths % 12 == 0,
    486                  "maxYearMonths expects maxMonths is divisible by 12");
    487 
    488    static constexpr int32_t maxYearMonths = maxYears + (maxMonths / 12);
    489    static constexpr int32_t maxYearDay = DayFromYear(maxYearMonths);
    490    static constexpr int32_t minYearDay = DayFromYear(-maxYearMonths);
    491    static constexpr int32_t daysInLeapYear = 366;
    492    static constexpr int32_t maxDay = maxYearDay + daysInLeapYear + maxDate;
    493    static constexpr int32_t minDay = minYearDay + daysInLeapYear - maxDate;
    494 
    495    static_assert(maxYearMonths == 2'000'000);
    496    static_assert(maxYearDay == 729'765'472);
    497    static_assert(minYearDay == -731'204'528);
    498    static_assert(maxDay == maxYearDay + daysInLeapYear + maxDate);
    499    static_assert(minDay == minYearDay + daysInLeapYear - maxDate);
    500 
    501    // Step 5.
    502    int32_t ym = year + FloorDiv(month, 12);
    503    MOZ_ASSERT(std::abs(ym) <= maxYearMonths);
    504 
    505    // Step 6. (Implicit)
    506 
    507    // Step 7.
    508    int32_t mn = PositiveModulo(month, 12);
    509 
    510    // Step 8.
    511    bool leap = IsLeapYear(ym);
    512    int32_t yearday = ::DayFromYear(ym);
    513    int32_t monthday = DayFromMonth(mn, leap);
    514    MOZ_ASSERT(minYearDay <= yearday && yearday <= maxYearDay);
    515 
    516    // Step 9.
    517    int32_t day = yearday + monthday + date - 1;
    518    MOZ_ASSERT(minDay <= day && day <= maxDay);
    519    return day;
    520  }
    521 
    522  // Step 5.
    523  double ym = y + floor(m / 12);
    524 
    525  // Step 6.
    526  if (!std::isfinite(ym)) {
    527    return GenericNaN();
    528  }
    529 
    530  // Step 7.
    531  int mn = int(PositiveModulo(m, 12));
    532 
    533  // Step 8.
    534  bool leap = IsLeapYear(ym);
    535  double yearday = floor(TimeFromYear(ym) / msPerDay);
    536  double monthday = DayFromMonth(mn, leap);
    537 
    538  // Step 9.
    539  return yearday + monthday + dt - 1;
    540 }
    541 
    542 /**
    543 * 21.4.1.29 MakeDate ( day, time )
    544 *
    545 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    546 */
    547 static inline double MakeDate(double day, double time) {
    548  // Step 1.
    549  if (!std::isfinite(day) || !std::isfinite(time)) {
    550    return GenericNaN();
    551  }
    552 
    553  // Steps 2-4.
    554  return day * msPerDay + time;
    555 }
    556 
    557 JS_PUBLIC_API double JS::MakeDate(double year, unsigned month, unsigned day) {
    558  MOZ_ASSERT(month <= 11);
    559  MOZ_ASSERT(day >= 1 && day <= 31);
    560 
    561  return ::MakeDate(MakeDay(year, month, day), 0);
    562 }
    563 
    564 JS_PUBLIC_API double JS::MakeDate(double year, unsigned month, unsigned day,
    565                                  double time) {
    566  MOZ_ASSERT(month <= 11);
    567  MOZ_ASSERT(day >= 1 && day <= 31);
    568 
    569  return ::MakeDate(MakeDay(year, month, day), time);
    570 }
    571 
    572 JS_PUBLIC_API double JS::YearFromTime(double time) {
    573  const auto clipped = TimeClip(time);
    574  if (!clipped.isValid()) {
    575    return GenericNaN();
    576  }
    577  int64_t tv;
    578  MOZ_ALWAYS_TRUE(mozilla::NumberEqualsInt64(clipped.toDouble(), &tv));
    579  return ::YearFromTime(tv);
    580 }
    581 
    582 JS_PUBLIC_API double JS::MonthFromTime(double time) {
    583  const auto clipped = TimeClip(time);
    584  if (!clipped.isValid()) {
    585    return GenericNaN();
    586  }
    587  int64_t tv;
    588  MOZ_ALWAYS_TRUE(mozilla::NumberEqualsInt64(clipped.toDouble(), &tv));
    589  return ::MonthFromTime(tv);
    590 }
    591 
    592 JS_PUBLIC_API double JS::DayFromTime(double time) {
    593  const auto clipped = TimeClip(time);
    594  if (!clipped.isValid()) {
    595    return GenericNaN();
    596  }
    597  int64_t tv;
    598  MOZ_ALWAYS_TRUE(mozilla::NumberEqualsInt64(clipped.toDouble(), &tv));
    599  return DateFromTime(tv);
    600 }
    601 
    602 JS_PUBLIC_API double JS::DayFromYear(double year) {
    603  return ::DayFromYear(year);
    604 }
    605 
    606 JS_PUBLIC_API double JS::DayWithinYear(double time, double year) {
    607  const auto clipped = TimeClip(time);
    608  if (!clipped.isValid()) {
    609    return GenericNaN();
    610  }
    611  int64_t tv;
    612  MOZ_ALWAYS_TRUE(mozilla::NumberEqualsInt64(clipped.toDouble(), &tv));
    613  return ::DayWithinYear(tv, year);
    614 }
    615 
    616 JS_PUBLIC_API void JS::SetReduceMicrosecondTimePrecisionCallback(
    617    JS::ReduceMicrosecondTimePrecisionCallback callback) {
    618  sReduceMicrosecondTimePrecisionCallback = callback;
    619 }
    620 
    621 JS_PUBLIC_API JS::ReduceMicrosecondTimePrecisionCallback
    622 JS::GetReduceMicrosecondTimePrecisionCallback() {
    623  return sReduceMicrosecondTimePrecisionCallback;
    624 }
    625 
    626 JS_PUBLIC_API void JS::SetTimeResolutionUsec(uint32_t resolution, bool jitter) {
    627  sResolutionUsec = resolution;
    628  sJitter = jitter;
    629 }
    630 
    631 #if JS_HAS_INTL_API
    632 int32_t DateTimeHelper::getTimeZoneOffset(DateTimeInfo* dtInfo,
    633                                          int64_t epochMilliseconds,
    634                                          DateTimeInfo::TimeZoneOffset offset) {
    635  MOZ_ASSERT_IF(offset == DateTimeInfo::TimeZoneOffset::UTC,
    636                IsTimeValue(epochMilliseconds));
    637  MOZ_ASSERT_IF(offset == DateTimeInfo::TimeZoneOffset::Local,
    638                IsLocalTimeValue(epochMilliseconds));
    639 
    640  return DateTimeInfo::getOffsetMilliseconds(dtInfo, epochMilliseconds, offset);
    641 }
    642 #else
    643 /*
    644 * Find a year for which any given date will fall on the same weekday.
    645 *
    646 * This function should be used with caution when used other than
    647 * for determining DST; it hasn't been proven not to produce an
    648 * incorrect year for times near year boundaries.
    649 */
    650 int DateTimeHelper::equivalentYearForDST(int year) {
    651  /*
    652   * Years and leap years on which Jan 1 is a Sunday, Monday, etc.
    653   *
    654   * yearStartingWith[0][i] is an example non-leap year where
    655   * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
    656   *
    657   * yearStartingWith[1][i] is an example leap year where
    658   * Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
    659   *
    660   * Keep two different mappings, one for past years (< 1970), and a
    661   * different one for future years (> 2037).
    662   */
    663  static const int pastYearStartingWith[2][7] = {
    664      {1978, 1973, 1974, 1975, 1981, 1971, 1977},
    665      {1984, 1996, 1980, 1992, 1976, 1988, 1972}};
    666  static const int futureYearStartingWith[2][7] = {
    667      {2034, 2035, 2030, 2031, 2037, 2027, 2033},
    668      {2012, 2024, 2036, 2020, 2032, 2016, 2028}};
    669 
    670  int day = int(::DayFromYear(year) + 4) % 7;
    671  if (day < 0) {
    672    day += 7;
    673  }
    674 
    675  const auto& yearStartingWith =
    676      year < 1970 ? pastYearStartingWith : futureYearStartingWith;
    677  return yearStartingWith[IsLeapYear(year)][day];
    678 }
    679 
    680 // Return true if |t| is representable as a 32-bit time_t variable, that means
    681 // the year is in [1970, 2038).
    682 bool DateTimeHelper::isRepresentableAsTime32(int64_t t) {
    683  return 0 <= t && t < 2145916800000;
    684 }
    685 
    686 /* ES5 15.9.1.8. */
    687 int32_t DateTimeHelper::daylightSavingTA(int64_t t) {
    688  /*
    689   * If earlier than 1970 or after 2038, potentially beyond the ken of
    690   * many OSes, map it to an equivalent year before asking.
    691   */
    692  if (!isRepresentableAsTime32(t)) {
    693    auto [year, month, day] = ToYearMonthDay(t);
    694 
    695    int equivalentYear = equivalentYearForDST(year);
    696    double equivalentDay = MakeDay(equivalentYear, month, day);
    697    double equivalentDate = MakeDate(equivalentDay, TimeWithinDay(t));
    698 
    699    MOZ_ALWAYS_TRUE(mozilla::NumberEqualsInt64(equivalentDate, &t));
    700  }
    701 
    702  return DateTimeInfo::getDSTOffsetMilliseconds(nullptr, t);
    703 }
    704 
    705 int32_t DateTimeHelper::adjustTime(int64_t date) {
    706  int32_t localTZA = DateTimeInfo::localTZA();
    707  int32_t t = daylightSavingTA(date) + localTZA;
    708  return (localTZA >= 0) ? (t % msPerDay) : -((msPerDay - t) % msPerDay);
    709 }
    710 
    711 int32_t DateTimeHelper::getTimeZoneOffset(DateTimeInfo* dtInfo,
    712                                          int64_t epochMilliseconds,
    713                                          DateTimeInfo::TimeZoneOffset offset) {
    714  MOZ_ASSERT(dtInfo == nullptr);
    715  MOZ_ASSERT_IF(offset == DateTimeInfo::TimeZoneOffset::UTC,
    716                IsTimeValue(epochMilliseconds));
    717  MOZ_ASSERT_IF(offset == DateTimeInfo::TimeZoneOffset::Local,
    718                IsLocalTimeValue(epochMilliseconds));
    719 
    720  if (offset == DateTimeInfo::TimeZoneOffset::UTC) {
    721    return adjustTime(epochMilliseconds);
    722  }
    723 
    724  // Following the ES2017 specification creates undesirable results at DST
    725  // transitions. For example when transitioning from PST to PDT,
    726  // |new Date(2016,2,13,2,0,0).toTimeString()| returns the string value
    727  // "01:00:00 GMT-0800 (PST)" instead of "03:00:00 GMT-0700 (PDT)". Follow
    728  // V8 and subtract one hour before computing the offset.
    729  // Spec bug: https://bugs.ecmascript.org/show_bug.cgi?id=4007
    730 
    731  return adjustTime(epochMilliseconds - int64_t(DateTimeInfo::localTZA()) -
    732                    int64_t(msPerHour));
    733 }
    734 #endif /* JS_HAS_INTL_API */
    735 
    736 /**
    737 * 21.4.1.25 LocalTime ( t )
    738 *
    739 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    740 */
    741 static int64_t LocalTime(DateTimeInfo* dtInfo, double t) {
    742  MOZ_ASSERT(std::isfinite(t));
    743  MOZ_ASSERT(IsTimeValue(t));
    744 
    745  // Steps 1-4.
    746  int32_t offsetMs = DateTimeHelper::getTimeZoneOffset(
    747      dtInfo, static_cast<int64_t>(t), DateTimeInfo::TimeZoneOffset::UTC);
    748  MOZ_ASSERT(std::abs(offsetMs) < msPerDay);
    749 
    750  // Step 5.
    751  return static_cast<int64_t>(t) + offsetMs;
    752 }
    753 
    754 /**
    755 * 21.4.1.26 UTC ( t )
    756 *
    757 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    758 */
    759 static double UTC(DateTimeInfo* dtInfo, double t) {
    760  // Step 1.
    761  if (!std::isfinite(t)) {
    762    return GenericNaN();
    763  }
    764 
    765  // Return early when |t| is outside the valid local time value limits.
    766  if (!IsLocalTimeValue(t)) {
    767    return GenericNaN();
    768  }
    769 
    770  // Steps 2-5.
    771  int32_t offsetMs = DateTimeHelper::getTimeZoneOffset(
    772      dtInfo, static_cast<int64_t>(t), DateTimeInfo::TimeZoneOffset::Local);
    773  MOZ_ASSERT(std::abs(offsetMs) < msPerDay);
    774 
    775  // Step 6.
    776  return static_cast<double>(static_cast<int64_t>(t) - offsetMs);
    777 }
    778 
    779 /**
    780 * 21.4.1.14 HourFromTime ( t )
    781 *
    782 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    783 */
    784 static int32_t HourFromTime(int64_t t) {
    785  MOZ_ASSERT(IsLocalTimeValue(t));
    786  return PositiveModulo(FloorDiv(t, msPerHour), HoursPerDay);
    787 }
    788 
    789 /**
    790 * 21.4.1.15 MinFromTime ( t )
    791 *
    792 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    793 */
    794 static int32_t MinFromTime(int64_t t) {
    795  MOZ_ASSERT(IsLocalTimeValue(t));
    796  return PositiveModulo(FloorDiv(t, msPerMinute), MinutesPerHour);
    797 }
    798 
    799 /**
    800 * 21.4.1.16 SecFromTime ( t )
    801 *
    802 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    803 */
    804 static int32_t SecFromTime(int64_t t) {
    805  MOZ_ASSERT(IsLocalTimeValue(t));
    806  return PositiveModulo(FloorDiv(t, msPerSecond), SecondsPerMinute);
    807 }
    808 
    809 /**
    810 * 21.4.1.17 msFromTime ( t )
    811 *
    812 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    813 */
    814 static int32_t msFromTime(int64_t t) {
    815  MOZ_ASSERT(IsLocalTimeValue(t));
    816  return PositiveModulo(t, msPerSecond);
    817 }
    818 
    819 HourMinuteSecond js::ToHourMinuteSecond(int64_t epochMilliseconds) {
    820  MOZ_ASSERT(IsLocalTimeValue(epochMilliseconds));
    821 
    822  int32_t hour = HourFromTime(epochMilliseconds);
    823  MOZ_ASSERT(0 <= hour && hour < HoursPerDay);
    824 
    825  int32_t minute = MinFromTime(epochMilliseconds);
    826  MOZ_ASSERT(0 <= minute && minute < MinutesPerHour);
    827 
    828  int32_t second = SecFromTime(epochMilliseconds);
    829  MOZ_ASSERT(0 <= minute && minute < SecondsPerMinute);
    830 
    831  return {hour, minute, second};
    832 }
    833 
    834 /**
    835 * 21.4.1.27 MakeTime ( hour, min, sec, ms )
    836 *
    837 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    838 */
    839 static double MakeTime(double hour, double min, double sec, double ms) {
    840  // Step 1.
    841  if (!std::isfinite(hour) || !std::isfinite(min) || !std::isfinite(sec) ||
    842      !std::isfinite(ms)) {
    843    return GenericNaN();
    844  }
    845 
    846  // Step 2.
    847  double h = ToInteger(hour);
    848 
    849  // Step 3.
    850  double m = ToInteger(min);
    851 
    852  // Step 4.
    853  double s = ToInteger(sec);
    854 
    855  // Step 5.
    856  double milli = ToInteger(ms);
    857 
    858  // Step 6.
    859  return h * msPerHour + m * msPerMinute + s * msPerSecond + milli;
    860 }
    861 
    862 /**
    863 * 21.4.1.30 MakeFullYear ( year )
    864 *
    865 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    866 */
    867 static double MakeFullYear(double year) {
    868  // Step 1.
    869  if (std::isnan(year)) {
    870    return year;
    871  }
    872 
    873  // Step 2.
    874  double truncated = ToInteger(year);
    875 
    876  // Step 3.
    877  if (0 <= truncated && truncated <= 99) {
    878    return 1900 + truncated;
    879  }
    880 
    881  // Step 4.
    882  return truncated;
    883 }
    884 
    885 /**
    886 * end of ECMA 'support' functions
    887 */
    888 
    889 /**
    890 * 21.4.3.4 Date.UTC ( year [ , month [ , date [ , hours [ , minutes [ , seconds
    891 * [ , ms ] ] ] ] ] ] )
    892 *
    893 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
    894 */
    895 static bool date_UTC(JSContext* cx, unsigned argc, Value* vp) {
    896  AutoJSMethodProfilerEntry pseudoFrame(cx, "Date", "UTC");
    897  CallArgs args = CallArgsFromVp(argc, vp);
    898 
    899  // Step 1.
    900  double y;
    901  if (!ToNumber(cx, args.get(0), &y)) {
    902    return false;
    903  }
    904 
    905  // Step 2.
    906  double m;
    907  if (args.length() >= 2) {
    908    if (!ToNumber(cx, args[1], &m)) {
    909      return false;
    910    }
    911  } else {
    912    m = 0;
    913  }
    914 
    915  // Step 3.
    916  double dt;
    917  if (args.length() >= 3) {
    918    if (!ToNumber(cx, args[2], &dt)) {
    919      return false;
    920    }
    921  } else {
    922    dt = 1;
    923  }
    924 
    925  // Step 4.
    926  double h;
    927  if (args.length() >= 4) {
    928    if (!ToNumber(cx, args[3], &h)) {
    929      return false;
    930    }
    931  } else {
    932    h = 0;
    933  }
    934 
    935  // Step 5.
    936  double min;
    937  if (args.length() >= 5) {
    938    if (!ToNumber(cx, args[4], &min)) {
    939      return false;
    940    }
    941  } else {
    942    min = 0;
    943  }
    944 
    945  // Step 6.
    946  double s;
    947  if (args.length() >= 6) {
    948    if (!ToNumber(cx, args[5], &s)) {
    949      return false;
    950    }
    951  } else {
    952    s = 0;
    953  }
    954 
    955  // Step 7.
    956  double milli;
    957  if (args.length() >= 7) {
    958    if (!ToNumber(cx, args[6], &milli)) {
    959      return false;
    960    }
    961  } else {
    962    milli = 0;
    963  }
    964 
    965  // Step 8.
    966  double yr = MakeFullYear(y);
    967 
    968  // Step 9.
    969  ClippedTime time =
    970      TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)));
    971  args.rval().set(TimeValue(time));
    972  return true;
    973 }
    974 
    975 /*
    976 * Read and convert decimal digits from s[*i] into *result
    977 * while *i < limit.
    978 *
    979 * Succeed if any digits are converted. Advance *i only
    980 * as digits are consumed.
    981 */
    982 template <typename CharT>
    983 static bool ParseDigits(size_t* result, const CharT* s, size_t* i,
    984                        size_t limit) {
    985  size_t init = *i;
    986  *result = 0;
    987  while (*i < limit && ('0' <= s[*i] && s[*i] <= '9')) {
    988    *result *= 10;
    989    *result += (s[*i] - '0');
    990    ++(*i);
    991  }
    992  return *i != init;
    993 }
    994 
    995 /*
    996 * Read and convert decimal digits to the right of a decimal point,
    997 * representing a fractional integer, from s[*i] into *result
    998 * while *i < limit, up to 3 digits. Consumes any digits beyond 3
    999 * without affecting the result.
   1000 *
   1001 * Succeed if any digits are converted. Advance *i only
   1002 * as digits are consumed.
   1003 */
   1004 template <typename CharT>
   1005 static bool ParseFractional(int* result, const CharT* s, size_t* i,
   1006                            size_t limit) {
   1007  int factor = 100;
   1008  size_t init = *i;
   1009  *result = 0;
   1010  for (; *i < limit && ('0' <= s[*i] && s[*i] <= '9'); ++(*i)) {
   1011    if (*i - init >= 3) {
   1012      // If we're past 3 digits, do nothing with it, but continue to
   1013      // consume the remainder of the digits
   1014      continue;
   1015    }
   1016    *result += (s[*i] - '0') * factor;
   1017    factor /= 10;
   1018  }
   1019  return *i != init;
   1020 }
   1021 
   1022 /*
   1023 * Read and convert exactly n decimal digits from s[*i]
   1024 * to s[min(*i+n,limit)] into *result.
   1025 *
   1026 * Succeed if exactly n digits are converted. Advance *i only
   1027 * on success.
   1028 */
   1029 template <typename CharT>
   1030 static bool ParseDigitsN(size_t n, size_t* result, const CharT* s, size_t* i,
   1031                         size_t limit) {
   1032  size_t init = *i;
   1033 
   1034  if (ParseDigits(result, s, i, std::min(limit, init + n))) {
   1035    return (*i - init) == n;
   1036  }
   1037 
   1038  *i = init;
   1039  return false;
   1040 }
   1041 
   1042 /*
   1043 * Read and convert n or less decimal digits from s[*i]
   1044 * to s[min(*i+n,limit)] into *result.
   1045 *
   1046 * Succeed only if greater than zero but less than or equal to n digits are
   1047 * converted. Advance *i only on success.
   1048 */
   1049 template <typename CharT>
   1050 static bool ParseDigitsNOrLess(size_t n, size_t* result, const CharT* s,
   1051                               size_t* i, size_t limit) {
   1052  size_t init = *i;
   1053 
   1054  if (ParseDigits(result, s, i, std::min(limit, init + n))) {
   1055    return ((*i - init) > 0) && ((*i - init) <= n);
   1056  }
   1057 
   1058  *i = init;
   1059  return false;
   1060 }
   1061 
   1062 /*
   1063 * Parse a string according to the formats specified in the standard:
   1064 *
   1065 * https://tc39.es/ecma262/#sec-date-time-string-format
   1066 * https://tc39.es/ecma262/#sec-expanded-years
   1067 *
   1068 * These formats are based upon a simplification of the ISO 8601 Extended
   1069 * Format. As per the spec omitted month and day values are defaulted to '01',
   1070 * omitted HH:mm:ss values are defaulted to '00' and an omitted sss field is
   1071 * defaulted to '000'.
   1072 *
   1073 * For cross compatibility we allow the following extensions.
   1074 *
   1075 * These are:
   1076 *
   1077 *   One or more decimal digits for milliseconds:
   1078 *     The specification requires exactly three decimal digits for
   1079 *     the fractional part but we allow for one or more digits.
   1080 *
   1081 *   Time zone specifier without ':':
   1082 *     We allow the time zone to be specified without a ':' character.
   1083 *     E.g. "T19:00:00+0700" is equivalent to "T19:00:00+07:00".
   1084 *
   1085 * Date part:
   1086 *
   1087 *  Year:
   1088 *     YYYY (eg 1997)
   1089 *
   1090 *  Year and month:
   1091 *     YYYY-MM (eg 1997-07)
   1092 *
   1093 *  Complete date:
   1094 *     YYYY-MM-DD (eg 1997-07-16)
   1095 *
   1096 * Time part:
   1097 *
   1098 *  Hours and minutes:
   1099 *     Thh:mmTZD (eg T19:20+01:00)
   1100 *
   1101 *  Hours, minutes and seconds:
   1102 *     Thh:mm:ssTZD (eg T19:20:30+01:00)
   1103 *
   1104 *  Hours, minutes, seconds and a decimal fraction of a second:
   1105 *     Thh:mm:ss.sssTZD (eg T19:20:30.45+01:00)
   1106 *
   1107 * where:
   1108 *
   1109 *   YYYY = four-digit year or six digit year as +YYYYYY or -YYYYYY
   1110 *   MM   = two-digit month (01=January, etc.)
   1111 *   DD   = two-digit day of month (01 through 31)
   1112 *   hh   = two digits of hour (00 through 24) (am/pm NOT allowed)
   1113 *   mm   = two digits of minute (00 through 59)
   1114 *   ss   = two digits of second (00 through 59)
   1115 *   sss  = one or more digits representing a decimal fraction of a second
   1116 *   TZD  = time zone designator (Z or +hh:mm or -hh:mm or missing for local)
   1117 */
   1118 template <typename CharT>
   1119 static bool ParseISOStyleDate(DateTimeInfo* dtInfo, const CharT* s,
   1120                              size_t length, ClippedTime* result) {
   1121  size_t i = 0;
   1122  int tzMul = 1;
   1123  int dateMul = 1;
   1124  size_t year = 1970;
   1125  size_t month = 1;
   1126  size_t day = 1;
   1127  size_t hour = 0;
   1128  size_t min = 0;
   1129  size_t sec = 0;
   1130  int msec = 0;
   1131  bool isLocalTime = false;
   1132  size_t tzHour = 0;
   1133  size_t tzMin = 0;
   1134 
   1135 #define PEEK(ch) (i < length && s[i] == ch)
   1136 
   1137 #define NEED(ch)                   \
   1138  if (i >= length || s[i] != ch) { \
   1139    return false;                  \
   1140  } else {                         \
   1141    ++i;                           \
   1142  }
   1143 
   1144 #define DONE_DATE_UNLESS(ch)       \
   1145  if (i >= length || s[i] != ch) { \
   1146    goto done_date;                \
   1147  } else {                         \
   1148    ++i;                           \
   1149  }
   1150 
   1151 #define DONE_UNLESS(ch)            \
   1152  if (i >= length || s[i] != ch) { \
   1153    goto done;                     \
   1154  } else {                         \
   1155    ++i;                           \
   1156  }
   1157 
   1158 #define NEED_NDIGITS(n, field)                   \
   1159  if (!ParseDigitsN(n, &field, s, &i, length)) { \
   1160    return false;                                \
   1161  }
   1162 
   1163  if (PEEK('+') || PEEK('-')) {
   1164    if (PEEK('-')) {
   1165      dateMul = -1;
   1166    }
   1167    ++i;
   1168    NEED_NDIGITS(6, year);
   1169 
   1170    // https://tc39.es/ecma262/#sec-expanded-years
   1171    // -000000 is not a valid expanded year.
   1172    if (year == 0 && dateMul == -1) {
   1173      return false;
   1174    }
   1175  } else {
   1176    NEED_NDIGITS(4, year);
   1177  }
   1178  DONE_DATE_UNLESS('-');
   1179  NEED_NDIGITS(2, month);
   1180  DONE_DATE_UNLESS('-');
   1181  NEED_NDIGITS(2, day);
   1182 
   1183 done_date:
   1184  if (PEEK('T')) {
   1185    ++i;
   1186  } else {
   1187    goto done;
   1188  }
   1189 
   1190  NEED_NDIGITS(2, hour);
   1191  NEED(':');
   1192  NEED_NDIGITS(2, min);
   1193 
   1194  if (PEEK(':')) {
   1195    ++i;
   1196    NEED_NDIGITS(2, sec);
   1197    if (PEEK('.')) {
   1198      ++i;
   1199      if (!ParseFractional(&msec, s, &i, length)) {
   1200        return false;
   1201      }
   1202    }
   1203  }
   1204 
   1205  if (PEEK('Z')) {
   1206    ++i;
   1207  } else if (PEEK('+') || PEEK('-')) {
   1208    if (PEEK('-')) {
   1209      tzMul = -1;
   1210    }
   1211    ++i;
   1212    NEED_NDIGITS(2, tzHour);
   1213    /*
   1214     * Non-standard extension to the ISO date format (permitted by ES5):
   1215     * allow "-0700" as a time zone offset, not just "-07:00".
   1216     */
   1217    if (PEEK(':')) {
   1218      ++i;
   1219    }
   1220    NEED_NDIGITS(2, tzMin);
   1221  } else {
   1222    isLocalTime = true;
   1223  }
   1224 
   1225 done:
   1226  if (year > 275943  // ceil(1e8/365) + 1970
   1227      || month == 0 || month > 12 || day == 0 || day > 31 || hour > 24 ||
   1228      (hour == 24 && (min > 0 || sec > 0 || msec > 0)) || min > 59 ||
   1229      sec > 59 || tzHour > 23 || tzMin > 59) {
   1230    return false;
   1231  }
   1232 
   1233  if (i != length) {
   1234    return false;
   1235  }
   1236 
   1237  month -= 1; /* convert month to 0-based */
   1238 
   1239  double date = MakeDate(MakeDay(dateMul * int32_t(year), month, day),
   1240                         MakeTime(hour, min, sec, msec));
   1241 
   1242  if (isLocalTime) {
   1243    date = UTC(dtInfo, date);
   1244  } else {
   1245    date -=
   1246        tzMul * (int32_t(tzHour) * msPerHour + int32_t(tzMin) * msPerMinute);
   1247  }
   1248 
   1249  *result = TimeClip(date);
   1250  return NumbersAreIdentical(date, result->toDouble());
   1251 
   1252 #undef PEEK
   1253 #undef NEED
   1254 #undef DONE_UNLESS
   1255 #undef NEED_NDIGITS
   1256 }
   1257 
   1258 /**
   1259 * Non-ISO years < 100 get fixed up, to allow 2-digit year formats.
   1260 * year < 50 becomes 2000-2049, 50-99 becomes 1950-1999.
   1261 */
   1262 static int FixupYear(int year) {
   1263  if (year < 50) {
   1264    year += 2000;
   1265  } else if (year >= 50 && year < 100) {
   1266    year += 1900;
   1267  }
   1268  return year;
   1269 }
   1270 
   1271 template <typename CharT>
   1272 static bool MatchesKeyword(const CharT* s, size_t len, const char* keyword) {
   1273  while (len > 0) {
   1274    MOZ_ASSERT(IsAsciiAlpha(*s));
   1275    MOZ_ASSERT(IsAsciiLowercaseAlpha(*keyword) || *keyword == '\0');
   1276 
   1277    if (unicode::ToLowerCase(static_cast<Latin1Char>(*s)) != *keyword) {
   1278      return false;
   1279    }
   1280 
   1281    ++s, ++keyword;
   1282    --len;
   1283  }
   1284 
   1285  return *keyword == '\0';
   1286 }
   1287 
   1288 static constexpr const char* const month_prefixes[] = {
   1289    "jan", "feb", "mar", "apr", "may", "jun",
   1290    "jul", "aug", "sep", "oct", "nov", "dec",
   1291 };
   1292 
   1293 /**
   1294 * Given a string s of length >= 3, checks if it begins,
   1295 * case-insensitive, with the given lower case prefix.
   1296 */
   1297 template <typename CharT>
   1298 static bool StartsWithMonthPrefix(const CharT* s, const char* prefix) {
   1299  MOZ_ASSERT(strlen(prefix) == 3);
   1300 
   1301  for (size_t i = 0; i < 3; ++i) {
   1302    MOZ_ASSERT(IsAsciiAlpha(*s));
   1303    MOZ_ASSERT(IsAsciiLowercaseAlpha(*prefix));
   1304 
   1305    if (unicode::ToLowerCase(static_cast<Latin1Char>(*s)) != *prefix) {
   1306      return false;
   1307    }
   1308 
   1309    ++s, ++prefix;
   1310  }
   1311 
   1312  return true;
   1313 }
   1314 
   1315 template <typename CharT>
   1316 static bool IsMonthName(const CharT* s, size_t len, int* mon) {
   1317  // Month abbreviations < 3 chars are not accepted.
   1318  if (len < 3) {
   1319    return false;
   1320  }
   1321 
   1322  for (size_t m = 0; m < std::size(month_prefixes); ++m) {
   1323    if (StartsWithMonthPrefix(s, month_prefixes[m])) {
   1324      // Use numeric value.
   1325      *mon = m + 1;
   1326      return true;
   1327    }
   1328  }
   1329 
   1330  return false;
   1331 }
   1332 
   1333 /*
   1334 * Try to parse the following date formats:
   1335 *   dd-MMM-yyyy
   1336 *   dd-MMM-yy
   1337 *   MMM-dd-yyyy
   1338 *   MMM-dd-yy
   1339 *   yyyy-MMM-dd
   1340 *   yy-MMM-dd
   1341 *
   1342 * Returns true and fills all out parameters when successfully parsed
   1343 * dashed-date.  Otherwise returns false and leaves out parameters untouched.
   1344 */
   1345 template <typename CharT>
   1346 static bool TryParseDashedDatePrefix(const CharT* s, size_t length,
   1347                                     size_t* indexOut, int* yearOut,
   1348                                     int* monOut, int* mdayOut) {
   1349  size_t i = *indexOut;
   1350 
   1351  size_t pre = i;
   1352  size_t mday;
   1353  if (!ParseDigitsNOrLess(6, &mday, s, &i, length)) {
   1354    return false;
   1355  }
   1356  size_t mdayDigits = i - pre;
   1357 
   1358  if (i >= length || s[i] != '-') {
   1359    return false;
   1360  }
   1361  ++i;
   1362 
   1363  int mon = 0;
   1364  if (*monOut == -1) {
   1365    // If month wasn't already set by ParseDate, it must be in the middle of
   1366    // this format, let's look for it
   1367    size_t start = i;
   1368    for (; i < length; i++) {
   1369      if (!IsAsciiAlpha(s[i])) {
   1370        break;
   1371      }
   1372    }
   1373 
   1374    if (!IsMonthName(s + start, i - start, &mon)) {
   1375      return false;
   1376    }
   1377 
   1378    if (i >= length || s[i] != '-') {
   1379      return false;
   1380    }
   1381    ++i;
   1382  }
   1383 
   1384  pre = i;
   1385  size_t year;
   1386  if (!ParseDigitsNOrLess(6, &year, s, &i, length)) {
   1387    return false;
   1388  }
   1389  size_t yearDigits = i - pre;
   1390 
   1391  if (i < length && IsAsciiDigit(s[i])) {
   1392    return false;
   1393  }
   1394 
   1395  // Swap the mday and year if the year wasn't specified in full.
   1396  if (mday > 31 && year <= 31 && yearDigits < 4) {
   1397    std::swap(mday, year);
   1398    std::swap(mdayDigits, yearDigits);
   1399  }
   1400 
   1401  if (mday > 31 || mdayDigits > 2) {
   1402    return false;
   1403  }
   1404 
   1405  year = FixupYear(year);
   1406 
   1407  *indexOut = i;
   1408  *yearOut = year;
   1409  if (*monOut == -1) {
   1410    *monOut = mon;
   1411  }
   1412  *mdayOut = mday;
   1413  return true;
   1414 }
   1415 
   1416 /*
   1417 * Try to parse dates in the style of YYYY-MM-DD which do not conform to
   1418 * the formal standard from ParseISOStyleDate. This includes cases such as
   1419 *
   1420 *  - Year does not have 4 digits
   1421 *  - Month or mday has 1 digit
   1422 *  - Space in between date and time, rather than a 'T'
   1423 *
   1424 * Regarding the last case, this function only parses out the date, returning
   1425 * to ParseDate to finish parsing the time and timezone, if present.
   1426 *
   1427 * Returns true and fills all out parameters when successfully parsed
   1428 * dashed-date.  Otherwise returns false and leaves out parameters untouched.
   1429 */
   1430 template <typename CharT>
   1431 static bool TryParseDashedNumericDatePrefix(const CharT* s, size_t length,
   1432                                            size_t* indexOut, int* yearOut,
   1433                                            int* monOut, int* mdayOut) {
   1434  size_t i = *indexOut;
   1435 
   1436  size_t first;
   1437  if (!ParseDigitsNOrLess(6, &first, s, &i, length)) {
   1438    return false;
   1439  }
   1440 
   1441  if (i >= length || s[i] != '-') {
   1442    return false;
   1443  }
   1444  ++i;
   1445 
   1446  size_t second;
   1447  if (!ParseDigitsNOrLess(2, &second, s, &i, length)) {
   1448    return false;
   1449  }
   1450 
   1451  if (i >= length || s[i] != '-') {
   1452    return false;
   1453  }
   1454  ++i;
   1455 
   1456  size_t third;
   1457  if (!ParseDigitsNOrLess(6, &third, s, &i, length)) {
   1458    return false;
   1459  }
   1460 
   1461  int year;
   1462  int mon = -1;
   1463  int mday = -1;
   1464 
   1465  // 1 or 2 digits for the first number is tricky; 1-12 means it's a month, 0 or
   1466  // >31 means it's a year, and 13-31 is invalid due to ambiguity.
   1467  if (first >= 1 && first <= 12) {
   1468    mon = first;
   1469  } else if (first == 0 || first > 31) {
   1470    year = first;
   1471  } else {
   1472    return false;
   1473  }
   1474 
   1475  if (mon < 0) {
   1476    // If month hasn't been set yet, it's definitely the 2nd number
   1477    mon = second;
   1478  } else {
   1479    // If it has, the next number is the mday
   1480    mday = second;
   1481  }
   1482 
   1483  if (mday < 0) {
   1484    // The third number is probably the mday...
   1485    mday = third;
   1486  } else {
   1487    // But otherwise, it's the year
   1488    year = third;
   1489  }
   1490 
   1491  if (mon < 1 || mon > 12 || mday < 1 || mday > 31) {
   1492    return false;
   1493  }
   1494 
   1495  year = FixupYear(year);
   1496 
   1497  *indexOut = i;
   1498  *yearOut = year;
   1499  *monOut = mon;
   1500  *mdayOut = mday;
   1501  return true;
   1502 }
   1503 
   1504 struct CharsAndAction {
   1505  const char* chars;
   1506  int action;
   1507 };
   1508 
   1509 static constexpr CharsAndAction keywords[] = {
   1510    // clang-format off
   1511  // AM/PM
   1512  { "am", -1 },
   1513  { "pm", -2 },
   1514  // Time zone abbreviations.
   1515  { "gmt", 10000 + 0 },
   1516  { "z", 10000 + 0 },
   1517  { "ut", 10000 + 0 },
   1518  { "utc", 10000 + 0 },
   1519  { "est", 10000 + 5 * 60 },
   1520  { "edt", 10000 + 4 * 60 },
   1521  { "cst", 10000 + 6 * 60 },
   1522  { "cdt", 10000 + 5 * 60 },
   1523  { "mst", 10000 + 7 * 60 },
   1524  { "mdt", 10000 + 6 * 60 },
   1525  { "pst", 10000 + 8 * 60 },
   1526  { "pdt", 10000 + 7 * 60 },
   1527    // clang-format on
   1528 };
   1529 
   1530 template <size_t N>
   1531 static constexpr size_t MinKeywordLength(const CharsAndAction (&keywords)[N]) {
   1532  size_t min = size_t(-1);
   1533  for (const CharsAndAction& keyword : keywords) {
   1534    min = std::min(min, std::char_traits<char>::length(keyword.chars));
   1535  }
   1536  return min;
   1537 }
   1538 
   1539 template <typename CharT>
   1540 static bool ParseDate(JSContext* cx, DateTimeInfo* dtInfo, const CharT* s,
   1541                      size_t length, ClippedTime* result) {
   1542  if (length == 0) {
   1543    return false;
   1544  }
   1545 
   1546  if (ParseISOStyleDate(dtInfo, s, length, result)) {
   1547    return true;
   1548  }
   1549 
   1550  // Collect telemetry on how often Date.parse enters implementation defined
   1551  // code. This can be removed in the future, see Bug 1944630.
   1552  cx->runtime()->setUseCounter(cx->global(), JSUseCounter::DATEPARSE_IMPL_DEF);
   1553 
   1554  size_t index = 0;
   1555  int mon = -1;
   1556  bool seenMonthName = false;
   1557 
   1558  // Before we begin, we need to scrub any words from the beginning of the
   1559  // string up to the first number, recording the month if we encounter it
   1560  for (; index < length; index++) {
   1561    int c = s[index];
   1562 
   1563    if (strchr(" ,.-/", c)) {
   1564      continue;
   1565    }
   1566    if (!IsAsciiAlpha(c)) {
   1567      break;
   1568    }
   1569 
   1570    size_t start = index;
   1571    index++;
   1572    for (; index < length; index++) {
   1573      if (!IsAsciiAlpha(s[index])) {
   1574        break;
   1575      }
   1576    }
   1577 
   1578    if (index >= length) {
   1579      return false;
   1580    }
   1581 
   1582    if (IsMonthName(s + start, index - start, &mon)) {
   1583      seenMonthName = true;
   1584      // If the next digit is a number, we need to break so it
   1585      // gets parsed as mday
   1586      if (IsAsciiDigit(s[index])) {
   1587        break;
   1588      }
   1589    } else if (!strchr(" ,.-/", s[index])) {
   1590      // We're only allowing the above delimiters after the day of
   1591      // week to prevent things such as "foo_1" from being parsed
   1592      // as a date, which may break software which uses this function
   1593      // to determine whether or not something is a date.
   1594      return false;
   1595    }
   1596  }
   1597 
   1598  int year = -1;
   1599  int mday = -1;
   1600  int hour = -1;
   1601  int min = -1;
   1602  int sec = -1;
   1603  int msec = 0;
   1604  int tzOffset = -1;
   1605 
   1606  // One of '+', '-', ':', '/', or 0 (the default value).
   1607  int prevc = 0;
   1608 
   1609  bool seenPlusMinus = false;
   1610  bool seenFullYear = false;
   1611  bool negativeYear = false;
   1612  // Includes "GMT", "UTC", "UT", and "Z" timezone keywords
   1613  bool seenGmtAbbr = false;
   1614 
   1615  // Try parsing the leading dashed-date.
   1616  //
   1617  // If successfully parsed, index is updated to the end of the date part,
   1618  // and year, mon, mday are set to the date.
   1619  // Continue parsing optional time + tzOffset parts.
   1620  //
   1621  // Otherwise, this is no-op.
   1622  bool isDashedDate =
   1623      TryParseDashedDatePrefix(s, length, &index, &year, &mon, &mday) ||
   1624      TryParseDashedNumericDatePrefix(s, length, &index, &year, &mon, &mday);
   1625 
   1626  if (isDashedDate && index < length && strchr("T:+", s[index])) {
   1627    return false;
   1628  }
   1629 
   1630  while (index < length) {
   1631    int c = s[index];
   1632    index++;
   1633 
   1634    // Normalize U+202F (NARROW NO-BREAK SPACE). This character appears between
   1635    // the AM/PM markers for |date.toLocaleString("en")|. We have to normalize
   1636    // it for backward compatibility reasons.
   1637    if (c == 0x202F) {
   1638      c = ' ';
   1639    }
   1640 
   1641    if ((c == '+' || c == '-') &&
   1642        // Reject + or - after timezone (still allowing for negative year)
   1643        ((seenPlusMinus && year != -1) ||
   1644         // Reject timezones like "1995-09-26 -04:30" (if the - is right up
   1645         // against the previous number, it will get parsed as a time,
   1646         // see the other comment below)
   1647         (year != -1 && hour == -1 && !seenGmtAbbr &&
   1648          !IsAsciiDigit(s[index - 2])))) {
   1649      return false;
   1650    }
   1651 
   1652    // Spaces, ASCII control characters, periods, and commas are simply ignored.
   1653    if (c <= ' ' || c == '.' || c == ',') {
   1654      continue;
   1655    }
   1656 
   1657    // Parse delimiter characters.  Save them to the side for future use.
   1658    if (c == '/' || c == ':' || c == '+') {
   1659      prevc = c;
   1660      continue;
   1661    }
   1662 
   1663    // Dashes are delimiters if they're immediately followed by a number field.
   1664    // If they're not followed by a number field, they're simply ignored.
   1665    if (c == '-') {
   1666      if (index < length && IsAsciiDigit(s[index])) {
   1667        prevc = c;
   1668      }
   1669      continue;
   1670    }
   1671 
   1672    // Skip over comments -- text inside matching parentheses.  (Comments
   1673    // themselves may contain comments as long as all the parentheses properly
   1674    // match up.  And apparently comments, including nested ones, may validly be
   1675    // terminated by end of input...)
   1676    if (c == '(') {
   1677      int depth = 1;
   1678      while (index < length) {
   1679        c = s[index];
   1680        index++;
   1681        if (c == '(') {
   1682          depth++;
   1683        } else if (c == ')') {
   1684          if (--depth <= 0) {
   1685            break;
   1686          }
   1687        }
   1688      }
   1689      continue;
   1690    }
   1691 
   1692    // Parse a number field.
   1693    if (IsAsciiDigit(c)) {
   1694      size_t partStart = index - 1;
   1695      uint32_t u = c - '0';
   1696      while (index < length) {
   1697        c = s[index];
   1698        if (!IsAsciiDigit(c)) {
   1699          break;
   1700        }
   1701        u = u * 10 + (c - '0');
   1702        index++;
   1703      }
   1704      size_t partLength = index - partStart;
   1705 
   1706      // See above for why we have to normalize U+202F.
   1707      if (c == 0x202F) {
   1708        c = ' ';
   1709      }
   1710 
   1711      int n = int(u);
   1712 
   1713      /*
   1714       * Allow TZA before the year, so 'Wed Nov 05 21:49:11 GMT-0800 1997'
   1715       * works.
   1716       *
   1717       * Uses of seenPlusMinus allow ':' in TZA, so Java no-timezone style
   1718       * of GMT+4:30 works.
   1719       */
   1720 
   1721      if (prevc == '-' && (tzOffset != 0 || seenPlusMinus) && partLength >= 4 &&
   1722          year < 0) {
   1723        // Parse as a negative, possibly zero-padded year if
   1724        // 1. the preceding character is '-',
   1725        // 2. the TZA is not 'GMT' (tested by |tzOffset != 0|),
   1726        // 3. or a TZA was already parsed |seenPlusMinus == true|,
   1727        // 4. the part length is at least 4 (to parse '-08' as a TZA),
   1728        // 5. and we did not already parse a year |year < 0|.
   1729        year = n;
   1730        seenFullYear = true;
   1731        negativeYear = true;
   1732      } else if ((prevc == '+' || prevc == '-') &&
   1733                 // "1995-09-26-04:30" needs to be parsed as a time,
   1734                 // not a time zone
   1735                 (seenGmtAbbr || hour != -1)) {
   1736        /* Make ':' case below change tzOffset. */
   1737        seenPlusMinus = true;
   1738 
   1739        /* offset */
   1740        if (n < 24 && partLength <= 2) {
   1741          n = n * 60; /* EG. "GMT-3" */
   1742        } else {
   1743          n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */
   1744        }
   1745 
   1746        if (prevc == '+') /* plus means east of GMT */
   1747          n = -n;
   1748 
   1749        // Reject if not preceded by 'GMT' or if a time zone offset
   1750        // was already parsed.
   1751        if (tzOffset != 0 && tzOffset != -1) {
   1752          return false;
   1753        }
   1754 
   1755        tzOffset = n;
   1756      } else if (prevc == '/' && mon >= 0 && mday >= 0 && year < 0) {
   1757        if (c <= ' ' || c == ',' || c == '/' || index >= length) {
   1758          year = n;
   1759        } else {
   1760          return false;
   1761        }
   1762      } else if (c == ':') {
   1763        if (hour < 0) {
   1764          hour = /*byte*/ n;
   1765        } else if (min < 0) {
   1766          min = /*byte*/ n;
   1767        } else {
   1768          return false;
   1769        }
   1770      } else if (c == '/') {
   1771        /*
   1772         * Until it is determined that mon is the actual month, keep
   1773         * it as 1-based rather than 0-based.
   1774         */
   1775        if (mon < 0) {
   1776          mon = /*byte*/ n;
   1777        } else if (mday < 0) {
   1778          mday = /*byte*/ n;
   1779        } else {
   1780          return false;
   1781        }
   1782      } else if (index < length && c != ',' && c > ' ' && c != '-' &&
   1783                 c != '(' &&
   1784                 // Allow '.' as a delimiter until seconds have been parsed
   1785                 // (this allows the decimal for milliseconds)
   1786                 (c != '.' || sec != -1) &&
   1787                 // Allow zulu time e.g. "09/26/1995 16:00Z", or
   1788                 // '+' directly after time e.g. 00:00+0500
   1789                 !(hour != -1 && strchr("Zz+", c)) &&
   1790                 // Allow month or AM/PM directly after a number
   1791                 (!IsAsciiAlpha(c) ||
   1792                  (mon != -1 && !(strchr("AaPp", c) && index < length - 1 &&
   1793                                  strchr("Mm", s[index + 1]))))) {
   1794        return false;
   1795      } else if (seenPlusMinus && n < 60) { /* handle GMT-3:30 */
   1796        if (tzOffset < 0) {
   1797          tzOffset -= n;
   1798        } else {
   1799          tzOffset += n;
   1800        }
   1801      } else if (hour >= 0 && min < 0) {
   1802        min = /*byte*/ n;
   1803      } else if (prevc == ':' && min >= 0 && sec < 0) {
   1804        sec = /*byte*/ n;
   1805        if (c == '.') {
   1806          index++;
   1807          if (!ParseFractional(&msec, s, &index, length)) {
   1808            return false;
   1809          }
   1810        }
   1811      } else if (mon < 0) {
   1812        mon = /*byte*/ n;
   1813      } else if (mon >= 0 && mday < 0) {
   1814        mday = /*byte*/ n;
   1815      } else if (mon >= 0 && mday >= 0 && year < 0) {
   1816        year = n;
   1817        seenFullYear = partLength >= 4;
   1818      } else {
   1819        return false;
   1820      }
   1821 
   1822      prevc = 0;
   1823      continue;
   1824    }
   1825 
   1826    // Parse fields that are words: ASCII letters spelling out in English AM/PM,
   1827    // day of week, month, or an extremely limited set of legacy time zone
   1828    // abbreviations.
   1829    if (IsAsciiAlpha(c)) {
   1830      size_t start = index - 1;
   1831      while (index < length) {
   1832        c = s[index];
   1833        if (!IsAsciiAlpha(c)) {
   1834          break;
   1835        }
   1836        index++;
   1837      }
   1838 
   1839      // There must be at least as many letters as in the shortest keyword.
   1840      constexpr size_t MinLength = MinKeywordLength(keywords);
   1841      if (index - start < MinLength) {
   1842        return false;
   1843      }
   1844 
   1845      // Record a month if it is a month name. Note that some numbers are
   1846      // initially treated as months; if a numeric field has already been
   1847      // interpreted as a month, store that value to the actually appropriate
   1848      // date component and set the month here.
   1849      int tryMonth;
   1850      if (IsMonthName(s + start, index - start, &tryMonth)) {
   1851        if (seenMonthName) {
   1852          // Overwrite the previous month name
   1853          mon = tryMonth;
   1854          prevc = 0;
   1855          continue;
   1856        }
   1857 
   1858        seenMonthName = true;
   1859 
   1860        if (mon < 0) {
   1861          mon = tryMonth;
   1862        } else if (mday < 0) {
   1863          mday = mon;
   1864          mon = tryMonth;
   1865        } else if (year < 0) {
   1866          if (mday > 0) {
   1867            // If the date is of the form f l month, then when month is
   1868            // reached we have f in mon and l in mday. In order to be
   1869            // consistent with the f month l and month f l forms, we need to
   1870            // swap so that f is in mday and l is in year.
   1871            year = mday;
   1872            mday = mon;
   1873          } else {
   1874            year = mon;
   1875          }
   1876          mon = tryMonth;
   1877        } else {
   1878          return false;
   1879        }
   1880 
   1881        prevc = 0;
   1882        continue;
   1883      }
   1884 
   1885      size_t k = std::size(keywords);
   1886      while (k-- > 0) {
   1887        const CharsAndAction& keyword = keywords[k];
   1888 
   1889        // If the field doesn't match the keyword, try the next one.
   1890        if (!MatchesKeyword(s + start, index - start, keyword.chars)) {
   1891          continue;
   1892        }
   1893 
   1894        int action = keyword.action;
   1895 
   1896        if (action == 10000) {
   1897          seenGmtAbbr = true;
   1898        }
   1899 
   1900        // Perform action tests from smallest action values to largest.
   1901 
   1902        // Adjust a previously-specified hour for AM/PM accordingly (taking care
   1903        // to treat 12:xx AM as 00:xx, 12:xx PM as 12:xx).
   1904        if (action < 0) {
   1905          MOZ_ASSERT(action == -1 || action == -2);
   1906          if (hour > 12 || hour < 0) {
   1907            return false;
   1908          }
   1909 
   1910          if (action == -1 && hour == 12) {
   1911            hour = 0;
   1912          } else if (action == -2 && hour != 12) {
   1913            hour += 12;
   1914          }
   1915 
   1916          break;
   1917        }
   1918 
   1919        // Finally, record a time zone offset.
   1920        MOZ_ASSERT(action >= 10000);
   1921        tzOffset = action - 10000;
   1922        break;
   1923      }
   1924 
   1925      if (k == size_t(-1)) {
   1926        return false;
   1927      }
   1928 
   1929      prevc = 0;
   1930      continue;
   1931    }
   1932 
   1933    // Any other character fails to parse.
   1934    return false;
   1935  }
   1936 
   1937  // Handle cases where the input is a single number. Single numbers >= 1000
   1938  // are handled by the spec (ParseISOStyleDate), so we don't need to account
   1939  // for that here.
   1940  if (mon != -1 && year < 0 && mday < 0) {
   1941    // Reject 13-31 for Chrome parity
   1942    if (mon >= 13 && mon <= 31) {
   1943      return false;
   1944    }
   1945 
   1946    mday = 1;
   1947    if (mon >= 1 && mon <= 12) {
   1948      // 1-12 is parsed as a month with the year defaulted to 2001
   1949      // (again, for Chrome parity)
   1950      year = 2001;
   1951    } else {
   1952      year = FixupYear(mon);
   1953      mon = 1;
   1954    }
   1955  }
   1956 
   1957  if (year < 0 || mon < 0 || mday < 0) {
   1958    return false;
   1959  }
   1960 
   1961  if (!isDashedDate) {
   1962    // NOTE: TryParseDashedDatePrefix already handles the following fixup.
   1963 
   1964    /*
   1965     * Case 1. The input string contains an English month name.
   1966     *         The form of the string can be month f l, or f month l, or
   1967     *         f l month which each evaluate to the same date.
   1968     *         If f and l are both greater than or equal to 100 the date
   1969     *         is invalid.
   1970     *
   1971     *         The year is taken to be either l, f if f > 31, or whichever
   1972     *         is set to zero.
   1973     *
   1974     * Case 2. The input string is of the form "f/m/l" where f, m and l are
   1975     *         integers, e.g. 7/16/45. mon, mday and year values are adjusted
   1976     *         to achieve Chrome compatibility.
   1977     *
   1978     *         a. If 0 < f <= 12 and 0 < l <= 31, f/m/l is interpreted as
   1979     *         month/day/year.
   1980     *         b. If 31 < f and 0 < m <= 12 and 0 < l <= 31 f/m/l is
   1981     *         interpreted as year/month/day
   1982     */
   1983    if (seenMonthName) {
   1984      if (mday >= 100 && mon >= 100) {
   1985        return false;
   1986      }
   1987 
   1988      if (year > 0 && (mday == 0 || mday > 31) && !seenFullYear) {
   1989        int temp = year;
   1990        year = mday;
   1991        mday = temp;
   1992      }
   1993 
   1994      if (mday <= 0 || mday > 31) {
   1995        return false;
   1996      }
   1997 
   1998    } else if (0 < mon && mon <= 12 && 0 < mday && mday <= 31) {
   1999      /* (a) month/day/year */
   2000    } else {
   2001      /* (b) year/month/day */
   2002      if (mon > 31 && mday <= 12 && year <= 31 && !seenFullYear) {
   2003        int temp = year;
   2004        year = mon;
   2005        mon = mday;
   2006        mday = temp;
   2007      } else {
   2008        return false;
   2009      }
   2010    }
   2011 
   2012    year = FixupYear(year);
   2013 
   2014    if (negativeYear) {
   2015      year = -year;
   2016    }
   2017  }
   2018 
   2019  mon -= 1; /* convert month to 0-based */
   2020  if (sec < 0) {
   2021    sec = 0;
   2022  }
   2023  if (min < 0) {
   2024    min = 0;
   2025  }
   2026  if (hour < 0) {
   2027    hour = 0;
   2028  }
   2029 
   2030  double date =
   2031      MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, msec));
   2032 
   2033  if (tzOffset == -1) { /* no time zone specified, have to use local */
   2034    date = UTC(dtInfo, date);
   2035  } else {
   2036    date += double(tzOffset) * msPerMinute;
   2037  }
   2038 
   2039  *result = TimeClip(date);
   2040  return true;
   2041 }
   2042 
   2043 static bool ParseDate(JSContext* cx, DateTimeInfo* dtInfo,
   2044                      const JSLinearString* s, ClippedTime* result) {
   2045  AutoCheckCannotGC nogc;
   2046  // Collect telemetry on how often Date.parse is being used.
   2047  // This can be removed in the future, see Bug 1944630.
   2048  cx->runtime()->setUseCounter(cx->global(), JSUseCounter::DATEPARSE);
   2049  return s->hasLatin1Chars()
   2050             ? ParseDate(cx, dtInfo, s->latin1Chars(nogc), s->length(), result)
   2051             : ParseDate(cx, dtInfo, s->twoByteChars(nogc), s->length(),
   2052                         result);
   2053 }
   2054 
   2055 /**
   2056 * 21.4.3.2 Date.parse ( string )
   2057 *
   2058 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2059 */
   2060 static bool date_parse(JSContext* cx, unsigned argc, Value* vp) {
   2061  AutoJSMethodProfilerEntry pseudoFrame(cx, "Date", "parse");
   2062 
   2063  CallArgs args = CallArgsFromVp(argc, vp);
   2064  if (args.length() == 0) {
   2065    args.rval().setNaN();
   2066    return true;
   2067  }
   2068 
   2069  JSString* str = ToString<CanGC>(cx, args[0]);
   2070  if (!str) {
   2071    return false;
   2072  }
   2073 
   2074  JSLinearString* linearStr = str->ensureLinear(cx);
   2075  if (!linearStr) {
   2076    return false;
   2077  }
   2078 
   2079  ClippedTime result;
   2080  if (!ParseDate(cx, cx->realm()->getDateTimeInfo(), linearStr, &result)) {
   2081    args.rval().setNaN();
   2082    return true;
   2083  }
   2084 
   2085  args.rval().set(TimeValue(result));
   2086  return true;
   2087 }
   2088 
   2089 static ClippedTime NowAsMillis(JSContext* cx) {
   2090  if (js::SupportDifferentialTesting()) {
   2091    return TimeClip(0);
   2092  }
   2093 
   2094  double now = PRMJ_Now();
   2095  bool clampAndJitter = cx->realm()->behaviors().clampAndJitterTime();
   2096  if (clampAndJitter && sReduceMicrosecondTimePrecisionCallback) {
   2097    now = sReduceMicrosecondTimePrecisionCallback(
   2098        now, cx->realm()->behaviors().reduceTimerPrecisionCallerType().value(),
   2099        cx);
   2100  } else if (clampAndJitter && sResolutionUsec) {
   2101    double clamped = floor(now / sResolutionUsec) * sResolutionUsec;
   2102 
   2103    if (sJitter) {
   2104      // Calculate a random midpoint for jittering. In the browser, we are
   2105      // adversarial: Web Content may try to calculate the midpoint themselves
   2106      // and use that to bypass it's security. In the JS Shell, we are not
   2107      // adversarial, we want to jitter the time to recreate the operating
   2108      // environment, but we do not concern ourselves with trying to prevent an
   2109      // attacker from calculating the midpoint themselves. So we use a very
   2110      // simple, very fast CRC with a hardcoded seed.
   2111 
   2112      uint64_t midpoint = BitwiseCast<uint64_t>(clamped);
   2113      midpoint ^= 0x0F00DD1E2BAD2DED;  // XOR in a 'secret'
   2114      // MurmurHash3 internal component from
   2115      //   https://searchfox.org/mozilla-central/rev/61d400da1c692453c2dc2c1cf37b616ce13dea5b/dom/canvas/MurmurHash3.cpp#85
   2116      midpoint ^= midpoint >> 33;
   2117      midpoint *= uint64_t{0xFF51AFD7ED558CCD};
   2118      midpoint ^= midpoint >> 33;
   2119      midpoint *= uint64_t{0xC4CEB9FE1A85EC53};
   2120      midpoint ^= midpoint >> 33;
   2121      midpoint %= sResolutionUsec;
   2122 
   2123      if (now > clamped + midpoint) {  // We're jittering up to the next step
   2124        now = clamped + sResolutionUsec;
   2125      } else {  // We're staying at the clamped value
   2126        now = clamped;
   2127      }
   2128    } else {  // No jitter, only clamping
   2129      now = clamped;
   2130    }
   2131  }
   2132 
   2133  return TimeClip(now / PRMJ_USEC_PER_MSEC);
   2134 }
   2135 
   2136 JS::ClippedTime js::DateNow(JSContext* cx) { return NowAsMillis(cx); }
   2137 
   2138 static bool date_now(JSContext* cx, unsigned argc, Value* vp) {
   2139  AutoJSMethodProfilerEntry pseudoFrame(cx, "Date", "now");
   2140  CallArgs args = CallArgsFromVp(argc, vp);
   2141  args.rval().set(TimeValue(NowAsMillis(cx)));
   2142  return true;
   2143 }
   2144 
   2145 DateTimeInfo* DateObject::dateTimeInfo() const {
   2146  return realm()->getDateTimeInfo();
   2147 }
   2148 
   2149 void DateObject::setUTCTime(ClippedTime t) {
   2150  for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++) {
   2151    setReservedSlot(ind, UndefinedValue());
   2152  }
   2153 
   2154  setFixedSlot(UTC_TIME_SLOT, TimeValue(t));
   2155 }
   2156 
   2157 void DateObject::setUTCTime(ClippedTime t, MutableHandleValue vp) {
   2158  setUTCTime(t);
   2159  vp.set(TimeValue(t));
   2160 }
   2161 
   2162 void DateObject::fillLocalTimeSlots() {
   2163  auto* dtInfo = dateTimeInfo();
   2164 
   2165  // |timeZoneCacheKey| is used to validate that the cached local time values
   2166  // are still valid. They can become invalid if either:
   2167  // - the system default time zone changed, or
   2168  // - the realm time zone override was changed.
   2169  const int32_t timeZoneCacheKey = DateTimeInfo::timeZoneCacheKey(dtInfo);
   2170 
   2171  /* Check if the cache is already populated. */
   2172  if (!getReservedSlot(LOCAL_TIME_SLOT).isUndefined() &&
   2173      getReservedSlot(TIME_ZONE_CACHE_KEY_SLOT).toInt32() == timeZoneCacheKey) {
   2174    return;
   2175  }
   2176 
   2177  /* Remember time zone used to generate the local cache. */
   2178  setReservedSlot(TIME_ZONE_CACHE_KEY_SLOT, Int32Value(timeZoneCacheKey));
   2179 
   2180  double utcTime = UTCTime().toDouble();
   2181 
   2182  if (!std::isfinite(utcTime)) {
   2183    for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++) {
   2184      setReservedSlot(ind, DoubleValue(utcTime));
   2185    }
   2186    return;
   2187  }
   2188 
   2189  int64_t localTime = LocalTime(dtInfo, utcTime);
   2190 
   2191  setReservedSlot(LOCAL_TIME_SLOT, DoubleValue(localTime));
   2192 
   2193  const auto [year, month, day] = ToYearMonthDay(localTime);
   2194 
   2195  setReservedSlot(LOCAL_YEAR_SLOT, Int32Value(year));
   2196  setReservedSlot(LOCAL_MONTH_SLOT, Int32Value(month));
   2197  setReservedSlot(LOCAL_DATE_SLOT, Int32Value(day));
   2198 
   2199  int weekday = WeekDay(localTime);
   2200  setReservedSlot(LOCAL_DAY_SLOT, Int32Value(weekday));
   2201 
   2202  int64_t yearStartTime = TimeFromYear(year);
   2203  uint64_t yearTime = uint64_t(localTime - yearStartTime);
   2204  int32_t yearSeconds = int32_t(yearTime / msPerSecond);
   2205  setReservedSlot(LOCAL_SECONDS_INTO_YEAR_SLOT, Int32Value(yearSeconds));
   2206 }
   2207 
   2208 /**
   2209 * 21.4.4.10 Date.prototype.getTime ( )
   2210 *
   2211 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2212 */
   2213 static bool date_getTime(JSContext* cx, unsigned argc, Value* vp) {
   2214  CallArgs args = CallArgsFromVp(argc, vp);
   2215 
   2216  // Steps 1-2.
   2217  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getTime");
   2218  if (!unwrapped) {
   2219    return false;
   2220  }
   2221 
   2222  // Step 3.
   2223  args.rval().set(unwrapped->UTCTime());
   2224  return true;
   2225 }
   2226 
   2227 /**
   2228 * B.2.3.1 Date.prototype.getYear ( )
   2229 *
   2230 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2231 */
   2232 static bool date_getYear(JSContext* cx, unsigned argc, Value* vp) {
   2233  CallArgs args = CallArgsFromVp(argc, vp);
   2234 
   2235  // Steps 1-2.
   2236  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getYear");
   2237  if (!unwrapped) {
   2238    return false;
   2239  }
   2240 
   2241  // Steps 3-5.
   2242  unwrapped->fillLocalTimeSlots();
   2243 
   2244  Value yearVal = unwrapped->localYear();
   2245  if (yearVal.isInt32()) {
   2246    int year = yearVal.toInt32() - 1900;
   2247    args.rval().setInt32(year);
   2248  } else {
   2249    args.rval().set(yearVal);
   2250  }
   2251  return true;
   2252 }
   2253 
   2254 /**
   2255 * 21.4.4.4 Date.prototype.getFullYear ( )
   2256 *
   2257 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2258 */
   2259 static bool date_getFullYear(JSContext* cx, unsigned argc, Value* vp) {
   2260  CallArgs args = CallArgsFromVp(argc, vp);
   2261 
   2262  // Steps 1-2.
   2263  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getFullYear");
   2264  if (!unwrapped) {
   2265    return false;
   2266  }
   2267 
   2268  // Steps 3-5.
   2269  unwrapped->fillLocalTimeSlots();
   2270 
   2271  args.rval().set(unwrapped->localYear());
   2272  return true;
   2273 }
   2274 
   2275 /**
   2276 * 21.4.4.18 Date.prototype.getUTCMonth ( )
   2277 *
   2278 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2279 */
   2280 static bool date_getUTCFullYear(JSContext* cx, unsigned argc, Value* vp) {
   2281  CallArgs args = CallArgsFromVp(argc, vp);
   2282 
   2283  // Steps 1-2.
   2284  auto* unwrapped =
   2285      UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCFullYear");
   2286  if (!unwrapped) {
   2287    return false;
   2288  }
   2289 
   2290  // Step 3.
   2291  double t = unwrapped->UTCTime().toDouble();
   2292  MOZ_ASSERT(IsTimeValue(t));
   2293 
   2294  // Step 4.
   2295  if (std::isnan(t)) {
   2296    args.rval().setNaN();
   2297    return true;
   2298  }
   2299  int64_t tv = static_cast<int64_t>(t);
   2300 
   2301  // Step 5.
   2302  args.rval().setInt32(::YearFromTime(tv));
   2303  return true;
   2304 }
   2305 
   2306 /**
   2307 * 21.4.4.8 Date.prototype.getMonth ( )
   2308 *
   2309 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2310 */
   2311 static bool date_getMonth(JSContext* cx, unsigned argc, Value* vp) {
   2312  CallArgs args = CallArgsFromVp(argc, vp);
   2313 
   2314  // Steps 1-2.
   2315  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getMonth");
   2316  if (!unwrapped) {
   2317    return false;
   2318  }
   2319 
   2320  // Steps 3-5.
   2321  unwrapped->fillLocalTimeSlots();
   2322 
   2323  args.rval().set(unwrapped->localMonth());
   2324  return true;
   2325 }
   2326 
   2327 /**
   2328 * 21.4.4.18 Date.prototype.getUTCMonth ( )
   2329 *
   2330 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2331 */
   2332 static bool date_getUTCMonth(JSContext* cx, unsigned argc, Value* vp) {
   2333  CallArgs args = CallArgsFromVp(argc, vp);
   2334 
   2335  // Steps 1-2.
   2336  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCMonth");
   2337  if (!unwrapped) {
   2338    return false;
   2339  }
   2340 
   2341  // Step 3.
   2342  double t = unwrapped->UTCTime().toDouble();
   2343  MOZ_ASSERT(IsTimeValue(t));
   2344 
   2345  // Step 4.
   2346  if (std::isnan(t)) {
   2347    args.rval().setNaN();
   2348    return true;
   2349  }
   2350  int64_t tv = static_cast<int64_t>(t);
   2351 
   2352  // Step 5.
   2353  args.rval().setInt32(::MonthFromTime(tv));
   2354  return true;
   2355 }
   2356 
   2357 /**
   2358 * 21.4.4.2 Date.prototype.getDate ( )
   2359 *
   2360 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2361 */
   2362 static bool date_getDate(JSContext* cx, unsigned argc, Value* vp) {
   2363  CallArgs args = CallArgsFromVp(argc, vp);
   2364 
   2365  // Steps 1-2.
   2366  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getDate");
   2367  if (!unwrapped) {
   2368    return false;
   2369  }
   2370 
   2371  // Steps 3-5.
   2372  unwrapped->fillLocalTimeSlots();
   2373 
   2374  args.rval().set(unwrapped->localDate());
   2375  return true;
   2376 }
   2377 
   2378 /**
   2379 * 21.4.4.12 Date.prototype.getUTCDate ( )
   2380 *
   2381 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2382 */
   2383 static bool date_getUTCDate(JSContext* cx, unsigned argc, Value* vp) {
   2384  CallArgs args = CallArgsFromVp(argc, vp);
   2385 
   2386  // Steps 1-2.
   2387  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCDate");
   2388  if (!unwrapped) {
   2389    return false;
   2390  }
   2391 
   2392  // Step 3.
   2393  double t = unwrapped->UTCTime().toDouble();
   2394  MOZ_ASSERT(IsTimeValue(t));
   2395 
   2396  // Step 4.
   2397  if (std::isnan(t)) {
   2398    args.rval().setNaN();
   2399    return true;
   2400  }
   2401  int64_t tv = static_cast<int64_t>(t);
   2402 
   2403  // Step 5.
   2404  args.rval().setInt32(DateFromTime(tv));
   2405  return true;
   2406 }
   2407 
   2408 /**
   2409 * 21.4.4.3 Date.prototype.getDay ( )
   2410 *
   2411 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2412 */
   2413 static bool date_getDay(JSContext* cx, unsigned argc, Value* vp) {
   2414  CallArgs args = CallArgsFromVp(argc, vp);
   2415 
   2416  // Steps 1-2.
   2417  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getDay");
   2418  if (!unwrapped) {
   2419    return false;
   2420  }
   2421 
   2422  // Steps 3-5.
   2423  unwrapped->fillLocalTimeSlots();
   2424 
   2425  args.rval().set(unwrapped->localDay());
   2426  return true;
   2427 }
   2428 
   2429 /**
   2430 * 21.4.4.13 Date.prototype.getUTCDay ( )
   2431 *
   2432 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2433 */
   2434 static bool date_getUTCDay(JSContext* cx, unsigned argc, Value* vp) {
   2435  CallArgs args = CallArgsFromVp(argc, vp);
   2436 
   2437  // Steps 1-2.
   2438  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCDay");
   2439  if (!unwrapped) {
   2440    return false;
   2441  }
   2442 
   2443  // Step 3.
   2444  double t = unwrapped->UTCTime().toDouble();
   2445  MOZ_ASSERT(IsTimeValue(t));
   2446 
   2447  // Step 4.
   2448  if (std::isnan(t)) {
   2449    args.rval().setNaN();
   2450    return true;
   2451  }
   2452  int64_t tv = static_cast<int64_t>(t);
   2453 
   2454  // Step 5.
   2455  args.rval().setInt32(WeekDay(tv));
   2456  return true;
   2457 }
   2458 
   2459 /**
   2460 * 21.4.4.5 Date.prototype.getHours ( )
   2461 *
   2462 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2463 */
   2464 static bool date_getHours(JSContext* cx, unsigned argc, Value* vp) {
   2465  CallArgs args = CallArgsFromVp(argc, vp);
   2466 
   2467  // Steps 1-2.
   2468  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getHours");
   2469  if (!unwrapped) {
   2470    return false;
   2471  }
   2472 
   2473  // Steps 3-5.
   2474  unwrapped->fillLocalTimeSlots();
   2475 
   2476  // Note: localSecondsIntoYear is guaranteed to return an
   2477  // int32 or NaN after the call to fillLocalTimeSlots.
   2478  Value yearSeconds = unwrapped->localSecondsIntoYear();
   2479  if (yearSeconds.isDouble()) {
   2480    MOZ_ASSERT(std::isnan(yearSeconds.toDouble()));
   2481    args.rval().set(yearSeconds);
   2482  } else {
   2483    args.rval().setInt32((yearSeconds.toInt32() / SecondsPerHour) %
   2484                         HoursPerDay);
   2485  }
   2486  return true;
   2487 }
   2488 
   2489 /**
   2490 * 21.4.4.15 Date.prototype.getUTCHours ( )
   2491 *
   2492 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2493 */
   2494 static bool date_getUTCHours(JSContext* cx, unsigned argc, Value* vp) {
   2495  CallArgs args = CallArgsFromVp(argc, vp);
   2496 
   2497  // Steps 1-2.
   2498  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCHours");
   2499  if (!unwrapped) {
   2500    return false;
   2501  }
   2502 
   2503  // Step 3.
   2504  double t = unwrapped->UTCTime().toDouble();
   2505  MOZ_ASSERT(IsTimeValue(t));
   2506 
   2507  // Step 4.
   2508  if (std::isnan(t)) {
   2509    args.rval().setNaN();
   2510    return true;
   2511  }
   2512  int64_t tv = static_cast<int64_t>(t);
   2513 
   2514  // Step 5.
   2515  args.rval().setInt32(HourFromTime(tv));
   2516  return true;
   2517 }
   2518 
   2519 /**
   2520 * 21.4.4.7 Date.prototype.getMinutes ( )
   2521 *
   2522 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2523 */
   2524 static bool date_getMinutes(JSContext* cx, unsigned argc, Value* vp) {
   2525  CallArgs args = CallArgsFromVp(argc, vp);
   2526 
   2527  // Steps 1-2.
   2528  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getMinutes");
   2529  if (!unwrapped) {
   2530    return false;
   2531  }
   2532 
   2533  // Steps 3-5.
   2534  unwrapped->fillLocalTimeSlots();
   2535 
   2536  // Note: localSecondsIntoYear is guaranteed to return an
   2537  // int32 or NaN after the call to fillLocalTimeSlots.
   2538  Value yearSeconds = unwrapped->localSecondsIntoYear();
   2539  if (yearSeconds.isDouble()) {
   2540    MOZ_ASSERT(std::isnan(yearSeconds.toDouble()));
   2541    args.rval().set(yearSeconds);
   2542  } else {
   2543    args.rval().setInt32((yearSeconds.toInt32() / SecondsPerMinute) %
   2544                         MinutesPerHour);
   2545  }
   2546  return true;
   2547 }
   2548 
   2549 /**
   2550 * 21.4.4.17 Date.prototype.getUTCMinutes ( )
   2551 *
   2552 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2553 */
   2554 static bool date_getUTCMinutes(JSContext* cx, unsigned argc, Value* vp) {
   2555  CallArgs args = CallArgsFromVp(argc, vp);
   2556 
   2557  // Steps 1-2.
   2558  auto* unwrapped =
   2559      UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCMinutes");
   2560  if (!unwrapped) {
   2561    return false;
   2562  }
   2563 
   2564  // Step 3.
   2565  double t = unwrapped->UTCTime().toDouble();
   2566  MOZ_ASSERT(IsTimeValue(t));
   2567 
   2568  // Step 4.
   2569  if (std::isnan(t)) {
   2570    args.rval().setNaN();
   2571    return true;
   2572  }
   2573  int64_t tv = static_cast<int64_t>(t);
   2574 
   2575  // Step 5.
   2576  args.rval().setInt32(MinFromTime(tv));
   2577  return true;
   2578 }
   2579 
   2580 /**
   2581 * 21.4.4.9 Date.prototype.getSeconds ( )
   2582 *
   2583 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2584 */
   2585 static bool date_getSeconds(JSContext* cx, unsigned argc, Value* vp) {
   2586  CallArgs args = CallArgsFromVp(argc, vp);
   2587 
   2588  // Steps 1-2.
   2589  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "getSeconds");
   2590  if (!unwrapped) {
   2591    return false;
   2592  }
   2593 
   2594  // Steps 3-5.
   2595  unwrapped->fillLocalTimeSlots();
   2596 
   2597  // Note: localSecondsIntoYear is guaranteed to return an
   2598  // int32 or NaN after the call to fillLocalTimeSlots.
   2599  Value yearSeconds = unwrapped->localSecondsIntoYear();
   2600  if (yearSeconds.isDouble()) {
   2601    MOZ_ASSERT(std::isnan(yearSeconds.toDouble()));
   2602    args.rval().set(yearSeconds);
   2603  } else {
   2604    args.rval().setInt32(yearSeconds.toInt32() % SecondsPerMinute);
   2605  }
   2606  return true;
   2607 }
   2608 
   2609 /**
   2610 * 21.4.4.19 Date.prototype.getUTCSeconds ( )
   2611 *
   2612 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2613 */
   2614 static bool date_getUTCSeconds(JSContext* cx, unsigned argc, Value* vp) {
   2615  CallArgs args = CallArgsFromVp(argc, vp);
   2616 
   2617  // Steps 1-2.
   2618  auto* unwrapped =
   2619      UnwrapAndTypeCheckThis<DateObject>(cx, args, "getUTCSeconds");
   2620  if (!unwrapped) {
   2621    return false;
   2622  }
   2623 
   2624  // Step 3.
   2625  double t = unwrapped->UTCTime().toDouble();
   2626  MOZ_ASSERT(IsTimeValue(t));
   2627 
   2628  // Step 4.
   2629  if (std::isnan(t)) {
   2630    args.rval().setNaN();
   2631    return true;
   2632  }
   2633  int64_t tv = static_cast<int64_t>(t);
   2634 
   2635  // Step 5.
   2636  args.rval().setInt32(SecFromTime(tv));
   2637  return true;
   2638 }
   2639 
   2640 /*
   2641 * Date.getMilliseconds is mapped to getUTCMilliseconds. As long as no
   2642 * supported time zone has a fractional-second component, the differences in
   2643 * their specifications aren't observable.
   2644 *
   2645 * The 'tz' database explicitly does not support fractional-second time zones.
   2646 * For example the Netherlands observed Amsterdam Mean Time, estimated to be
   2647 * UT +00:19:32.13, from 1909 to 1937, but in tzdata AMT is defined as exactly
   2648 * UT +00:19:32.
   2649 */
   2650 
   2651 /**
   2652 * 21.4.4.6 Date.prototype.getMilliseconds ( )
   2653 * 21.4.4.16 Date.prototype.getUTCMilliseconds ( )
   2654 *
   2655 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2656 */
   2657 static bool getMilliseconds(JSContext* cx, unsigned argc, Value* vp,
   2658                            const char* methodName) {
   2659  CallArgs args = CallArgsFromVp(argc, vp);
   2660 
   2661  // Steps 1-2.
   2662  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, methodName);
   2663  if (!unwrapped) {
   2664    return false;
   2665  }
   2666 
   2667  // Step 3.
   2668  double t = unwrapped->UTCTime().toDouble();
   2669  MOZ_ASSERT(IsTimeValue(t));
   2670 
   2671  // Step 4.
   2672  if (std::isnan(t)) {
   2673    args.rval().setNaN();
   2674    return true;
   2675  }
   2676  int64_t tv = static_cast<int64_t>(t);
   2677 
   2678  // Step 5.
   2679  args.rval().setInt32(msFromTime(tv));
   2680  return true;
   2681 }
   2682 
   2683 static bool date_getMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
   2684  return getMilliseconds(cx, argc, vp, "getMilliseconds");
   2685 }
   2686 
   2687 static bool date_getUTCMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
   2688  return getMilliseconds(cx, argc, vp, "getUTCMilliseconds");
   2689 }
   2690 
   2691 /**
   2692 * 21.4.4.11 Date.prototype.getTimezoneOffset ( )
   2693 *
   2694 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2695 */
   2696 static bool date_getTimezoneOffset(JSContext* cx, unsigned argc, Value* vp) {
   2697  CallArgs args = CallArgsFromVp(argc, vp);
   2698 
   2699  // Steps 1-2.
   2700  auto* unwrapped =
   2701      UnwrapAndTypeCheckThis<DateObject>(cx, args, "getTimezoneOffset");
   2702  if (!unwrapped) {
   2703    return false;
   2704  }
   2705 
   2706  // Steps 3-5.
   2707  unwrapped->fillLocalTimeSlots();
   2708 
   2709  double utctime = unwrapped->UTCTime().toDouble();
   2710  double localtime = unwrapped->localTime().toDouble();
   2711 
   2712  /*
   2713   * Return the time zone offset in minutes for the current locale that is
   2714   * appropriate for this time. This value would be a constant except for
   2715   * daylight savings time.
   2716   */
   2717  double result = (utctime - localtime) / double(msPerMinute);
   2718  args.rval().setNumber(result);
   2719  return true;
   2720 }
   2721 
   2722 /**
   2723 * 21.4.4.27 Date.prototype.setTime ( time )
   2724 *
   2725 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2726 */
   2727 static bool date_setTime(JSContext* cx, unsigned argc, Value* vp) {
   2728  CallArgs args = CallArgsFromVp(argc, vp);
   2729 
   2730  // Steps 1-2.
   2731  Rooted<DateObject*> unwrapped(
   2732      cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setTime"));
   2733  if (!unwrapped) {
   2734    return false;
   2735  }
   2736 
   2737  // Step 3.
   2738  double result;
   2739  if (!ToNumber(cx, args.get(0), &result)) {
   2740    return false;
   2741  }
   2742 
   2743  // Steps 4-6.
   2744  unwrapped->setUTCTime(TimeClip(result), args.rval());
   2745  return true;
   2746 }
   2747 
   2748 /**
   2749 * 21.4.4.23 Date.prototype.setMilliseconds ( ms )
   2750 *
   2751 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2752 */
   2753 static bool date_setMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
   2754  CallArgs args = CallArgsFromVp(argc, vp);
   2755 
   2756  // Steps 1-2.
   2757  Rooted<DateObject*> unwrapped(
   2758      cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setMilliseconds"));
   2759  if (!unwrapped) {
   2760    return false;
   2761  }
   2762 
   2763  // Step 3.
   2764  double t = unwrapped->UTCTime().toDouble();
   2765  MOZ_ASSERT(IsTimeValue(t));
   2766 
   2767  // Step 4.
   2768  double ms;
   2769  if (!ToNumber(cx, args.get(0), &ms)) {
   2770    return false;
   2771  }
   2772 
   2773  // Step 5.
   2774  if (std::isnan(t)) {
   2775    args.rval().setNaN();
   2776    return true;
   2777  }
   2778 
   2779  // Step 6.
   2780  auto* dtInfo = unwrapped->dateTimeInfo();
   2781  int64_t tv = LocalTime(dtInfo, t);
   2782 
   2783  // Step 7.
   2784  double time =
   2785      MakeTime(HourFromTime(tv), MinFromTime(tv), SecFromTime(tv), ms);
   2786 
   2787  // Step 8.
   2788  ClippedTime u = TimeClip(UTC(dtInfo, MakeDate(Day(tv), time)));
   2789 
   2790  // Steps 9-10.
   2791  unwrapped->setUTCTime(u, args.rval());
   2792  return true;
   2793 }
   2794 
   2795 /**
   2796 * 21.4.4.31 Date.prototype.setUTCMilliseconds ( ms )
   2797 *
   2798 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2799 */
   2800 static bool date_setUTCMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
   2801  CallArgs args = CallArgsFromVp(argc, vp);
   2802 
   2803  // Steps 1-2.
   2804  Rooted<DateObject*> unwrapped(
   2805      cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCMilliseconds"));
   2806  if (!unwrapped) {
   2807    return false;
   2808  }
   2809 
   2810  // Step 3.
   2811  double t = unwrapped->UTCTime().toDouble();
   2812  MOZ_ASSERT(IsTimeValue(t));
   2813 
   2814  // Step 4.
   2815  double milli;
   2816  if (!ToNumber(cx, args.get(0), &milli)) {
   2817    return false;
   2818  }
   2819 
   2820  // Step 5.
   2821  if (std::isnan(t)) {
   2822    args.rval().setNaN();
   2823    return true;
   2824  }
   2825  int64_t tv = static_cast<int64_t>(t);
   2826 
   2827  // Step 6.
   2828  double time =
   2829      MakeTime(HourFromTime(tv), MinFromTime(tv), SecFromTime(tv), milli);
   2830 
   2831  // Step 7.
   2832  ClippedTime v = TimeClip(MakeDate(Day(tv), time));
   2833 
   2834  // Steps 8-9.
   2835  unwrapped->setUTCTime(v, args.rval());
   2836  return true;
   2837 }
   2838 
   2839 /**
   2840 * 21.4.4.26 Date.prototype.setSeconds ( sec [ , ms ] )
   2841 *
   2842 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2843 */
   2844 static bool date_setSeconds(JSContext* cx, unsigned argc, Value* vp) {
   2845  CallArgs args = CallArgsFromVp(argc, vp);
   2846 
   2847  // Steps 1-2.
   2848  Rooted<DateObject*> unwrapped(
   2849      cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setSeconds"));
   2850  if (!unwrapped) {
   2851    return false;
   2852  }
   2853 
   2854  // Step 3.
   2855  double t = unwrapped->UTCTime().toDouble();
   2856  MOZ_ASSERT(IsTimeValue(t));
   2857 
   2858  // Step 4.
   2859  double s;
   2860  if (!ToNumber(cx, args.get(0), &s)) {
   2861    return false;
   2862  }
   2863 
   2864  // Step 5.
   2865  double milli;
   2866  if (args.length() > 1 && !ToNumber(cx, args[1], &milli)) {
   2867    return false;
   2868  }
   2869 
   2870  // Step 6.
   2871  if (std::isnan(t)) {
   2872    args.rval().setNaN();
   2873    return true;
   2874  }
   2875 
   2876  // Step 7.
   2877  auto* dtInfo = unwrapped->dateTimeInfo();
   2878  int64_t tv = LocalTime(dtInfo, t);
   2879 
   2880  // Step 8.
   2881  if (args.length() <= 1) {
   2882    milli = msFromTime(tv);
   2883  }
   2884 
   2885  // Step 9.
   2886  double date =
   2887      MakeDate(Day(tv), MakeTime(HourFromTime(tv), MinFromTime(tv), s, milli));
   2888 
   2889  // Step 10.
   2890  ClippedTime u = TimeClip(UTC(dtInfo, date));
   2891 
   2892  // Steps 11-12.
   2893  unwrapped->setUTCTime(u, args.rval());
   2894  return true;
   2895 }
   2896 
   2897 /**
   2898 * 21.4.4.34 Date.prototype.setUTCSeconds ( sec [ , ms ] )
   2899 *
   2900 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2901 */
   2902 static bool date_setUTCSeconds(JSContext* cx, unsigned argc, Value* vp) {
   2903  CallArgs args = CallArgsFromVp(argc, vp);
   2904 
   2905  // Steps 1-2.
   2906  Rooted<DateObject*> unwrapped(
   2907      cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCSeconds"));
   2908  if (!unwrapped) {
   2909    return false;
   2910  }
   2911 
   2912  // Step 3.
   2913  double t = unwrapped->UTCTime().toDouble();
   2914  MOZ_ASSERT(IsTimeValue(t));
   2915 
   2916  // Step 4.
   2917  double s;
   2918  if (!ToNumber(cx, args.get(0), &s)) {
   2919    return false;
   2920  }
   2921 
   2922  // Step 5.
   2923  double milli;
   2924  if (args.length() > 1 && !ToNumber(cx, args[1], &milli)) {
   2925    return false;
   2926  }
   2927 
   2928  // Step 6.
   2929  if (std::isnan(t)) {
   2930    args.rval().setNaN();
   2931    return true;
   2932  }
   2933  int64_t tv = static_cast<int64_t>(t);
   2934 
   2935  // Step 7.
   2936  if (args.length() <= 1) {
   2937    milli = msFromTime(tv);
   2938  }
   2939 
   2940  // Step 8.
   2941  double date =
   2942      MakeDate(Day(tv), MakeTime(HourFromTime(tv), MinFromTime(tv), s, milli));
   2943 
   2944  // Step 9.
   2945  ClippedTime v = TimeClip(date);
   2946 
   2947  // Steps 10-11.
   2948  unwrapped->setUTCTime(v, args.rval());
   2949  return true;
   2950 }
   2951 
   2952 /**
   2953 * 21.4.4.24 Date.prototype.setMinutes ( min [ , sec [ , ms ] ] )
   2954 *
   2955 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   2956 */
   2957 static bool date_setMinutes(JSContext* cx, unsigned argc, Value* vp) {
   2958  CallArgs args = CallArgsFromVp(argc, vp);
   2959 
   2960  // Steps 1-2.
   2961  Rooted<DateObject*> unwrapped(
   2962      cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setMinutes"));
   2963  if (!unwrapped) {
   2964    return false;
   2965  }
   2966 
   2967  // Step 3.
   2968  double t = unwrapped->UTCTime().toDouble();
   2969  MOZ_ASSERT(IsTimeValue(t));
   2970 
   2971  // Step 4.
   2972  double m;
   2973  if (!ToNumber(cx, args.get(0), &m)) {
   2974    return false;
   2975  }
   2976 
   2977  // Step 5.
   2978  double s;
   2979  if (args.length() > 1 && !ToNumber(cx, args[1], &s)) {
   2980    return false;
   2981  }
   2982 
   2983  // Step 6.
   2984  double milli;
   2985  if (args.length() > 2 && !ToNumber(cx, args[2], &milli)) {
   2986    return false;
   2987  }
   2988 
   2989  // Step 7.
   2990  if (std::isnan(t)) {
   2991    args.rval().setNaN();
   2992    return true;
   2993  }
   2994 
   2995  // Step 8.
   2996  auto* dtInfo = unwrapped->dateTimeInfo();
   2997  int64_t tv = LocalTime(dtInfo, t);
   2998 
   2999  // Step 9.
   3000  if (args.length() <= 1) {
   3001    s = SecFromTime(tv);
   3002  }
   3003 
   3004  // Step 10.
   3005  if (args.length() <= 2) {
   3006    milli = msFromTime(tv);
   3007  }
   3008 
   3009  // Step 11.
   3010  double date = MakeDate(Day(tv), MakeTime(HourFromTime(tv), m, s, milli));
   3011 
   3012  // Step 12.
   3013  ClippedTime u = TimeClip(UTC(dtInfo, date));
   3014 
   3015  // Steps 13-14.
   3016  unwrapped->setUTCTime(u, args.rval());
   3017  return true;
   3018 }
   3019 
   3020 /**
   3021 * 21.4.4.32 Date.prototype.setUTCMinutes ( min [ , sec [ , ms ] ] )
   3022 *
   3023 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   3024 */
   3025 static bool date_setUTCMinutes(JSContext* cx, unsigned argc, Value* vp) {
   3026  CallArgs args = CallArgsFromVp(argc, vp);
   3027 
   3028  // Steps 1-2.
   3029  Rooted<DateObject*> unwrapped(
   3030      cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCMinutes"));
   3031  if (!unwrapped) {
   3032    return false;
   3033  }
   3034 
   3035  // Step 3.
   3036  double t = unwrapped->UTCTime().toDouble();
   3037  MOZ_ASSERT(IsTimeValue(t));
   3038 
   3039  // Step 4.
   3040  double m;
   3041  if (!ToNumber(cx, args.get(0), &m)) {
   3042    return false;
   3043  }
   3044 
   3045  // Step 5.
   3046  double s;
   3047  if (args.length() > 1 && !ToNumber(cx, args[1], &s)) {
   3048    return false;
   3049  }
   3050 
   3051  // Step 6.
   3052  double milli;
   3053  if (args.length() > 2 && !ToNumber(cx, args[2], &milli)) {
   3054    return false;
   3055  }
   3056 
   3057  // Step 7.
   3058  if (std::isnan(t)) {
   3059    args.rval().setNaN();
   3060    return true;
   3061  }
   3062  int64_t tv = static_cast<int64_t>(t);
   3063 
   3064  // Step 8.
   3065  if (args.length() <= 1) {
   3066    s = SecFromTime(tv);
   3067  }
   3068 
   3069  // Step 9.
   3070  if (args.length() <= 2) {
   3071    milli = msFromTime(tv);
   3072  }
   3073 
   3074  // Step 10.
   3075  double date = MakeDate(Day(tv), MakeTime(HourFromTime(tv), m, s, milli));
   3076 
   3077  // Step 11.
   3078  ClippedTime v = TimeClip(date);
   3079 
   3080  // Steps 12-13.
   3081  unwrapped->setUTCTime(v, args.rval());
   3082  return true;
   3083 }
   3084 
   3085 /**
   3086 * 21.4.4.22 Date.prototype.setHours ( hour [ , min [ , sec [ , ms ] ] ] )
   3087 *
   3088 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   3089 */
   3090 static bool date_setHours(JSContext* cx, unsigned argc, Value* vp) {
   3091  CallArgs args = CallArgsFromVp(argc, vp);
   3092 
   3093  // Steps 1-2.
   3094  Rooted<DateObject*> unwrapped(
   3095      cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setHours"));
   3096  if (!unwrapped) {
   3097    return false;
   3098  }
   3099 
   3100  // Step 3.
   3101  double t = unwrapped->UTCTime().toDouble();
   3102  MOZ_ASSERT(IsTimeValue(t));
   3103 
   3104  // Step 4.
   3105  double h;
   3106  if (!ToNumber(cx, args.get(0), &h)) {
   3107    return false;
   3108  }
   3109 
   3110  // Step 5.
   3111  double m;
   3112  if (args.length() > 1 && !ToNumber(cx, args[1], &m)) {
   3113    return false;
   3114  }
   3115 
   3116  // Step 6.
   3117  double s;
   3118  if (args.length() > 2 && !ToNumber(cx, args[2], &s)) {
   3119    return false;
   3120  }
   3121 
   3122  // Step 7.
   3123  double milli;
   3124  if (args.length() > 3 && !ToNumber(cx, args[3], &milli)) {
   3125    return false;
   3126  }
   3127 
   3128  // Step 8.
   3129  if (std::isnan(t)) {
   3130    args.rval().setNaN();
   3131    return true;
   3132  }
   3133 
   3134  // Step 9.
   3135  auto* dtInfo = unwrapped->dateTimeInfo();
   3136  int64_t tv = LocalTime(dtInfo, t);
   3137 
   3138  // Step 10.
   3139  if (args.length() <= 1) {
   3140    m = MinFromTime(tv);
   3141  }
   3142 
   3143  // Step 11.
   3144  if (args.length() <= 2) {
   3145    s = SecFromTime(tv);
   3146  }
   3147 
   3148  // Step 12.
   3149  if (args.length() <= 3) {
   3150    milli = msFromTime(tv);
   3151  }
   3152 
   3153  // Step 13.
   3154  double date = MakeDate(Day(tv), MakeTime(h, m, s, milli));
   3155 
   3156  // Step 14.
   3157  ClippedTime u = TimeClip(UTC(dtInfo, date));
   3158 
   3159  // Steps 15-16.
   3160  unwrapped->setUTCTime(u, args.rval());
   3161  return true;
   3162 }
   3163 
   3164 /**
   3165 * 21.4.4.30 Date.prototype.setUTCHours ( hour [ , min [ , sec [ , ms ] ] ] )
   3166 *
   3167 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   3168 */
   3169 static bool date_setUTCHours(JSContext* cx, unsigned argc, Value* vp) {
   3170  CallArgs args = CallArgsFromVp(argc, vp);
   3171 
   3172  // Steps 1-2.
   3173  Rooted<DateObject*> unwrapped(
   3174      cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCHours"));
   3175  if (!unwrapped) {
   3176    return false;
   3177  }
   3178 
   3179  // Step 3.
   3180  double t = unwrapped->UTCTime().toDouble();
   3181  MOZ_ASSERT(IsTimeValue(t));
   3182 
   3183  // Step 4.
   3184  double h;
   3185  if (!ToNumber(cx, args.get(0), &h)) {
   3186    return false;
   3187  }
   3188 
   3189  // Step 5.
   3190  double m;
   3191  if (args.length() > 1 && !ToNumber(cx, args[1], &m)) {
   3192    return false;
   3193  }
   3194 
   3195  // Step 6.
   3196  double s;
   3197  if (args.length() > 2 && !ToNumber(cx, args[2], &s)) {
   3198    return false;
   3199  }
   3200 
   3201  // Step 7.
   3202  double milli;
   3203  if (args.length() > 3 && !ToNumber(cx, args[3], &milli)) {
   3204    return false;
   3205  }
   3206 
   3207  // Step 8.
   3208  if (std::isnan(t)) {
   3209    args.rval().setNaN();
   3210    return true;
   3211  }
   3212  int64_t tv = static_cast<int64_t>(t);
   3213 
   3214  // Step 9.
   3215  if (args.length() <= 1) {
   3216    m = MinFromTime(tv);
   3217  }
   3218 
   3219  // Step 10.
   3220  if (args.length() <= 2) {
   3221    s = SecFromTime(tv);
   3222  }
   3223 
   3224  // Step 11.
   3225  if (args.length() <= 3) {
   3226    milli = msFromTime(tv);
   3227  }
   3228 
   3229  // Step 12.
   3230  double date = MakeDate(Day(tv), MakeTime(h, m, s, milli));
   3231 
   3232  // Step 13.
   3233  ClippedTime v = TimeClip(date);
   3234 
   3235  // Steps 14-15.
   3236  unwrapped->setUTCTime(v, args.rval());
   3237  return true;
   3238 }
   3239 
   3240 /**
   3241 * 21.4.4.20 Date.prototype.setDate ( date )
   3242 *
   3243 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   3244 */
   3245 static bool date_setDate(JSContext* cx, unsigned argc, Value* vp) {
   3246  CallArgs args = CallArgsFromVp(argc, vp);
   3247 
   3248  // Steps 1-2.
   3249  Rooted<DateObject*> unwrapped(
   3250      cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setDate"));
   3251  if (!unwrapped) {
   3252    return false;
   3253  }
   3254 
   3255  // Step 3.
   3256  double t = unwrapped->UTCTime().toDouble();
   3257  MOZ_ASSERT(IsTimeValue(t));
   3258 
   3259  // Step 4.
   3260  double dt;
   3261  if (!ToNumber(cx, args.get(0), &dt)) {
   3262    return false;
   3263  }
   3264 
   3265  // Step 5.
   3266  if (std::isnan(t)) {
   3267    args.rval().setNaN();
   3268    return true;
   3269  }
   3270 
   3271  // Step 6.
   3272  auto* dtInfo = unwrapped->dateTimeInfo();
   3273  int64_t tv = LocalTime(dtInfo, t);
   3274 
   3275  // Step 7.
   3276  double newDate = MakeDate(
   3277      MakeDay(::YearFromTime(tv), ::MonthFromTime(tv), dt), TimeWithinDay(tv));
   3278 
   3279  // Step 8.
   3280  ClippedTime u = TimeClip(UTC(dtInfo, newDate));
   3281 
   3282  // Steps 9-10.
   3283  unwrapped->setUTCTime(u, args.rval());
   3284  return true;
   3285 }
   3286 
   3287 /**
   3288 * 21.4.4.28 Date.prototype.setUTCDate ( date )
   3289 *
   3290 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   3291 */
   3292 static bool date_setUTCDate(JSContext* cx, unsigned argc, Value* vp) {
   3293  CallArgs args = CallArgsFromVp(argc, vp);
   3294 
   3295  // Steps 1-2.
   3296  Rooted<DateObject*> unwrapped(
   3297      cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCDate"));
   3298  if (!unwrapped) {
   3299    return false;
   3300  }
   3301 
   3302  // Step 3.
   3303  double t = unwrapped->UTCTime().toDouble();
   3304  MOZ_ASSERT(IsTimeValue(t));
   3305 
   3306  // Step 4.
   3307  double dt;
   3308  if (!ToNumber(cx, args.get(0), &dt)) {
   3309    return false;
   3310  }
   3311 
   3312  // Step 5.
   3313  if (std::isnan(t)) {
   3314    args.rval().setNaN();
   3315    return true;
   3316  }
   3317  int64_t tv = static_cast<int64_t>(t);
   3318 
   3319  // Step 6.
   3320  double newDate = MakeDate(
   3321      MakeDay(::YearFromTime(tv), ::MonthFromTime(tv), dt), TimeWithinDay(tv));
   3322 
   3323  // Step 7.
   3324  ClippedTime v = TimeClip(newDate);
   3325 
   3326  // Steps 8-9.
   3327  unwrapped->setUTCTime(v, args.rval());
   3328  return true;
   3329 }
   3330 
   3331 /**
   3332 * 21.4.4.25 Date.prototype.setMonth ( month [ , date ] )
   3333 *
   3334 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   3335 */
   3336 static bool date_setMonth(JSContext* cx, unsigned argc, Value* vp) {
   3337  CallArgs args = CallArgsFromVp(argc, vp);
   3338 
   3339  // Steps 1-2.
   3340  Rooted<DateObject*> unwrapped(
   3341      cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setMonth"));
   3342  if (!unwrapped) {
   3343    return false;
   3344  }
   3345 
   3346  // Step 3.
   3347  double t = unwrapped->UTCTime().toDouble();
   3348  MOZ_ASSERT(IsTimeValue(t));
   3349 
   3350  // Step 4.
   3351  double m;
   3352  if (!ToNumber(cx, args.get(0), &m)) {
   3353    return false;
   3354  }
   3355 
   3356  // Step 5.
   3357  double dt;
   3358  if (args.length() > 1 && !ToNumber(cx, args[1], &dt)) {
   3359    return false;
   3360  }
   3361 
   3362  // Step 6.
   3363  if (std::isnan(t)) {
   3364    args.rval().setNaN();
   3365    return true;
   3366  }
   3367 
   3368  // Step 7.
   3369  auto* dtInfo = unwrapped->dateTimeInfo();
   3370  int64_t tv = LocalTime(dtInfo, t);
   3371 
   3372  // Step 8.
   3373  if (args.length() <= 1) {
   3374    dt = DateFromTime(tv);
   3375  }
   3376 
   3377  // Step 9.
   3378  double newDate =
   3379      MakeDate(MakeDay(::YearFromTime(tv), m, dt), TimeWithinDay(tv));
   3380 
   3381  // Step 10
   3382  ClippedTime u = TimeClip(UTC(dtInfo, newDate));
   3383 
   3384  // Steps 11-12.
   3385  unwrapped->setUTCTime(u, args.rval());
   3386  return true;
   3387 }
   3388 
   3389 /**
   3390 * 21.4.4.33 Date.prototype.setUTCMonth ( month [ , date ] )
   3391 *
   3392 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   3393 */
   3394 static bool date_setUTCMonth(JSContext* cx, unsigned argc, Value* vp) {
   3395  CallArgs args = CallArgsFromVp(argc, vp);
   3396 
   3397  // Steps 1-2.
   3398  Rooted<DateObject*> unwrapped(
   3399      cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCMonth"));
   3400  if (!unwrapped) {
   3401    return false;
   3402  }
   3403 
   3404  // Step 3.
   3405  double t = unwrapped->UTCTime().toDouble();
   3406  MOZ_ASSERT(IsTimeValue(t));
   3407 
   3408  // Step 4.
   3409  double m;
   3410  if (!ToNumber(cx, args.get(0), &m)) {
   3411    return false;
   3412  }
   3413 
   3414  // Step 5.
   3415  double dt;
   3416  if (args.length() > 1 && !ToNumber(cx, args[1], &dt)) {
   3417    return false;
   3418  }
   3419 
   3420  // Step 6.
   3421  if (std::isnan(t)) {
   3422    args.rval().setNaN();
   3423    return true;
   3424  }
   3425  int64_t tv = static_cast<int64_t>(t);
   3426 
   3427  // Step 7.
   3428  if (args.length() <= 1) {
   3429    dt = DateFromTime(tv);
   3430  }
   3431 
   3432  // Step 8.
   3433  double newDate =
   3434      MakeDate(MakeDay(::YearFromTime(tv), m, dt), TimeWithinDay(tv));
   3435 
   3436  // Step 9.
   3437  ClippedTime v = TimeClip(newDate);
   3438 
   3439  // Steps 10-11.
   3440  unwrapped->setUTCTime(v, args.rval());
   3441  return true;
   3442 }
   3443 
   3444 /**
   3445 * 21.4.4.21 Date.prototype.setFullYear ( year [ , month [ , date ] ] )
   3446 *
   3447 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   3448 */
   3449 static bool date_setFullYear(JSContext* cx, unsigned argc, Value* vp) {
   3450  CallArgs args = CallArgsFromVp(argc, vp);
   3451 
   3452  // Steps 1-2.
   3453  Rooted<DateObject*> unwrapped(
   3454      cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setFullYear"));
   3455  if (!unwrapped) {
   3456    return false;
   3457  }
   3458 
   3459  // Step 3.
   3460  double t = unwrapped->UTCTime().toDouble();
   3461  MOZ_ASSERT(IsTimeValue(t));
   3462 
   3463  // Step 4.
   3464  double y;
   3465  if (!ToNumber(cx, args.get(0), &y)) {
   3466    return false;
   3467  }
   3468 
   3469  // Step 5.
   3470  auto* dtInfo = unwrapped->dateTimeInfo();
   3471  int64_t tv;
   3472  if (std::isnan(t)) {
   3473    tv = 0;
   3474  } else {
   3475    tv = LocalTime(dtInfo, t);
   3476  }
   3477 
   3478  // Step 6.
   3479  double m;
   3480  if (args.length() <= 1) {
   3481    m = MonthFromTime(tv);
   3482  } else {
   3483    if (!ToNumber(cx, args[1], &m)) {
   3484      return false;
   3485    }
   3486  }
   3487 
   3488  // Step 7.
   3489  double dt;
   3490  if (args.length() <= 2) {
   3491    dt = DateFromTime(tv);
   3492  } else {
   3493    if (!ToNumber(cx, args[2], &dt)) {
   3494      return false;
   3495    }
   3496  }
   3497 
   3498  // Step 8.
   3499  double newDate = MakeDate(MakeDay(y, m, dt), TimeWithinDay(tv));
   3500 
   3501  // Step 9.
   3502  ClippedTime u = TimeClip(UTC(dtInfo, newDate));
   3503 
   3504  // Steps 10-11.
   3505  unwrapped->setUTCTime(u, args.rval());
   3506  return true;
   3507 }
   3508 
   3509 /**
   3510 * 21.4.4.29 Date.prototype.setUTCFullYear ( year [ , month [ , date ] ] )
   3511 *
   3512 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   3513 */
   3514 static bool date_setUTCFullYear(JSContext* cx, unsigned argc, Value* vp) {
   3515  CallArgs args = CallArgsFromVp(argc, vp);
   3516 
   3517  // Steps 1-2.
   3518  Rooted<DateObject*> unwrapped(
   3519      cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setUTCFullYear"));
   3520  if (!unwrapped) {
   3521    return false;
   3522  }
   3523 
   3524  // Step 3.
   3525  double t = unwrapped->UTCTime().toDouble();
   3526  MOZ_ASSERT(IsTimeValue(t));
   3527 
   3528  // Step 4.
   3529  int64_t tv;
   3530  if (std::isnan(t)) {
   3531    tv = 0;
   3532  } else {
   3533    tv = static_cast<int64_t>(t);
   3534  }
   3535 
   3536  // Step 5.
   3537  double y;
   3538  if (!ToNumber(cx, args.get(0), &y)) {
   3539    return false;
   3540  }
   3541 
   3542  // Step 6.
   3543  double m;
   3544  if (args.length() <= 1) {
   3545    m = MonthFromTime(tv);
   3546  } else {
   3547    if (!ToNumber(cx, args[1], &m)) {
   3548      return false;
   3549    }
   3550  }
   3551 
   3552  // Step 7.
   3553  double dt;
   3554  if (args.length() <= 2) {
   3555    dt = DateFromTime(tv);
   3556  } else {
   3557    if (!ToNumber(cx, args[2], &dt)) {
   3558      return false;
   3559    }
   3560  }
   3561 
   3562  // Step 8.
   3563  double newDate = MakeDate(MakeDay(y, m, dt), TimeWithinDay(tv));
   3564 
   3565  // Step 9.
   3566  ClippedTime v = TimeClip(newDate);
   3567 
   3568  // Steps 10-11.
   3569  unwrapped->setUTCTime(v, args.rval());
   3570  return true;
   3571 }
   3572 
   3573 /**
   3574 * B.2.3.2 Date.prototype.setYear ( year )
   3575 *
   3576 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   3577 */
   3578 static bool date_setYear(JSContext* cx, unsigned argc, Value* vp) {
   3579  CallArgs args = CallArgsFromVp(argc, vp);
   3580 
   3581  // Steps 1-2.
   3582  Rooted<DateObject*> unwrapped(
   3583      cx, UnwrapAndTypeCheckThis<DateObject>(cx, args, "setYear"));
   3584  if (!unwrapped) {
   3585    return false;
   3586  }
   3587 
   3588  // Step 3.
   3589  double t = unwrapped->UTCTime().toDouble();
   3590  MOZ_ASSERT(IsTimeValue(t));
   3591 
   3592  // Step 4.
   3593  double y;
   3594  if (!ToNumber(cx, args.get(0), &y)) {
   3595    return false;
   3596  }
   3597 
   3598  // Step 5.
   3599  auto* dtInfo = unwrapped->dateTimeInfo();
   3600  int64_t tv;
   3601  if (std::isnan(t)) {
   3602    tv = 0;
   3603  } else {
   3604    tv = LocalTime(dtInfo, t);
   3605  }
   3606 
   3607  // Step 6.
   3608  double yyyy = MakeFullYear(y);
   3609 
   3610  // Step 7.
   3611  double day = MakeDay(yyyy, ::MonthFromTime(tv), DateFromTime(tv));
   3612 
   3613  // Step 8.
   3614  double date = MakeDate(day, TimeWithinDay(tv));
   3615 
   3616  // Step 9.
   3617  ClippedTime u = TimeClip(UTC(dtInfo, date));
   3618 
   3619  // Steps 10-11.
   3620  unwrapped->setUTCTime(u, args.rval());
   3621  return true;
   3622 }
   3623 
   3624 /**
   3625 * Simple date formatter for toISOString, toUTCString, and FormatDate.
   3626 */
   3627 class DateFormatter {
   3628  // Longest possible string is 36 characters. Round up to the next multiple of
   3629  // sixteen.
   3630  static constexpr size_t BufferLength = 48;
   3631 
   3632  char buffer_[BufferLength] = {};
   3633  char* ptr_ = buffer_;
   3634 
   3635  size_t written() const { return size_t(ptr_ - buffer_); }
   3636 
   3637  static constexpr uint32_t powerOfTen(uint32_t exp) {
   3638    uint32_t result = 1;
   3639    while (exp--) {
   3640      result *= 10;
   3641    }
   3642    return result;
   3643  }
   3644 
   3645  // Add |N| digits, padded with zeroes.
   3646  template <uint32_t N>
   3647  void digits(uint32_t value) {
   3648    static_assert(1 <= N && N <= 6);
   3649    MOZ_ASSERT(written() + N <= BufferLength);
   3650 
   3651    constexpr uint32_t divisor = powerOfTen(N - 1);
   3652    MOZ_ASSERT(value < divisor * 10);
   3653 
   3654    uint32_t quot = value / divisor;
   3655    [[maybe_unused]] uint32_t rem = value % divisor;
   3656 
   3657    *ptr_++ = char('0' + quot);
   3658    if constexpr (N > 1) {
   3659      digits<N - 1>(rem);
   3660    }
   3661  }
   3662 
   3663  // Constants for toString and toUTCString.
   3664  static constexpr char const days[][4] = {
   3665      "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
   3666  };
   3667  static constexpr char const months[][4] = {
   3668      "Jan", "Feb", "Mar", "Apr", "May", "Jun",
   3669      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
   3670  };
   3671 
   3672 public:
   3673  std::string_view string() const { return {buffer_, written()}; }
   3674 
   3675  auto& literal(char ch) {
   3676    MOZ_ASSERT(written() + 1 <= BufferLength);
   3677    *ptr_++ = ch;
   3678    return *this;
   3679  }
   3680 
   3681  template <size_t N>
   3682  auto& literal(const char (&str)[N]) {
   3683    static_assert(N > 0);
   3684    size_t length = N - 1;
   3685    MOZ_ASSERT(written() + length <= BufferLength);
   3686    std::memcpy(ptr_, str, length);
   3687    ptr_ += length;
   3688    return *this;
   3689  }
   3690 
   3691  auto& year(int32_t value) {
   3692    MOZ_ASSERT(-999'999 <= value && value <= 999'999);
   3693    if (value < 0) {
   3694      literal('-');
   3695      value = std::abs(value);
   3696    }
   3697    if (value <= 9999) {
   3698      digits<4>(value);
   3699    } else if (value <= 99999) {
   3700      digits<5>(value);
   3701    } else {
   3702      digits<6>(value);
   3703    }
   3704    return *this;
   3705  }
   3706 
   3707  auto& isoYear(int32_t value) {
   3708    MOZ_ASSERT(-999'999 <= value && value <= 999'999);
   3709    if (0 <= value && value <= 9999) {
   3710      digits<4>(value);
   3711    } else {
   3712      literal(value < 0 ? '-' : '+');
   3713      digits<6>(std::abs(value));
   3714    }
   3715    return *this;
   3716  }
   3717 
   3718  auto& month(int32_t value) {
   3719    MOZ_ASSERT(1 <= value && value <= 12);
   3720    digits<2>(value);
   3721    return *this;
   3722  }
   3723 
   3724  auto& day(int32_t value) {
   3725    MOZ_ASSERT(1 <= value && value <= 31);
   3726    digits<2>(value);
   3727    return *this;
   3728  }
   3729 
   3730  auto& hour(int32_t value) {
   3731    MOZ_ASSERT(0 <= value && value <= 23);
   3732    digits<2>(value);
   3733    return *this;
   3734  }
   3735 
   3736  auto& minute(int32_t value) {
   3737    MOZ_ASSERT(0 <= value && value <= 59);
   3738    digits<2>(value);
   3739    return *this;
   3740  }
   3741 
   3742  auto& second(int32_t value) {
   3743    MOZ_ASSERT(0 <= value && value <= 59);
   3744    digits<2>(value);
   3745    return *this;
   3746  }
   3747 
   3748  auto& time(int32_t h, int32_t m, int32_t s) {
   3749    return hour(h).literal(':').minute(m).literal(':').second(s);
   3750  }
   3751 
   3752  auto& millisecond(int32_t value) {
   3753    MOZ_ASSERT(0 <= value && value <= 999);
   3754    digits<3>(value);
   3755    return *this;
   3756  }
   3757 
   3758  auto& monthName(int32_t value) {
   3759    MOZ_ASSERT(0 <= value && value < 12);
   3760    return literal(months[value]);
   3761  }
   3762 
   3763  auto& weekDay(int32_t value) {
   3764    MOZ_ASSERT(0 <= value && value < 7);
   3765    return literal(days[value]);
   3766  }
   3767 
   3768  auto& timeZoneOffset(int32_t value) {
   3769    MOZ_ASSERT(-2400 < value && value < 2400);
   3770    literal(value < 0 ? '-' : '+');
   3771    digits<4>(std::abs(value));
   3772    return *this;
   3773  }
   3774 };
   3775 
   3776 /**
   3777 * 21.4.4.43 Date.prototype.toUTCString ( )
   3778 *
   3779 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   3780 */
   3781 static bool date_toUTCString(JSContext* cx, unsigned argc, Value* vp) {
   3782  AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toUTCString");
   3783  CallArgs args = CallArgsFromVp(argc, vp);
   3784 
   3785  // Steps 1-2.
   3786  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "toUTCString");
   3787  if (!unwrapped) {
   3788    return false;
   3789  }
   3790 
   3791  // Step 3.
   3792  double utctime = unwrapped->UTCTime().toDouble();
   3793  MOZ_ASSERT(IsTimeValue(utctime));
   3794 
   3795  // Step 4.
   3796  if (std::isnan(utctime)) {
   3797    args.rval().setString(cx->names().Invalid_Date_);
   3798    return true;
   3799  }
   3800  int64_t epochMilliseconds = static_cast<int64_t>(utctime);
   3801 
   3802  // Steps 5-11.
   3803  auto [year, month, day] = ToYearMonthDay(epochMilliseconds);
   3804  auto [hour, minute, second] = ToHourMinuteSecond(epochMilliseconds);
   3805 
   3806  DateFormatter fmt{};
   3807  fmt.weekDay(WeekDay(epochMilliseconds))
   3808      .literal(", ")
   3809      .day(day)
   3810      .literal(' ')
   3811      .monthName(month)
   3812      .literal(' ')
   3813      .year(year)
   3814      .literal(' ')
   3815      .time(hour, minute, second)
   3816      .literal(" GMT");
   3817 
   3818  JSString* str = NewStringCopy<CanGC>(cx, fmt.string());
   3819  if (!str) {
   3820    return false;
   3821  }
   3822 
   3823  args.rval().setString(str);
   3824  return true;
   3825 }
   3826 
   3827 /**
   3828 * 21.4.4.36 Date.prototype.toISOString ( )
   3829 *
   3830 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   3831 */
   3832 static bool date_toISOString(JSContext* cx, unsigned argc, Value* vp) {
   3833  AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toISOString");
   3834  CallArgs args = CallArgsFromVp(argc, vp);
   3835 
   3836  // Steps 1-2.
   3837  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "toISOString");
   3838  if (!unwrapped) {
   3839    return false;
   3840  }
   3841 
   3842  // Steps 3 and 5.
   3843  double utctime = unwrapped->UTCTime().toDouble();
   3844  MOZ_ASSERT(IsTimeValue(utctime));
   3845 
   3846  // Step 4.
   3847  if (!std::isfinite(utctime)) {
   3848    JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
   3849                              JSMSG_INVALID_DATE);
   3850    return false;
   3851  }
   3852  int64_t epochMilliseconds = static_cast<int64_t>(utctime);
   3853 
   3854  // Step 6. (Not applicable in our implementation.)
   3855 
   3856  // Step 7.
   3857  auto [year, month, day] = ToYearMonthDay(epochMilliseconds);
   3858  auto [hour, minute, second] = ToHourMinuteSecond(epochMilliseconds);
   3859 
   3860  DateFormatter fmt{};
   3861  fmt.isoYear(year)
   3862      .literal('-')
   3863      .month(month + 1)
   3864      .literal('-')
   3865      .day(day)
   3866      .literal('T')
   3867      .time(hour, minute, second)
   3868      .literal('.')
   3869      .millisecond(msFromTime(epochMilliseconds))
   3870      .literal('Z');
   3871 
   3872  JSString* str = NewStringCopy<CanGC>(cx, fmt.string());
   3873  if (!str) {
   3874    return false;
   3875  }
   3876  args.rval().setString(str);
   3877  return true;
   3878 }
   3879 
   3880 /**
   3881 * 21.4.4.37 Date.prototype.toJSON ( key )
   3882 *
   3883 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   3884 */
   3885 static bool date_toJSON(JSContext* cx, unsigned argc, Value* vp) {
   3886  AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toJSON");
   3887  CallArgs args = CallArgsFromVp(argc, vp);
   3888 
   3889  // Step 1.
   3890  RootedObject obj(cx, ToObject(cx, args.thisv()));
   3891  if (!obj) {
   3892    return false;
   3893  }
   3894 
   3895  // Step 2.
   3896  RootedValue tv(cx, ObjectValue(*obj));
   3897  if (!ToPrimitive(cx, JSTYPE_NUMBER, &tv)) {
   3898    return false;
   3899  }
   3900 
   3901  // Step 3.
   3902  if (tv.isDouble() && !std::isfinite(tv.toDouble())) {
   3903    args.rval().setNull();
   3904    return true;
   3905  }
   3906 
   3907  // Step 4.
   3908  RootedValue toISO(cx);
   3909  if (!GetProperty(cx, obj, obj, cx->names().toISOString, &toISO)) {
   3910    return false;
   3911  }
   3912 
   3913  if (!IsCallable(toISO)) {
   3914    JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
   3915                              JSMSG_BAD_TOISOSTRING_PROP);
   3916    return false;
   3917  }
   3918 
   3919  return Call(cx, toISO, obj, args.rval());
   3920 }
   3921 
   3922 #if JS_HAS_INTL_API
   3923 JSString* DateTimeHelper::timeZoneComment(JSContext* cx, DateTimeInfo* dtInfo,
   3924                                          const char* locale, int64_t utcTime,
   3925                                          int64_t localTime) {
   3926  MOZ_ASSERT(IsTimeValue(utcTime));
   3927  MOZ_ASSERT(IsLocalTimeValue(localTime));
   3928 
   3929  TimeZoneDisplayNameVector displayName;
   3930 
   3931  // Parenthesize the returned display name.
   3932  if (!displayName.append(' ') || !displayName.append('(') ||
   3933      !DateTimeInfo::timeZoneDisplayName(dtInfo, displayName, utcTime,
   3934                                         locale) ||
   3935      !displayName.append(')')) {
   3936    ReportOutOfMemory(cx);
   3937    return nullptr;
   3938  }
   3939 
   3940  return NewStringCopy<CanGC>(
   3941      cx, static_cast<mozilla::Span<const char16_t>>(displayName));
   3942 }
   3943 #else
   3944 /* Interface to PRMJTime date struct. */
   3945 PRMJTime DateTimeHelper::toPRMJTime(int64_t localTime, int64_t utcTime) {
   3946  auto [year, month, day] = ToYearMonthDay(localTime);
   3947  auto [hour, minute, second] = ToHourMinuteSecond(localTime);
   3948 
   3949  PRMJTime prtm;
   3950  prtm.tm_usec = int32_t(msFromTime(localTime)) * 1000;
   3951  prtm.tm_sec = int8_t(second);
   3952  prtm.tm_min = int8_t(minute);
   3953  prtm.tm_hour = int8_t(hour);
   3954  prtm.tm_mday = int8_t(day);
   3955  prtm.tm_mon = int8_t(month);
   3956  prtm.tm_wday = int8_t(WeekDay(localTime));
   3957  prtm.tm_year = year;
   3958  prtm.tm_yday = int16_t(::DayWithinYear(localTime, year));
   3959  prtm.tm_isdst = (daylightSavingTA(utcTime) != 0);
   3960 
   3961  return prtm;
   3962 }
   3963 
   3964 size_t DateTimeHelper::formatTime(char* buf, size_t buflen, const char* fmt,
   3965                                  int64_t utcTime, int64_t localTime) {
   3966  PRMJTime prtm = toPRMJTime(localTime, utcTime);
   3967 
   3968  // If an equivalent year was used to compute the date/time components, use
   3969  // the same equivalent year to determine the time zone name and offset in
   3970  // PRMJ_FormatTime(...).
   3971  int timeZoneYear = isRepresentableAsTime32(utcTime)
   3972                         ? prtm.tm_year
   3973                         : equivalentYearForDST(prtm.tm_year);
   3974 
   3975  int32_t offsetInSeconds = FloorDiv(localTime - utcTime, msPerSecond);
   3976 
   3977  return PRMJ_FormatTime(buf, buflen, fmt, &prtm, timeZoneYear,
   3978                         offsetInSeconds);
   3979 }
   3980 
   3981 JSString* DateTimeHelper::timeZoneComment(JSContext* cx, DateTimeInfo* dtInfo,
   3982                                          const char* locale, int64_t utcTime,
   3983                                          int64_t localTime) {
   3984  MOZ_ASSERT(dtInfo == nullptr);
   3985 
   3986  char tzbuf[100];
   3987 
   3988  size_t tzlen = formatTime(tzbuf, sizeof tzbuf, " (%Z)", utcTime, localTime);
   3989  if (tzlen != 0) {
   3990    // Decide whether to use the resulting time zone string.
   3991    //
   3992    // Reject it if it contains any non-ASCII or non-printable characters.
   3993    // It's then likely in some other character encoding, and we probably
   3994    // won't display it correctly.
   3995    bool usetz = true;
   3996    for (size_t i = 0; i < tzlen; i++) {
   3997      char16_t c = tzbuf[i];
   3998      if (!IsAsciiPrintable(c)) {
   3999        usetz = false;
   4000        break;
   4001      }
   4002    }
   4003 
   4004    // Also reject it if it's not parenthesized or if it's ' ()'.
   4005    if (tzbuf[0] != ' ' || tzbuf[1] != '(' || tzbuf[2] == ')') {
   4006      usetz = false;
   4007    }
   4008 
   4009    if (usetz) {
   4010      return NewStringCopyN<CanGC>(cx, tzbuf, tzlen);
   4011    }
   4012  }
   4013 
   4014  return cx->names().empty_;
   4015 }
   4016 #endif /* JS_HAS_INTL_API */
   4017 
   4018 enum class FormatSpec { DateTime, Date, Time };
   4019 
   4020 static bool FormatDate(JSContext* cx, DateTimeInfo* dtInfo, const char* locale,
   4021                       double utcTime, FormatSpec format,
   4022                       MutableHandleValue rval) {
   4023  MOZ_ASSERT(IsTimeValue(utcTime));
   4024 
   4025  if (!std::isfinite(utcTime)) {
   4026    rval.setString(cx->names().Invalid_Date_);
   4027    return true;
   4028  }
   4029 
   4030  int64_t epochMilliseconds = static_cast<int64_t>(utcTime);
   4031  int64_t localTime = LocalTime(dtInfo, utcTime);
   4032 
   4033  int offset = 0;
   4034  RootedString timeZoneComment(cx);
   4035  if (format == FormatSpec::DateTime || format == FormatSpec::Time) {
   4036    // Offset from GMT in minutes. The offset includes daylight savings,
   4037    // if it applies.
   4038    int32_t minutes = int32_t(localTime - epochMilliseconds) / msPerMinute;
   4039 
   4040    // Map 510 minutes to 0830 hours.
   4041    offset = (minutes / 60) * 100 + minutes % 60;
   4042 
   4043    // Print as "Wed Nov 05 1997 19:38:03 GMT-0800 (PST)".
   4044    //
   4045    // The TZA is printed as 'GMT-0800' rather than as 'PST' to avoid
   4046    // operating-system dependence on strftime (which PRMJ_FormatTime
   4047    // calls, for %Z only.) win32 prints PST as 'Pacific Standard Time.'
   4048    // This way we always know what we're getting, and can parse it if
   4049    // we produce it. The OS time zone string is included as a comment.
   4050    //
   4051    // When ICU is used to retrieve the time zone string, the localized
   4052    // 'long' name format from CLDR is used. For example when the default
   4053    // locale is "en-US", PST is displayed as 'Pacific Standard Time', but
   4054    // when it is "ru", 'Тихоокеанское стандартное время' is used. This
   4055    // also means the time zone string may not fit into Latin-1.
   4056 
   4057    // Get a time zone string from the OS or ICU to include as a comment.
   4058    timeZoneComment = DateTimeHelper::timeZoneComment(
   4059        cx, dtInfo, locale, epochMilliseconds, localTime);
   4060    if (!timeZoneComment) {
   4061      return false;
   4062    }
   4063  }
   4064 
   4065  DateFormatter fmt{};
   4066  switch (format) {
   4067    case FormatSpec::DateTime: {
   4068      /* Tue Oct 31 2000 09:41:40 GMT-0800 */
   4069      auto [year, month, day] = ToYearMonthDay(localTime);
   4070      auto [hour, minute, second] = ToHourMinuteSecond(localTime);
   4071 
   4072      fmt.weekDay(WeekDay(localTime))
   4073          .literal(' ')
   4074          .monthName(month)
   4075          .literal(' ')
   4076          .day(day)
   4077          .literal(' ')
   4078          .year(year)
   4079          .literal(' ')
   4080          .time(hour, minute, second)
   4081          .literal(" GMT")
   4082          .timeZoneOffset(offset);
   4083      break;
   4084    }
   4085    case FormatSpec::Date: {
   4086      /* Tue Oct 31 2000 */
   4087      auto [year, month, day] = ToYearMonthDay(localTime);
   4088 
   4089      fmt.weekDay(WeekDay(localTime))
   4090          .literal(' ')
   4091          .monthName(month)
   4092          .literal(' ')
   4093          .day(day)
   4094          .literal(' ')
   4095          .year(year);
   4096      break;
   4097    }
   4098    case FormatSpec::Time:
   4099      /* 09:41:40 GMT-0800 */
   4100      auto [hour, minute, second] = ToHourMinuteSecond(localTime);
   4101      fmt.time(hour, minute, second).literal(" GMT").timeZoneOffset(offset);
   4102      break;
   4103  }
   4104 
   4105  RootedString str(cx, NewStringCopy<CanGC>(cx, fmt.string()));
   4106  if (!str) {
   4107    return false;
   4108  }
   4109 
   4110  // Append the time zone string if present.
   4111  if (timeZoneComment && !timeZoneComment->empty()) {
   4112    str = js::ConcatStrings<CanGC>(cx, str, timeZoneComment);
   4113    if (!str) {
   4114      return false;
   4115    }
   4116  }
   4117 
   4118  rval.setString(str);
   4119  return true;
   4120 }
   4121 
   4122 #if JS_HAS_INTL_API
   4123 static bool ToLocaleFormatHelper(JSContext* cx, DateObject* unwrapped,
   4124                                 intl::DateTimeFormatKind kind,
   4125                                 HandleValue locales, HandleValue options,
   4126                                 MutableHandleValue rval) {
   4127  double utcTime = unwrapped->UTCTime().toDouble();
   4128  MOZ_ASSERT(IsTimeValue(utcTime));
   4129 
   4130  if (!std::isfinite(utcTime)) {
   4131    rval.setString(cx->names().Invalid_Date_);
   4132    return true;
   4133  }
   4134 
   4135  Rooted<DateTimeFormatObject*> dateTimeFormat(
   4136      cx, intl::GetOrCreateDateTimeFormat(cx, locales, options, kind));
   4137  if (!dateTimeFormat) {
   4138    return false;
   4139  }
   4140  return intl::FormatDateTime(cx, dateTimeFormat, utcTime, rval);
   4141 }
   4142 #else
   4143 static bool ToLocaleFormatHelper(JSContext* cx, DateObject* unwrapped,
   4144                                 const char* format, MutableHandleValue rval) {
   4145  double utcTime = unwrapped->UTCTime().toDouble();
   4146 
   4147  const char* locale = unwrapped->realm()->getLocale();
   4148  if (!locale) {
   4149    return false;
   4150  }
   4151 
   4152  char buf[100];
   4153  if (!std::isfinite(utcTime)) {
   4154    strcpy(buf, "InvalidDate");
   4155  } else {
   4156    MOZ_ASSERT(IsTimeValue(utcTime));
   4157 
   4158    int64_t epochMilliseconds = static_cast<int64_t>(utcTime);
   4159    int64_t localTime = static_cast<int64_t>(LocalTime(nullptr, utcTime));
   4160 
   4161    /* Let PRMJTime format it. */
   4162    size_t result_len = DateTimeHelper::formatTime(
   4163        buf, sizeof buf, format, epochMilliseconds, localTime);
   4164 
   4165    /* If it failed, default to toString. */
   4166    if (result_len == 0) {
   4167      return FormatDate(cx, nullptr, locale, utcTime, FormatSpec::DateTime,
   4168                        rval);
   4169    }
   4170 
   4171    /* Hacked check against undesired 2-digit year 00/00/00 form. */
   4172    if (strcmp(format, "%x") == 0 && result_len >= 6 &&
   4173        /* Format %x means use OS settings, which may have 2-digit yr, so
   4174           hack end of 3/11/22 or 11.03.22 or 11Mar22 to use 4-digit yr...*/
   4175        !IsAsciiDigit(buf[result_len - 3]) &&
   4176        IsAsciiDigit(buf[result_len - 2]) &&
   4177        IsAsciiDigit(buf[result_len - 1]) &&
   4178        /* ...but not if starts with 4-digit year, like 2022/3/11. */
   4179        !(IsAsciiDigit(buf[0]) && IsAsciiDigit(buf[1]) &&
   4180          IsAsciiDigit(buf[2]) && IsAsciiDigit(buf[3]))) {
   4181      int year = int(::YearFromTime(localTime));
   4182      snprintf(buf + (result_len - 2), (sizeof buf) - (result_len - 2), "%d",
   4183               year);
   4184    }
   4185  }
   4186 
   4187  if (cx->runtime()->localeCallbacks &&
   4188      cx->runtime()->localeCallbacks->localeToUnicode) {
   4189    return cx->runtime()->localeCallbacks->localeToUnicode(cx, buf, rval);
   4190  }
   4191 
   4192  JSString* str = NewStringCopyZ<CanGC>(cx, buf);
   4193  if (!str) {
   4194    return false;
   4195  }
   4196  rval.setString(str);
   4197  return true;
   4198 }
   4199 #endif
   4200 
   4201 /**
   4202 * 21.4.4.39 Date.prototype.toLocaleString ( [ reserved1 [ , reserved2 ] ] )
   4203 *
   4204 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   4205 *
   4206 * 20.4.1 Date.prototype.toLocaleString ( [ locales [ , options ] ] )
   4207 *
   4208 * ES2025 Intl draft rev 6827e6e40b45fb313472595be31352451a2d85fa
   4209 */
   4210 static bool date_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
   4211  AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toLocaleString");
   4212  CallArgs args = CallArgsFromVp(argc, vp);
   4213 
   4214  auto* unwrapped =
   4215      UnwrapAndTypeCheckThis<DateObject>(cx, args, "toLocaleString");
   4216  if (!unwrapped) {
   4217    return false;
   4218  }
   4219 
   4220 #if JS_HAS_INTL_API
   4221  return ToLocaleFormatHelper(cx, unwrapped, intl::DateTimeFormatKind::All,
   4222                              args.get(0), args.get(1), args.rval());
   4223 #else
   4224  /*
   4225   * Use '%#c' for windows, because '%c' is backward-compatible and non-y2k
   4226   * with msvc; '%#c' requests that a full year be used in the result string.
   4227   */
   4228  static const char format[] =
   4229 #  if defined(_WIN32)
   4230      "%#c"
   4231 #  else
   4232      "%c"
   4233 #  endif
   4234      ;
   4235 
   4236  return ToLocaleFormatHelper(cx, unwrapped, format, args.rval());
   4237 #endif
   4238 }
   4239 
   4240 /**
   4241 * 21.4.4.38 Date.prototype.toLocaleDateString ( [ reserved1 [ , reserved2 ] ] )
   4242 *
   4243 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   4244 *
   4245 * 20.4.2 Date.prototype.toLocaleDateString ( [ locales [ , options ] ] )
   4246 *
   4247 * ES2025 Intl draft rev 6827e6e40b45fb313472595be31352451a2d85fa
   4248 */
   4249 static bool date_toLocaleDateString(JSContext* cx, unsigned argc, Value* vp) {
   4250  AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype",
   4251                                        "toLocaleDateString");
   4252  CallArgs args = CallArgsFromVp(argc, vp);
   4253 
   4254  auto* unwrapped =
   4255      UnwrapAndTypeCheckThis<DateObject>(cx, args, "toLocaleDateString");
   4256  if (!unwrapped) {
   4257    return false;
   4258  }
   4259 
   4260 #if JS_HAS_INTL_API
   4261  return ToLocaleFormatHelper(cx, unwrapped, intl::DateTimeFormatKind::Date,
   4262                              args.get(0), args.get(1), args.rval());
   4263 #else
   4264  /*
   4265   * Use '%#x' for windows, because '%x' is backward-compatible and non-y2k
   4266   * with msvc; '%#x' requests that a full year be used in the result string.
   4267   */
   4268  static const char format[] =
   4269 #  if defined(_WIN32)
   4270      "%#x"
   4271 #  else
   4272      "%x"
   4273 #  endif
   4274      ;
   4275 
   4276  return ToLocaleFormatHelper(cx, unwrapped, format, args.rval());
   4277 #endif
   4278 }
   4279 
   4280 /**
   4281 * 21.4.4.40 Date.prototype.toLocaleTimeString ( [ reserved1 [ , reserved2 ] ] )
   4282 *
   4283 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   4284 *
   4285 * 20.4.3 Date.prototype.toLocaleTimeString ( [ locales [ , options ] ] )
   4286 *
   4287 * ES2025 Intl draft rev 6827e6e40b45fb313472595be31352451a2d85fa
   4288 */
   4289 static bool date_toLocaleTimeString(JSContext* cx, unsigned argc, Value* vp) {
   4290  AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype",
   4291                                        "toLocaleTimeString");
   4292  CallArgs args = CallArgsFromVp(argc, vp);
   4293 
   4294  auto* unwrapped =
   4295      UnwrapAndTypeCheckThis<DateObject>(cx, args, "toLocaleTimeString");
   4296  if (!unwrapped) {
   4297    return false;
   4298  }
   4299 
   4300 #if JS_HAS_INTL_API
   4301  return ToLocaleFormatHelper(cx, unwrapped, intl::DateTimeFormatKind::Time,
   4302                              args.get(0), args.get(1), args.rval());
   4303 #else
   4304  return ToLocaleFormatHelper(cx, unwrapped, "%X", args.rval());
   4305 #endif
   4306 }
   4307 
   4308 /**
   4309 * 21.4.4.42 Date.prototype.toTimeString ( )
   4310 *
   4311 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   4312 */
   4313 static bool date_toTimeString(JSContext* cx, unsigned argc, Value* vp) {
   4314  AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toTimeString");
   4315  CallArgs args = CallArgsFromVp(argc, vp);
   4316 
   4317  // Steps 1-2.
   4318  auto* unwrapped =
   4319      UnwrapAndTypeCheckThis<DateObject>(cx, args, "toTimeString");
   4320  if (!unwrapped) {
   4321    return false;
   4322  }
   4323 
   4324  // Steps 3-6.
   4325  const char* locale = unwrapped->realm()->getLocale();
   4326  if (!locale) {
   4327    return false;
   4328  }
   4329  return FormatDate(cx, unwrapped->dateTimeInfo(), locale,
   4330                    unwrapped->UTCTime().toDouble(), FormatSpec::Time,
   4331                    args.rval());
   4332 }
   4333 
   4334 /**
   4335 * 21.4.4.35 Date.prototype.toDateString ( )
   4336 *
   4337 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   4338 */
   4339 static bool date_toDateString(JSContext* cx, unsigned argc, Value* vp) {
   4340  AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toDateString");
   4341  CallArgs args = CallArgsFromVp(argc, vp);
   4342 
   4343  // Steps 1-2.
   4344  auto* unwrapped =
   4345      UnwrapAndTypeCheckThis<DateObject>(cx, args, "toDateString");
   4346  if (!unwrapped) {
   4347    return false;
   4348  }
   4349 
   4350  // Steps 3-6.
   4351  const char* locale = unwrapped->realm()->getLocale();
   4352  if (!locale) {
   4353    return false;
   4354  }
   4355  return FormatDate(cx, unwrapped->dateTimeInfo(), locale,
   4356                    unwrapped->UTCTime().toDouble(), FormatSpec::Date,
   4357                    args.rval());
   4358 }
   4359 
   4360 static bool date_toSource(JSContext* cx, unsigned argc, Value* vp) {
   4361  AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toSource");
   4362  CallArgs args = CallArgsFromVp(argc, vp);
   4363 
   4364  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "toSource");
   4365  if (!unwrapped) {
   4366    return false;
   4367  }
   4368 
   4369  JSStringBuilder sb(cx);
   4370  if (!sb.append("(new Date(") ||
   4371      !NumberValueToStringBuilder(unwrapped->UTCTime(), sb) ||
   4372      !sb.append("))")) {
   4373    return false;
   4374  }
   4375 
   4376  JSString* str = sb.finishString();
   4377  if (!str) {
   4378    return false;
   4379  }
   4380  args.rval().setString(str);
   4381  return true;
   4382 }
   4383 
   4384 /**
   4385 * 21.4.4.41 Date.prototype.toString ( )
   4386 *
   4387 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   4388 */
   4389 static bool date_toString(JSContext* cx, unsigned argc, Value* vp) {
   4390  AutoJSMethodProfilerEntry pseudoFrame(cx, "Date.prototype", "toString");
   4391  CallArgs args = CallArgsFromVp(argc, vp);
   4392 
   4393  // Steps 1-2.
   4394  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "toString");
   4395  if (!unwrapped) {
   4396    return false;
   4397  }
   4398 
   4399  // Steps 3-4.
   4400  const char* locale = unwrapped->realm()->getLocale();
   4401  if (!locale) {
   4402    return false;
   4403  }
   4404  return FormatDate(cx, unwrapped->dateTimeInfo(), locale,
   4405                    unwrapped->UTCTime().toDouble(), FormatSpec::DateTime,
   4406                    args.rval());
   4407 }
   4408 
   4409 /**
   4410 * 21.4.4.44 Date.prototype.valueOf ( )
   4411 *
   4412 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   4413 */
   4414 bool js::date_valueOf(JSContext* cx, unsigned argc, Value* vp) {
   4415  CallArgs args = CallArgsFromVp(argc, vp);
   4416 
   4417  // Steps 1-2.
   4418  auto* unwrapped = UnwrapAndTypeCheckThis<DateObject>(cx, args, "valueOf");
   4419  if (!unwrapped) {
   4420    return false;
   4421  }
   4422 
   4423  // Step 3.
   4424  args.rval().set(unwrapped->UTCTime());
   4425  return true;
   4426 }
   4427 
   4428 /**
   4429 * 21.4.4.45 Date.prototype [ %Symbol.toPrimitive% ] ( hint )
   4430 *
   4431 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   4432 */
   4433 bool js::date_toPrimitive(JSContext* cx, unsigned argc, Value* vp) {
   4434  CallArgs args = CallArgsFromVp(argc, vp);
   4435 
   4436  // Steps 1-2.
   4437  if (!args.thisv().isObject()) {
   4438    ReportIncompatible(cx, args);
   4439    return false;
   4440  }
   4441 
   4442  // Steps 3-5.
   4443  JSType hint;
   4444  if (!GetFirstArgumentAsTypeHint(cx, args, &hint)) {
   4445    return false;
   4446  }
   4447  if (hint == JSTYPE_UNDEFINED) {
   4448    hint = JSTYPE_STRING;
   4449  }
   4450 
   4451  // Step 6.
   4452  RootedObject obj(cx, &args.thisv().toObject());
   4453  return OrdinaryToPrimitive(cx, obj, hint, args.rval());
   4454 }
   4455 
   4456 #if JS_HAS_INTL_API
   4457 /**
   4458 * Date.prototype.toTemporalInstant ( )
   4459 */
   4460 static bool date_toTemporalInstant(JSContext* cx, unsigned argc, Value* vp) {
   4461  CallArgs args = CallArgsFromVp(argc, vp);
   4462 
   4463  // Steps 1-2.
   4464  auto* unwrapped =
   4465      UnwrapAndTypeCheckThis<DateObject>(cx, args, "toTemporalInstant");
   4466  if (!unwrapped) {
   4467    return false;
   4468  }
   4469 
   4470  // Step 3.
   4471  double t = unwrapped->UTCTime().toDouble();
   4472  MOZ_ASSERT(IsTimeValue(t));
   4473 
   4474  // Step 4.
   4475  if (std::isnan(t)) {
   4476    JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr,
   4477                              JSMSG_INVALID_DATE);
   4478    return false;
   4479  }
   4480  int64_t tv = static_cast<int64_t>(t);
   4481 
   4482  auto epochNs = temporal::EpochNanoseconds::fromMilliseconds(tv);
   4483  MOZ_ASSERT(temporal::IsValidEpochNanoseconds(epochNs));
   4484 
   4485  // Step 5.
   4486  auto* result = temporal::CreateTemporalInstant(cx, epochNs);
   4487  if (!result) {
   4488    return false;
   4489  }
   4490  args.rval().setObject(*result);
   4491  return true;
   4492 }
   4493 #endif /* JS_HAS_INTL_API */
   4494 
   4495 static const JSFunctionSpec date_static_methods[] = {
   4496    JS_FN("UTC", date_UTC, 7, 0),
   4497    JS_FN("parse", date_parse, 1, 0),
   4498    JS_FN("now", date_now, 0, 0),
   4499    JS_FS_END,
   4500 };
   4501 
   4502 static const JSFunctionSpec date_methods[] = {
   4503    JS_INLINABLE_FN("getTime", date_getTime, 0, 0, DateGetTime),
   4504    JS_FN("getTimezoneOffset", date_getTimezoneOffset, 0, 0),
   4505    JS_FN("getYear", date_getYear, 0, 0),
   4506    JS_INLINABLE_FN("getFullYear", date_getFullYear, 0, 0, DateGetFullYear),
   4507    JS_FN("getUTCFullYear", date_getUTCFullYear, 0, 0),
   4508    JS_INLINABLE_FN("getMonth", date_getMonth, 0, 0, DateGetMonth),
   4509    JS_FN("getUTCMonth", date_getUTCMonth, 0, 0),
   4510    JS_INLINABLE_FN("getDate", date_getDate, 0, 0, DateGetDate),
   4511    JS_FN("getUTCDate", date_getUTCDate, 0, 0),
   4512    JS_INLINABLE_FN("getDay", date_getDay, 0, 0, DateGetDay),
   4513    JS_FN("getUTCDay", date_getUTCDay, 0, 0),
   4514    JS_INLINABLE_FN("getHours", date_getHours, 0, 0, DateGetHours),
   4515    JS_FN("getUTCHours", date_getUTCHours, 0, 0),
   4516    JS_INLINABLE_FN("getMinutes", date_getMinutes, 0, 0, DateGetMinutes),
   4517    JS_FN("getUTCMinutes", date_getUTCMinutes, 0, 0),
   4518    JS_INLINABLE_FN("getSeconds", date_getSeconds, 0, 0, DateGetSeconds),
   4519    JS_FN("getUTCSeconds", date_getUTCSeconds, 0, 0),
   4520    JS_FN("getMilliseconds", date_getMilliseconds, 0, 0),
   4521    JS_FN("getUTCMilliseconds", date_getUTCMilliseconds, 0, 0),
   4522    JS_FN("setTime", date_setTime, 1, 0),
   4523    JS_FN("setYear", date_setYear, 1, 0),
   4524    JS_FN("setFullYear", date_setFullYear, 3, 0),
   4525    JS_FN("setUTCFullYear", date_setUTCFullYear, 3, 0),
   4526    JS_FN("setMonth", date_setMonth, 2, 0),
   4527    JS_FN("setUTCMonth", date_setUTCMonth, 2, 0),
   4528    JS_FN("setDate", date_setDate, 1, 0),
   4529    JS_FN("setUTCDate", date_setUTCDate, 1, 0),
   4530    JS_FN("setHours", date_setHours, 4, 0),
   4531    JS_FN("setUTCHours", date_setUTCHours, 4, 0),
   4532    JS_FN("setMinutes", date_setMinutes, 3, 0),
   4533    JS_FN("setUTCMinutes", date_setUTCMinutes, 3, 0),
   4534    JS_FN("setSeconds", date_setSeconds, 2, 0),
   4535    JS_FN("setUTCSeconds", date_setUTCSeconds, 2, 0),
   4536    JS_FN("setMilliseconds", date_setMilliseconds, 1, 0),
   4537    JS_FN("setUTCMilliseconds", date_setUTCMilliseconds, 1, 0),
   4538    JS_FN("toUTCString", date_toUTCString, 0, 0),
   4539 #if JS_HAS_INTL_API
   4540    JS_FN("toTemporalInstant", date_toTemporalInstant, 0, 0),
   4541 #endif
   4542    JS_FN("toLocaleString", date_toLocaleString, 0, 0),
   4543    JS_FN("toLocaleDateString", date_toLocaleDateString, 0, 0),
   4544    JS_FN("toLocaleTimeString", date_toLocaleTimeString, 0, 0),
   4545    JS_FN("toDateString", date_toDateString, 0, 0),
   4546    JS_FN("toTimeString", date_toTimeString, 0, 0),
   4547    JS_FN("toISOString", date_toISOString, 0, 0),
   4548    JS_FN("toJSON", date_toJSON, 1, 0),
   4549    JS_FN("toSource", date_toSource, 0, 0),
   4550    JS_FN("toString", date_toString, 0, 0),
   4551    JS_INLINABLE_FN("valueOf", date_valueOf, 0, 0, DateGetTime),
   4552    JS_SYM_FN(toPrimitive, date_toPrimitive, 1, JSPROP_READONLY),
   4553    JS_FS_END,
   4554 };
   4555 
   4556 static bool NewDateObject(JSContext* cx, const CallArgs& args, ClippedTime t) {
   4557  MOZ_ASSERT(args.isConstructing());
   4558 
   4559  RootedObject proto(cx);
   4560  if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Date, &proto)) {
   4561    return false;
   4562  }
   4563 
   4564  JSObject* obj = NewDateObjectMsec(cx, t, proto);
   4565  if (!obj) {
   4566    return false;
   4567  }
   4568 
   4569  args.rval().setObject(*obj);
   4570  return true;
   4571 }
   4572 
   4573 /**
   4574 * 21.4.4.41.4 ToDateString ( tv )
   4575 *
   4576 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   4577 */
   4578 static bool ToDateString(JSContext* cx, const CallArgs& args, ClippedTime t) {
   4579  const char* locale = cx->realm()->getLocale();
   4580  if (!locale) {
   4581    return false;
   4582  }
   4583  return FormatDate(cx, cx->realm()->getDateTimeInfo(), locale, t.toDouble(),
   4584                    FormatSpec::DateTime, args.rval());
   4585 }
   4586 
   4587 /**
   4588 * 21.4.2.1 Date ( ...values )
   4589 *
   4590 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   4591 */
   4592 static bool DateNoArguments(JSContext* cx, const CallArgs& args) {
   4593  MOZ_ASSERT(args.isConstructing());
   4594  MOZ_ASSERT(args.length() == 0);
   4595 
   4596  // Step 3.
   4597  ClippedTime now = NowAsMillis(cx);
   4598 
   4599  // Steps 6-8.
   4600  return NewDateObject(cx, args, now);
   4601 }
   4602 
   4603 /**
   4604 * 21.4.2.1 Date ( ...values )
   4605 *
   4606 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   4607 */
   4608 static bool DateOneArgument(JSContext* cx, const CallArgs& args) {
   4609  MOZ_ASSERT(args.isConstructing());
   4610  MOZ_ASSERT(args.length() == 1);
   4611 
   4612  // Step 4.a.
   4613  MutableHandle<Value> value = args[0];
   4614 
   4615  // Step 4.b.
   4616  if (value.isObject()) {
   4617    RootedObject obj(cx, &value.toObject());
   4618 
   4619    ESClass cls;
   4620    if (!GetBuiltinClass(cx, obj, &cls)) {
   4621      return false;
   4622    }
   4623 
   4624    if (cls == ESClass::Date) {
   4625      RootedValue unboxed(cx);
   4626      if (!Unbox(cx, obj, &unboxed)) {
   4627        return false;
   4628      }
   4629 
   4630      // Steps 6-8.
   4631      return NewDateObject(cx, args, TimeClip(unboxed.toNumber()));
   4632    }
   4633  }
   4634 
   4635  // Step 4.c.i.
   4636  if (!ToPrimitive(cx, value)) {
   4637    return false;
   4638  }
   4639 
   4640  // Steps 4.c.ii-iii.
   4641  ClippedTime t;
   4642  if (value.isString()) {
   4643    JSLinearString* linearStr = value.toString()->ensureLinear(cx);
   4644    if (!linearStr) {
   4645      return false;
   4646    }
   4647 
   4648    if (!ParseDate(cx, cx->realm()->getDateTimeInfo(), linearStr, &t)) {
   4649      t = ClippedTime::invalid();
   4650    }
   4651  } else {
   4652    double d;
   4653    if (!ToNumber(cx, value, &d)) {
   4654      return false;
   4655    }
   4656    t = TimeClip(d);
   4657  }
   4658 
   4659  // Steps 6-8.
   4660  return NewDateObject(cx, args, t);
   4661 }
   4662 
   4663 /**
   4664 * 21.4.2.1 Date ( ...values )
   4665 *
   4666 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   4667 */
   4668 static bool DateMultipleArguments(JSContext* cx, const CallArgs& args) {
   4669  // Step 5.a.
   4670  MOZ_ASSERT(args.isConstructing());
   4671  MOZ_ASSERT(args.length() >= 2);
   4672 
   4673  // Step 5.b.
   4674  double y;
   4675  if (!ToNumber(cx, args[0], &y)) {
   4676    return false;
   4677  }
   4678 
   4679  // Step 5.c.
   4680  double m;
   4681  if (!ToNumber(cx, args[1], &m)) {
   4682    return false;
   4683  }
   4684 
   4685  // Step 5.d.
   4686  double dt;
   4687  if (args.length() >= 3) {
   4688    if (!ToNumber(cx, args[2], &dt)) {
   4689      return false;
   4690    }
   4691  } else {
   4692    dt = 1;
   4693  }
   4694 
   4695  // Step 5.e.
   4696  double h;
   4697  if (args.length() >= 4) {
   4698    if (!ToNumber(cx, args[3], &h)) {
   4699      return false;
   4700    }
   4701  } else {
   4702    h = 0;
   4703  }
   4704 
   4705  // Step 5.f.
   4706  double min;
   4707  if (args.length() >= 5) {
   4708    if (!ToNumber(cx, args[4], &min)) {
   4709      return false;
   4710    }
   4711  } else {
   4712    min = 0;
   4713  }
   4714 
   4715  // Step 5.g.
   4716  double s;
   4717  if (args.length() >= 6) {
   4718    if (!ToNumber(cx, args[5], &s)) {
   4719      return false;
   4720    }
   4721  } else {
   4722    s = 0;
   4723  }
   4724 
   4725  // Step 5.h.
   4726  double milli;
   4727  if (args.length() >= 7) {
   4728    if (!ToNumber(cx, args[6], &milli)) {
   4729      return false;
   4730    }
   4731  } else {
   4732    milli = 0;
   4733  }
   4734 
   4735  // Step 5.i.
   4736  double yr = MakeFullYear(y);
   4737 
   4738  // Step 5.j.
   4739  double finalDate = MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli));
   4740 
   4741  // Steps 5.k and 6-8.
   4742  return NewDateObject(
   4743      cx, args, TimeClip(UTC(cx->realm()->getDateTimeInfo(), finalDate)));
   4744 }
   4745 
   4746 /**
   4747 * 21.4.2.1 Date ( ...values )
   4748 *
   4749 * ES2025 draft rev 76814cbd5d7842c2a99d28e6e8c7833f1de5bee0
   4750 */
   4751 static bool DateConstructor(JSContext* cx, unsigned argc, Value* vp) {
   4752  AutoJSConstructorProfilerEntry pseudoFrame(cx, "Date");
   4753  CallArgs args = CallArgsFromVp(argc, vp);
   4754 
   4755  // Step 1.
   4756  if (!args.isConstructing()) {
   4757    return ToDateString(cx, args, NowAsMillis(cx));
   4758  }
   4759 
   4760  // Step 2.
   4761  unsigned numberOfArgs = args.length();
   4762 
   4763  // Steps 3 and 6-8.
   4764  if (numberOfArgs == 0) {
   4765    return DateNoArguments(cx, args);
   4766  }
   4767 
   4768  // Steps 4 and 6-8.
   4769  if (numberOfArgs == 1) {
   4770    return DateOneArgument(cx, args);
   4771  }
   4772 
   4773  // Steps 5-8.
   4774  return DateMultipleArguments(cx, args);
   4775 }
   4776 
   4777 static bool FinishDateClassInit(JSContext* cx, HandleObject ctor,
   4778                                HandleObject proto) {
   4779  /*
   4780   * Date.prototype.toGMTString has the same initial value as
   4781   * Date.prototype.toUTCString.
   4782   */
   4783  RootedValue toUTCStringFun(cx);
   4784  RootedId toUTCStringId(cx, NameToId(cx->names().toUTCString));
   4785  RootedId toGMTStringId(cx, NameToId(cx->names().toGMTString));
   4786  return NativeGetProperty(cx, proto.as<NativeObject>(), toUTCStringId,
   4787                           &toUTCStringFun) &&
   4788         NativeDefineDataProperty(cx, proto.as<NativeObject>(), toGMTStringId,
   4789                                  toUTCStringFun, 0);
   4790 }
   4791 
   4792 static const ClassSpec DateObjectClassSpec = {
   4793    GenericCreateConstructor<DateConstructor, 7, gc::AllocKind::FUNCTION>,
   4794    GenericCreatePrototype<DateObject>,
   4795    date_static_methods,
   4796    nullptr,
   4797    date_methods,
   4798    nullptr,
   4799    FinishDateClassInit,
   4800 };
   4801 
   4802 const JSClass DateObject::class_ = {
   4803    "Date",
   4804    JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS) |
   4805        JSCLASS_HAS_CACHED_PROTO(JSProto_Date),
   4806    JS_NULL_CLASS_OPS,
   4807    &DateObjectClassSpec,
   4808 };
   4809 
   4810 const JSClass DateObject::protoClass_ = {
   4811    "Date.prototype",
   4812    JSCLASS_HAS_CACHED_PROTO(JSProto_Date),
   4813    JS_NULL_CLASS_OPS,
   4814    &DateObjectClassSpec,
   4815 };
   4816 
   4817 JSObject* js::NewDateObjectMsec(JSContext* cx, ClippedTime t,
   4818                                HandleObject proto /* = nullptr */) {
   4819  DateObject* obj = NewObjectWithClassProto<DateObject>(cx, proto);
   4820  if (!obj) {
   4821    return nullptr;
   4822  }
   4823  obj->setUTCTime(t);
   4824  return obj;
   4825 }
   4826 
   4827 JS_PUBLIC_API JSObject* JS::NewDateObject(JSContext* cx, ClippedTime time) {
   4828  AssertHeapIsIdle();
   4829  CHECK_THREAD(cx);
   4830  return NewDateObjectMsec(cx, time);
   4831 }
   4832 
   4833 JS_PUBLIC_API JSObject* js::NewDateObject(JSContext* cx, int year, int mon,
   4834                                          int mday, int hour, int min,
   4835                                          int sec) {
   4836  MOZ_ASSERT(mon < 12);
   4837  double msec_time =
   4838      MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0.0));
   4839  return NewDateObjectMsec(
   4840      cx, TimeClip(UTC(cx->realm()->getDateTimeInfo(), msec_time)));
   4841 }
   4842 
   4843 JS_PUBLIC_API bool js::DateIsValid(JSContext* cx, HandleObject obj,
   4844                                   bool* isValid) {
   4845  ESClass cls;
   4846  if (!GetBuiltinClass(cx, obj, &cls)) {
   4847    return false;
   4848  }
   4849 
   4850  if (cls != ESClass::Date) {
   4851    *isValid = false;
   4852    return true;
   4853  }
   4854 
   4855  RootedValue unboxed(cx);
   4856  if (!Unbox(cx, obj, &unboxed)) {
   4857    return false;
   4858  }
   4859 
   4860  *isValid = !std::isnan(unboxed.toNumber());
   4861  return true;
   4862 }
   4863 
   4864 JS_PUBLIC_API JSObject* JS::NewDateObject(JSContext* cx, int year, int mon,
   4865                                          int mday, int hour, int min,
   4866                                          int sec) {
   4867  AssertHeapIsIdle();
   4868  CHECK_THREAD(cx);
   4869  return js::NewDateObject(cx, year, mon, mday, hour, min, sec);
   4870 }
   4871 
   4872 JS_PUBLIC_API bool JS::ObjectIsDate(JSContext* cx, Handle<JSObject*> obj,
   4873                                    bool* isDate) {
   4874  cx->check(obj);
   4875 
   4876  ESClass cls;
   4877  if (!GetBuiltinClass(cx, obj, &cls)) {
   4878    return false;
   4879  }
   4880 
   4881  *isDate = cls == ESClass::Date;
   4882  return true;
   4883 }
   4884 
   4885 JS_PUBLIC_API bool js::DateGetMsecSinceEpoch(JSContext* cx, HandleObject obj,
   4886                                             double* msecsSinceEpoch) {
   4887  ESClass cls;
   4888  if (!GetBuiltinClass(cx, obj, &cls)) {
   4889    return false;
   4890  }
   4891 
   4892  if (cls != ESClass::Date) {
   4893    *msecsSinceEpoch = 0;
   4894    return true;
   4895  }
   4896 
   4897  RootedValue unboxed(cx);
   4898  if (!Unbox(cx, obj, &unboxed)) {
   4899    return false;
   4900  }
   4901 
   4902  *msecsSinceEpoch = unboxed.toNumber();
   4903  return true;
   4904 }
   4905 
   4906 JS_PUBLIC_API bool JS::IsISOStyleDate(JSContext* cx,
   4907                                      const JS::Latin1Chars& str) {
   4908  ClippedTime result;
   4909  return ParseISOStyleDate(cx->realm()->getDateTimeInfo(), str.begin().get(),
   4910                           str.length(), &result);
   4911 }