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 }