/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "builtin/temporal/Instant.h" #include "mozilla/Assertions.h" #include "mozilla/Casting.h" #include "mozilla/CheckedInt.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Maybe.h" #include "mozilla/Span.h" #include #include #include #include #include #include #include #include "jsnum.h" #include "jspubtd.h" #include "NamespaceImports.h" #include "builtin/temporal/Calendar.h" #include "builtin/temporal/Duration.h" #include "builtin/temporal/Int96.h" #include "builtin/temporal/PlainDateTime.h" #include "builtin/temporal/Temporal.h" #include "builtin/temporal/TemporalParser.h" #include "builtin/temporal/TemporalRoundingMode.h" #include "builtin/temporal/TemporalTypes.h" #include "builtin/temporal/TemporalUnit.h" #include "builtin/temporal/TimeZone.h" #include "builtin/temporal/ToString.h" #include "builtin/temporal/Wrapped.h" #include "builtin/temporal/ZonedDateTime.h" #include "gc/AllocKind.h" #include "gc/Barrier.h" #include "js/CallArgs.h" #include "js/CallNonGenericMethod.h" #include "js/Class.h" #include "js/Conversions.h" #include "js/ErrorReport.h" #include "js/friend/ErrorMessages.h" #include "js/PropertyDescriptor.h" #include "js/PropertySpec.h" #include "js/RootingAPI.h" #include "js/TypeDecls.h" #include "js/Value.h" #include "vm/BigIntType.h" #include "vm/BytecodeUtil.h" #include "vm/GlobalObject.h" #include "vm/JSAtomState.h" #include "vm/JSContext.h" #include "vm/JSObject.h" #include "vm/PlainObject.h" #include "vm/StringType.h" #include "vm/JSObject-inl.h" #include "vm/NativeObject-inl.h" #include "vm/ObjectOperations-inl.h" using namespace js; using namespace js::temporal; static inline bool IsInstant(Handle v) { return v.isObject() && v.toObject().is(); } /** * Check if the absolute value is less-or-equal to the given limit. */ template static bool AbsoluteValueIsLessOrEqual(const BigInt* bigInt) { size_t length = bigInt->digitLength(); // Fewer digits than the limit, so definitely in range. if (length < std::size(digits)) { return true; } // More digits than the limit, so definitely out of range. if (length > std::size(digits)) { return false; } // Compare each digit when the input has the same number of digits. size_t index = std::size(digits); for (auto digit : digits) { auto d = bigInt->digit(--index); if (d < digit) { return true; } if (d > digit) { return false; } } return true; } static constexpr auto NanosecondsMaxInstant() { static_assert(BigInt::DigitBits == 64 || BigInt::DigitBits == 32); // ±8.64 × 10^21 is the nanoseconds from epoch limit. // 8.64 × 10^21 is 86_40000_00000_00000_00000 or 0x1d4_60162f51_6f000000. // Return the BigInt digits of that number for fast BigInt comparisons. if constexpr (BigInt::DigitBits == 64) { return std::array{ BigInt::Digit(0x1d4), BigInt::Digit(0x6016'2f51'6f00'0000), }; } else { return std::array{ BigInt::Digit(0x1d4), BigInt::Digit(0x6016'2f51), BigInt::Digit(0x6f00'0000), }; } } /** * IsValidEpochNanoseconds ( epochNanoseconds ) */ bool js::temporal::IsValidEpochNanoseconds(const BigInt* epochNanoseconds) { // Steps 1-3. static constexpr auto epochLimit = NanosecondsMaxInstant(); return AbsoluteValueIsLessOrEqual(epochNanoseconds); } static bool IsValidEpochMicroseconds(const BigInt* epochMicroseconds) { int64_t i; if (!BigInt::isInt64(epochMicroseconds, &i)) { return false; } constexpr int64_t MicrosecondsMaxInstant = Instant::max().toMicroseconds(); return -MicrosecondsMaxInstant <= i && i <= MicrosecondsMaxInstant; } static bool IsValidEpochMilliseconds(double epochMilliseconds) { MOZ_ASSERT(IsInteger(epochMilliseconds)); constexpr int64_t MillisecondsMaxInstant = Instant::max().toMilliseconds(); return std::abs(epochMilliseconds) <= double(MillisecondsMaxInstant); } static bool IsValidEpochSeconds(double epochSeconds) { MOZ_ASSERT(IsInteger(epochSeconds)); constexpr int64_t SecondsMaxInstant = Instant::max().toSeconds(); return std::abs(epochSeconds) <= double(SecondsMaxInstant); } /** * IsValidEpochNanoseconds ( epochNanoseconds ) */ bool js::temporal::IsValidEpochInstant(const Instant& instant) { MOZ_ASSERT(0 <= instant.nanoseconds && instant.nanoseconds <= 999'999'999); // Steps 1-3. return Instant::min() <= instant && instant <= Instant::max(); } #ifdef DEBUG /** * Validates a nanoseconds amount is at most as large as the difference * between two valid nanoseconds from the epoch instants. */ bool js::temporal::IsValidInstantSpan(const InstantSpan& span) { MOZ_ASSERT(0 <= span.nanoseconds && span.nanoseconds <= 999'999'999); // Steps 1-3. return InstantSpan::min() <= span && span <= InstantSpan::max(); } #endif /** * Return the BigInt as a 96-bit integer. The BigInt digits must not consist of * more than 96-bits. */ static Int96 ToInt96(const BigInt* ns) { static_assert(BigInt::DigitBits == 64 || BigInt::DigitBits == 32); auto digits = ns->digits(); if constexpr (BigInt::DigitBits == 64) { BigInt::Digit x = 0, y = 0; switch (digits.size()) { case 2: y = digits[1]; [[fallthrough]]; case 1: x = digits[0]; [[fallthrough]]; case 0: break; default: MOZ_ASSERT_UNREACHABLE("unexpected digit length"); } return Int96{ Int96::Digits{Int96::Digit(x), Int96::Digit(x >> 32), Int96::Digit(y)}, ns->isNegative()}; } else { BigInt::Digit x = 0, y = 0, z = 0; switch (digits.size()) { case 3: z = digits[2]; [[fallthrough]]; case 2: y = digits[1]; [[fallthrough]]; case 1: x = digits[0]; [[fallthrough]]; case 0: break; default: MOZ_ASSERT_UNREACHABLE("unexpected digit length"); } return Int96{ Int96::Digits{Int96::Digit(x), Int96::Digit(y), Int96::Digit(z)}, ns->isNegative()}; } } Instant js::temporal::ToInstant(const BigInt* epochNanoseconds) { MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds)); auto [seconds, nanos] = ToInt96(epochNanoseconds) / ToNanoseconds(TemporalUnit::Second); return {seconds, nanos}; } static BigInt* CreateBigInt(JSContext* cx, const std::array& digits, bool negative) { static_assert(BigInt::DigitBits == 64 || BigInt::DigitBits == 32); if constexpr (BigInt::DigitBits == 64) { uint64_t x = (uint64_t(digits[1]) << 32) | digits[0]; uint64_t y = digits[2]; size_t length = y ? 2 : x ? 1 : 0; auto* result = BigInt::createUninitialized(cx, length, negative); if (!result) { return nullptr; } if (y) { result->setDigit(1, y); } if (x) { result->setDigit(0, x); } return result; } else { size_t length = digits[2] ? 3 : digits[1] ? 2 : digits[0] ? 1 : 0; auto* result = BigInt::createUninitialized(cx, length, negative); if (!result) { return nullptr; } while (length--) { result->setDigit(length, digits[length]); } return result; } } static auto ToBigIntDigits(uint64_t seconds, uint32_t nanoseconds) { // Multiplies two uint32_t values and returns the lower 32-bits. The higher // 32-bits are stored in |high|. auto digitMul = [](uint32_t a, uint32_t b, uint32_t* high) { uint64_t result = static_cast(a) * static_cast(b); *high = result >> 32; return static_cast(result); }; // Adds two uint32_t values and returns the result. Overflow is added to the // out-param |carry|. auto digitAdd = [](uint32_t a, uint32_t b, uint32_t* carry) { uint32_t result = a + b; *carry += static_cast(result < a); return result; }; constexpr uint32_t secToNanos = ToNanoseconds(TemporalUnit::Second); // uint32_t digits stored in the same order as BigInt digits, i.e. the least // significant digit is stored at index zero. std::array multiplicand = {uint32_t(seconds), uint32_t(seconds >> 32)}; std::array accumulator = {nanoseconds, 0, 0}; // This code follows the implementation of |BigInt::multiplyAccumulate()|. uint32_t carry = 0; { uint32_t high = 0; uint32_t low = digitMul(secToNanos, multiplicand[0], &high); uint32_t newCarry = 0; accumulator[0] = digitAdd(accumulator[0], low, &newCarry); accumulator[1] = digitAdd(high, newCarry, &carry); } { uint32_t high = 0; uint32_t low = digitMul(secToNanos, multiplicand[1], &high); uint32_t newCarry = 0; accumulator[1] = digitAdd(accumulator[1], low, &carry); accumulator[2] = digitAdd(high, carry, &newCarry); MOZ_ASSERT(newCarry == 0); } return accumulator; } template static BigInt* ToBigInt(JSContext* cx, const SecondsAndNanoseconds& secondsAndNanoseconds) { uint64_t seconds = std::abs(secondsAndNanoseconds.seconds); uint32_t nanoseconds = secondsAndNanoseconds.nanoseconds; // Negative nanoseconds are represented as the difference to 1'000'000'000. // Convert these back to their absolute value and adjust the seconds part // accordingly. // // For example the nanoseconds from the epoch value |-1n| is represented as // the instant {seconds: -1, nanoseconds: 999'999'999}. if (secondsAndNanoseconds.seconds < 0 && nanoseconds != 0) { nanoseconds = ToNanoseconds(TemporalUnit::Second) - nanoseconds; seconds -= 1; } auto digits = ToBigIntDigits(seconds, nanoseconds); return CreateBigInt(cx, digits, secondsAndNanoseconds.seconds < 0); } BigInt* js::temporal::ToEpochNanoseconds(JSContext* cx, const Instant& instant) { MOZ_ASSERT(IsValidEpochInstant(instant)); return ::ToBigInt(cx, instant); } /** * GetUTCEpochNanoseconds ( year, month, day, hour, minute, second, millisecond, * microsecond, nanosecond [ , offsetNanoseconds ] ) */ Instant js::temporal::GetUTCEpochNanoseconds(const PlainDateTime& dateTime) { const auto& [date, time] = dateTime; // Step 1. MOZ_ASSERT(IsValidISODateTime(dateTime)); // Additionally ensure the date-time value can be represented as an Instant. MOZ_ASSERT(ISODateTimeWithinLimits(dateTime)); // Steps 2-5. int64_t ms = MakeDate(dateTime); // Propagate the input range to the compiler. int32_t nanos = std::clamp(time.microsecond * 1'000 + time.nanosecond, 0, 999'999); // Steps 6-8. return Instant::fromMilliseconds(ms) + InstantSpan{0, nanos}; } /** * GetUTCEpochNanoseconds ( year, month, day, hour, minute, second, millisecond, * microsecond, nanosecond [ , offsetNanoseconds ] ) */ Instant js::temporal::GetUTCEpochNanoseconds( const PlainDateTime& dateTime, const InstantSpan& offsetNanoseconds) { MOZ_ASSERT(offsetNanoseconds.abs() < InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day))); // Steps 1-6. auto epochNanoseconds = GetUTCEpochNanoseconds(dateTime); // Steps 7-9. return epochNanoseconds - offsetNanoseconds; } /** * CompareEpochNanoseconds ( epochNanosecondsOne, epochNanosecondsTwo ) */ static int32_t CompareEpochNanoseconds(const Instant& epochNanosecondsOne, const Instant& epochNanosecondsTwo) { // Step 1. if (epochNanosecondsOne > epochNanosecondsTwo) { return 1; } // Step 2. if (epochNanosecondsOne < epochNanosecondsTwo) { return -1; } // Step 3. return 0; } /** * CreateTemporalInstant ( epochNanoseconds [ , newTarget ] ) */ InstantObject* js::temporal::CreateTemporalInstant(JSContext* cx, const Instant& instant) { // Step 1. MOZ_ASSERT(IsValidEpochInstant(instant)); // Steps 2-3. auto* object = NewBuiltinClassInstance(cx); if (!object) { return nullptr; } // Step 4. object->setFixedSlot(InstantObject::SECONDS_SLOT, NumberValue(instant.seconds)); object->setFixedSlot(InstantObject::NANOSECONDS_SLOT, Int32Value(instant.nanoseconds)); // Step 5. return object; } /** * CreateTemporalInstant ( epochNanoseconds [ , newTarget ] ) */ static InstantObject* CreateTemporalInstant(JSContext* cx, const CallArgs& args, Handle epochNanoseconds) { // Step 1. MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds)); // Steps 2-3. Rooted proto(cx); if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Instant, &proto)) { return nullptr; } auto* object = NewObjectWithClassProto(cx, proto); if (!object) { return nullptr; } // Step 4. auto instant = ToInstant(epochNanoseconds); object->setFixedSlot(InstantObject::SECONDS_SLOT, NumberValue(instant.seconds)); object->setFixedSlot(InstantObject::NANOSECONDS_SLOT, Int32Value(instant.nanoseconds)); // Step 5. return object; } /** * ToTemporalInstant ( item ) */ Wrapped js::temporal::ToTemporalInstant(JSContext* cx, Handle item) { // Step 1. if (item.isObject()) { JSObject* itemObj = &item.toObject(); // Step 1.a. if (itemObj->canUnwrapAs()) { return itemObj; } } // Steps 1.b-d and 3-7 Instant epochNanoseconds; if (!ToTemporalInstant(cx, item, &epochNanoseconds)) { return nullptr; } // Step 8. return CreateTemporalInstant(cx, epochNanoseconds); } /** * ToTemporalInstant ( item ) */ bool js::temporal::ToTemporalInstant(JSContext* cx, Handle item, Instant* result) { // Step 1. Rooted primitiveValue(cx, item); if (item.isObject()) { JSObject* itemObj = &item.toObject(); // Step 1.a. if (auto* instant = itemObj->maybeUnwrapIf()) { *result = ToInstant(instant); return true; } // Step 1.b. if (auto* zonedDateTime = itemObj->maybeUnwrapIf()) { *result = ToInstant(zonedDateTime); return true; } // Steps 1.c-d. if (!ToPrimitive(cx, JSTYPE_STRING, &primitiveValue)) { return false; } } // Step 2. if (!primitiveValue.isString()) { // The value is always on the stack, so JSDVG_SEARCH_STACK can be used for // better error reporting. ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, primitiveValue, nullptr, "not a string"); return false; } Rooted string(cx, primitiveValue.toString()); // Steps 3-4. PlainDateTime dateTime; int64_t offset; if (!ParseTemporalInstantString(cx, string, &dateTime, &offset)) { return false; } MOZ_ASSERT(std::abs(offset) < ToNanoseconds(TemporalUnit::Day)); // Steps 5-6. (Reordered) if (!ISODateTimeWithinLimits(dateTime)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INSTANT_INVALID); return false; } // Step 4. auto epochNanoseconds = GetUTCEpochNanoseconds(dateTime, InstantSpan::fromNanoseconds(offset)); // Step 7. if (!IsValidEpochInstant(epochNanoseconds)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INSTANT_INVALID); return false; } // Step 8. *result = epochNanoseconds; return true; } /** * AddInstant ( epochNanoseconds, hours, minutes, seconds, milliseconds, * microseconds, nanoseconds ) */ bool js::temporal::AddInstant(JSContext* cx, const Instant& instant, const NormalizedTimeDuration& duration, Instant* result) { MOZ_ASSERT(IsValidEpochInstant(instant)); MOZ_ASSERT(IsValidNormalizedTimeDuration(duration)); // Step 1. (Inlined AddNormalizedTimeDurationToEpochNanoseconds) auto r = instant + duration.to(); // Step 2. if (!IsValidEpochInstant(r)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INSTANT_INVALID); return false; } // Step 3. *result = r; return true; } /** * DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, roundingMode ) */ NormalizedTimeDuration js::temporal::DifferenceInstant( const Instant& ns1, const Instant& ns2, Increment roundingIncrement, TemporalUnit smallestUnit, TemporalRoundingMode roundingMode) { MOZ_ASSERT(IsValidEpochInstant(ns1)); MOZ_ASSERT(IsValidEpochInstant(ns2)); MOZ_ASSERT(smallestUnit > TemporalUnit::Day); MOZ_ASSERT(roundingIncrement <= MaximumTemporalDurationRoundingIncrement(smallestUnit)); // Step 1. auto diff = NormalizedTimeDurationFromEpochNanosecondsDifference(ns2, ns1); MOZ_ASSERT(IsValidInstantSpan(diff.to())); // Step 2. if (smallestUnit == TemporalUnit::Nanosecond && roundingIncrement == Increment{1}) { return diff; } // Steps 3-4. return RoundDuration(diff, roundingIncrement, smallestUnit, roundingMode); } /** * RoundNumberToIncrementAsIfPositive ( x, increment, roundingMode ) */ static Instant RoundNumberToIncrementAsIfPositive( const Instant& x, int64_t increment, TemporalRoundingMode roundingMode) { MOZ_ASSERT(IsValidEpochInstant(x)); MOZ_ASSERT(increment > 0); MOZ_ASSERT(increment <= ToNanoseconds(TemporalUnit::Day)); // This operation is equivalent to adjusting the rounding mode through // |ToPositiveRoundingMode| and then calling |RoundNumberToIncrement|. auto rounded = RoundNumberToIncrement(x.toNanoseconds(), Int128{increment}, ToPositiveRoundingMode(roundingMode)); return Instant::fromNanoseconds(rounded); } /** * RoundTemporalInstant ( ns, increment, unit, roundingMode ) */ Instant js::temporal::RoundTemporalInstant(const Instant& ns, Increment increment, TemporalUnit unit, TemporalRoundingMode roundingMode) { MOZ_ASSERT(IsValidEpochInstant(ns)); MOZ_ASSERT(increment >= Increment::min()); MOZ_ASSERT(uint64_t(increment.value()) <= ToNanoseconds(TemporalUnit::Day)); MOZ_ASSERT(unit > TemporalUnit::Day); // Steps 1-6. int64_t toNanoseconds = ToNanoseconds(unit); MOZ_ASSERT( (increment.value() * toNanoseconds) <= ToNanoseconds(TemporalUnit::Day), "increment * toNanoseconds shouldn't overflow instant resolution"); // Step 7. return RoundNumberToIncrementAsIfPositive( ns, increment.value() * toNanoseconds, roundingMode); } /** * DifferenceTemporalInstant ( operation, instant, other, options ) */ static bool DifferenceTemporalInstant(JSContext* cx, TemporalDifference operation, const CallArgs& args) { auto instant = ToInstant(&args.thisv().toObject().as()); // Step 1. (Not applicable in our implementation.) // Step 2. Instant other; if (!ToTemporalInstant(cx, args.get(0), &other)) { return false; } // Steps 3-4. DifferenceSettings settings; if (args.hasDefined(1)) { Rooted options( cx, RequireObjectArg(cx, "options", ToName(operation), args[1])); if (!options) { return false; } // Step 3. Rooted resolvedOptions(cx, SnapshotOwnProperties(cx, options)); if (!resolvedOptions) { return false; } // Step 4. if (!GetDifferenceSettings( cx, operation, resolvedOptions, TemporalUnitGroup::Time, TemporalUnit::Nanosecond, TemporalUnit::Second, &settings)) { return false; } } else { // Steps 3-4. settings = { TemporalUnit::Nanosecond, TemporalUnit::Second, TemporalRoundingMode::Trunc, Increment{1}, }; } // Step 5. auto difference = DifferenceInstant(instant, other, settings.roundingIncrement, settings.smallestUnit, settings.roundingMode); // Step 6. TimeDuration balanced; if (!BalanceTimeDuration(cx, difference, settings.largestUnit, &balanced)) { return false; } // Step 7. auto duration = balanced.toDuration(); if (operation == TemporalDifference::Since) { duration = duration.negate(); } auto* obj = CreateTemporalDuration(cx, duration); if (!obj) { return false; } args.rval().setObject(*obj); return true; } enum class InstantDuration { Add, Subtract }; /** * AddDurationToOrSubtractDurationFromInstant ( operation, instant, * temporalDurationLike ) */ static bool AddDurationToOrSubtractDurationFromInstant( JSContext* cx, InstantDuration operation, const CallArgs& args) { auto* instant = &args.thisv().toObject().as(); auto epochNanoseconds = ToInstant(instant); // Step 1. (Not applicable in our implementation.) // Step 2. Duration duration; if (!ToTemporalDurationRecord(cx, args.get(0), &duration)) { return false; } // Steps 3-6. if (duration.years != 0 || duration.months != 0 || duration.weeks != 0 || duration.days != 0) { const char* part = duration.years != 0 ? "years" : duration.months != 0 ? "months" : duration.weeks != 0 ? "weeks" : "days"; JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INSTANT_BAD_DURATION, part); return false; } // Step 7. if (operation == InstantDuration::Subtract) { duration = duration.negate(); } auto timeDuration = NormalizeTimeDuration(duration); // Step 8. Instant ns; if (!AddInstant(cx, epochNanoseconds, timeDuration, &ns)) { return false; } // Step 9. auto* result = CreateTemporalInstant(cx, ns); if (!result) { return false; } args.rval().setObject(*result); return true; } /** * Temporal.Instant ( epochNanoseconds ) */ static bool InstantConstructor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Step 1. if (!ThrowIfNotConstructing(cx, args, "Temporal.Instant")) { return false; } // Step 2. Rooted epochNanoseconds(cx, js::ToBigInt(cx, args.get(0))); if (!epochNanoseconds) { return false; } // Step 3. if (!IsValidEpochNanoseconds(epochNanoseconds)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INSTANT_INVALID); return false; } // Step 4. auto* result = CreateTemporalInstant(cx, args, epochNanoseconds); if (!result) { return false; } args.rval().setObject(*result); return true; } /** * Temporal.Instant.from ( item ) */ static bool Instant_from(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Steps 1-2. Instant epochInstant; if (!ToTemporalInstant(cx, args.get(0), &epochInstant)) { return false; } auto* result = CreateTemporalInstant(cx, epochInstant); if (!result) { return false; } args.rval().setObject(*result); return true; } /** * Temporal.Instant.fromEpochSeconds ( epochSeconds ) */ static bool Instant_fromEpochSeconds(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Step 1. double epochSeconds; if (!JS::ToNumber(cx, args.get(0), &epochSeconds)) { return false; } // Step 2. // // NumberToBigInt throws a RangeError for non-integral numbers. if (!IsInteger(epochSeconds)) { ToCStringBuf cbuf; const char* str = NumberToCString(&cbuf, epochSeconds); JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INSTANT_NONINTEGER, str); return false; } // Step 3. (Not applicable) // Step 4. if (!IsValidEpochSeconds(epochSeconds)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INSTANT_INVALID); return false; } // Step 5. int64_t seconds = mozilla::AssertedCast(epochSeconds); auto* result = CreateTemporalInstant(cx, Instant::fromSeconds(seconds)); if (!result) { return false; } args.rval().setObject(*result); return true; } /** * Temporal.Instant.fromEpochMilliseconds ( epochMilliseconds ) */ static bool Instant_fromEpochMilliseconds(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Step 1. double epochMilliseconds; if (!JS::ToNumber(cx, args.get(0), &epochMilliseconds)) { return false; } // Step 2. // // NumberToBigInt throws a RangeError for non-integral numbers. if (!IsInteger(epochMilliseconds)) { ToCStringBuf cbuf; const char* str = NumberToCString(&cbuf, epochMilliseconds); JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INSTANT_NONINTEGER, str); return false; } // Step 3. (Not applicable) // Step 4. if (!IsValidEpochMilliseconds(epochMilliseconds)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INSTANT_INVALID); return false; } // Step 5. int64_t milliseconds = mozilla::AssertedCast(epochMilliseconds); auto* result = CreateTemporalInstant(cx, Instant::fromMilliseconds(milliseconds)); if (!result) { return false; } args.rval().setObject(*result); return true; } /** * Temporal.Instant.fromEpochMicroseconds ( epochMicroseconds ) */ static bool Instant_fromEpochMicroseconds(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Step 1. Rooted epochMicroseconds(cx, js::ToBigInt(cx, args.get(0))); if (!epochMicroseconds) { return false; } // Step 2. (Not applicable) // Step 3. if (!IsValidEpochMicroseconds(epochMicroseconds)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INSTANT_INVALID); return false; } int64_t i; MOZ_ALWAYS_TRUE(BigInt::isInt64(epochMicroseconds, &i)); // Step 4. auto* result = CreateTemporalInstant(cx, Instant::fromMicroseconds(i)); if (!result) { return false; } args.rval().setObject(*result); return true; } /** * Temporal.Instant.fromEpochNanoseconds ( epochNanoseconds ) */ static bool Instant_fromEpochNanoseconds(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Step 1. Rooted epochNanoseconds(cx, js::ToBigInt(cx, args.get(0))); if (!epochNanoseconds) { return false; } // Step 2. if (!IsValidEpochNanoseconds(epochNanoseconds)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INSTANT_INVALID); return false; } // Step 3. auto* result = CreateTemporalInstant(cx, ToInstant(epochNanoseconds)); if (!result) { return false; } args.rval().setObject(*result); return true; } /** * Temporal.Instant.compare ( one, two ) */ static bool Instant_compare(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); // Step 1. Instant one; if (!ToTemporalInstant(cx, args.get(0), &one)) { return false; } // Step 2. Instant two; if (!ToTemporalInstant(cx, args.get(1), &two)) { return false; } // Step 3. args.rval().setInt32(CompareEpochNanoseconds(one, two)); return true; } /** * get Temporal.Instant.prototype.epochSeconds */ static bool Instant_epochSeconds(JSContext* cx, const CallArgs& args) { // Step 3. auto instant = ToInstant(&args.thisv().toObject().as()); // Steps 4-5. args.rval().setNumber(instant.seconds); return true; } /** * get Temporal.Instant.prototype.epochSeconds */ static bool Instant_epochSeconds(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /** * get Temporal.Instant.prototype.epochMilliseconds */ static bool Instant_epochMilliseconds(JSContext* cx, const CallArgs& args) { // Step 3. auto instant = ToInstant(&args.thisv().toObject().as()); // Step 4-5. args.rval().setNumber(instant.floorToMilliseconds()); return true; } /** * get Temporal.Instant.prototype.epochMilliseconds */ static bool Instant_epochMilliseconds(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /** * get Temporal.Instant.prototype.epochMicroseconds */ static bool Instant_epochMicroseconds(JSContext* cx, const CallArgs& args) { // Step 3. auto instant = ToInstant(&args.thisv().toObject().as()); // Step 4. auto* microseconds = BigInt::createFromInt64(cx, instant.floorToMicroseconds()); if (!microseconds) { return false; } // Step 5. args.rval().setBigInt(microseconds); return true; } /** * get Temporal.Instant.prototype.epochMicroseconds */ static bool Instant_epochMicroseconds(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /** * get Temporal.Instant.prototype.epochNanoseconds */ static bool Instant_epochNanoseconds(JSContext* cx, const CallArgs& args) { // Step 3. auto instant = ToInstant(&args.thisv().toObject().as()); auto* nanoseconds = ToEpochNanoseconds(cx, instant); if (!nanoseconds) { return false; } // Step 4. args.rval().setBigInt(nanoseconds); return true; } /** * get Temporal.Instant.prototype.epochNanoseconds */ static bool Instant_epochNanoseconds(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /** * Temporal.Instant.prototype.add ( temporalDurationLike ) */ static bool Instant_add(JSContext* cx, const CallArgs& args) { return AddDurationToOrSubtractDurationFromInstant(cx, InstantDuration::Add, args); } /** * Temporal.Instant.prototype.add ( temporalDurationLike ) */ static bool Instant_add(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /** * Temporal.Instant.prototype.subtract ( temporalDurationLike ) */ static bool Instant_subtract(JSContext* cx, const CallArgs& args) { return AddDurationToOrSubtractDurationFromInstant( cx, InstantDuration::Subtract, args); } /** * Temporal.Instant.prototype.subtract ( temporalDurationLike ) */ static bool Instant_subtract(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /** * Temporal.Instant.prototype.until ( other [ , options ] ) */ static bool Instant_until(JSContext* cx, const CallArgs& args) { return DifferenceTemporalInstant(cx, TemporalDifference::Until, args); } /** * Temporal.Instant.prototype.until ( other [ , options ] ) */ static bool Instant_until(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /** * Temporal.Instant.prototype.since ( other [ , options ] ) */ static bool Instant_since(JSContext* cx, const CallArgs& args) { return DifferenceTemporalInstant(cx, TemporalDifference::Since, args); } /** * Temporal.Instant.prototype.since ( other [ , options ] ) */ static bool Instant_since(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /** * Temporal.Instant.prototype.round ( roundTo ) */ static bool Instant_round(JSContext* cx, const CallArgs& args) { auto instant = ToInstant(&args.thisv().toObject().as()); // Steps 3-16. auto smallestUnit = TemporalUnit::Auto; auto roundingMode = TemporalRoundingMode::HalfExpand; auto roundingIncrement = Increment{1}; if (args.get(0).isString()) { // Steps 4 and 6-8. (Not applicable in our implementation.) // Step 9. Rooted paramString(cx, args[0].toString()); if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::SmallestUnit, TemporalUnitGroup::Time, &smallestUnit)) { return false; } // Steps 10-16. (Not applicable in our implementation.) } else { // Steps 3 and 5. Rooted options( cx, RequireObjectArg(cx, "roundTo", "round", args.get(0))); if (!options) { return false; } // Steps 6-7. if (!ToTemporalRoundingIncrement(cx, options, &roundingIncrement)) { return false; } // Step 8. if (!ToTemporalRoundingMode(cx, options, &roundingMode)) { return false; } // Step 9. if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit, TemporalUnitGroup::Time, &smallestUnit)) { return false; } if (smallestUnit == TemporalUnit::Auto) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_MISSING_OPTION, "smallestUnit"); return false; } // Steps 10-15. int64_t maximum = UnitsPerDay(smallestUnit); // Step 16. if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum, true)) { return false; } } // Step 17. auto roundedNs = RoundTemporalInstant(instant, roundingIncrement, smallestUnit, roundingMode); // Step 18. auto* result = CreateTemporalInstant(cx, roundedNs); if (!result) { return false; } args.rval().setObject(*result); return true; } /** * Temporal.Instant.prototype.round ( options ) */ static bool Instant_round(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /** * Temporal.Instant.prototype.equals ( other ) */ static bool Instant_equals(JSContext* cx, const CallArgs& args) { auto instant = ToInstant(&args.thisv().toObject().as()); // Step 3. Instant other; if (!ToTemporalInstant(cx, args.get(0), &other)) { return false; } // Steps 4-5. args.rval().setBoolean(instant == other); return true; } /** * Temporal.Instant.prototype.equals ( other ) */ static bool Instant_equals(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /** * Temporal.Instant.prototype.toString ( [ options ] ) */ static bool Instant_toString(JSContext* cx, const CallArgs& args) { auto instant = ToInstant(&args.thisv().toObject().as()); Rooted timeZone(cx); auto roundingMode = TemporalRoundingMode::Trunc; SecondsStringPrecision precision = {Precision::Auto(), TemporalUnit::Nanosecond, Increment{1}}; if (args.hasDefined(0)) { // Step 3. Rooted options( cx, RequireObjectArg(cx, "options", "toString", args[0])); if (!options) { return false; } // Steps 4-5. auto digits = Precision::Auto(); if (!ToFractionalSecondDigits(cx, options, &digits)) { return false; } // Step 6. if (!ToTemporalRoundingMode(cx, options, &roundingMode)) { return false; } // Step 7. auto smallestUnit = TemporalUnit::Auto; if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit, TemporalUnitGroup::Time, &smallestUnit)) { return false; } // Step 8. if (smallestUnit == TemporalUnit::Hour) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INVALID_UNIT_OPTION, "hour", "smallestUnit"); return false; } // Step 9. Rooted value(cx); if (!GetProperty(cx, options, options, cx->names().timeZone, &value)) { return false; } // Step 10. if (!value.isUndefined()) { if (!ToTemporalTimeZone(cx, value, &timeZone)) { return false; } } // Step 11. precision = ToSecondsStringPrecision(smallestUnit, digits); } // Step 12. auto ns = RoundTemporalInstant(instant, precision.increment, precision.unit, roundingMode); // Step 13. Rooted roundedInstant(cx, CreateTemporalInstant(cx, ns)); if (!roundedInstant) { return false; } // Step 14. JSString* str = TemporalInstantToString(cx, roundedInstant, timeZone, precision.precision); if (!str) { return false; } args.rval().setString(str); return true; } /** * Temporal.Instant.prototype.toString ( [ options ] ) */ static bool Instant_toString(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /** * Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] ) */ static bool Instant_toLocaleString(JSContext* cx, const CallArgs& args) { Rooted instant(cx, &args.thisv().toObject().as()); // Step 3. Rooted timeZone(cx); JSString* str = TemporalInstantToString(cx, instant, timeZone, Precision::Auto()); if (!str) { return false; } args.rval().setString(str); return true; } /** * Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] ) */ static bool Instant_toLocaleString(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /** * Temporal.Instant.prototype.toJSON ( ) */ static bool Instant_toJSON(JSContext* cx, const CallArgs& args) { Rooted instant(cx, &args.thisv().toObject().as()); // Step 3. Rooted timeZone(cx); JSString* str = TemporalInstantToString(cx, instant, timeZone, Precision::Auto()); if (!str) { return false; } args.rval().setString(str); return true; } /** * Temporal.Instant.prototype.toJSON ( ) */ static bool Instant_toJSON(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /** * Temporal.Instant.prototype.valueOf ( ) */ static bool Instant_valueOf(JSContext* cx, unsigned argc, Value* vp) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, "Instant", "primitive type"); return false; } /** * Temporal.Instant.prototype.toZonedDateTime ( item ) */ static bool Instant_toZonedDateTime(JSContext* cx, const CallArgs& args) { auto instant = ToInstant(&args.thisv().toObject().as()); // Step 3. Rooted item( cx, RequireObjectArg(cx, "item", "toZonedDateTime", args.get(0))); if (!item) { return false; } // Step 4. Rooted calendarLike(cx); if (!GetProperty(cx, item, item, cx->names().calendar, &calendarLike)) { return false; } // Step 5. if (calendarLike.isUndefined()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_MISSING_PROPERTY, "calendar"); return false; } // Step 6. Rooted calendar(cx); if (!ToTemporalCalendar(cx, calendarLike, &calendar)) { return false; } // Step 7. Rooted timeZoneLike(cx); if (!GetProperty(cx, item, item, cx->names().timeZone, &timeZoneLike)) { return false; } // Step 8. if (timeZoneLike.isUndefined()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_MISSING_PROPERTY, "timeZone"); return false; } // Step 9. Rooted timeZone(cx); if (!ToTemporalTimeZone(cx, timeZoneLike, &timeZone)) { return false; } // Step 10. auto* result = CreateTemporalZonedDateTime(cx, instant, timeZone, calendar); if (!result) { return false; } args.rval().setObject(*result); return true; } /** * Temporal.Instant.prototype.toZonedDateTime ( item ) */ static bool Instant_toZonedDateTime(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /** * Temporal.Instant.prototype.toZonedDateTimeISO ( item ) */ static bool Instant_toZonedDateTimeISO(JSContext* cx, const CallArgs& args) { auto instant = ToInstant(&args.thisv().toObject().as()); // Step 3. Rooted timeZone(cx); if (!ToTemporalTimeZone(cx, args.get(0), &timeZone)) { return false; } // Step 4. Rooted calendar(cx, CalendarValue(cx->names().iso8601)); auto* result = CreateTemporalZonedDateTime(cx, instant, timeZone, calendar); if (!result) { return false; } args.rval().setObject(*result); return true; } /** * Temporal.Instant.prototype.toZonedDateTimeISO ( item ) */ static bool Instant_toZonedDateTimeISO(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } const JSClass InstantObject::class_ = { "Temporal.Instant", JSCLASS_HAS_RESERVED_SLOTS(InstantObject::SLOT_COUNT) | JSCLASS_HAS_CACHED_PROTO(JSProto_Instant), JS_NULL_CLASS_OPS, &InstantObject::classSpec_, }; const JSClass& InstantObject::protoClass_ = PlainObject::class_; static const JSFunctionSpec Instant_methods[] = { JS_FN("from", Instant_from, 1, 0), JS_FN("fromEpochSeconds", Instant_fromEpochSeconds, 1, 0), JS_FN("fromEpochMilliseconds", Instant_fromEpochMilliseconds, 1, 0), JS_FN("fromEpochMicroseconds", Instant_fromEpochMicroseconds, 1, 0), JS_FN("fromEpochNanoseconds", Instant_fromEpochNanoseconds, 1, 0), JS_FN("compare", Instant_compare, 2, 0), JS_FS_END, }; static const JSFunctionSpec Instant_prototype_methods[] = { JS_FN("add", Instant_add, 1, 0), JS_FN("subtract", Instant_subtract, 1, 0), JS_FN("until", Instant_until, 1, 0), JS_FN("since", Instant_since, 1, 0), JS_FN("round", Instant_round, 1, 0), JS_FN("equals", Instant_equals, 1, 0), JS_FN("toString", Instant_toString, 0, 0), JS_FN("toLocaleString", Instant_toLocaleString, 0, 0), JS_FN("toJSON", Instant_toJSON, 0, 0), JS_FN("valueOf", Instant_valueOf, 0, 0), JS_FN("toZonedDateTime", Instant_toZonedDateTime, 1, 0), JS_FN("toZonedDateTimeISO", Instant_toZonedDateTimeISO, 1, 0), JS_FS_END, }; static const JSPropertySpec Instant_prototype_properties[] = { JS_PSG("epochSeconds", Instant_epochSeconds, 0), JS_PSG("epochMilliseconds", Instant_epochMilliseconds, 0), JS_PSG("epochMicroseconds", Instant_epochMicroseconds, 0), JS_PSG("epochNanoseconds", Instant_epochNanoseconds, 0), JS_STRING_SYM_PS(toStringTag, "Temporal.Instant", JSPROP_READONLY), JS_PS_END, }; const ClassSpec InstantObject::classSpec_ = { GenericCreateConstructor, GenericCreatePrototype, Instant_methods, nullptr, Instant_prototype_methods, Instant_prototype_properties, nullptr, ClassSpec::DontDefineConstructor, };