Temporal.cpp (44601B)
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 #include "builtin/temporal/Temporal.h" 8 9 #include "mozilla/Casting.h" 10 #ifdef DEBUG 11 # include "mozilla/CheckedInt.h" 12 #endif 13 #include "mozilla/FloatingPoint.h" 14 #include "mozilla/Likely.h" 15 #include "mozilla/MathAlgorithms.h" 16 17 #include <algorithm> 18 #include <cmath> 19 #include <cstdlib> 20 #include <iterator> 21 #include <limits> 22 #include <stdint.h> 23 #include <string_view> 24 #include <utility> 25 26 #include "jsnum.h" 27 #include "jspubtd.h" 28 #include "NamespaceImports.h" 29 30 #include "builtin/temporal/PlainDate.h" 31 #include "builtin/temporal/PlainDateTime.h" 32 #include "builtin/temporal/PlainMonthDay.h" 33 #include "builtin/temporal/PlainTime.h" 34 #include "builtin/temporal/PlainYearMonth.h" 35 #include "builtin/temporal/TemporalRoundingMode.h" 36 #include "builtin/temporal/TemporalUnit.h" 37 #include "builtin/temporal/ZonedDateTime.h" 38 #include "gc/Barrier.h" 39 #include "js/Class.h" 40 #include "js/Conversions.h" 41 #include "js/ErrorReport.h" 42 #include "js/friend/ErrorMessages.h" 43 #include "js/Id.h" 44 #include "js/Printer.h" 45 #include "js/PropertyDescriptor.h" 46 #include "js/PropertySpec.h" 47 #include "js/RootingAPI.h" 48 #include "js/String.h" 49 #include "js/Value.h" 50 #include "vm/BytecodeUtil.h" 51 #include "vm/GlobalObject.h" 52 #include "vm/Int128.h" 53 #include "vm/JSAtomState.h" 54 #include "vm/JSAtomUtils.h" 55 #include "vm/JSContext.h" 56 #include "vm/JSObject.h" 57 #include "vm/ObjectOperations.h" 58 #include "vm/Realm.h" 59 #include "vm/StringType.h" 60 61 #include "vm/JSObject-inl.h" 62 #include "vm/ObjectOperations-inl.h" 63 64 using namespace js; 65 using namespace js::temporal; 66 67 /** 68 * GetOption ( options, property, type, values, default ) 69 * 70 * GetOption specialization when `type=string`. Default value handling must 71 * happen in the caller, so we don't provide the `default` parameter here. 72 */ 73 static bool GetStringOption(JSContext* cx, Handle<JSObject*> options, 74 Handle<PropertyName*> property, 75 MutableHandle<JSString*> string) { 76 // Step 1. 77 Rooted<Value> value(cx); 78 if (!GetProperty(cx, options, options, property, &value)) { 79 return false; 80 } 81 82 // Step 2. (Caller should fill in the fallback.) 83 if (value.isUndefined()) { 84 return true; 85 } 86 87 // Steps 3-4. (Not applicable when type=string) 88 89 // Step 5. 90 string.set(JS::ToString(cx, value)); 91 if (!string) { 92 return false; 93 } 94 95 // Step 6. (Not applicable in our implementation) 96 97 // Step 7. 98 return true; 99 } 100 101 /** 102 * GetRoundingIncrementOption ( normalizedOptions, dividend, inclusive ) 103 */ 104 bool js::temporal::GetRoundingIncrementOption(JSContext* cx, 105 Handle<JSObject*> options, 106 Increment* increment) { 107 // Step 1. 108 Rooted<Value> value(cx); 109 if (!GetProperty(cx, options, options, cx->names().roundingIncrement, 110 &value)) { 111 return false; 112 } 113 114 // Step 2. 115 if (value.isUndefined()) { 116 *increment = Increment{1}; 117 return true; 118 } 119 120 // Step 3. 121 double number; 122 if (!ToIntegerWithTruncation(cx, value, "roundingIncrement", &number)) { 123 return false; 124 } 125 126 // Step 4. 127 if (number < 1 || number > 1'000'000'000) { 128 ToCStringBuf cbuf; 129 const char* numStr = NumberToCString(&cbuf, number); 130 131 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 132 JSMSG_INVALID_OPTION_VALUE, "roundingIncrement", 133 numStr); 134 return false; 135 } 136 137 // Step 5. 138 *increment = Increment{uint32_t(number)}; 139 return true; 140 } 141 142 /** 143 * ValidateTemporalRoundingIncrement ( increment, dividend, inclusive ) 144 */ 145 bool js::temporal::ValidateTemporalRoundingIncrement(JSContext* cx, 146 Increment increment, 147 int64_t dividend, 148 bool inclusive) { 149 MOZ_ASSERT(dividend > 0); 150 MOZ_ASSERT_IF(!inclusive, dividend > 1); 151 152 // Steps 1-2. 153 int64_t maximum = inclusive ? dividend : dividend - 1; 154 155 // Steps 3-4. 156 if (increment.value() > maximum || dividend % increment.value() != 0) { 157 Int32ToCStringBuf cbuf; 158 const char* numStr = Int32ToCString(&cbuf, int32_t(increment.value())); 159 160 // TODO: Better error message could be helpful. 161 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 162 JSMSG_INVALID_OPTION_VALUE, "roundingIncrement", 163 numStr); 164 return false; 165 } 166 167 // Step 5. 168 return true; 169 } 170 171 static Handle<PropertyName*> ToPropertyName(JSContext* cx, 172 TemporalUnitKey key) { 173 switch (key) { 174 case TemporalUnitKey::SmallestUnit: 175 return cx->names().smallestUnit; 176 case TemporalUnitKey::LargestUnit: 177 return cx->names().largestUnit; 178 case TemporalUnitKey::Unit: 179 return cx->names().unit; 180 } 181 MOZ_CRASH("invalid temporal unit group"); 182 } 183 184 static const char* ToCString(TemporalUnitKey key) { 185 switch (key) { 186 case TemporalUnitKey::SmallestUnit: 187 return "smallestUnit"; 188 case TemporalUnitKey::LargestUnit: 189 return "largestUnit"; 190 case TemporalUnitKey::Unit: 191 return "unit"; 192 } 193 MOZ_CRASH("invalid temporal unit group"); 194 } 195 196 static bool ToTemporalUnit(JSContext* cx, JSLinearString* str, 197 TemporalUnitKey key, TemporalUnit* unit) { 198 struct UnitMap { 199 std::string_view name; 200 TemporalUnit unit; 201 }; 202 203 static constexpr UnitMap mapping[] = { 204 {"auto", TemporalUnit::Auto}, 205 {"year", TemporalUnit::Year}, 206 {"years", TemporalUnit::Year}, 207 {"month", TemporalUnit::Month}, 208 {"months", TemporalUnit::Month}, 209 {"week", TemporalUnit::Week}, 210 {"weeks", TemporalUnit::Week}, 211 {"day", TemporalUnit::Day}, 212 {"days", TemporalUnit::Day}, 213 {"hour", TemporalUnit::Hour}, 214 {"hours", TemporalUnit::Hour}, 215 {"minute", TemporalUnit::Minute}, 216 {"minutes", TemporalUnit::Minute}, 217 {"second", TemporalUnit::Second}, 218 {"seconds", TemporalUnit::Second}, 219 {"millisecond", TemporalUnit::Millisecond}, 220 {"milliseconds", TemporalUnit::Millisecond}, 221 {"microsecond", TemporalUnit::Microsecond}, 222 {"microseconds", TemporalUnit::Microsecond}, 223 {"nanosecond", TemporalUnit::Nanosecond}, 224 {"nanoseconds", TemporalUnit::Nanosecond}, 225 }; 226 227 // Compute the length of the longest name. 228 constexpr size_t maxNameLength = 229 std::max_element(std::begin(mapping), std::end(mapping), 230 [](const auto& x, const auto& y) { 231 return x.name.length() < y.name.length(); 232 }) 233 ->name.length(); 234 235 // Twenty StringEqualsLiteral calls for each possible combination seems a bit 236 // expensive, so let's instead copy the input name into a char array and rely 237 // on the compiler to generate optimized code for the comparisons. 238 239 size_t length = str->length(); 240 if (length <= maxNameLength && StringIsAscii(str)) { 241 char chars[maxNameLength] = {}; 242 JS::LossyCopyLinearStringChars(chars, str, length); 243 244 for (const auto& m : mapping) { 245 if (m.name == std::string_view(chars, length)) { 246 *unit = m.unit; 247 return true; 248 } 249 } 250 } 251 252 if (auto chars = QuoteString(cx, str, '"')) { 253 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 254 JSMSG_INVALID_OPTION_VALUE, ToCString(key), 255 chars.get()); 256 } 257 return false; 258 } 259 260 static std::pair<TemporalUnit, TemporalUnit> AllowedValues( 261 TemporalUnitGroup unitGroup) { 262 switch (unitGroup) { 263 case TemporalUnitGroup::Date: 264 return {TemporalUnit::Year, TemporalUnit::Day}; 265 case TemporalUnitGroup::Time: 266 return {TemporalUnit::Hour, TemporalUnit::Nanosecond}; 267 case TemporalUnitGroup::DateTime: 268 return {TemporalUnit::Year, TemporalUnit::Nanosecond}; 269 case TemporalUnitGroup::DayTime: 270 return {TemporalUnit::Day, TemporalUnit::Nanosecond}; 271 } 272 MOZ_CRASH("invalid temporal unit group"); 273 } 274 275 /** 276 * GetTemporalUnitValuedOption ( options, key, default ) 277 */ 278 bool js::temporal::GetTemporalUnitValuedOption(JSContext* cx, 279 Handle<JSObject*> options, 280 TemporalUnitKey key, 281 TemporalUnit* unit) { 282 // Steps 1-5. (Not applicable in our implementation.) 283 284 // Step 6. 285 Rooted<JSString*> value(cx); 286 if (!GetStringOption(cx, options, ToPropertyName(cx, key), &value)) { 287 return false; 288 } 289 290 // Step 7. 291 // 292 // Caller should fill in the fallback. 293 if (!value) { 294 return true; 295 } 296 297 // Steps 8-9. 298 auto* linear = value->ensureLinear(cx); 299 if (!linear) { 300 return false; 301 } 302 return ToTemporalUnit(cx, linear, key, unit); 303 } 304 305 /** 306 * GetTemporalUnitValuedOption ( options, key, default ) 307 */ 308 bool js::temporal::GetTemporalUnitValuedOption(JSContext* cx, 309 Handle<JSString*> value, 310 TemporalUnitKey key, 311 TemporalUnit* unit) { 312 // Steps 1-7. (Not applicable in our implementation.) 313 314 // Steps 8-9. 315 auto* linear = value->ensureLinear(cx); 316 if (!linear) { 317 return false; 318 } 319 return ToTemporalUnit(cx, linear, key, unit); 320 } 321 322 /** 323 * ValidateTemporalUnitValue ( value, unitGroup [ , extraValues ] ) 324 */ 325 bool js::temporal::ValidateTemporalUnitValue(JSContext* cx, TemporalUnitKey key, 326 TemporalUnit unit, 327 TemporalUnitGroup unitGroup) { 328 // Step 1. 329 if (unit == TemporalUnit::Unset) { 330 return true; 331 } 332 333 // Step 2. (Partial) 334 if (key == TemporalUnitKey::LargestUnit && unit == TemporalUnit::Auto) { 335 return true; 336 } 337 338 // Steps 2-5. 339 auto allowedValues = AllowedValues(unitGroup); 340 if (allowedValues.first <= unit && unit <= allowedValues.second) { 341 return true; 342 } 343 344 // Step 6. 345 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 346 JSMSG_INVALID_OPTION_VALUE, ToCString(key), 347 TemporalUnitToString(unit)); 348 return false; 349 } 350 351 /** 352 * GetRoundingModeOption ( normalizedOptions, fallback ) 353 */ 354 bool js::temporal::GetRoundingModeOption(JSContext* cx, 355 Handle<JSObject*> options, 356 TemporalRoundingMode* mode) { 357 // Steps 1-2. 358 Rooted<JSString*> string(cx); 359 if (!GetStringOption(cx, options, cx->names().roundingMode, &string)) { 360 return false; 361 } 362 363 // Caller should fill in the fallback. 364 if (!string) { 365 return true; 366 } 367 368 JSLinearString* linear = string->ensureLinear(cx); 369 if (!linear) { 370 return false; 371 } 372 373 if (StringEqualsLiteral(linear, "ceil")) { 374 *mode = TemporalRoundingMode::Ceil; 375 } else if (StringEqualsLiteral(linear, "floor")) { 376 *mode = TemporalRoundingMode::Floor; 377 } else if (StringEqualsLiteral(linear, "expand")) { 378 *mode = TemporalRoundingMode::Expand; 379 } else if (StringEqualsLiteral(linear, "trunc")) { 380 *mode = TemporalRoundingMode::Trunc; 381 } else if (StringEqualsLiteral(linear, "halfCeil")) { 382 *mode = TemporalRoundingMode::HalfCeil; 383 } else if (StringEqualsLiteral(linear, "halfFloor")) { 384 *mode = TemporalRoundingMode::HalfFloor; 385 } else if (StringEqualsLiteral(linear, "halfExpand")) { 386 *mode = TemporalRoundingMode::HalfExpand; 387 } else if (StringEqualsLiteral(linear, "halfTrunc")) { 388 *mode = TemporalRoundingMode::HalfTrunc; 389 } else if (StringEqualsLiteral(linear, "halfEven")) { 390 *mode = TemporalRoundingMode::HalfEven; 391 } else { 392 if (auto chars = QuoteString(cx, linear, '"')) { 393 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 394 JSMSG_INVALID_OPTION_VALUE, "roundingMode", 395 chars.get()); 396 } 397 return false; 398 } 399 return true; 400 } 401 402 #ifdef DEBUG 403 template <typename T> 404 static bool IsValidMul(const T& x, const T& y) { 405 return (mozilla::CheckedInt<T>(x) * y).isValid(); 406 } 407 408 // Copied from mozilla::CheckedInt. 409 template <> 410 bool IsValidMul<Int128>(const Int128& x, const Int128& y) { 411 static constexpr auto min = Int128{1} << 127; 412 static constexpr auto max = ~min; 413 414 if (x == Int128{0} || y == Int128{0}) { 415 return true; 416 } 417 if (x > Int128{0}) { 418 return y > Int128{0} ? x <= max / y : y >= min / x; 419 } 420 return y > Int128{0} ? x >= min / y : y >= max / x; 421 } 422 #endif 423 424 /** 425 * RoundNumberToIncrement ( x, increment, roundingMode ) 426 */ 427 Int128 js::temporal::RoundNumberToIncrement(const Int128& numerator, 428 int64_t denominator, 429 Increment increment, 430 TemporalRoundingMode roundingMode) { 431 MOZ_ASSERT(denominator > 0); 432 MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max()); 433 434 auto inc = Int128{increment.value()}; 435 MOZ_ASSERT(IsValidMul(Int128{denominator}, inc), "unsupported overflow"); 436 437 auto divisor = Int128{denominator} * inc; 438 MOZ_ASSERT(divisor > Int128{0}); 439 440 // Steps 1-8. 441 auto rounded = Divide(numerator, divisor, roundingMode); 442 443 // Step 9. 444 MOZ_ASSERT(IsValidMul(rounded, inc), "unsupported overflow"); 445 return rounded * inc; 446 } 447 448 /** 449 * RoundNumberToIncrement ( x, increment, roundingMode ) 450 */ 451 int64_t js::temporal::RoundNumberToIncrement( 452 int64_t x, int64_t increment, TemporalRoundingMode roundingMode) { 453 MOZ_ASSERT(increment > 0); 454 455 // Steps 1-8. 456 int64_t rounded = Divide(x, increment, roundingMode); 457 458 // Step 9. 459 MOZ_ASSERT(IsValidMul(rounded, increment), "unsupported overflow"); 460 return rounded * increment; 461 } 462 463 /** 464 * RoundNumberToIncrement ( x, increment, roundingMode ) 465 */ 466 Int128 js::temporal::RoundNumberToIncrement(const Int128& x, 467 const Int128& increment, 468 TemporalRoundingMode roundingMode) { 469 MOZ_ASSERT(increment > Int128{0}); 470 471 // Steps 1-8. 472 auto rounded = Divide(x, increment, roundingMode); 473 474 // Step 9. 475 MOZ_ASSERT(IsValidMul(rounded, increment), "unsupported overflow"); 476 return rounded * increment; 477 } 478 479 template <typename IntT> 480 static inline constexpr bool IsSafeInteger(const IntT& x) { 481 constexpr IntT MaxSafeInteger = IntT{int64_t(1) << 53}; 482 constexpr IntT MinSafeInteger = -MaxSafeInteger; 483 return MinSafeInteger < x && x < MaxSafeInteger; 484 } 485 486 /** 487 * Return the real number value of the fraction |numerator / denominator|. 488 * 489 * As an optimization we multiply the remainder by 16 when computing the number 490 * of digits after the decimal point, i.e. we compute four instead of one bit of 491 * the fractional digits. The denominator is therefore required to not exceed 492 * 2**(N - log2(16)), where N is the number of non-sign bits in the mantissa. 493 */ 494 template <typename T> 495 static double FractionToDoubleSlow(const T& numerator, const T& denominator) { 496 MOZ_ASSERT(denominator > T{0}, "expected positive denominator"); 497 MOZ_ASSERT(denominator <= (T{1} << (std::numeric_limits<T>::digits - 4)), 498 "denominator too large"); 499 500 auto absValue = [](const T& value) { 501 if constexpr (std::is_same_v<T, Int128>) { 502 return value.abs(); 503 } else { 504 // NB: Not std::abs, because std::abs(INT64_MIN) is undefined behavior. 505 return mozilla::Abs(value); 506 } 507 }; 508 509 using UnsignedT = decltype(absValue(T{0})); 510 static_assert(!std::numeric_limits<UnsignedT>::is_signed); 511 512 auto divrem = [](const UnsignedT& x, const UnsignedT& y) { 513 if constexpr (std::is_same_v<T, Int128>) { 514 return x.divrem(y); 515 } else { 516 return std::pair{x / y, x % y}; 517 } 518 }; 519 520 auto [quot, rem] = 521 divrem(absValue(numerator), static_cast<UnsignedT>(denominator)); 522 523 // Simple case when no remainder is present. 524 if (rem == UnsignedT{0}) { 525 double sign = numerator < T{0} ? -1 : 1; 526 return sign * double(quot); 527 } 528 529 using Double = mozilla::FloatingPoint<double>; 530 531 // Significand including the implicit one of IEEE-754 floating point numbers. 532 static constexpr uint32_t SignificandWidthWithImplicitOne = 533 Double::kSignificandWidth + 1; 534 535 // Number of leading zeros for a correctly adjusted significand. 536 static constexpr uint32_t SignificandLeadingZeros = 537 64 - SignificandWidthWithImplicitOne; 538 539 // Exponent bias for an integral significand. (`Double::kExponentBias` is the 540 // bias for the binary fraction `1.xyz * 2**exp`. For an integral significand 541 // the significand width has to be added to the bias.) 542 static constexpr int32_t ExponentBias = 543 Double::kExponentBias + Double::kSignificandWidth; 544 545 // Significand, possibly unnormalized. 546 uint64_t significand = 0; 547 548 // Significand ignored msd bits. 549 uint32_t ignoredBits = 0; 550 551 // Read quotient, from most to least significant digit. Stop when the 552 // significand got too large for double precision. 553 int32_t shift = std::numeric_limits<UnsignedT>::digits; 554 for (; shift != 0 && ignoredBits == 0; shift -= 4) { 555 uint64_t digit = uint64_t(quot >> (shift - 4)) & 0xf; 556 557 significand = significand * 16 + digit; 558 ignoredBits = significand >> SignificandWidthWithImplicitOne; 559 } 560 561 // Read remainder, from most to least significant digit. Stop when the 562 // remainder is zero or the significand got too large. 563 int32_t fractionDigit = 0; 564 for (; rem != UnsignedT{0} && ignoredBits == 0; fractionDigit++) { 565 auto [digit, next] = 566 divrem(rem * UnsignedT{16}, static_cast<UnsignedT>(denominator)); 567 rem = next; 568 569 significand = significand * 16 + uint64_t(digit); 570 ignoredBits = significand >> SignificandWidthWithImplicitOne; 571 } 572 573 // Unbiased exponent. (`shift` remaining bits in the quotient, minus the 574 // fractional digits.) 575 int32_t exponent = shift - (fractionDigit * 4); 576 577 // Significand got too large and some bits are now ignored. Adjust the 578 // significand and exponent. 579 if (ignoredBits != 0) { 580 // significand 581 // ___________|__________ 582 // / | 583 // [xxx················yyy| 584 // \_/ \_/ 585 // | | 586 // ignoredBits extraBits 587 // 588 // `ignoredBits` have to be shifted back into the 53 bits of the significand 589 // and `extraBits` has to be checked if the result has to be rounded up. 590 591 // Number of ignored/extra bits in the significand. 592 uint32_t extraBitsCount = 32 - mozilla::CountLeadingZeroes32(ignoredBits); 593 MOZ_ASSERT(extraBitsCount > 0); 594 595 // Extra bits in the significand. 596 uint32_t extraBits = uint32_t(significand) & ((1 << extraBitsCount) - 1); 597 598 // Move the ignored bits into the proper significand position and adjust the 599 // exponent to reflect the now moved out extra bits. 600 significand >>= extraBitsCount; 601 exponent += extraBitsCount; 602 603 MOZ_ASSERT((significand >> SignificandWidthWithImplicitOne) == 0, 604 "no excess bits in the significand"); 605 606 // When the most significant digit in the extra bits is set, we may need to 607 // round the result. 608 uint32_t msdExtraBit = extraBits >> (extraBitsCount - 1); 609 if (msdExtraBit != 0) { 610 // Extra bits, excluding the most significant digit. 611 uint32_t extraBitExcludingMsdMask = (1 << (extraBitsCount - 1)) - 1; 612 613 // Unprocessed bits in the quotient. 614 auto bitsBelowExtraBits = quot & ((UnsignedT{1} << shift) - UnsignedT{1}); 615 616 // Round up if the extra bit's msd is set and either the significand is 617 // odd or any other bits below the extra bit's msd are non-zero. 618 // 619 // Bits below the extra bit's msd are: 620 // 1. The remaining bits of the extra bits. 621 // 2. Any bits below the extra bits. 622 // 3. Any rest of the remainder. 623 bool shouldRoundUp = (significand & 1) != 0 || 624 (extraBits & extraBitExcludingMsdMask) != 0 || 625 bitsBelowExtraBits != UnsignedT{0} || 626 rem != UnsignedT{0}; 627 if (shouldRoundUp) { 628 // Add one to the significand bits. 629 significand += 1; 630 631 // If they overflow, the exponent must also be increased. 632 if ((significand >> SignificandWidthWithImplicitOne) != 0) { 633 exponent++; 634 significand >>= 1; 635 } 636 } 637 } 638 } 639 640 MOZ_ASSERT(significand > 0, "significand is non-zero"); 641 MOZ_ASSERT((significand >> SignificandWidthWithImplicitOne) == 0, 642 "no excess bits in the significand"); 643 644 // Move the significand into the correct position and adjust the exponent 645 // accordingly. 646 uint32_t significandZeros = mozilla::CountLeadingZeroes64(significand); 647 if (significandZeros < SignificandLeadingZeros) { 648 uint32_t shift = SignificandLeadingZeros - significandZeros; 649 significand >>= shift; 650 exponent += shift; 651 } else if (significandZeros > SignificandLeadingZeros) { 652 uint32_t shift = significandZeros - SignificandLeadingZeros; 653 significand <<= shift; 654 exponent -= shift; 655 } 656 657 // Combine the individual bits of the double value and return it. 658 uint64_t signBit = uint64_t(numerator < T{0} ? 1 : 0) 659 << (Double::kExponentWidth + Double::kSignificandWidth); 660 uint64_t exponentBits = static_cast<uint64_t>(exponent + ExponentBias) 661 << Double::kExponentShift; 662 uint64_t significandBits = significand & Double::kSignificandBits; 663 return mozilla::BitwiseCast<double>(signBit | exponentBits | significandBits); 664 } 665 666 double js::temporal::FractionToDouble(int64_t numerator, int64_t denominator) { 667 MOZ_ASSERT(denominator > 0); 668 669 // Zero divided by any divisor is still zero. 670 if (numerator == 0) { 671 return 0; 672 } 673 674 // When both values can be represented as doubles, use double division to 675 // compute the exact result. The result is exact, because double division is 676 // guaranteed to return the exact result. 677 if (MOZ_LIKELY(::IsSafeInteger(numerator) && ::IsSafeInteger(denominator))) { 678 return double(numerator) / double(denominator); 679 } 680 681 // Otherwise call into |FractionToDoubleSlow| to compute the exact result. 682 if (denominator <= 683 (int64_t(1) << (std::numeric_limits<int64_t>::digits - 4))) { 684 // Slightly faster, but still slow approach when |denominator| is small 685 // enough to allow computing on int64 values. 686 return FractionToDoubleSlow(numerator, denominator); 687 } 688 return FractionToDoubleSlow(Int128{numerator}, Int128{denominator}); 689 } 690 691 double js::temporal::FractionToDouble(const Int128& numerator, 692 const Int128& denominator) { 693 MOZ_ASSERT(denominator > Int128{0}); 694 695 // Zero divided by any divisor is still zero. 696 if (numerator == Int128{0}) { 697 return 0; 698 } 699 700 // When both values can be represented as doubles, use double division to 701 // compute the exact result. The result is exact, because double division is 702 // guaranteed to return the exact result. 703 if (MOZ_LIKELY(::IsSafeInteger(numerator) && ::IsSafeInteger(denominator))) { 704 return double(numerator) / double(denominator); 705 } 706 707 // Otherwise call into |FractionToDoubleSlow| to compute the exact result. 708 return FractionToDoubleSlow(numerator, denominator); 709 } 710 711 /** 712 * GetTemporalShowCalendarNameOption ( normalizedOptions ) 713 */ 714 bool js::temporal::GetTemporalShowCalendarNameOption(JSContext* cx, 715 Handle<JSObject*> options, 716 ShowCalendar* result) { 717 // Step 1. 718 Rooted<JSString*> calendarName(cx); 719 if (!GetStringOption(cx, options, cx->names().calendarName, &calendarName)) { 720 return false; 721 } 722 723 // Caller should fill in the fallback. 724 if (!calendarName) { 725 return true; 726 } 727 728 JSLinearString* linear = calendarName->ensureLinear(cx); 729 if (!linear) { 730 return false; 731 } 732 733 if (StringEqualsLiteral(linear, "auto")) { 734 *result = ShowCalendar::Auto; 735 } else if (StringEqualsLiteral(linear, "always")) { 736 *result = ShowCalendar::Always; 737 } else if (StringEqualsLiteral(linear, "never")) { 738 *result = ShowCalendar::Never; 739 } else if (StringEqualsLiteral(linear, "critical")) { 740 *result = ShowCalendar::Critical; 741 } else { 742 if (auto chars = QuoteString(cx, linear, '"')) { 743 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 744 JSMSG_INVALID_OPTION_VALUE, "calendarName", 745 chars.get()); 746 } 747 return false; 748 } 749 return true; 750 } 751 752 /** 753 * GetTemporalFractionalSecondDigitsOption ( normalizedOptions ) 754 */ 755 bool js::temporal::GetTemporalFractionalSecondDigitsOption( 756 JSContext* cx, Handle<JSObject*> options, Precision* precision) { 757 // Step 1. 758 Rooted<Value> digitsValue(cx); 759 if (!GetProperty(cx, options, options, cx->names().fractionalSecondDigits, 760 &digitsValue)) { 761 return false; 762 } 763 764 // Step 2. 765 if (digitsValue.isUndefined()) { 766 *precision = Precision::Auto(); 767 return true; 768 } 769 770 // Step 3. 771 if (!digitsValue.isNumber()) { 772 // Step 3.a. 773 JSString* string = JS::ToString(cx, digitsValue); 774 if (!string) { 775 return false; 776 } 777 778 JSLinearString* linear = string->ensureLinear(cx); 779 if (!linear) { 780 return false; 781 } 782 783 if (!StringEqualsLiteral(linear, "auto")) { 784 if (auto chars = QuoteString(cx, linear, '"')) { 785 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 786 JSMSG_INVALID_OPTION_VALUE, 787 "fractionalSecondDigits", chars.get()); 788 } 789 return false; 790 } 791 792 // Step 3.b. 793 *precision = Precision::Auto(); 794 return true; 795 } 796 797 // Step 4. 798 double digitCount = digitsValue.toNumber(); 799 if (!std::isfinite(digitCount)) { 800 ToCStringBuf cbuf; 801 const char* numStr = NumberToCString(&cbuf, digitCount); 802 803 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 804 JSMSG_INVALID_OPTION_VALUE, 805 "fractionalSecondDigits", numStr); 806 return false; 807 } 808 809 // Step 5. 810 digitCount = std::floor(digitCount); 811 812 // Step 6. 813 if (digitCount < 0 || digitCount > 9) { 814 ToCStringBuf cbuf; 815 const char* numStr = NumberToCString(&cbuf, digitCount); 816 817 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 818 JSMSG_INVALID_OPTION_VALUE, 819 "fractionalSecondDigits", numStr); 820 return false; 821 } 822 823 // Step 7. 824 *precision = Precision{uint8_t(digitCount)}; 825 return true; 826 } 827 828 /** 829 * ToSecondsStringPrecisionRecord ( smallestUnit, fractionalDigitCount ) 830 */ 831 SecondsStringPrecision js::temporal::ToSecondsStringPrecision( 832 TemporalUnit smallestUnit, Precision fractionalDigitCount) { 833 MOZ_ASSERT(smallestUnit == TemporalUnit::Unset || 834 smallestUnit >= TemporalUnit::Minute); 835 MOZ_ASSERT(fractionalDigitCount == Precision::Auto() || 836 fractionalDigitCount.value() <= 9); 837 838 // Steps 1-5. 839 switch (smallestUnit) { 840 // Step 1. 841 case TemporalUnit::Minute: 842 return {Precision::Minute(), TemporalUnit::Minute, Increment{1}}; 843 844 // Step 2. 845 case TemporalUnit::Second: 846 return {Precision{0}, TemporalUnit::Second, Increment{1}}; 847 848 // Step 3. 849 case TemporalUnit::Millisecond: 850 return {Precision{3}, TemporalUnit::Millisecond, Increment{1}}; 851 852 // Step 4. 853 case TemporalUnit::Microsecond: 854 return {Precision{6}, TemporalUnit::Microsecond, Increment{1}}; 855 856 // Step 5. 857 case TemporalUnit::Nanosecond: 858 return {Precision{9}, TemporalUnit::Nanosecond, Increment{1}}; 859 860 case TemporalUnit::Unset: 861 break; 862 863 case TemporalUnit::Auto: 864 case TemporalUnit::Year: 865 case TemporalUnit::Month: 866 case TemporalUnit::Week: 867 case TemporalUnit::Day: 868 case TemporalUnit::Hour: 869 MOZ_CRASH("Unexpected temporal unit"); 870 } 871 872 // Step 6. (Not applicable in our implementation.) 873 874 // Step 7. 875 if (fractionalDigitCount == Precision::Auto()) { 876 return {Precision::Auto(), TemporalUnit::Nanosecond, Increment{1}}; 877 } 878 879 static constexpr Increment increments[] = { 880 Increment{1}, 881 Increment{10}, 882 Increment{100}, 883 }; 884 885 uint8_t digitCount = fractionalDigitCount.value(); 886 887 // Step 8. 888 if (digitCount == 0) { 889 return {Precision{0}, TemporalUnit::Second, Increment{1}}; 890 } 891 892 // Step 9. 893 if (digitCount <= 3) { 894 return {fractionalDigitCount, TemporalUnit::Millisecond, 895 increments[3 - digitCount]}; 896 } 897 898 // Step 10. 899 if (digitCount <= 6) { 900 return {fractionalDigitCount, TemporalUnit::Microsecond, 901 increments[6 - digitCount]}; 902 } 903 904 // Step 11. 905 MOZ_ASSERT(digitCount <= 9); 906 907 // Step 12. 908 return {fractionalDigitCount, TemporalUnit::Nanosecond, 909 increments[9 - digitCount]}; 910 } 911 912 /** 913 * GetTemporalOverflowOption ( normalizedOptions ) 914 */ 915 bool js::temporal::GetTemporalOverflowOption(JSContext* cx, 916 Handle<JSObject*> options, 917 TemporalOverflow* result) { 918 // Step 1. 919 Rooted<JSString*> overflow(cx); 920 if (!GetStringOption(cx, options, cx->names().overflow, &overflow)) { 921 return false; 922 } 923 924 // Caller should fill in the fallback. 925 if (!overflow) { 926 return true; 927 } 928 929 JSLinearString* linear = overflow->ensureLinear(cx); 930 if (!linear) { 931 return false; 932 } 933 934 if (StringEqualsLiteral(linear, "constrain")) { 935 *result = TemporalOverflow::Constrain; 936 } else if (StringEqualsLiteral(linear, "reject")) { 937 *result = TemporalOverflow::Reject; 938 } else { 939 if (auto chars = QuoteString(cx, linear, '"')) { 940 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 941 JSMSG_INVALID_OPTION_VALUE, "overflow", 942 chars.get()); 943 } 944 return false; 945 } 946 return true; 947 } 948 949 /** 950 * GetTemporalDisambiguationOption ( options ) 951 */ 952 bool js::temporal::GetTemporalDisambiguationOption( 953 JSContext* cx, Handle<JSObject*> options, 954 TemporalDisambiguation* disambiguation) { 955 // Step 1. (Not applicable) 956 957 // Step 2. 958 Rooted<JSString*> string(cx); 959 if (!GetStringOption(cx, options, cx->names().disambiguation, &string)) { 960 return false; 961 } 962 963 // Caller should fill in the fallback. 964 if (!string) { 965 return true; 966 } 967 968 JSLinearString* linear = string->ensureLinear(cx); 969 if (!linear) { 970 return false; 971 } 972 973 if (StringEqualsLiteral(linear, "compatible")) { 974 *disambiguation = TemporalDisambiguation::Compatible; 975 } else if (StringEqualsLiteral(linear, "earlier")) { 976 *disambiguation = TemporalDisambiguation::Earlier; 977 } else if (StringEqualsLiteral(linear, "later")) { 978 *disambiguation = TemporalDisambiguation::Later; 979 } else if (StringEqualsLiteral(linear, "reject")) { 980 *disambiguation = TemporalDisambiguation::Reject; 981 } else { 982 if (auto chars = QuoteString(cx, linear, '"')) { 983 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 984 JSMSG_INVALID_OPTION_VALUE, "disambiguation", 985 chars.get()); 986 } 987 return false; 988 } 989 return true; 990 } 991 992 /** 993 * GetTemporalOffsetOption ( options, fallback ) 994 */ 995 bool js::temporal::GetTemporalOffsetOption(JSContext* cx, 996 Handle<JSObject*> options, 997 TemporalOffset* offset) { 998 // Step 1. (Not applicable in our implementation.) 999 1000 // Step 2. 1001 Rooted<JSString*> string(cx); 1002 if (!GetStringOption(cx, options, cx->names().offset, &string)) { 1003 return false; 1004 } 1005 1006 // Caller should fill in the fallback. 1007 if (!string) { 1008 return true; 1009 } 1010 1011 JSLinearString* linear = string->ensureLinear(cx); 1012 if (!linear) { 1013 return false; 1014 } 1015 1016 if (StringEqualsLiteral(linear, "prefer")) { 1017 *offset = TemporalOffset::Prefer; 1018 } else if (StringEqualsLiteral(linear, "use")) { 1019 *offset = TemporalOffset::Use; 1020 } else if (StringEqualsLiteral(linear, "ignore")) { 1021 *offset = TemporalOffset::Ignore; 1022 } else if (StringEqualsLiteral(linear, "reject")) { 1023 *offset = TemporalOffset::Reject; 1024 } else { 1025 if (auto chars = QuoteString(cx, linear, '"')) { 1026 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 1027 JSMSG_INVALID_OPTION_VALUE, "offset", 1028 chars.get()); 1029 } 1030 return false; 1031 } 1032 return true; 1033 } 1034 1035 /** 1036 * GetTemporalShowTimeZoneNameOption ( normalizedOptions ) 1037 */ 1038 bool js::temporal::GetTemporalShowTimeZoneNameOption(JSContext* cx, 1039 Handle<JSObject*> options, 1040 ShowTimeZoneName* result) { 1041 // Step 1. 1042 Rooted<JSString*> timeZoneName(cx); 1043 if (!GetStringOption(cx, options, cx->names().timeZoneName, &timeZoneName)) { 1044 return false; 1045 } 1046 1047 // Caller should fill in the fallback. 1048 if (!timeZoneName) { 1049 return true; 1050 } 1051 1052 JSLinearString* linear = timeZoneName->ensureLinear(cx); 1053 if (!linear) { 1054 return false; 1055 } 1056 1057 if (StringEqualsLiteral(linear, "auto")) { 1058 *result = ShowTimeZoneName::Auto; 1059 } else if (StringEqualsLiteral(linear, "never")) { 1060 *result = ShowTimeZoneName::Never; 1061 } else if (StringEqualsLiteral(linear, "critical")) { 1062 *result = ShowTimeZoneName::Critical; 1063 } else { 1064 if (auto chars = QuoteString(cx, linear, '"')) { 1065 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 1066 JSMSG_INVALID_OPTION_VALUE, "timeZoneName", 1067 chars.get()); 1068 } 1069 return false; 1070 } 1071 return true; 1072 } 1073 1074 /** 1075 * GetTemporalShowOffsetOption ( normalizedOptions ) 1076 */ 1077 bool js::temporal::GetTemporalShowOffsetOption(JSContext* cx, 1078 Handle<JSObject*> options, 1079 ShowOffset* result) { 1080 // Step 1. 1081 Rooted<JSString*> offset(cx); 1082 if (!GetStringOption(cx, options, cx->names().offset, &offset)) { 1083 return false; 1084 } 1085 1086 // Caller should fill in the fallback. 1087 if (!offset) { 1088 return true; 1089 } 1090 1091 JSLinearString* linear = offset->ensureLinear(cx); 1092 if (!linear) { 1093 return false; 1094 } 1095 1096 if (StringEqualsLiteral(linear, "auto")) { 1097 *result = ShowOffset::Auto; 1098 } else if (StringEqualsLiteral(linear, "never")) { 1099 *result = ShowOffset::Never; 1100 } else { 1101 if (auto chars = QuoteString(cx, linear, '"')) { 1102 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 1103 JSMSG_INVALID_OPTION_VALUE, "offset", 1104 chars.get()); 1105 } 1106 return false; 1107 } 1108 return true; 1109 } 1110 1111 /** 1112 * GetDirectionOption ( options ) 1113 */ 1114 bool js::temporal::GetDirectionOption(JSContext* cx, 1115 Handle<JSString*> direction, 1116 Direction* result) { 1117 JSLinearString* linear = direction->ensureLinear(cx); 1118 if (!linear) { 1119 return false; 1120 } 1121 1122 if (StringEqualsLiteral(linear, "next")) { 1123 *result = Direction::Next; 1124 } else if (StringEqualsLiteral(linear, "previous")) { 1125 *result = Direction::Previous; 1126 } else { 1127 if (auto chars = QuoteString(cx, linear, '"')) { 1128 JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, 1129 JSMSG_INVALID_OPTION_VALUE, "direction", 1130 chars.get()); 1131 } 1132 return false; 1133 } 1134 return true; 1135 } 1136 1137 /** 1138 * GetDirectionOption ( options ) 1139 */ 1140 bool js::temporal::GetDirectionOption(JSContext* cx, Handle<JSObject*> options, 1141 Direction* result) { 1142 // Step 1. 1143 Rooted<JSString*> direction(cx); 1144 if (!GetStringOption(cx, options, cx->names().direction, &direction)) { 1145 return false; 1146 } 1147 1148 if (!direction) { 1149 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1150 JSMSG_TEMPORAL_MISSING_OPTION, "direction"); 1151 return false; 1152 } 1153 1154 return GetDirectionOption(cx, direction, result); 1155 } 1156 1157 template <typename T, typename... Ts> 1158 static JSObject* MaybeUnwrapIf(JSObject* object) { 1159 if (auto* unwrapped = object->maybeUnwrapIf<T>()) { 1160 return unwrapped; 1161 } 1162 if constexpr (sizeof...(Ts) > 0) { 1163 return MaybeUnwrapIf<Ts...>(object); 1164 } 1165 return nullptr; 1166 } 1167 1168 /** 1169 * IsPartialTemporalObject ( object ) 1170 */ 1171 bool js::temporal::ThrowIfTemporalLikeObject(JSContext* cx, 1172 Handle<JSObject*> object) { 1173 // Step 1. (Handled in caller) 1174 1175 // Step 2. 1176 if (auto* unwrapped = 1177 MaybeUnwrapIf<PlainDateObject, PlainDateTimeObject, 1178 PlainMonthDayObject, PlainTimeObject, 1179 PlainYearMonthObject, ZonedDateTimeObject>(object)) { 1180 Rooted<Value> value(cx, ObjectValue(*object)); 1181 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, value, 1182 nullptr, unwrapped->getClass()->name); 1183 return false; 1184 } 1185 1186 Rooted<Value> property(cx); 1187 1188 // Step 3. 1189 if (!GetProperty(cx, object, object, cx->names().calendar, &property)) { 1190 return false; 1191 } 1192 1193 // Step 4. 1194 if (!property.isUndefined()) { 1195 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1196 JSMSG_TEMPORAL_UNEXPECTED_PROPERTY, "calendar"); 1197 return false; 1198 } 1199 1200 // Step 5. 1201 if (!GetProperty(cx, object, object, cx->names().timeZone, &property)) { 1202 return false; 1203 } 1204 1205 // Step 6. 1206 if (!property.isUndefined()) { 1207 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1208 JSMSG_TEMPORAL_UNEXPECTED_PROPERTY, "timeZone"); 1209 return false; 1210 } 1211 1212 // Step 7. 1213 return true; 1214 } 1215 1216 /** 1217 * ToPositiveIntegerWithTruncation ( argument ) 1218 */ 1219 bool js::temporal::ToPositiveIntegerWithTruncation(JSContext* cx, 1220 Handle<Value> value, 1221 const char* name, 1222 double* result) { 1223 // Step 1. 1224 double number; 1225 if (!ToIntegerWithTruncation(cx, value, name, &number)) { 1226 return false; 1227 } 1228 1229 // Step 2. 1230 if (number <= 0) { 1231 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1232 JSMSG_TEMPORAL_INVALID_NUMBER, name); 1233 return false; 1234 } 1235 1236 // Step 3. 1237 *result = number; 1238 return true; 1239 } 1240 1241 /** 1242 * ToIntegerWithTruncation ( argument ) 1243 */ 1244 bool js::temporal::ToIntegerWithTruncation(JSContext* cx, Handle<Value> value, 1245 const char* name, double* result) { 1246 // Step 1. 1247 double number; 1248 if (!JS::ToNumber(cx, value, &number)) { 1249 return false; 1250 } 1251 1252 // Step 2. 1253 if (!std::isfinite(number)) { 1254 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1255 JSMSG_TEMPORAL_INVALID_INTEGER, name); 1256 return false; 1257 } 1258 1259 // Step 3. 1260 *result = std::trunc(number) + (+0.0); // Add zero to convert -0 to +0. 1261 return true; 1262 } 1263 1264 /** 1265 * GetDifferenceSettings ( operation, options, unitGroup, disallowedUnits, 1266 * fallbackSmallestUnit, smallestLargestDefaultUnit ) 1267 */ 1268 bool js::temporal::GetDifferenceSettings( 1269 JSContext* cx, TemporalDifference operation, Handle<JSObject*> options, 1270 TemporalUnitGroup unitGroup, TemporalUnit smallestAllowedUnit, 1271 TemporalUnit fallbackSmallestUnit, TemporalUnit smallestLargestDefaultUnit, 1272 DifferenceSettings* result) { 1273 MOZ_ASSERT(TemporalUnit::Auto < fallbackSmallestUnit && 1274 fallbackSmallestUnit <= smallestAllowedUnit); 1275 MOZ_ASSERT(TemporalUnit::Auto < smallestLargestDefaultUnit && 1276 smallestLargestDefaultUnit <= smallestAllowedUnit); 1277 1278 // Steps 1-2. 1279 auto largestUnit = TemporalUnit::Auto; 1280 if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::LargestUnit, 1281 &largestUnit)) { 1282 return false; 1283 } 1284 1285 // Step 3. 1286 auto roundingIncrement = Increment{1}; 1287 if (!GetRoundingIncrementOption(cx, options, &roundingIncrement)) { 1288 return false; 1289 } 1290 1291 // Step 4. 1292 auto roundingMode = TemporalRoundingMode::Trunc; 1293 if (!GetRoundingModeOption(cx, options, &roundingMode)) { 1294 return false; 1295 } 1296 1297 // Step 5. 1298 auto smallestUnit = fallbackSmallestUnit; 1299 if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::SmallestUnit, 1300 &smallestUnit)) { 1301 return false; 1302 } 1303 1304 // Step 6. 1305 if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::LargestUnit, largestUnit, 1306 unitGroup)) { 1307 return false; 1308 } 1309 1310 // Step 7. (Not applicable in our implementation.) 1311 MOZ_ASSERT(largestUnit != TemporalUnit::Unset); 1312 1313 // Step 8. 1314 if (largestUnit > smallestAllowedUnit) { 1315 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1316 JSMSG_TEMPORAL_INVALID_UNIT_OPTION, 1317 TemporalUnitToString(largestUnit), "largestUnit"); 1318 return false; 1319 } 1320 1321 // Step 9. 1322 if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::SmallestUnit, 1323 smallestUnit, unitGroup)) { 1324 return false; 1325 } 1326 1327 // Step 10. (Not applicable in our implementation.) 1328 MOZ_ASSERT(smallestUnit != TemporalUnit::Unset); 1329 1330 // Step 11. 1331 if (smallestUnit > smallestAllowedUnit) { 1332 JS_ReportErrorNumberASCII( 1333 cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INVALID_UNIT_OPTION, 1334 TemporalUnitToString(smallestUnit), "smallestUnit"); 1335 return false; 1336 } 1337 1338 // Step 12. (Inlined call to LargerOfTwoTemporalUnits) 1339 auto defaultLargestUnit = std::min(smallestLargestDefaultUnit, smallestUnit); 1340 1341 // Step 13. 1342 if (largestUnit == TemporalUnit::Auto) { 1343 largestUnit = defaultLargestUnit; 1344 } 1345 1346 // Step 14. 1347 if (largestUnit > smallestUnit) { 1348 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1349 JSMSG_TEMPORAL_INVALID_UNIT_RANGE); 1350 return false; 1351 } 1352 1353 // Steps 15-16. 1354 if (smallestUnit > TemporalUnit::Day) { 1355 // Step 15. 1356 auto maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit); 1357 1358 // Step 16. 1359 if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum, 1360 false)) { 1361 return false; 1362 } 1363 } 1364 1365 // Step 17. 1366 if (operation == TemporalDifference::Since) { 1367 roundingMode = NegateRoundingMode(roundingMode); 1368 } 1369 1370 // Step 18. 1371 *result = {smallestUnit, largestUnit, roundingMode, roundingIncrement}; 1372 return true; 1373 } 1374 1375 static JSObject* CreateTemporalObject(JSContext* cx, JSProtoKey key) { 1376 Rooted<JSObject*> proto(cx, &cx->global()->getObjectPrototype()); 1377 1378 // The |Temporal| object is just a plain object with some "static" data 1379 // properties and some constructor properties. 1380 return NewTenuredObjectWithGivenProto<TemporalObject>(cx, proto); 1381 } 1382 1383 /** 1384 * Initializes the Temporal Object and its standard built-in properties. 1385 */ 1386 static bool TemporalClassFinish(JSContext* cx, Handle<JSObject*> temporal, 1387 Handle<JSObject*> proto) { 1388 Rooted<PropertyKey> ctorId(cx); 1389 Rooted<Value> ctorValue(cx); 1390 auto defineProperty = [&](JSProtoKey protoKey, Handle<PropertyName*> name) { 1391 JSObject* ctor = GlobalObject::getOrCreateConstructor(cx, protoKey); 1392 if (!ctor) { 1393 return false; 1394 } 1395 1396 ctorId = NameToId(name); 1397 ctorValue.setObject(*ctor); 1398 return DefineDataProperty(cx, temporal, ctorId, ctorValue, 0); 1399 }; 1400 1401 // Add the constructor properties. 1402 for (const auto& protoKey : { 1403 JSProto_Duration, 1404 JSProto_Instant, 1405 JSProto_PlainDate, 1406 JSProto_PlainDateTime, 1407 JSProto_PlainMonthDay, 1408 JSProto_PlainTime, 1409 JSProto_PlainYearMonth, 1410 JSProto_ZonedDateTime, 1411 }) { 1412 if (!defineProperty(protoKey, ClassName(protoKey, cx))) { 1413 return false; 1414 } 1415 } 1416 1417 // ClassName(JSProto_TemporalNow) returns "TemporalNow", so we need to handle 1418 // it separately. 1419 if (!defineProperty(JSProto_TemporalNow, cx->names().Now)) { 1420 return false; 1421 } 1422 1423 return true; 1424 } 1425 1426 const JSClass TemporalObject::class_ = { 1427 "Temporal", 1428 JSCLASS_HAS_CACHED_PROTO(JSProto_Temporal), 1429 JS_NULL_CLASS_OPS, 1430 &TemporalObject::classSpec_, 1431 }; 1432 1433 static const JSPropertySpec Temporal_properties[] = { 1434 JS_STRING_SYM_PS(toStringTag, "Temporal", JSPROP_READONLY), 1435 JS_PS_END, 1436 }; 1437 1438 const ClassSpec TemporalObject::classSpec_ = { 1439 CreateTemporalObject, nullptr, nullptr, 1440 Temporal_properties, nullptr, nullptr, 1441 TemporalClassFinish, 1442 };