Instant.cpp (37438B)
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/Instant.h" 8 9 #include "mozilla/Assertions.h" 10 #include "mozilla/Casting.h" 11 12 #include <algorithm> 13 #include <array> 14 #include <cstdlib> 15 #include <stddef.h> 16 #include <stdint.h> 17 18 #include "jsnum.h" 19 #include "jspubtd.h" 20 #include "NamespaceImports.h" 21 22 #include "builtin/intl/DateTimeFormat.h" 23 #include "builtin/temporal/Calendar.h" 24 #include "builtin/temporal/Duration.h" 25 #include "builtin/temporal/Int96.h" 26 #include "builtin/temporal/PlainDateTime.h" 27 #include "builtin/temporal/Temporal.h" 28 #include "builtin/temporal/TemporalParser.h" 29 #include "builtin/temporal/TemporalRoundingMode.h" 30 #include "builtin/temporal/TemporalTypes.h" 31 #include "builtin/temporal/TemporalUnit.h" 32 #include "builtin/temporal/TimeZone.h" 33 #include "builtin/temporal/ToString.h" 34 #include "builtin/temporal/ZonedDateTime.h" 35 #include "gc/AllocKind.h" 36 #include "gc/Barrier.h" 37 #include "js/CallArgs.h" 38 #include "js/CallNonGenericMethod.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/PropertyDescriptor.h" 44 #include "js/PropertySpec.h" 45 #include "js/RootingAPI.h" 46 #include "js/TypeDecls.h" 47 #include "js/Value.h" 48 #include "vm/BigIntType.h" 49 #include "vm/BytecodeUtil.h" 50 #include "vm/GlobalObject.h" 51 #include "vm/Int128.h" 52 #include "vm/JSAtomState.h" 53 #include "vm/JSContext.h" 54 #include "vm/JSObject.h" 55 #include "vm/PlainObject.h" 56 57 #include "vm/JSObject-inl.h" 58 #include "vm/NativeObject-inl.h" 59 #include "vm/ObjectOperations-inl.h" 60 61 using namespace js; 62 using namespace js::temporal; 63 64 static inline bool IsInstant(Handle<Value> v) { 65 return v.isObject() && v.toObject().is<InstantObject>(); 66 } 67 68 /** 69 * Check if the absolute value is less-or-equal to the given limit. 70 */ 71 template <const auto& digits> 72 static bool AbsoluteValueIsLessOrEqual(const BigInt* bigInt) { 73 size_t length = bigInt->digitLength(); 74 75 // Fewer digits than the limit, so definitely in range. 76 if (length < std::size(digits)) { 77 return true; 78 } 79 80 // More digits than the limit, so definitely out of range. 81 if (length > std::size(digits)) { 82 return false; 83 } 84 85 // Compare each digit when the input has the same number of digits. 86 size_t index = std::size(digits); 87 for (auto digit : digits) { 88 auto d = bigInt->digit(--index); 89 if (d < digit) { 90 return true; 91 } 92 if (d > digit) { 93 return false; 94 } 95 } 96 return true; 97 } 98 99 static constexpr auto NanosecondsMaxInstant() { 100 static_assert(BigInt::DigitBits == 64 || BigInt::DigitBits == 32); 101 102 // ±8.64 × 10^21 is the nanoseconds from epoch limit. 103 // 8.64 × 10^21 is 86_40000_00000_00000_00000 or 0x1d4_60162f51_6f000000. 104 // Return the BigInt digits of that number for fast BigInt comparisons. 105 if constexpr (BigInt::DigitBits == 64) { 106 return std::array{ 107 BigInt::Digit(0x1d4), 108 BigInt::Digit(0x6016'2f51'6f00'0000), 109 }; 110 } else { 111 return std::array{ 112 BigInt::Digit(0x1d4), 113 BigInt::Digit(0x6016'2f51), 114 BigInt::Digit(0x6f00'0000), 115 }; 116 } 117 } 118 119 // Can't be defined in IsValidEpochNanoseconds when compiling with GCC 8. 120 static constexpr auto EpochLimitBigIntDigits = NanosecondsMaxInstant(); 121 122 /** 123 * IsValidEpochNanoseconds ( epochNanoseconds ) 124 */ 125 bool js::temporal::IsValidEpochNanoseconds(const BigInt* epochNanoseconds) { 126 // Steps 1-2. 127 return AbsoluteValueIsLessOrEqual<EpochLimitBigIntDigits>(epochNanoseconds); 128 } 129 130 static bool IsValidEpochMilliseconds(double epochMilliseconds) { 131 MOZ_ASSERT(IsInteger(epochMilliseconds)); 132 133 constexpr int64_t MillisecondsMaxInstant = 134 EpochNanoseconds::max().toMilliseconds(); 135 return std::abs(epochMilliseconds) <= double(MillisecondsMaxInstant); 136 } 137 138 /** 139 * IsValidEpochNanoseconds ( epochNanoseconds ) 140 */ 141 bool js::temporal::IsValidEpochNanoseconds( 142 const EpochNanoseconds& epochNanoseconds) { 143 MOZ_ASSERT(0 <= epochNanoseconds.nanoseconds && 144 epochNanoseconds.nanoseconds <= 999'999'999); 145 146 // Steps 1-2. 147 return EpochNanoseconds::min() <= epochNanoseconds && 148 epochNanoseconds <= EpochNanoseconds::max(); 149 } 150 151 #ifdef DEBUG 152 /** 153 * Validates a nanoseconds amount is at most as large as the difference 154 * between two valid epoch nanoseconds. 155 */ 156 bool js::temporal::IsValidEpochDuration(const EpochDuration& duration) { 157 MOZ_ASSERT(0 <= duration.nanoseconds && duration.nanoseconds <= 999'999'999); 158 159 // Steps 1-2. 160 return EpochDuration::min() <= duration && duration <= EpochDuration::max(); 161 } 162 #endif 163 164 /** 165 * Return the BigInt as a 96-bit integer. The BigInt digits must not consist of 166 * more than 96-bits. 167 */ 168 static Int96 ToInt96(const BigInt* ns) { 169 static_assert(BigInt::DigitBits == 64 || BigInt::DigitBits == 32); 170 171 auto digits = ns->digits(); 172 if constexpr (BigInt::DigitBits == 64) { 173 BigInt::Digit x = 0, y = 0; 174 switch (digits.size()) { 175 case 2: 176 y = digits[1]; 177 [[fallthrough]]; 178 case 1: 179 x = digits[0]; 180 [[fallthrough]]; 181 case 0: 182 break; 183 default: 184 MOZ_ASSERT_UNREACHABLE("unexpected digit length"); 185 } 186 return Int96{ 187 Int96::Digits{Int96::Digit(x), Int96::Digit(x >> 32), Int96::Digit(y)}, 188 ns->isNegative()}; 189 } else { 190 BigInt::Digit x = 0, y = 0, z = 0; 191 switch (digits.size()) { 192 case 3: 193 z = digits[2]; 194 [[fallthrough]]; 195 case 2: 196 y = digits[1]; 197 [[fallthrough]]; 198 case 1: 199 x = digits[0]; 200 [[fallthrough]]; 201 case 0: 202 break; 203 default: 204 MOZ_ASSERT_UNREACHABLE("unexpected digit length"); 205 } 206 return Int96{ 207 Int96::Digits{Int96::Digit(x), Int96::Digit(y), Int96::Digit(z)}, 208 ns->isNegative()}; 209 } 210 } 211 212 EpochNanoseconds js::temporal::ToEpochNanoseconds( 213 const BigInt* epochNanoseconds) { 214 MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds)); 215 216 auto [seconds, nanos] = 217 ToInt96(epochNanoseconds) / ToNanoseconds(TemporalUnit::Second); 218 return {{seconds, nanos}}; 219 } 220 221 static BigInt* CreateBigInt(JSContext* cx, 222 const std::array<uint32_t, 3>& digits, 223 bool negative) { 224 static_assert(BigInt::DigitBits == 64 || BigInt::DigitBits == 32); 225 226 if constexpr (BigInt::DigitBits == 64) { 227 uint64_t x = (uint64_t(digits[1]) << 32) | digits[0]; 228 uint64_t y = digits[2]; 229 230 size_t length = y ? 2 : x ? 1 : 0; 231 auto* result = BigInt::createUninitialized(cx, length, negative); 232 if (!result) { 233 return nullptr; 234 } 235 if (y) { 236 result->setDigit(1, y); 237 } 238 if (x) { 239 result->setDigit(0, x); 240 } 241 return result; 242 } else { 243 size_t length = digits[2] ? 3 : digits[1] ? 2 : digits[0] ? 1 : 0; 244 auto* result = BigInt::createUninitialized(cx, length, negative); 245 if (!result) { 246 return nullptr; 247 } 248 while (length--) { 249 result->setDigit(length, digits[length]); 250 } 251 return result; 252 } 253 } 254 255 static auto ToBigIntDigits(uint64_t seconds, uint32_t nanoseconds) { 256 // Multiplies two uint32_t values and returns the lower 32-bits. The higher 257 // 32-bits are stored in |high|. 258 auto digitMul = [](uint32_t a, uint32_t b, uint32_t* high) { 259 uint64_t result = static_cast<uint64_t>(a) * static_cast<uint64_t>(b); 260 *high = result >> 32; 261 return static_cast<uint32_t>(result); 262 }; 263 264 // Adds two uint32_t values and returns the result. Overflow is added to the 265 // out-param |carry|. 266 auto digitAdd = [](uint32_t a, uint32_t b, uint32_t* carry) { 267 uint32_t result = a + b; 268 *carry += static_cast<uint32_t>(result < a); 269 return result; 270 }; 271 272 constexpr uint32_t secToNanos = ToNanoseconds(TemporalUnit::Second); 273 274 // uint32_t digits stored in the same order as BigInt digits, i.e. the least 275 // significant digit is stored at index zero. 276 std::array<uint32_t, 2> multiplicand = {uint32_t(seconds), 277 uint32_t(seconds >> 32)}; 278 std::array<uint32_t, 3> accumulator = {nanoseconds, 0, 0}; 279 280 // This code follows the implementation of |BigInt::multiplyAccumulate()|. 281 282 uint32_t carry = 0; 283 { 284 uint32_t high = 0; 285 uint32_t low = digitMul(secToNanos, multiplicand[0], &high); 286 287 uint32_t newCarry = 0; 288 accumulator[0] = digitAdd(accumulator[0], low, &newCarry); 289 accumulator[1] = digitAdd(high, newCarry, &carry); 290 } 291 { 292 uint32_t high = 0; 293 uint32_t low = digitMul(secToNanos, multiplicand[1], &high); 294 295 uint32_t newCarry = 0; 296 accumulator[1] = digitAdd(accumulator[1], low, &carry); 297 accumulator[2] = digitAdd(high, carry, &newCarry); 298 MOZ_ASSERT(newCarry == 0); 299 } 300 301 return accumulator; 302 } 303 304 BigInt* js::temporal::ToBigInt(JSContext* cx, 305 const EpochNanoseconds& epochNanoseconds) { 306 MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds)); 307 308 auto [seconds, nanoseconds] = epochNanoseconds.abs(); 309 auto digits = ToBigIntDigits(uint64_t(seconds), uint32_t(nanoseconds)); 310 return CreateBigInt(cx, digits, epochNanoseconds.seconds < 0); 311 } 312 313 /** 314 * GetUTCEpochNanoseconds ( isoDateTime ) 315 */ 316 EpochNanoseconds js::temporal::GetUTCEpochNanoseconds( 317 const ISODateTime& isoDateTime) { 318 MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime)); 319 320 const auto& [date, time] = isoDateTime; 321 322 // Steps 1-4. 323 int64_t ms = MakeDate(isoDateTime); 324 325 // Propagate the input range to the compiler. 326 int32_t nanos = 327 std::clamp(time.microsecond * 1'000 + time.nanosecond, 0, 999'999); 328 329 // Step 5. 330 // 331 // The returned epoch nanoseconds value can exceed ±8.64 × 10^21, because it 332 // includes the local time zone offset. 333 return EpochNanoseconds::fromMilliseconds(ms) + EpochDuration{{0, nanos}}; 334 } 335 336 /** 337 * CompareEpochNanoseconds ( epochNanosecondsOne, epochNanosecondsTwo ) 338 */ 339 static int32_t CompareEpochNanoseconds( 340 const EpochNanoseconds& epochNanosecondsOne, 341 const EpochNanoseconds& epochNanosecondsTwo) { 342 // Step 1. 343 if (epochNanosecondsOne > epochNanosecondsTwo) { 344 return 1; 345 } 346 347 // Step 2. 348 if (epochNanosecondsOne < epochNanosecondsTwo) { 349 return -1; 350 } 351 352 // Step 3. 353 return 0; 354 } 355 356 /** 357 * CreateTemporalInstant ( epochNanoseconds [ , newTarget ] ) 358 */ 359 InstantObject* js::temporal::CreateTemporalInstant( 360 JSContext* cx, const EpochNanoseconds& epochNanoseconds) { 361 // Step 1. 362 MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds)); 363 364 // Steps 2-3. 365 auto* object = NewBuiltinClassInstance<InstantObject>(cx); 366 if (!object) { 367 return nullptr; 368 } 369 370 // Step 4. 371 object->initFixedSlot(InstantObject::SECONDS_SLOT, 372 NumberValue(epochNanoseconds.seconds)); 373 object->initFixedSlot(InstantObject::NANOSECONDS_SLOT, 374 Int32Value(epochNanoseconds.nanoseconds)); 375 376 // Step 5. 377 return object; 378 } 379 380 /** 381 * CreateTemporalInstant ( epochNanoseconds [ , newTarget ] ) 382 */ 383 static InstantObject* CreateTemporalInstant(JSContext* cx, const CallArgs& args, 384 Handle<BigInt*> epochNanoseconds) { 385 // Step 1. 386 MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds)); 387 388 // Steps 2-3. 389 Rooted<JSObject*> proto(cx); 390 if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Instant, &proto)) { 391 return nullptr; 392 } 393 394 auto* object = NewObjectWithClassProto<InstantObject>(cx, proto); 395 if (!object) { 396 return nullptr; 397 } 398 399 // Step 4. 400 auto epochNs = ToEpochNanoseconds(epochNanoseconds); 401 object->initFixedSlot(InstantObject::SECONDS_SLOT, 402 NumberValue(epochNs.seconds)); 403 object->initFixedSlot(InstantObject::NANOSECONDS_SLOT, 404 Int32Value(epochNs.nanoseconds)); 405 406 // Step 5. 407 return object; 408 } 409 410 /** 411 * ToTemporalInstant ( item ) 412 */ 413 static bool ToTemporalInstant(JSContext* cx, Handle<Value> item, 414 EpochNanoseconds* result) { 415 // Step 1. 416 Rooted<Value> primitiveValue(cx, item); 417 if (item.isObject()) { 418 JSObject* itemObj = &item.toObject(); 419 420 // Step 1.a. 421 if (auto* instant = itemObj->maybeUnwrapIf<InstantObject>()) { 422 *result = instant->epochNanoseconds(); 423 return true; 424 } 425 if (auto* zonedDateTime = itemObj->maybeUnwrapIf<ZonedDateTimeObject>()) { 426 *result = zonedDateTime->epochNanoseconds(); 427 return true; 428 } 429 430 // Steps 1.b-c. 431 if (!ToPrimitive(cx, JSTYPE_STRING, &primitiveValue)) { 432 return false; 433 } 434 } 435 436 // Step 2. 437 if (!primitiveValue.isString()) { 438 // The value is always on the stack, so JSDVG_SEARCH_STACK can be used for 439 // better error reporting. 440 ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, 441 primitiveValue, nullptr, "not a string"); 442 return false; 443 } 444 Rooted<JSString*> string(cx, primitiveValue.toString()); 445 446 // Steps 3-7. 447 ISODateTime dateTime; 448 int64_t offset; 449 if (!ParseTemporalInstantString(cx, string, &dateTime, &offset)) { 450 return false; 451 } 452 MOZ_ASSERT(std::abs(offset) < ToNanoseconds(TemporalUnit::Day)); 453 454 // Steps 8-9. 455 // 456 // Modified to call ISODateTimeWithinLimits instead of BalanceISODateTime. 457 if (!ISODateTimeWithinLimits(dateTime)) { 458 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 459 JSMSG_TEMPORAL_INSTANT_INVALID); 460 return false; 461 } 462 463 // Step 10. 464 auto epochNanoseconds = 465 GetUTCEpochNanoseconds(dateTime) - EpochDuration::fromNanoseconds(offset); 466 467 // Step 11. 468 if (!IsValidEpochNanoseconds(epochNanoseconds)) { 469 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 470 JSMSG_TEMPORAL_INSTANT_INVALID); 471 return false; 472 } 473 474 // Step 12. 475 *result = epochNanoseconds; 476 return true; 477 } 478 479 /** 480 * AddInstant ( epochNanoseconds, hours, minutes, seconds, milliseconds, 481 * microseconds, nanoseconds ) 482 */ 483 bool js::temporal::AddInstant(JSContext* cx, 484 const EpochNanoseconds& epochNanoseconds, 485 const TimeDuration& duration, 486 EpochNanoseconds* result) { 487 MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds)); 488 MOZ_ASSERT(IsValidTimeDuration(duration)); 489 490 // Step 1. (Inlined AddTimeDurationToEpochNanoseconds) 491 auto r = epochNanoseconds + duration.to<EpochDuration>(); 492 493 // Step 2. 494 if (!IsValidEpochNanoseconds(r)) { 495 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 496 JSMSG_TEMPORAL_INSTANT_INVALID); 497 return false; 498 } 499 500 // Step 3. 501 *result = r; 502 return true; 503 } 504 505 /** 506 * DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, roundingMode ) 507 */ 508 TimeDuration js::temporal::DifferenceInstant( 509 const EpochNanoseconds& ns1, const EpochNanoseconds& ns2, 510 Increment roundingIncrement, TemporalUnit smallestUnit, 511 TemporalRoundingMode roundingMode) { 512 MOZ_ASSERT(IsValidEpochNanoseconds(ns1)); 513 MOZ_ASSERT(IsValidEpochNanoseconds(ns2)); 514 MOZ_ASSERT(smallestUnit > TemporalUnit::Day); 515 MOZ_ASSERT(roundingIncrement <= 516 MaximumTemporalDurationRoundingIncrement(smallestUnit)); 517 518 // Step 1. 519 auto diff = TimeDurationFromEpochNanosecondsDifference(ns2, ns1); 520 MOZ_ASSERT(IsValidEpochDuration(diff.to<EpochDuration>())); 521 522 // Steps 2-3. 523 return RoundTimeDuration(diff, roundingIncrement, smallestUnit, roundingMode); 524 } 525 526 /** 527 * RoundNumberToIncrementAsIfPositive ( x, increment, roundingMode ) 528 */ 529 static EpochNanoseconds RoundNumberToIncrementAsIfPositive( 530 const EpochNanoseconds& x, int64_t increment, 531 TemporalRoundingMode roundingMode) { 532 MOZ_ASSERT(IsValidEpochNanoseconds(x)); 533 MOZ_ASSERT(increment > 0); 534 MOZ_ASSERT(increment <= ToNanoseconds(TemporalUnit::Day)); 535 536 // This operation is equivalent to adjusting the rounding mode through 537 // |ToPositiveRoundingMode| and then calling |RoundNumberToIncrement|. 538 auto rounded = RoundNumberToIncrement(x.toNanoseconds(), Int128{increment}, 539 ToPositiveRoundingMode(roundingMode)); 540 return EpochNanoseconds::fromNanoseconds(rounded); 541 } 542 543 /** 544 * RoundTemporalInstant ( ns, increment, unit, roundingMode ) 545 */ 546 EpochNanoseconds js::temporal::RoundTemporalInstant( 547 const EpochNanoseconds& ns, Increment increment, TemporalUnit unit, 548 TemporalRoundingMode roundingMode) { 549 MOZ_ASSERT(IsValidEpochNanoseconds(ns)); 550 MOZ_ASSERT(increment >= Increment::min()); 551 MOZ_ASSERT(uint64_t(increment.value()) <= ToNanoseconds(TemporalUnit::Day)); 552 MOZ_ASSERT(unit > TemporalUnit::Day); 553 554 // Step 1. 555 int64_t unitLength = ToNanoseconds(unit); 556 557 // Step 2. 558 int64_t incrementNs = increment.value() * unitLength; 559 MOZ_ASSERT(incrementNs <= ToNanoseconds(TemporalUnit::Day), 560 "incrementNs doesn't overflow epoch nanoseconds resolution"); 561 562 // Step 3. 563 return RoundNumberToIncrementAsIfPositive(ns, incrementNs, roundingMode); 564 } 565 566 /** 567 * DifferenceTemporalInstant ( operation, instant, other, options ) 568 */ 569 static bool DifferenceTemporalInstant(JSContext* cx, 570 TemporalDifference operation, 571 const CallArgs& args) { 572 auto epochNs = args.thisv().toObject().as<InstantObject>().epochNanoseconds(); 573 574 // Step 1. 575 EpochNanoseconds other; 576 if (!ToTemporalInstant(cx, args.get(0), &other)) { 577 return false; 578 } 579 580 // Steps 2-3. 581 DifferenceSettings settings; 582 if (args.hasDefined(1)) { 583 // Step 2. 584 Rooted<JSObject*> options( 585 cx, RequireObjectArg(cx, "options", ToName(operation), args[1])); 586 if (!options) { 587 return false; 588 } 589 590 // Step 3. 591 if (!GetDifferenceSettings(cx, operation, options, TemporalUnitGroup::Time, 592 TemporalUnit::Nanosecond, TemporalUnit::Second, 593 &settings)) { 594 return false; 595 } 596 } else { 597 // Steps 2-3. 598 settings = { 599 TemporalUnit::Nanosecond, 600 TemporalUnit::Second, 601 TemporalRoundingMode::Trunc, 602 Increment{1}, 603 }; 604 } 605 606 // Steps 4. 607 auto timeDuration = 608 DifferenceInstant(epochNs, other, settings.roundingIncrement, 609 settings.smallestUnit, settings.roundingMode); 610 611 // Step 5. 612 Duration duration; 613 if (!TemporalDurationFromInternal(cx, timeDuration, settings.largestUnit, 614 &duration)) { 615 return false; 616 } 617 618 // Step 6. 619 if (operation == TemporalDifference::Since) { 620 duration = duration.negate(); 621 } 622 623 // Step 7. 624 auto* obj = CreateTemporalDuration(cx, duration); 625 if (!obj) { 626 return false; 627 } 628 629 args.rval().setObject(*obj); 630 return true; 631 } 632 633 /** 634 * AddDurationToInstant ( operation, instant, temporalDurationLike ) 635 */ 636 static bool AddDurationToInstant(JSContext* cx, TemporalAddDuration operation, 637 const CallArgs& args) { 638 auto* instant = &args.thisv().toObject().as<InstantObject>(); 639 auto epochNanoseconds = instant->epochNanoseconds(); 640 641 // Step 1. 642 Duration duration; 643 if (!ToTemporalDuration(cx, args.get(0), &duration)) { 644 return false; 645 } 646 647 // Step 2. 648 if (operation == TemporalAddDuration::Subtract) { 649 duration = duration.negate(); 650 } 651 652 // Steps 3-4. (Inlined DefaultTemporalLargestUnit and TemporalUnitCategory.) 653 if (duration.years != 0 || duration.months != 0 || duration.weeks != 0 || 654 duration.days != 0) { 655 const char* part = duration.years != 0 ? "years" 656 : duration.months != 0 ? "months" 657 : duration.weeks != 0 ? "weeks" 658 : "days"; 659 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 660 JSMSG_TEMPORAL_INSTANT_BAD_DURATION, part); 661 return false; 662 } 663 664 // Step 5. (Inlined ToInternalDurationRecordWith24HourDays.) 665 auto timeDuration = TimeDurationFromComponents(duration); 666 667 // Step 6. 668 EpochNanoseconds ns; 669 if (!AddInstant(cx, epochNanoseconds, timeDuration, &ns)) { 670 return false; 671 } 672 673 // Step 7. 674 auto* result = CreateTemporalInstant(cx, ns); 675 if (!result) { 676 return false; 677 } 678 679 args.rval().setObject(*result); 680 return true; 681 } 682 683 /** 684 * Temporal.Instant ( epochNanoseconds ) 685 */ 686 static bool InstantConstructor(JSContext* cx, unsigned argc, Value* vp) { 687 CallArgs args = CallArgsFromVp(argc, vp); 688 689 // Step 1. 690 if (!ThrowIfNotConstructing(cx, args, "Temporal.Instant")) { 691 return false; 692 } 693 694 // Step 2. 695 Rooted<BigInt*> epochNanoseconds(cx, js::ToBigInt(cx, args.get(0))); 696 if (!epochNanoseconds) { 697 return false; 698 } 699 700 // Step 3. 701 if (!IsValidEpochNanoseconds(epochNanoseconds)) { 702 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 703 JSMSG_TEMPORAL_INSTANT_INVALID); 704 return false; 705 } 706 707 // Step 4. 708 auto* result = CreateTemporalInstant(cx, args, epochNanoseconds); 709 if (!result) { 710 return false; 711 } 712 713 args.rval().setObject(*result); 714 return true; 715 } 716 717 /** 718 * Temporal.Instant.from ( item ) 719 */ 720 static bool Instant_from(JSContext* cx, unsigned argc, Value* vp) { 721 CallArgs args = CallArgsFromVp(argc, vp); 722 723 // Step 1. 724 EpochNanoseconds epochNs; 725 if (!ToTemporalInstant(cx, args.get(0), &epochNs)) { 726 return false; 727 } 728 729 auto* result = CreateTemporalInstant(cx, epochNs); 730 if (!result) { 731 return false; 732 } 733 args.rval().setObject(*result); 734 return true; 735 } 736 737 /** 738 * Temporal.Instant.fromEpochMilliseconds ( epochMilliseconds ) 739 */ 740 static bool Instant_fromEpochMilliseconds(JSContext* cx, unsigned argc, 741 Value* vp) { 742 CallArgs args = CallArgsFromVp(argc, vp); 743 744 // Step 1. 745 double epochMilliseconds; 746 if (!JS::ToNumber(cx, args.get(0), &epochMilliseconds)) { 747 return false; 748 } 749 750 // Step 2. 751 // 752 // NumberToBigInt throws a RangeError for non-integral numbers. 753 if (!IsInteger(epochMilliseconds)) { 754 ToCStringBuf cbuf; 755 const char* str = NumberToCString(&cbuf, epochMilliseconds); 756 757 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 758 JSMSG_TEMPORAL_INSTANT_NONINTEGER, str); 759 return false; 760 } 761 762 // Step 3. (Not applicable) 763 764 // Step 4. 765 if (!IsValidEpochMilliseconds(epochMilliseconds)) { 766 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 767 JSMSG_TEMPORAL_INSTANT_INVALID); 768 return false; 769 } 770 771 // Step 5. 772 int64_t milliseconds = mozilla::AssertedCast<int64_t>(epochMilliseconds); 773 auto* result = CreateTemporalInstant( 774 cx, EpochNanoseconds::fromMilliseconds(milliseconds)); 775 if (!result) { 776 return false; 777 } 778 args.rval().setObject(*result); 779 return true; 780 } 781 782 /** 783 * Temporal.Instant.fromEpochNanoseconds ( epochNanoseconds ) 784 */ 785 static bool Instant_fromEpochNanoseconds(JSContext* cx, unsigned argc, 786 Value* vp) { 787 CallArgs args = CallArgsFromVp(argc, vp); 788 789 // Step 1. 790 Rooted<BigInt*> epochNanoseconds(cx, js::ToBigInt(cx, args.get(0))); 791 if (!epochNanoseconds) { 792 return false; 793 } 794 795 // Step 2. 796 if (!IsValidEpochNanoseconds(epochNanoseconds)) { 797 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 798 JSMSG_TEMPORAL_INSTANT_INVALID); 799 return false; 800 } 801 802 // Step 3. 803 auto* result = 804 CreateTemporalInstant(cx, ToEpochNanoseconds(epochNanoseconds)); 805 if (!result) { 806 return false; 807 } 808 args.rval().setObject(*result); 809 return true; 810 } 811 812 /** 813 * Temporal.Instant.compare ( one, two ) 814 */ 815 static bool Instant_compare(JSContext* cx, unsigned argc, Value* vp) { 816 CallArgs args = CallArgsFromVp(argc, vp); 817 818 // Step 1. 819 EpochNanoseconds one; 820 if (!ToTemporalInstant(cx, args.get(0), &one)) { 821 return false; 822 } 823 824 // Step 2. 825 EpochNanoseconds two; 826 if (!ToTemporalInstant(cx, args.get(1), &two)) { 827 return false; 828 } 829 830 // Step 3. 831 args.rval().setInt32(CompareEpochNanoseconds(one, two)); 832 return true; 833 } 834 835 /** 836 * get Temporal.Instant.prototype.epochMilliseconds 837 */ 838 static bool Instant_epochMilliseconds(JSContext* cx, const CallArgs& args) { 839 // Step 3. 840 auto epochNs = args.thisv().toObject().as<InstantObject>().epochNanoseconds(); 841 842 // Step 4-5. 843 args.rval().setNumber(epochNs.floorToMilliseconds()); 844 return true; 845 } 846 847 /** 848 * get Temporal.Instant.prototype.epochMilliseconds 849 */ 850 static bool Instant_epochMilliseconds(JSContext* cx, unsigned argc, Value* vp) { 851 // Steps 1-2. 852 CallArgs args = CallArgsFromVp(argc, vp); 853 return CallNonGenericMethod<IsInstant, Instant_epochMilliseconds>(cx, args); 854 } 855 856 /** 857 * get Temporal.Instant.prototype.epochNanoseconds 858 */ 859 static bool Instant_epochNanoseconds(JSContext* cx, const CallArgs& args) { 860 // Step 3. 861 auto epochNs = args.thisv().toObject().as<InstantObject>().epochNanoseconds(); 862 auto* nanoseconds = ToBigInt(cx, epochNs); 863 if (!nanoseconds) { 864 return false; 865 } 866 867 args.rval().setBigInt(nanoseconds); 868 return true; 869 } 870 871 /** 872 * get Temporal.Instant.prototype.epochNanoseconds 873 */ 874 static bool Instant_epochNanoseconds(JSContext* cx, unsigned argc, Value* vp) { 875 // Steps 1-2. 876 CallArgs args = CallArgsFromVp(argc, vp); 877 return CallNonGenericMethod<IsInstant, Instant_epochNanoseconds>(cx, args); 878 } 879 880 /** 881 * Temporal.Instant.prototype.add ( temporalDurationLike ) 882 */ 883 static bool Instant_add(JSContext* cx, const CallArgs& args) { 884 // Step 3. 885 return AddDurationToInstant(cx, TemporalAddDuration::Add, args); 886 } 887 888 /** 889 * Temporal.Instant.prototype.add ( temporalDurationLike ) 890 */ 891 static bool Instant_add(JSContext* cx, unsigned argc, Value* vp) { 892 // Steps 1-2. 893 CallArgs args = CallArgsFromVp(argc, vp); 894 return CallNonGenericMethod<IsInstant, Instant_add>(cx, args); 895 } 896 897 /** 898 * Temporal.Instant.prototype.subtract ( temporalDurationLike ) 899 */ 900 static bool Instant_subtract(JSContext* cx, const CallArgs& args) { 901 // Step 3. 902 return AddDurationToInstant(cx, TemporalAddDuration::Subtract, args); 903 } 904 905 /** 906 * Temporal.Instant.prototype.subtract ( temporalDurationLike ) 907 */ 908 static bool Instant_subtract(JSContext* cx, unsigned argc, Value* vp) { 909 // Steps 1-2. 910 CallArgs args = CallArgsFromVp(argc, vp); 911 return CallNonGenericMethod<IsInstant, Instant_subtract>(cx, args); 912 } 913 914 /** 915 * Temporal.Instant.prototype.until ( other [ , options ] ) 916 */ 917 static bool Instant_until(JSContext* cx, const CallArgs& args) { 918 // Step 3. 919 return DifferenceTemporalInstant(cx, TemporalDifference::Until, args); 920 } 921 922 /** 923 * Temporal.Instant.prototype.until ( other [ , options ] ) 924 */ 925 static bool Instant_until(JSContext* cx, unsigned argc, Value* vp) { 926 // Steps 1-2. 927 CallArgs args = CallArgsFromVp(argc, vp); 928 return CallNonGenericMethod<IsInstant, Instant_until>(cx, args); 929 } 930 931 /** 932 * Temporal.Instant.prototype.since ( other [ , options ] ) 933 */ 934 static bool Instant_since(JSContext* cx, const CallArgs& args) { 935 // Step 3. 936 return DifferenceTemporalInstant(cx, TemporalDifference::Since, args); 937 } 938 939 /** 940 * Temporal.Instant.prototype.since ( other [ , options ] ) 941 */ 942 static bool Instant_since(JSContext* cx, unsigned argc, Value* vp) { 943 // Steps 1-2. 944 CallArgs args = CallArgsFromVp(argc, vp); 945 return CallNonGenericMethod<IsInstant, Instant_since>(cx, args); 946 } 947 948 /** 949 * Temporal.Instant.prototype.round ( roundTo ) 950 */ 951 static bool Instant_round(JSContext* cx, const CallArgs& args) { 952 auto epochNs = args.thisv().toObject().as<InstantObject>().epochNanoseconds(); 953 954 // Steps 3-17. 955 auto smallestUnit = TemporalUnit::Unset; 956 auto roundingMode = TemporalRoundingMode::HalfExpand; 957 auto roundingIncrement = Increment{1}; 958 if (args.get(0).isString()) { 959 // Steps 4 and 6-8. (Not applicable in our implementation.) 960 961 // Step 9. 962 Rooted<JSString*> paramString(cx, args[0].toString()); 963 if (!GetTemporalUnitValuedOption( 964 cx, paramString, TemporalUnitKey::SmallestUnit, &smallestUnit)) { 965 return false; 966 } 967 968 // Step 10. 969 if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::SmallestUnit, 970 smallestUnit, TemporalUnitGroup::Time)) { 971 return false; 972 } 973 974 // Steps 11-17. (Not applicable in our implementation.) 975 } else { 976 // Steps 3 and 5. 977 Rooted<JSObject*> options( 978 cx, RequireObjectArg(cx, "roundTo", "round", args.get(0))); 979 if (!options) { 980 return false; 981 } 982 983 // Steps 6-7. 984 if (!GetRoundingIncrementOption(cx, options, &roundingIncrement)) { 985 return false; 986 } 987 988 // Step 8. 989 if (!GetRoundingModeOption(cx, options, &roundingMode)) { 990 return false; 991 } 992 993 // Step 9. 994 if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::SmallestUnit, 995 &smallestUnit)) { 996 return false; 997 } 998 999 if (smallestUnit == TemporalUnit::Unset) { 1000 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1001 JSMSG_TEMPORAL_MISSING_OPTION, "smallestUnit"); 1002 return false; 1003 } 1004 1005 // Step 10. 1006 if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::SmallestUnit, 1007 smallestUnit, TemporalUnitGroup::Time)) { 1008 return false; 1009 } 1010 1011 // Steps 11-16. 1012 int64_t maximum = UnitsPerDay(smallestUnit); 1013 1014 // Step 17. 1015 if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum, 1016 true)) { 1017 return false; 1018 } 1019 } 1020 1021 // Step 18. 1022 auto roundedNs = RoundTemporalInstant(epochNs, roundingIncrement, 1023 smallestUnit, roundingMode); 1024 1025 // Step 19. 1026 auto* result = CreateTemporalInstant(cx, roundedNs); 1027 if (!result) { 1028 return false; 1029 } 1030 args.rval().setObject(*result); 1031 return true; 1032 } 1033 1034 /** 1035 * Temporal.Instant.prototype.round ( options ) 1036 */ 1037 static bool Instant_round(JSContext* cx, unsigned argc, Value* vp) { 1038 // Steps 1-2. 1039 CallArgs args = CallArgsFromVp(argc, vp); 1040 return CallNonGenericMethod<IsInstant, Instant_round>(cx, args); 1041 } 1042 1043 /** 1044 * Temporal.Instant.prototype.equals ( other ) 1045 */ 1046 static bool Instant_equals(JSContext* cx, const CallArgs& args) { 1047 auto epochNs = args.thisv().toObject().as<InstantObject>().epochNanoseconds(); 1048 1049 // Step 3. 1050 EpochNanoseconds other; 1051 if (!ToTemporalInstant(cx, args.get(0), &other)) { 1052 return false; 1053 } 1054 1055 // Steps 4-5. 1056 args.rval().setBoolean(epochNs == other); 1057 return true; 1058 } 1059 1060 /** 1061 * Temporal.Instant.prototype.equals ( other ) 1062 */ 1063 static bool Instant_equals(JSContext* cx, unsigned argc, Value* vp) { 1064 // Steps 1-2. 1065 CallArgs args = CallArgsFromVp(argc, vp); 1066 return CallNonGenericMethod<IsInstant, Instant_equals>(cx, args); 1067 } 1068 1069 /** 1070 * Temporal.Instant.prototype.toString ( [ options ] ) 1071 */ 1072 static bool Instant_toString(JSContext* cx, const CallArgs& args) { 1073 auto epochNs = args.thisv().toObject().as<InstantObject>().epochNanoseconds(); 1074 1075 Rooted<TimeZoneValue> timeZone(cx); 1076 auto roundingMode = TemporalRoundingMode::Trunc; 1077 SecondsStringPrecision precision = {Precision::Auto(), 1078 TemporalUnit::Nanosecond, Increment{1}}; 1079 if (args.hasDefined(0)) { 1080 // Step 3. 1081 Rooted<JSObject*> options( 1082 cx, RequireObjectArg(cx, "options", "toString", args[0])); 1083 if (!options) { 1084 return false; 1085 } 1086 1087 // Steps 4-5. 1088 auto digits = Precision::Auto(); 1089 if (!GetTemporalFractionalSecondDigitsOption(cx, options, &digits)) { 1090 return false; 1091 } 1092 1093 // Step 6. 1094 if (!GetRoundingModeOption(cx, options, &roundingMode)) { 1095 return false; 1096 } 1097 1098 // Step 7. 1099 auto smallestUnit = TemporalUnit::Unset; 1100 if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::SmallestUnit, 1101 &smallestUnit)) { 1102 return false; 1103 } 1104 1105 // Step 8. 1106 Rooted<Value> timeZoneValue(cx); 1107 if (!GetProperty(cx, options, options, cx->names().timeZone, 1108 &timeZoneValue)) { 1109 return false; 1110 } 1111 1112 // Step 9. 1113 if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::SmallestUnit, 1114 smallestUnit, TemporalUnitGroup::Time)) { 1115 return false; 1116 } 1117 1118 // Step 10. 1119 if (smallestUnit == TemporalUnit::Hour) { 1120 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, 1121 JSMSG_TEMPORAL_INVALID_UNIT_OPTION, "hour", 1122 "smallestUnit"); 1123 return false; 1124 } 1125 1126 // Step 11. 1127 if (!timeZoneValue.isUndefined()) { 1128 if (!ToTemporalTimeZone(cx, timeZoneValue, &timeZone)) { 1129 return false; 1130 } 1131 } 1132 1133 // Step 12. 1134 precision = ToSecondsStringPrecision(smallestUnit, digits); 1135 } 1136 1137 // Steps 13-14. 1138 auto roundedNs = RoundTemporalInstant(epochNs, precision.increment, 1139 precision.unit, roundingMode); 1140 1141 // Step 15. 1142 JSString* str = 1143 TemporalInstantToString(cx, roundedNs, timeZone, precision.precision); 1144 if (!str) { 1145 return false; 1146 } 1147 1148 args.rval().setString(str); 1149 return true; 1150 } 1151 1152 /** 1153 * Temporal.Instant.prototype.toString ( [ options ] ) 1154 */ 1155 static bool Instant_toString(JSContext* cx, unsigned argc, Value* vp) { 1156 // Steps 1-2. 1157 CallArgs args = CallArgsFromVp(argc, vp); 1158 return CallNonGenericMethod<IsInstant, Instant_toString>(cx, args); 1159 } 1160 1161 /** 1162 * Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] ) 1163 */ 1164 static bool Instant_toLocaleString(JSContext* cx, const CallArgs& args) { 1165 // Steps 3-4. 1166 return intl::TemporalObjectToLocaleString(cx, args, 1167 intl::DateTimeFormatKind::All); 1168 } 1169 1170 /** 1171 * Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] ) 1172 */ 1173 static bool Instant_toLocaleString(JSContext* cx, unsigned argc, Value* vp) { 1174 // Steps 1-2. 1175 CallArgs args = CallArgsFromVp(argc, vp); 1176 return CallNonGenericMethod<IsInstant, Instant_toLocaleString>(cx, args); 1177 } 1178 1179 /** 1180 * Temporal.Instant.prototype.toJSON ( ) 1181 */ 1182 static bool Instant_toJSON(JSContext* cx, const CallArgs& args) { 1183 auto epochNs = args.thisv().toObject().as<InstantObject>().epochNanoseconds(); 1184 1185 // Step 3. 1186 Rooted<TimeZoneValue> timeZone(cx); 1187 JSString* str = 1188 TemporalInstantToString(cx, epochNs, timeZone, Precision::Auto()); 1189 if (!str) { 1190 return false; 1191 } 1192 1193 args.rval().setString(str); 1194 return true; 1195 } 1196 1197 /** 1198 * Temporal.Instant.prototype.toJSON ( ) 1199 */ 1200 static bool Instant_toJSON(JSContext* cx, unsigned argc, Value* vp) { 1201 // Steps 1-2. 1202 CallArgs args = CallArgsFromVp(argc, vp); 1203 return CallNonGenericMethod<IsInstant, Instant_toJSON>(cx, args); 1204 } 1205 1206 /** 1207 * Temporal.Instant.prototype.valueOf ( ) 1208 */ 1209 static bool Instant_valueOf(JSContext* cx, unsigned argc, Value* vp) { 1210 JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, 1211 "Instant", "primitive type"); 1212 return false; 1213 } 1214 1215 /** 1216 * Temporal.Instant.prototype.toZonedDateTimeISO ( item ) 1217 */ 1218 static bool Instant_toZonedDateTimeISO(JSContext* cx, const CallArgs& args) { 1219 auto epochNs = args.thisv().toObject().as<InstantObject>().epochNanoseconds(); 1220 1221 // Step 3. 1222 Rooted<TimeZoneValue> timeZone(cx); 1223 if (!ToTemporalTimeZone(cx, args.get(0), &timeZone)) { 1224 return false; 1225 } 1226 1227 // Step 4. 1228 Rooted<CalendarValue> calendar(cx, CalendarValue(CalendarId::ISO8601)); 1229 auto* result = CreateTemporalZonedDateTime(cx, epochNs, timeZone, calendar); 1230 if (!result) { 1231 return false; 1232 } 1233 1234 args.rval().setObject(*result); 1235 return true; 1236 } 1237 1238 /** 1239 * Temporal.Instant.prototype.toZonedDateTimeISO ( item ) 1240 */ 1241 static bool Instant_toZonedDateTimeISO(JSContext* cx, unsigned argc, 1242 Value* vp) { 1243 // Steps 1-2. 1244 CallArgs args = CallArgsFromVp(argc, vp); 1245 return CallNonGenericMethod<IsInstant, Instant_toZonedDateTimeISO>(cx, args); 1246 } 1247 1248 const JSClass InstantObject::class_ = { 1249 "Temporal.Instant", 1250 JSCLASS_HAS_RESERVED_SLOTS(InstantObject::SLOT_COUNT) | 1251 JSCLASS_HAS_CACHED_PROTO(JSProto_Instant), 1252 JS_NULL_CLASS_OPS, 1253 &InstantObject::classSpec_, 1254 }; 1255 1256 const JSClass& InstantObject::protoClass_ = PlainObject::class_; 1257 1258 static const JSFunctionSpec Instant_methods[] = { 1259 JS_FN("from", Instant_from, 1, 0), 1260 JS_FN("fromEpochMilliseconds", Instant_fromEpochMilliseconds, 1, 0), 1261 JS_FN("fromEpochNanoseconds", Instant_fromEpochNanoseconds, 1, 0), 1262 JS_FN("compare", Instant_compare, 2, 0), 1263 JS_FS_END, 1264 }; 1265 1266 static const JSFunctionSpec Instant_prototype_methods[] = { 1267 JS_FN("add", Instant_add, 1, 0), 1268 JS_FN("subtract", Instant_subtract, 1, 0), 1269 JS_FN("until", Instant_until, 1, 0), 1270 JS_FN("since", Instant_since, 1, 0), 1271 JS_FN("round", Instant_round, 1, 0), 1272 JS_FN("equals", Instant_equals, 1, 0), 1273 JS_FN("toString", Instant_toString, 0, 0), 1274 JS_FN("toLocaleString", Instant_toLocaleString, 0, 0), 1275 JS_FN("toJSON", Instant_toJSON, 0, 0), 1276 JS_FN("valueOf", Instant_valueOf, 0, 0), 1277 JS_FN("toZonedDateTimeISO", Instant_toZonedDateTimeISO, 1, 0), 1278 JS_FS_END, 1279 }; 1280 1281 static const JSPropertySpec Instant_prototype_properties[] = { 1282 JS_PSG("epochMilliseconds", Instant_epochMilliseconds, 0), 1283 JS_PSG("epochNanoseconds", Instant_epochNanoseconds, 0), 1284 JS_STRING_SYM_PS(toStringTag, "Temporal.Instant", JSPROP_READONLY), 1285 JS_PS_END, 1286 }; 1287 1288 const ClassSpec InstantObject::classSpec_ = { 1289 GenericCreateConstructor<InstantConstructor, 1, gc::AllocKind::FUNCTION>, 1290 GenericCreatePrototype<InstantObject>, 1291 Instant_methods, 1292 nullptr, 1293 Instant_prototype_methods, 1294 Instant_prototype_properties, 1295 nullptr, 1296 ClassSpec::DontDefineConstructor, 1297 };