diff options
Diffstat (limited to 'js/src/builtin/temporal/ZonedDateTime.cpp')
-rw-r--r-- | js/src/builtin/temporal/ZonedDateTime.cpp | 4124 |
1 files changed, 4124 insertions, 0 deletions
diff --git a/js/src/builtin/temporal/ZonedDateTime.cpp b/js/src/builtin/temporal/ZonedDateTime.cpp new file mode 100644 index 0000000000..690ff223b1 --- /dev/null +++ b/js/src/builtin/temporal/ZonedDateTime.cpp @@ -0,0 +1,4124 @@ +/* -*- 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/ZonedDateTime.h" + +#include "mozilla/Assertions.h" +#include "mozilla/Maybe.h" + +#include <cstdlib> +#include <utility> + +#include "jspubtd.h" +#include "NamespaceImports.h" + +#include "builtin/temporal/Calendar.h" +#include "builtin/temporal/Duration.h" +#include "builtin/temporal/Instant.h" +#include "builtin/temporal/PlainDate.h" +#include "builtin/temporal/PlainDateTime.h" +#include "builtin/temporal/PlainMonthDay.h" +#include "builtin/temporal/PlainTime.h" +#include "builtin/temporal/PlainYearMonth.h" +#include "builtin/temporal/Temporal.h" +#include "builtin/temporal/TemporalFields.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 "ds/IdValuePair.h" +#include "gc/AllocKind.h" +#include "gc/Barrier.h" +#include "js/AllocPolicy.h" +#include "js/CallArgs.h" +#include "js/CallNonGenericMethod.h" +#include "js/Class.h" +#include "js/ComparisonOperators.h" +#include "js/ErrorReport.h" +#include "js/friend/ErrorMessages.h" +#include "js/GCVector.h" +#include "js/Id.h" +#include "js/Printer.h" +#include "js/PropertyDescriptor.h" +#include "js/PropertySpec.h" +#include "js/RootingAPI.h" +#include "js/TracingAPI.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/ObjectOperations.h" +#include "vm/PlainObject.h" +#include "vm/StringType.h" + +#include "vm/JSContext-inl.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 IsZonedDateTime(Handle<Value> v) { + return v.isObject() && v.toObject().is<ZonedDateTimeObject>(); +} + +// Returns |RoundNumberToIncrement(offsetNanoseconds, 60 × 10^9, "halfExpand")|. +static int64_t RoundNanosecondsToMinutesIncrement(int64_t offsetNanoseconds) { + MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day)); + + constexpr int64_t increment = ToNanoseconds(TemporalUnit::Minute); + + int64_t quotient = offsetNanoseconds / increment; + int64_t remainder = offsetNanoseconds % increment; + if (std::abs(remainder * 2) >= increment) { + quotient += (offsetNanoseconds > 0 ? 1 : -1); + } + return quotient * increment; +} + +/** + * InterpretISODateTimeOffset ( year, month, day, hour, minute, second, + * millisecond, microsecond, nanosecond, offsetBehaviour, offsetNanoseconds, + * timeZoneRec, disambiguation, offsetOption, matchBehaviour ) + */ +bool js::temporal::InterpretISODateTimeOffset( + JSContext* cx, const PlainDateTime& dateTime, + OffsetBehaviour offsetBehaviour, int64_t offsetNanoseconds, + Handle<TimeZoneRecord> timeZone, TemporalDisambiguation disambiguation, + TemporalOffset offsetOption, MatchBehaviour matchBehaviour, + Instant* result) { + MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day)); + + // Step 1. + MOZ_ASSERT(IsValidISODate(dateTime.date)); + + // Step 2. + MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp( + timeZone, TimeZoneMethod::GetOffsetNanosecondsFor)); + + // Step 3. + MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp( + timeZone, TimeZoneMethod::GetPossibleInstantsFor)); + + // Step 4. + Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601)); + Rooted<PlainDateTimeWithCalendar> temporalDateTime(cx); + if (!CreateTemporalDateTime(cx, dateTime, calendar, &temporalDateTime)) { + return false; + } + + // Step 5. + if (offsetBehaviour == OffsetBehaviour::Wall || + offsetOption == TemporalOffset::Ignore) { + // Steps 5.a-b. + return GetInstantFor(cx, timeZone, temporalDateTime, disambiguation, + result); + } + + // Step 6. + if (offsetBehaviour == OffsetBehaviour::Exact || + offsetOption == TemporalOffset::Use) { + // Step 6.a. + auto epochNanoseconds = GetUTCEpochNanoseconds( + dateTime, InstantSpan::fromNanoseconds(offsetNanoseconds)); + + // Step 6.b. + if (!IsValidEpochInstant(epochNanoseconds)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_INSTANT_INVALID); + return false; + } + + // Step 6.c. + *result = epochNanoseconds; + return true; + } + + // Step 7. + MOZ_ASSERT(offsetBehaviour == OffsetBehaviour::Option); + + // Step 8. + MOZ_ASSERT(offsetOption == TemporalOffset::Prefer || + offsetOption == TemporalOffset::Reject); + + // FIXME: spec issue - duplicate assertion + + // Step 9. + MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp( + timeZone, TimeZoneMethod::GetPossibleInstantsFor)); + + // Step 10. + Rooted<InstantVector> possibleInstants(cx, InstantVector(cx)); + if (!GetPossibleInstantsFor(cx, timeZone, temporalDateTime, + &possibleInstants)) { + return false; + } + + // Step 11. + if (!possibleInstants.empty()) { + // Step 11.a. + Rooted<Wrapped<InstantObject*>> candidate(cx); + for (size_t i = 0; i < possibleInstants.length(); i++) { + candidate = possibleInstants[i]; + + // Step 11.a.i. + int64_t candidateNanoseconds; + if (!GetOffsetNanosecondsFor(cx, timeZone, candidate, + &candidateNanoseconds)) { + return false; + } + MOZ_ASSERT(std::abs(candidateNanoseconds) < + ToNanoseconds(TemporalUnit::Day)); + + // Step 11.a.ii. + if (candidateNanoseconds == offsetNanoseconds) { + auto* unwrapped = candidate.unwrap(cx); + if (!unwrapped) { + return false; + } + + *result = ToInstant(unwrapped); + return true; + } + + // Step 11.a.iii. + if (matchBehaviour == MatchBehaviour::MatchMinutes) { + // Step 11.a.iii.1. + int64_t roundedCandidateNanoseconds = + RoundNanosecondsToMinutesIncrement(candidateNanoseconds); + + // Step 11.a.iii.2. + if (roundedCandidateNanoseconds == offsetNanoseconds) { + auto* unwrapped = candidate.unwrap(cx); + if (!unwrapped) { + return false; + } + + // Step 11.a.iii.2.a. + *result = ToInstant(unwrapped); + return true; + } + } + } + } + + // Step 12. + if (offsetOption == TemporalOffset::Reject) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_ZONED_DATE_TIME_NO_TIME_FOUND); + return false; + } + + // Step 13. + Rooted<Wrapped<InstantObject*>> instant(cx); + if (!DisambiguatePossibleInstants(cx, possibleInstants, timeZone, + ToPlainDateTime(temporalDateTime), + disambiguation, &instant)) { + return false; + } + + auto* unwrappedInstant = instant.unwrap(cx); + if (!unwrappedInstant) { + return false; + } + + // Step 14. + *result = ToInstant(unwrappedInstant); + return true; +} + +/** + * ToTemporalZonedDateTime ( item [ , options ] ) + */ +static bool ToTemporalZonedDateTime(JSContext* cx, Handle<Value> item, + Handle<JSObject*> maybeOptions, + MutableHandle<ZonedDateTime> result) { + // Step 1. (Not applicable in our implementation) + + // Step 2. + Rooted<PlainObject*> maybeResolvedOptions(cx); + if (maybeOptions) { + maybeResolvedOptions = SnapshotOwnProperties(cx, maybeOptions); + if (!maybeResolvedOptions) { + return false; + } + } + + // Step 3. + auto offsetBehaviour = OffsetBehaviour::Option; + + // Step 4. + auto matchBehaviour = MatchBehaviour::MatchExactly; + + // Step 7. (Reordered) + int64_t offsetNanoseconds = 0; + + // Step 5. + Rooted<CalendarValue> calendar(cx); + Rooted<TimeZoneValue> timeZone(cx); + PlainDateTime dateTime; + auto disambiguation = TemporalDisambiguation::Compatible; + auto offsetOption = TemporalOffset::Reject; + if (item.isObject()) { + Rooted<JSObject*> itemObj(cx, &item.toObject()); + + // Step 5.a. + if (auto* zonedDateTime = itemObj->maybeUnwrapIf<ZonedDateTimeObject>()) { + auto instant = ToInstant(zonedDateTime); + Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone()); + Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar()); + + if (!timeZone.wrap(cx)) { + return false; + } + if (!calendar.wrap(cx)) { + return false; + } + + result.set(ZonedDateTime{instant, timeZone, calendar}); + return true; + } + + // Step 5.b. + if (!GetTemporalCalendarWithISODefault(cx, itemObj, &calendar)) { + return false; + } + + // Step 5.c. + Rooted<CalendarRecord> calendarRec(cx); + if (!CreateCalendarMethodsRecord(cx, calendar, + { + CalendarMethod::DateFromFields, + CalendarMethod::Fields, + }, + &calendarRec)) { + return false; + } + + // Step 5.d. + JS::RootedVector<PropertyKey> fieldNames(cx); + if (!CalendarFields(cx, calendarRec, + {CalendarField::Day, CalendarField::Month, + CalendarField::MonthCode, CalendarField::Year}, + &fieldNames)) { + return false; + } + + // Step 5.e. + if (!AppendSorted(cx, fieldNames.get(), + { + TemporalField::Hour, + TemporalField::Microsecond, + TemporalField::Millisecond, + TemporalField::Minute, + TemporalField::Nanosecond, + TemporalField::Offset, + TemporalField::Second, + TemporalField::TimeZone, + })) { + return false; + } + + // Step 5.f. + Rooted<PlainObject*> fields( + cx, PrepareTemporalFields(cx, itemObj, fieldNames, + {TemporalField::TimeZone})); + if (!fields) { + return false; + } + + // Step 5.g. + Rooted<Value> timeZoneValue(cx); + if (!GetProperty(cx, fields, fields, cx->names().timeZone, + &timeZoneValue)) { + return false; + } + + // Step 5.h. + if (!ToTemporalTimeZone(cx, timeZoneValue, &timeZone)) { + return false; + } + + // Step 5.i. + Rooted<Value> offsetValue(cx); + if (!GetProperty(cx, fields, fields, cx->names().offset, &offsetValue)) { + return false; + } + + // Step 5.j. + MOZ_ASSERT(offsetValue.isString() || offsetValue.isUndefined()); + + // Step 5.k. + Rooted<JSString*> offsetString(cx); + if (offsetValue.isString()) { + offsetString = offsetValue.toString(); + } else { + offsetBehaviour = OffsetBehaviour::Wall; + } + + if (maybeResolvedOptions) { + // Steps 5.l-m. + if (!ToTemporalDisambiguation(cx, maybeResolvedOptions, + &disambiguation)) { + return false; + } + + // Step 5.n. + if (!ToTemporalOffset(cx, maybeResolvedOptions, &offsetOption)) { + return false; + } + + // Step 5.o. + if (!InterpretTemporalDateTimeFields(cx, calendarRec, fields, + maybeResolvedOptions, &dateTime)) { + return false; + } + } else { + // Steps 5.l-n. (Not applicable) + + // Step 5.o. + if (!InterpretTemporalDateTimeFields(cx, calendarRec, fields, + &dateTime)) { + return false; + } + } + + // Step 8. + if (offsetBehaviour == OffsetBehaviour::Option) { + if (!ParseDateTimeUTCOffset(cx, offsetString, &offsetNanoseconds)) { + return false; + } + } + } else { + // Step 6.a. + if (!item.isString()) { + ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, item, + nullptr, "not a string"); + return false; + } + Rooted<JSString*> string(cx, item.toString()); + + // Case 1: 19700101Z[+02:00] + // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "+02:00" } + // + // Case 2: 19700101+00:00[+02:00] + // { [[Z]]: false, [[OffsetString]]: "+00:00", [[Name]]: "+02:00" } + // + // Case 3: 19700101[+02:00] + // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "+02:00" } + // + // Case 4: 19700101Z[Europe/Berlin] + // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" } + // + // Case 5: 19700101+00:00[Europe/Berlin] + // { [[Z]]: false, [[OffsetString]]: "+00:00", [[Name]]: "Europe/Berlin" } + // + // Case 6: 19700101[Europe/Berlin] + // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" } + + // Steps 6.b-c. + bool isUTC; + bool hasOffset; + int64_t timeZoneOffset; + Rooted<ParsedTimeZone> timeZoneString(cx); + Rooted<JSString*> calendarString(cx); + if (!ParseTemporalZonedDateTimeString(cx, string, &dateTime, &isUTC, + &hasOffset, &timeZoneOffset, + &timeZoneString, &calendarString)) { + return false; + } + + // Step 6.d. + MOZ_ASSERT(timeZoneString); + + // Step 6.e. + if (!ToTemporalTimeZone(cx, timeZoneString, &timeZone)) { + return false; + } + + // Step 6.f. (Not applicable in our implementation.) + + // Step 6.g. + if (isUTC) { + offsetBehaviour = OffsetBehaviour::Exact; + } + + // Step 6.h. + else if (!hasOffset) { + offsetBehaviour = OffsetBehaviour::Wall; + } + + // Steps 6.i-l. + if (calendarString) { + if (!ToBuiltinCalendar(cx, calendarString, &calendar)) { + return false; + } + } else { + calendar.set(CalendarValue(cx->names().iso8601)); + } + + // Step 6.m. + matchBehaviour = MatchBehaviour::MatchMinutes; + + if (maybeResolvedOptions) { + // Step 6.n. + if (!ToTemporalDisambiguation(cx, maybeResolvedOptions, + &disambiguation)) { + return false; + } + + // Step 6.o. + if (!ToTemporalOffset(cx, maybeResolvedOptions, &offsetOption)) { + return false; + } + + // Step 6.p. + TemporalOverflow ignored; + if (!ToTemporalOverflow(cx, maybeResolvedOptions, &ignored)) { + return false; + } + } + + // Step 8. + if (offsetBehaviour == OffsetBehaviour::Option) { + MOZ_ASSERT(hasOffset); + offsetNanoseconds = timeZoneOffset; + } + } + + // Step 9. + Rooted<TimeZoneRecord> timeZoneRec(cx); + if (!CreateTimeZoneMethodsRecord(cx, timeZone, + { + TimeZoneMethod::GetOffsetNanosecondsFor, + TimeZoneMethod::GetPossibleInstantsFor, + }, + &timeZoneRec)) { + return false; + } + + // Step 10. + Instant epochNanoseconds; + if (!InterpretISODateTimeOffset( + cx, dateTime, offsetBehaviour, offsetNanoseconds, timeZoneRec, + disambiguation, offsetOption, matchBehaviour, &epochNanoseconds)) { + return false; + } + + // Step 11. + result.set(ZonedDateTime{epochNanoseconds, timeZone, calendar}); + return true; +} + +/** + * ToTemporalZonedDateTime ( item [ , options ] ) + */ +static bool ToTemporalZonedDateTime(JSContext* cx, Handle<Value> item, + MutableHandle<ZonedDateTime> result) { + return ToTemporalZonedDateTime(cx, item, nullptr, result); +} + +/** + * ToTemporalZonedDateTime ( item [ , options ] ) + */ +static ZonedDateTimeObject* ToTemporalZonedDateTime( + JSContext* cx, Handle<Value> item, Handle<JSObject*> maybeOptions) { + Rooted<ZonedDateTime> result(cx); + if (!ToTemporalZonedDateTime(cx, item, maybeOptions, &result)) { + return nullptr; + } + return CreateTemporalZonedDateTime(cx, result.instant(), result.timeZone(), + result.calendar()); +} + +/** + * CreateTemporalZonedDateTime ( epochNanoseconds, timeZone, calendar [ , + * newTarget ] ) + */ +static ZonedDateTimeObject* CreateTemporalZonedDateTime( + JSContext* cx, const CallArgs& args, Handle<BigInt*> epochNanoseconds, + Handle<TimeZoneValue> timeZone, Handle<CalendarValue> calendar) { + // Step 1. + MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds)); + + // Steps 3-4. + Rooted<JSObject*> proto(cx); + if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ZonedDateTime, + &proto)) { + return nullptr; + } + + auto* obj = NewObjectWithClassProto<ZonedDateTimeObject>(cx, proto); + if (!obj) { + return nullptr; + } + + // Step 4. + auto instant = ToInstant(epochNanoseconds); + obj->setFixedSlot(ZonedDateTimeObject::SECONDS_SLOT, + NumberValue(instant.seconds)); + obj->setFixedSlot(ZonedDateTimeObject::NANOSECONDS_SLOT, + Int32Value(instant.nanoseconds)); + + // Step 5. + obj->setFixedSlot(ZonedDateTimeObject::TIMEZONE_SLOT, timeZone.toSlotValue()); + + // Step 6. + obj->setFixedSlot(ZonedDateTimeObject::CALENDAR_SLOT, calendar.toValue()); + + // Step 7. + return obj; +} + +/** + * CreateTemporalZonedDateTime ( epochNanoseconds, timeZone, calendar [ , + * newTarget ] ) + */ +ZonedDateTimeObject* js::temporal::CreateTemporalZonedDateTime( + JSContext* cx, const Instant& instant, Handle<TimeZoneValue> timeZone, + Handle<CalendarValue> calendar) { + // Step 1. + MOZ_ASSERT(IsValidEpochInstant(instant)); + + // Steps 2-3. + auto* obj = NewBuiltinClassInstance<ZonedDateTimeObject>(cx); + if (!obj) { + return nullptr; + } + + // Step 4. + obj->setFixedSlot(ZonedDateTimeObject::SECONDS_SLOT, + NumberValue(instant.seconds)); + obj->setFixedSlot(ZonedDateTimeObject::NANOSECONDS_SLOT, + Int32Value(instant.nanoseconds)); + + // Step 5. + obj->setFixedSlot(ZonedDateTimeObject::TIMEZONE_SLOT, timeZone.toSlotValue()); + + // Step 6. + obj->setFixedSlot(ZonedDateTimeObject::CALENDAR_SLOT, calendar.toValue()); + + // Step 7. + return obj; +} + +struct PlainDateTimeAndInstant { + PlainDateTime dateTime; + Instant instant; +}; + +/** + * AddDaysToZonedDateTime ( instant, dateTime, timeZoneRec, calendar, days [ , + * overflow ] ) + */ +static bool AddDaysToZonedDateTime(JSContext* cx, const Instant& instant, + const PlainDateTime& dateTime, + Handle<TimeZoneRecord> timeZone, + Handle<CalendarValue> calendar, double days, + TemporalOverflow overflow, + PlainDateTimeAndInstant* result) { + // Step 1. (Not applicable in our implementation.) + + // Step 2. (Not applicable) + + // Step 3. + if (days == 0) { + *result = {dateTime, instant}; + return true; + } + + // Step 4. + PlainDate addedDate; + if (!AddISODate(cx, dateTime.date, {0, 0, 0, days}, overflow, &addedDate)) { + return false; + } + + // Step 5. + Rooted<PlainDateTimeWithCalendar> dateTimeResult(cx); + if (!CreateTemporalDateTime(cx, {addedDate, dateTime.time}, calendar, + &dateTimeResult)) { + return false; + } + + // Step 6. + Instant instantResult; + if (!GetInstantFor(cx, timeZone, dateTimeResult, + TemporalDisambiguation::Compatible, &instantResult)) { + return false; + } + + // Step 7. + *result = {ToPlainDateTime(dateTimeResult), instantResult}; + return true; +} + +/** + * AddDaysToZonedDateTime ( instant, dateTime, timeZoneRec, calendar, days [ , + * overflow ] ) + */ +bool js::temporal::AddDaysToZonedDateTime( + JSContext* cx, const Instant& instant, const PlainDateTime& dateTime, + Handle<TimeZoneRecord> timeZone, Handle<CalendarValue> calendar, + double days, TemporalOverflow overflow, Instant* result) { + // Steps 1-7. + PlainDateTimeAndInstant dateTimeAndInstant; + if (!::AddDaysToZonedDateTime(cx, instant, dateTime, timeZone, calendar, days, + overflow, &dateTimeAndInstant)) { + return false; + } + + *result = dateTimeAndInstant.instant; + return true; +} + +/** + * AddDaysToZonedDateTime ( instant, dateTime, timeZoneRec, calendar, days [ , + * overflow ] ) + */ +bool js::temporal::AddDaysToZonedDateTime(JSContext* cx, const Instant& instant, + const PlainDateTime& dateTime, + Handle<TimeZoneRecord> timeZone, + Handle<CalendarValue> calendar, + double days, Instant* result) { + // Step 2. + auto overflow = TemporalOverflow::Constrain; + + // Steps 1 and 3-7. + return AddDaysToZonedDateTime(cx, instant, dateTime, timeZone, calendar, days, + overflow, result); +} + +/** + * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months, + * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds + * [ , precalculatedPlainDateTime [ , options ] ] ) + */ +static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds, + Handle<TimeZoneRecord> timeZone, + Handle<CalendarRecord> calendar, + const Duration& duration, + mozilla::Maybe<const PlainDateTime&> dateTime, + Handle<JSObject*> maybeOptions, Instant* result) { + MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds)); + MOZ_ASSERT(IsValidDuration(duration.date())); + MOZ_ASSERT(IsValidDuration(duration.time())); + + // Step 1. + MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp( + timeZone, TimeZoneMethod::GetPossibleInstantsFor)); + + // Steps 2-3. + MOZ_ASSERT_IF(!dateTime, + TimeZoneMethodsRecordHasLookedUp( + timeZone, TimeZoneMethod::GetOffsetNanosecondsFor)); + + // Steps 4-5. (Not applicable in our implementation) + + // Step 6. + if (duration.years == 0 && duration.months == 0 && duration.weeks == 0 && + duration.days == 0) { + // Step 6.a. + return AddInstant(cx, epochNanoseconds, duration, result); + } + + // Step 7. (Not applicable in our implementation) + + // Steps 8-9. + PlainDateTime temporalDateTime; + if (dateTime) { + // Step 8.a. + temporalDateTime = *dateTime; + } else { + // Step 9.a. + if (!GetPlainDateTimeFor(cx, timeZone, epochNanoseconds, + &temporalDateTime)) { + return false; + } + } + auto& [date, time] = temporalDateTime; + + // Step 10. + if (duration.years == 0 && duration.months == 0 && duration.weeks == 0) { + // Step 10.a. + auto overflow = TemporalOverflow::Constrain; + if (maybeOptions) { + if (!ToTemporalOverflow(cx, maybeOptions, &overflow)) { + return false; + } + } + + // Step 10.b. + Instant intermediate; + if (!AddDaysToZonedDateTime(cx, epochNanoseconds, temporalDateTime, + timeZone, calendar.receiver(), duration.days, + overflow, &intermediate)) { + return false; + } + + // Step 10.c. + return AddInstant(cx, intermediate, duration.time(), result); + } + + // Step 11. + MOZ_ASSERT( + CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd)); + + // Step 12. + const auto& datePart = date; + + // Step 13. + auto dateDuration = duration.date(); + + // Step 14. + PlainDate addedDate; + if (maybeOptions) { + if (!CalendarDateAdd(cx, calendar, datePart, dateDuration, maybeOptions, + &addedDate)) { + return false; + } + } else { + if (!CalendarDateAdd(cx, calendar, datePart, dateDuration, &addedDate)) { + return false; + } + } + + // Step 15. + Rooted<PlainDateTimeWithCalendar> intermediateDateTime(cx); + if (!CreateTemporalDateTime(cx, {addedDate, time}, calendar.receiver(), + &intermediateDateTime)) { + return false; + } + + // Step 16. + Instant intermediateInstant; + if (!GetInstantFor(cx, timeZone, intermediateDateTime, + TemporalDisambiguation::Compatible, + &intermediateInstant)) { + return false; + } + + // Step 17. + return AddInstant(cx, intermediateInstant, duration.time(), result); +} + +/** + * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months, + * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds + * [ , precalculatedPlainDateTime [ , options ] ] ) + */ +static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds, + Handle<TimeZoneRecord> timeZone, + Handle<CalendarRecord> calendar, + const Duration& duration, + Handle<JSObject*> maybeOptions, Instant* result) { + return ::AddZonedDateTime(cx, epochNanoseconds, timeZone, calendar, duration, + mozilla::Nothing(), maybeOptions, result); +} + +/** + * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months, + * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds + * [ , precalculatedPlainDateTime [ , options ] ] ) + */ +bool js::temporal::AddZonedDateTime(JSContext* cx, + const Instant& epochNanoseconds, + Handle<TimeZoneRecord> timeZone, + Handle<CalendarRecord> calendar, + const Duration& duration, Instant* result) { + return ::AddZonedDateTime(cx, epochNanoseconds, timeZone, calendar, duration, + mozilla::Nothing(), nullptr, result); +} + +/** + * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months, + * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds + * [ , precalculatedPlainDateTime [ , options ] ] ) + */ +bool js::temporal::AddZonedDateTime( + JSContext* cx, const Instant& epochNanoseconds, + Handle<TimeZoneRecord> timeZone, Handle<CalendarRecord> calendar, + const Duration& duration, const PlainDateTime& dateTime, Instant* result) { + return ::AddZonedDateTime(cx, epochNanoseconds, timeZone, calendar, duration, + mozilla::SomeRef(dateTime), nullptr, result); +} + +double js::temporal::NanosecondsAndDays::daysNumber() const { + if (days) { + return BigInt::numberValue(days); + } + return double(daysInt); +} + +void js::temporal::NanosecondsAndDays::trace(JSTracer* trc) { + if (days) { + TraceRoot(trc, &days, "NanosecondsAndDays::days"); + } +} + +/** + * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ , + * precalculatedPlainDateTime ] ) + */ +static bool NanosecondsToDays( + JSContext* cx, const InstantSpan& nanoseconds, + Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone, + mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime, + MutableHandle<NanosecondsAndDays> result) { + MOZ_ASSERT(IsValidInstantSpan(nanoseconds)); + + // Step 1. + if (nanoseconds == InstantSpan{}) { + result.set(NanosecondsAndDays::from( + int64_t(0), InstantSpan{}, + InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day)))); + return true; + } + + // Step 2. + int32_t sign = nanoseconds < InstantSpan{} ? -1 : 1; + + // Step 3. + auto startNs = zonedRelativeTo.instant(); + auto calendar = zonedRelativeTo.calendar(); + + // Step 5. + // + // NB: This addition can't overflow, because we've checked that |nanoseconds| + // can be represented as an InstantSpan value. + auto endNs = startNs + nanoseconds; + + // Step 6. + if (!IsValidEpochInstant(endNs)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_INSTANT_INVALID); + return false; + } + + // Steps 4 and 8. + PlainDateTime startDateTime; + if (!precalculatedPlainDateTime) { + if (!GetPlainDateTimeFor(cx, timeZone, startNs, &startDateTime)) { + return false; + } + } else { + startDateTime = *precalculatedPlainDateTime; + } + + // Steps 7 and 9. + PlainDateTime endDateTime; + if (!GetPlainDateTimeFor(cx, timeZone, endNs, &endDateTime)) { + return false; + } + + // Steps 10-11. (Not applicable in our implementation.) + + // Step 12. + // + // Overflows in step 21 can be safely ignored, because they take too long to + // happen for int64. + int64_t days = DaysUntil(startDateTime.date, endDateTime.date); + + // Step 13. + int32_t timeSign = CompareTemporalTime(startDateTime.time, endDateTime.time); + + // Steps 14-15. + if (days > 0 && timeSign > 0) { + days -= 1; + } else if (days < 0 && timeSign < 0) { + days += 1; + } + + // Step 16. + PlainDateTimeAndInstant relativeResult; + if (!::AddDaysToZonedDateTime(cx, startNs, startDateTime, timeZone, calendar, + days, TemporalOverflow::Constrain, + &relativeResult)) { + return false; + } + MOZ_ASSERT(IsValidISODateTime(relativeResult.dateTime)); + MOZ_ASSERT(IsValidEpochInstant(relativeResult.instant)); + + // Step 17. + if (sign > 0) { + // Step 17.a. + while (days > 0 && relativeResult.instant > endNs) { + // This loop can iterate indefinitely when given a specially crafted + // time zone object, so we need to check for interrupts. + if (!CheckForInterrupt(cx)) { + return false; + } + + // Step 17.a.i. + days -= 1; + + // Step 17.a.ii. + if (!::AddDaysToZonedDateTime(cx, startNs, startDateTime, timeZone, + calendar, days, TemporalOverflow::Constrain, + &relativeResult)) { + return false; + } + MOZ_ASSERT(IsValidISODateTime(relativeResult.dateTime)); + MOZ_ASSERT(IsValidEpochInstant(relativeResult.instant)); + } + + MOZ_ASSERT_IF(days > 0, relativeResult.instant <= endNs); + } + + MOZ_ASSERT_IF(days == 0, relativeResult.instant == startNs); + + // Step 18. + auto ns = endNs - relativeResult.instant; + MOZ_ASSERT(IsValidInstantSpan(ns)); + + // Steps 19-21. + InstantSpan dayLengthNs{}; + while (true) { + // This loop can iterate indefinitely when given a specially crafted time + // zone object, so we need to check for interrupts. + if (!CheckForInterrupt(cx)) { + return false; + } + + // Step 21.a. + PlainDateTimeAndInstant oneDayFarther; + if (!::AddDaysToZonedDateTime( + cx, relativeResult.instant, relativeResult.dateTime, timeZone, + calendar, sign, TemporalOverflow::Constrain, &oneDayFarther)) { + return false; + } + MOZ_ASSERT(IsValidISODateTime(oneDayFarther.dateTime)); + MOZ_ASSERT(IsValidEpochInstant(oneDayFarther.instant)); + + // Step 21.b. + dayLengthNs = oneDayFarther.instant - relativeResult.instant; + MOZ_ASSERT(IsValidInstantSpan(dayLengthNs)); + + // clang-format off + // + // First iteration: + // + // ns = endNs - relativeResult.instant + // dayLengthNs = oneDayFarther.instant - relativeResult.instant + // diff = ns - dayLengthNs + // = (endNs - relativeResult.instant) - (oneDayFarther.instant - relativeResult.instant) + // = endNs - relativeResult.instant - oneDayFarther.instant + relativeResult.instant + // = endNs - oneDayFarther.instant + // + // Second iteration: + // + // ns = diff' + // = endNs - oneDayFarther.instant' + // relativeResult.instant = oneDayFarther.instant' + // dayLengthNs = oneDayFarther.instant - relativeResult.instant + // = oneDayFarther.instant - oneDayFarther.instant' + // diff = ns - dayLengthNs + // = (endNs - oneDayFarther.instant') - (oneDayFarther.instant - oneDayFarther.instant') + // = endNs - oneDayFarther.instant' - oneDayFarther.instant + oneDayFarther.instant' + // = endNs - oneDayFarther.instant + // + // Where |diff'| and |oneDayFarther.instant'| denote the variables from the + // previous iteration. + // + // This repeats for all following iterations. + // + // |endNs| and |oneDayFarther.instant| are both valid epoch instant values, + // so the difference is a valid epoch instant difference value, too. + // + // clang-format on + + // Step 21.c. + auto diff = ns - dayLengthNs; + MOZ_ASSERT(IsValidInstantSpan(diff)); + MOZ_ASSERT(diff == (endNs - oneDayFarther.instant)); + + if (diff == InstantSpan{} || ((diff < InstantSpan{}) == (sign < 0))) { + // Step 21.c.i. + ns = diff; + + // Step 21.c.ii. + relativeResult = oneDayFarther; + + // Step 21.c.iii. + days += sign; + } else { + // Step 21.d. + break; + } + } + + // Step 22. + if (days < 0 && sign > 0) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_ZONED_DATE_TIME_INCORRECT_SIGN, + "days"); + return false; + } + + // Step 23. + if (days > 0 && sign < 0) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_ZONED_DATE_TIME_INCORRECT_SIGN, + "days"); + return false; + } + + MOZ_ASSERT(IsValidInstantSpan(dayLengthNs)); + MOZ_ASSERT(IsValidInstantSpan(ns)); + + // FIXME: spec issue - rewrite steps 24-25 as: + // + // If sign = -1, then + // If nanoseconds > 0, throw a RangeError. + // Else, + // Assert: nanoseconds ≥ 0. + // + // https://github.com/tc39/proposal-temporal/issues/2530 + + // Steps 24-25. + if (sign < 0) { + if (ns > InstantSpan{}) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_ZONED_DATE_TIME_INCORRECT_SIGN, + "nanoseconds"); + return false; + } + } else { + MOZ_ASSERT(ns >= InstantSpan{}); + } + + // Step 26. + MOZ_ASSERT(ns.abs() < dayLengthNs.abs()); + + // Step 27. + result.set(NanosecondsAndDays::from(days, ns, dayLengthNs.abs())); + return true; +} + +/** + * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ , + * precalculatedPlainDateTime ] ) + */ +bool js::temporal::NanosecondsToDays(JSContext* cx, + const InstantSpan& nanoseconds, + Handle<ZonedDateTime> zonedRelativeTo, + Handle<TimeZoneRecord> timeZone, + MutableHandle<NanosecondsAndDays> result) { + return ::NanosecondsToDays(cx, nanoseconds, zonedRelativeTo, timeZone, + mozilla::Nothing(), result); +} + +/** + * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ , + * precalculatedPlainDateTime ] ) + */ +bool js::temporal::NanosecondsToDays( + JSContext* cx, const InstantSpan& nanoseconds, + Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone, + const PlainDateTime& precalculatedPlainDateTime, + MutableHandle<NanosecondsAndDays> result) { + return ::NanosecondsToDays(cx, nanoseconds, zonedRelativeTo, timeZone, + mozilla::SomeRef(precalculatedPlainDateTime), + result); +} + +/** + * DifferenceZonedDateTime ( ns1, ns2, timeZoneRec, calendarRec, largestUnit, + * options, precalculatedPlainDateTime ) + */ +static bool DifferenceZonedDateTime( + JSContext* cx, const Instant& ns1, const Instant& ns2, + Handle<TimeZoneRecord> timeZone, Handle<CalendarRecord> calendar, + TemporalUnit largestUnit, Handle<PlainObject*> maybeOptions, + mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime, + Duration* result) { + MOZ_ASSERT(IsValidEpochInstant(ns1)); + MOZ_ASSERT(IsValidEpochInstant(ns2)); + + // Steps 1. + if (ns1 == ns2) { + *result = {}; + return true; + } + + // Steps 2-3. + PlainDateTime startDateTime; + if (!precalculatedPlainDateTime) { + // Steps 2.a-b. + if (!GetPlainDateTimeFor(cx, timeZone, ns1, &startDateTime)) { + return false; + } + } else { + startDateTime = *precalculatedPlainDateTime; + } + + // Steps 4-5. + PlainDateTime endDateTime; + if (!GetPlainDateTimeFor(cx, timeZone, ns2, &endDateTime)) { + return false; + } + + // Step 6. + Duration dateDifference; + if (maybeOptions) { + if (!DifferenceISODateTime(cx, startDateTime, endDateTime, calendar, + largestUnit, maybeOptions, &dateDifference)) { + return false; + } + } else { + if (!DifferenceISODateTime(cx, startDateTime, endDateTime, calendar, + largestUnit, &dateDifference)) { + return false; + } + } + + // Step 7. + Instant intermediateNs; + if (!AddZonedDateTime(cx, ns1, timeZone, calendar, + { + dateDifference.years, + dateDifference.months, + dateDifference.weeks, + }, + startDateTime, &intermediateNs)) { + return false; + } + MOZ_ASSERT(IsValidEpochInstant(intermediateNs)); + + // Step 8. + auto timeRemainder = ns2 - intermediateNs; + MOZ_ASSERT(IsValidInstantSpan(timeRemainder)); + + // Step 9. + Rooted<ZonedDateTime> intermediate( + cx, + ZonedDateTime{intermediateNs, timeZone.receiver(), calendar.receiver()}); + + // Step 10. + Rooted<NanosecondsAndDays> nanosAndDays(cx); + if (!NanosecondsToDays(cx, timeRemainder, intermediate, timeZone, + &nanosAndDays)) { + return false; + } + + // Step 11. + TimeDuration timeDifference; + if (!BalanceTimeDuration(cx, nanosAndDays.nanoseconds(), TemporalUnit::Hour, + &timeDifference)) { + return false; + } + + // Step 12. + *result = { + dateDifference.years, dateDifference.months, + dateDifference.weeks, nanosAndDays.daysNumber(), + timeDifference.hours, timeDifference.minutes, + timeDifference.seconds, timeDifference.milliseconds, + timeDifference.microseconds, timeDifference.nanoseconds, + }; + MOZ_ASSERT(IsValidDuration(*result)); + return true; +} + +/** + * DifferenceZonedDateTime ( ns1, ns2, timeZoneRec, calendarRec, largestUnit, + * options, precalculatedPlainDateTime ) + */ +bool js::temporal::DifferenceZonedDateTime( + JSContext* cx, const Instant& ns1, const Instant& ns2, + Handle<TimeZoneRecord> timeZone, Handle<CalendarRecord> calendar, + TemporalUnit largestUnit, const PlainDateTime& precalculatedPlainDateTime, + Duration* result) { + return ::DifferenceZonedDateTime( + cx, ns1, ns2, timeZone, calendar, largestUnit, nullptr, + mozilla::SomeRef(precalculatedPlainDateTime), result); +} + +/** + * TimeZoneEquals ( one, two ) + */ +static bool TimeZoneEqualsOrThrow(JSContext* cx, Handle<TimeZoneValue> one, + Handle<TimeZoneValue> two) { + // Step 1. + if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) { + return true; + } + + // Step 2. + Rooted<JSString*> timeZoneOne(cx, ToTemporalTimeZoneIdentifier(cx, one)); + if (!timeZoneOne) { + return false; + } + + // Step 3. + Rooted<JSString*> timeZoneTwo(cx, ToTemporalTimeZoneIdentifier(cx, two)); + if (!timeZoneTwo) { + return false; + } + + // Steps 4-9. + bool equals; + if (!TimeZoneEquals(cx, timeZoneOne, timeZoneTwo, &equals)) { + return false; + } + if (equals) { + return true; + } + + // Throw an error when the time zone identifiers don't match. Used when + // unequal time zones throw a RangeError. + if (auto charsOne = QuoteString(cx, timeZoneOne)) { + if (auto charsTwo = QuoteString(cx, timeZoneTwo)) { + JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_TIMEZONE_INCOMPATIBLE, + charsOne.get(), charsTwo.get()); + } + } + return false; +} + +/** + * RoundISODateTime ( year, month, day, hour, minute, second, millisecond, + * microsecond, nanosecond, increment, unit, roundingMode [ , dayLength ] ) + */ +static bool RoundISODateTime(JSContext* cx, const PlainDateTime& dateTime, + Increment increment, TemporalUnit unit, + TemporalRoundingMode roundingMode, + const InstantSpan& dayLength, + PlainDateTime* result) { + MOZ_ASSERT(IsValidInstantSpan(dayLength)); + MOZ_ASSERT(dayLength > (InstantSpan{})); + + const auto& [date, time] = dateTime; + + // Step 1. + MOZ_ASSERT(IsValidISODateTime(dateTime)); + MOZ_ASSERT(ISODateTimeWithinLimits(dateTime)); + + // Step 2. (Not applicable in our implementation.) + + // Step 3. + auto roundedTime = RoundTime(time, increment, unit, roundingMode, dayLength); + + // |dayLength| can be as small as 1, so the number of rounded days can be as + // large as the number of nanoseconds in |time|. + MOZ_ASSERT(0 <= roundedTime.days && + roundedTime.days < ToNanoseconds(TemporalUnit::Day)); + + // Step 4. + PlainDate balanceResult; + if (!BalanceISODate(cx, date.year, date.month, + int64_t(date.day) + roundedTime.days, &balanceResult)) { + return false; + } + + // Step 5. + *result = {balanceResult, roundedTime.time}; + return true; +} + +/** + * DifferenceTemporalZonedDateTime ( operation, zonedDateTime, other, options ) + */ +static bool DifferenceTemporalZonedDateTime(JSContext* cx, + TemporalDifference operation, + const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Step 1. (Not applicable in our implementation.) + + // Step 2. + Rooted<ZonedDateTime> other(cx); + if (!ToTemporalZonedDateTime(cx, args.get(0), &other)) { + return false; + } + + // Step 3. + if (!CalendarEqualsOrThrow(cx, zonedDateTime.calendar(), other.calendar())) { + return false; + } + + // Steps 4-5. + Rooted<PlainObject*> resolvedOptions(cx); + DifferenceSettings settings; + if (args.hasDefined(1)) { + Rooted<JSObject*> options( + cx, RequireObjectArg(cx, "options", ToName(operation), args[1])); + if (!options) { + return false; + } + + // Step 4. + resolvedOptions = SnapshotOwnProperties(cx, options); + if (!resolvedOptions) { + return false; + } + + // Step 5. + if (!GetDifferenceSettings( + cx, operation, resolvedOptions, TemporalUnitGroup::DateTime, + TemporalUnit::Nanosecond, TemporalUnit::Hour, &settings)) { + return false; + } + } else { + // Steps 4-5. + settings = { + TemporalUnit::Nanosecond, + TemporalUnit::Hour, + TemporalRoundingMode::Trunc, + Increment{1}, + }; + } + + // Step 6. + if (settings.largestUnit > TemporalUnit::Day) { + MOZ_ASSERT(settings.smallestUnit >= settings.largestUnit); + + // Step 6.a. + Duration difference; + if (!DifferenceInstant(cx, zonedDateTime.instant(), other.instant(), + settings.roundingIncrement, settings.smallestUnit, + settings.largestUnit, settings.roundingMode, + &difference)) { + return false; + } + + // Step 6.b. + if (operation == TemporalDifference::Since) { + difference = difference.negate(); + } + + auto* result = CreateTemporalDuration(cx, difference); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; + } + + // FIXME: spec issue - move this step next to the calendar validation? + // https://github.com/tc39/proposal-temporal/issues/2533 + + // Step 7. + if (!TimeZoneEqualsOrThrow(cx, zonedDateTime.timeZone(), other.timeZone())) { + return false; + } + + // Step 8. + if (zonedDateTime.instant() == other.instant()) { + auto* obj = CreateTemporalDuration(cx, {}); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; + } + + // Step 9. + Rooted<TimeZoneRecord> timeZone(cx); + if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(), + { + TimeZoneMethod::GetOffsetNanosecondsFor, + TimeZoneMethod::GetPossibleInstantsFor, + }, + &timeZone)) { + return false; + } + + // Step 10. + Rooted<CalendarRecord> calendar(cx); + if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(), + { + CalendarMethod::DateAdd, + CalendarMethod::DateUntil, + }, + &calendar)) { + return false; + } + + // Steps 11-12. + PlainDateTime precalculatedPlainDateTime; + if (!GetPlainDateTimeFor(cx, timeZone, zonedDateTime.instant(), + &precalculatedPlainDateTime)) { + return false; + } + + // Step 13. + Rooted<PlainDateObject*> plainRelativeTo( + cx, CreateTemporalDate(cx, precalculatedPlainDateTime.date, + calendar.receiver())); + if (!plainRelativeTo) { + return false; + } + + // Step 14. + if (resolvedOptions) { + Rooted<Value> largestUnitValue( + cx, StringValue(TemporalUnitToString(cx, settings.largestUnit))); + if (!DefineDataProperty(cx, resolvedOptions, cx->names().largestUnit, + largestUnitValue)) { + return false; + } + } + + // Step 15. + Duration difference; + if (!::DifferenceZonedDateTime( + cx, zonedDateTime.instant(), other.instant(), timeZone, calendar, + settings.largestUnit, resolvedOptions, + mozilla::SomeRef<const PlainDateTime>(precalculatedPlainDateTime), + &difference)) { + return false; + } + + // Step 16. + bool roundingGranularityIsNoop = + settings.smallestUnit == TemporalUnit::Nanosecond && + settings.roundingIncrement == Increment{1}; + + // Step 17. + if (roundingGranularityIsNoop) { + if (operation == TemporalDifference::Since) { + difference = difference.negate(); + } + + auto* obj = CreateTemporalDuration(cx, difference); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; + } + + // Steps 18-19. + Duration roundResult; + if (!RoundDuration(cx, difference, settings.roundingIncrement, + settings.smallestUnit, settings.roundingMode, + plainRelativeTo, calendar, zonedDateTime, timeZone, + precalculatedPlainDateTime, &roundResult)) { + return false; + } + + // Step 20. + Duration adjustResult; + if (!AdjustRoundedDurationDays(cx, roundResult, settings.roundingIncrement, + settings.smallestUnit, settings.roundingMode, + zonedDateTime, calendar, timeZone, + precalculatedPlainDateTime, &adjustResult)) { + return false; + } + + // Step 21. + DateDuration balanceResult; + if (!temporal::BalanceDateDurationRelative( + cx, adjustResult.date(), settings.largestUnit, settings.smallestUnit, + plainRelativeTo, calendar, &balanceResult)) { + return false; + } + + // Step 22. + auto result = Duration{ + balanceResult.years, balanceResult.months, + balanceResult.weeks, balanceResult.days, + adjustResult.hours, adjustResult.minutes, + adjustResult.seconds, adjustResult.milliseconds, + adjustResult.microseconds, adjustResult.nanoseconds, + }; + if (operation == TemporalDifference::Since) { + result = result.negate(); + } + + auto* obj = CreateTemporalDuration(cx, result); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +enum class ZonedDateTimeDuration { Add, Subtract }; + +/** + * AddDurationToOrSubtractDurationFromZonedDateTime ( operation, zonedDateTime, + * temporalDurationLike, options ) + */ +static bool AddDurationToOrSubtractDurationFromZonedDateTime( + JSContext* cx, ZonedDateTimeDuration operation, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, &args.thisv().toObject().as<ZonedDateTimeObject>()); + + // Step 1. (Not applicable in our implementation.) + + // Step 2. + Duration duration; + if (!ToTemporalDurationRecord(cx, args.get(0), &duration)) { + return false; + } + + // Step 3. + Rooted<JSObject*> options(cx); + if (args.hasDefined(1)) { + const char* name = + operation == ZonedDateTimeDuration::Add ? "add" : "subtract"; + options = RequireObjectArg(cx, "options", name, args[1]); + } else { + options = NewPlainObjectWithProto(cx, nullptr); + } + if (!options) { + return false; + } + + // Step 4. + Rooted<TimeZoneRecord> timeZone(cx); + if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(), + { + TimeZoneMethod::GetOffsetNanosecondsFor, + TimeZoneMethod::GetPossibleInstantsFor, + }, + &timeZone)) { + return false; + } + + // Step 5. + Rooted<CalendarRecord> calendar(cx); + if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(), + { + CalendarMethod::DateAdd, + }, + &calendar)) { + return false; + } + + // Step 6. + if (operation == ZonedDateTimeDuration::Subtract) { + duration = duration.negate(); + } + + Instant resultInstant; + if (!::AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar, + duration, options, &resultInstant)) { + return false; + } + MOZ_ASSERT(IsValidEpochInstant(resultInstant)); + + // Step 7. + auto* result = CreateTemporalZonedDateTime( + cx, resultInstant, timeZone.receiver(), calendar.receiver()); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.ZonedDateTime ( epochNanoseconds, timeZoneLike [ , calendarLike ] ) + */ +static bool ZonedDateTimeConstructor(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + if (!ThrowIfNotConstructing(cx, args, "Temporal.ZonedDateTime")) { + return false; + } + + // Step 2. + Rooted<BigInt*> 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. + Rooted<TimeZoneValue> timeZone(cx); + if (!ToTemporalTimeZone(cx, args.get(1), &timeZone)) { + return false; + } + + // Step 5. + Rooted<CalendarValue> calendar(cx); + if (!ToTemporalCalendarWithISODefault(cx, args.get(2), &calendar)) { + return false; + } + + // Step 6. + auto* obj = CreateTemporalZonedDateTime(cx, args, epochNanoseconds, timeZone, + calendar); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.ZonedDateTime.from ( item [ , options ] ) + */ +static bool ZonedDateTime_from(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + Rooted<JSObject*> options(cx); + if (args.hasDefined(1)) { + options = RequireObjectArg(cx, "options", "from", args[1]); + if (!options) { + return false; + } + } + + // Step 2. + if (args.get(0).isObject()) { + JSObject* item = &args[0].toObject(); + if (auto* zonedDateTime = item->maybeUnwrapIf<ZonedDateTimeObject>()) { + auto epochInstant = ToInstant(zonedDateTime); + Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone()); + Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar()); + + if (!timeZone.wrap(cx)) { + return false; + } + if (!calendar.wrap(cx)) { + return false; + } + + if (options) { + // Steps 2.a-b. + TemporalDisambiguation ignoredDisambiguation; + if (!ToTemporalDisambiguation(cx, options, &ignoredDisambiguation)) { + return false; + } + + // Step 2.c. + TemporalOffset ignoredOffset; + if (!ToTemporalOffset(cx, options, &ignoredOffset)) { + return false; + } + + // Step 2.d. + TemporalOverflow ignoredOverflow; + if (!ToTemporalOverflow(cx, options, &ignoredOverflow)) { + return false; + } + } + + // Step 2.e. + auto* result = + CreateTemporalZonedDateTime(cx, epochInstant, timeZone, calendar); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; + } + } + + // Step 3. + auto* result = ToTemporalZonedDateTime(cx, args.get(0), options); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.ZonedDateTime.compare ( one, two ) + */ +static bool ZonedDateTime_compare(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + Rooted<ZonedDateTime> one(cx); + if (!ToTemporalZonedDateTime(cx, args.get(0), &one)) { + return false; + } + + // Step 2. + Rooted<ZonedDateTime> two(cx); + if (!ToTemporalZonedDateTime(cx, args.get(1), &two)) { + return false; + } + + // Step 3. + auto oneNs = one.instant(); + auto twoNs = two.instant(); + args.rval().setInt32(oneNs > twoNs ? 1 : oneNs < twoNs ? -1 : 0); + return true; +} + +/** + * get Temporal.ZonedDateTime.prototype.calendarId + */ +static bool ZonedDateTime_calendarId(JSContext* cx, const CallArgs& args) { + auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>(); + + // Step 3. + Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar()); + auto* calendarId = ToTemporalCalendarIdentifier(cx, calendar); + if (!calendarId) { + return false; + } + + args.rval().setString(calendarId); + return true; +} + +/** + * get Temporal.ZonedDateTime.prototype.calendarId + */ +static bool ZonedDateTime_calendarId(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_calendarId>(cx, + args); +} + +/** + * get Temporal.ZonedDateTime.prototype.timeZoneId + */ +static bool ZonedDateTime_timeZoneId(JSContext* cx, const CallArgs& args) { + auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>(); + + // Step 3. + Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone()); + auto* timeZoneId = ToTemporalTimeZoneIdentifier(cx, timeZone); + if (!timeZoneId) { + return false; + } + + args.rval().setString(timeZoneId); + return true; +} + +/** + * get Temporal.ZonedDateTime.prototype.timeZoneId + */ +static bool ZonedDateTime_timeZoneId(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_timeZoneId>(cx, + args); +} + +/** + * get Temporal.ZonedDateTime.prototype.year + */ +static bool ZonedDateTime_year(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + return CalendarYear(cx, zonedDateTime.calendar(), dateTime, args.rval()); +} + +/** + * get Temporal.ZonedDateTime.prototype.year + */ +static bool ZonedDateTime_year(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_year>(cx, args); +} + +/** + * get Temporal.ZonedDateTime.prototype.month + */ +static bool ZonedDateTime_month(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + return CalendarMonth(cx, zonedDateTime.calendar(), dateTime, args.rval()); +} + +/** + * get Temporal.ZonedDateTime.prototype.month + */ +static bool ZonedDateTime_month(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_month>(cx, args); +} + +/** + * get Temporal.ZonedDateTime.prototype.monthCode + */ +static bool ZonedDateTime_monthCode(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + return CalendarMonthCode(cx, zonedDateTime.calendar(), dateTime, args.rval()); +} + +/** + * get Temporal.ZonedDateTime.prototype.monthCode + */ +static bool ZonedDateTime_monthCode(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_monthCode>(cx, + args); +} + +/** + * get Temporal.ZonedDateTime.prototype.day + */ +static bool ZonedDateTime_day(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Step 4. (Reordered) + Rooted<CalendarRecord> calendar(cx); + if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(), + { + CalendarMethod::Day, + }, + &calendar)) { + return false; + } + + // Steps 3 and 5-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + return CalendarDay(cx, calendar, dateTime, args.rval()); +} + +/** + * get Temporal.ZonedDateTime.prototype.day + */ +static bool ZonedDateTime_day(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_day>(cx, args); +} + +/** + * get Temporal.ZonedDateTime.prototype.hour + */ +static bool ZonedDateTime_hour(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + args.rval().setInt32(dateTime.time.hour); + return true; +} + +/** + * get Temporal.ZonedDateTime.prototype.hour + */ +static bool ZonedDateTime_hour(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_hour>(cx, args); +} + +/** + * get Temporal.ZonedDateTime.prototype.minute + */ +static bool ZonedDateTime_minute(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + args.rval().setInt32(dateTime.time.minute); + return true; +} + +/** + * get Temporal.ZonedDateTime.prototype.minute + */ +static bool ZonedDateTime_minute(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_minute>(cx, args); +} + +/** + * get Temporal.ZonedDateTime.prototype.second + */ +static bool ZonedDateTime_second(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + args.rval().setInt32(dateTime.time.second); + return true; +} + +/** + * get Temporal.ZonedDateTime.prototype.second + */ +static bool ZonedDateTime_second(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_second>(cx, args); +} + +/** + * get Temporal.ZonedDateTime.prototype.millisecond + */ +static bool ZonedDateTime_millisecond(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + args.rval().setInt32(dateTime.time.millisecond); + return true; +} + +/** + * get Temporal.ZonedDateTime.prototype.millisecond + */ +static bool ZonedDateTime_millisecond(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_millisecond>(cx, + args); +} + +/** + * get Temporal.ZonedDateTime.prototype.microsecond + */ +static bool ZonedDateTime_microsecond(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + args.rval().setInt32(dateTime.time.microsecond); + return true; +} + +/** + * get Temporal.ZonedDateTime.prototype.microsecond + */ +static bool ZonedDateTime_microsecond(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_microsecond>(cx, + args); +} + +/** + * get Temporal.ZonedDateTime.prototype.nanosecond + */ +static bool ZonedDateTime_nanosecond(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + args.rval().setInt32(dateTime.time.nanosecond); + return true; +} + +/** + * get Temporal.ZonedDateTime.prototype.nanosecond + */ +static bool ZonedDateTime_nanosecond(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_nanosecond>(cx, + args); +} + +/** + * get Temporal.ZonedDateTime.prototype.epochSeconds + */ +static bool ZonedDateTime_epochSeconds(JSContext* cx, const CallArgs& args) { + auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>(); + + // Step 3. + auto instant = ToInstant(zonedDateTime); + + // Steps 4-5. + args.rval().setNumber(instant.seconds); + return true; +} + +/** + * get Temporal.ZonedDateTime.prototype.epochSeconds + */ +static bool ZonedDateTime_epochSeconds(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_epochSeconds>( + cx, args); +} + +/** + * get Temporal.ZonedDateTime.prototype.epochMilliseconds + */ +static bool ZonedDateTime_epochMilliseconds(JSContext* cx, + const CallArgs& args) { + auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>(); + + // Step 3. + auto instant = ToInstant(zonedDateTime); + + // Steps 4-5. + args.rval().setNumber(instant.floorToMilliseconds()); + return true; +} + +/** + * get Temporal.ZonedDateTime.prototype.epochMilliseconds + */ +static bool ZonedDateTime_epochMilliseconds(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_epochMilliseconds>( + cx, args); +} + +/** + * get Temporal.ZonedDateTime.prototype.epochMicroseconds + */ +static bool ZonedDateTime_epochMicroseconds(JSContext* cx, + const CallArgs& args) { + auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>(); + + // Step 3. + auto instant = ToInstant(zonedDateTime); + + // Step 4. + auto* microseconds = + BigInt::createFromInt64(cx, instant.floorToMicroseconds()); + if (!microseconds) { + return false; + } + + // Step 5. + args.rval().setBigInt(microseconds); + return true; +} + +/** + * get Temporal.ZonedDateTime.prototype.epochMicroseconds + */ +static bool ZonedDateTime_epochMicroseconds(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_epochMicroseconds>( + cx, args); +} + +/** + * get Temporal.ZonedDateTime.prototype.epochNanoseconds + */ +static bool ZonedDateTime_epochNanoseconds(JSContext* cx, + const CallArgs& args) { + auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>(); + + // Step 3. + auto* nanoseconds = ToEpochNanoseconds(cx, ToInstant(zonedDateTime)); + if (!nanoseconds) { + return false; + } + + args.rval().setBigInt(nanoseconds); + return true; +} + +/** + * get Temporal.ZonedDateTime.prototype.epochNanoseconds + */ +static bool ZonedDateTime_epochNanoseconds(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_epochNanoseconds>( + cx, args); +} + +/** + * get Temporal.ZonedDateTime.prototype.dayOfWeek + */ +static bool ZonedDateTime_dayOfWeek(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + return CalendarDayOfWeek(cx, zonedDateTime.calendar(), dateTime, args.rval()); +} + +/** + * get Temporal.ZonedDateTime.prototype.dayOfWeek + */ +static bool ZonedDateTime_dayOfWeek(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_dayOfWeek>(cx, + args); +} + +/** + * get Temporal.ZonedDateTime.prototype.dayOfYear + */ +static bool ZonedDateTime_dayOfYear(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + return CalendarDayOfYear(cx, zonedDateTime.calendar(), dateTime, args.rval()); +} + +/** + * get Temporal.ZonedDateTime.prototype.dayOfYear + */ +static bool ZonedDateTime_dayOfYear(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_dayOfYear>(cx, + args); +} + +/** + * get Temporal.ZonedDateTime.prototype.weekOfYear + */ +static bool ZonedDateTime_weekOfYear(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + return CalendarWeekOfYear(cx, zonedDateTime.calendar(), dateTime, + args.rval()); +} + +/** + * get Temporal.ZonedDateTime.prototype.weekOfYear + */ +static bool ZonedDateTime_weekOfYear(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_weekOfYear>(cx, + args); +} + +/** + * get Temporal.ZonedDateTime.prototype.yearOfWeek + */ +static bool ZonedDateTime_yearOfWeek(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + return CalendarYearOfWeek(cx, zonedDateTime.calendar(), dateTime, + args.rval()); +} + +/** + * get Temporal.ZonedDateTime.prototype.yearOfWeek + */ +static bool ZonedDateTime_yearOfWeek(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_yearOfWeek>(cx, + args); +} + +/** + * get Temporal.ZonedDateTime.prototype.hoursInDay + */ +static bool ZonedDateTime_hoursInDay(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Step 3. + Rooted<TimeZoneRecord> timeZone(cx); + if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(), + { + TimeZoneMethod::GetOffsetNanosecondsFor, + TimeZoneMethod::GetPossibleInstantsFor, + }, + &timeZone)) { + return false; + } + + // Step 4. + auto instant = zonedDateTime.instant(); + + // Step 5. + PlainDateTime temporalDateTime; + if (!GetPlainDateTimeFor(cx, timeZone, instant, &temporalDateTime)) { + return false; + } + + // Steps 6-8. + const auto& date = temporalDateTime.date; + Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601)); + + // Step 9. + Rooted<PlainDateTimeWithCalendar> today(cx); + if (!CreateTemporalDateTime(cx, {date, {}}, isoCalendar, &today)) { + return false; + } + + // Step 10. + auto tomorrowFields = BalanceISODate(date.year, date.month, date.day + 1); + + // Step 11. + Rooted<PlainDateTimeWithCalendar> tomorrow(cx); + if (!CreateTemporalDateTime(cx, {tomorrowFields, {}}, isoCalendar, + &tomorrow)) { + return false; + } + + // Step 12. + Instant todayInstant; + if (!GetInstantFor(cx, timeZone, today, TemporalDisambiguation::Compatible, + &todayInstant)) { + return false; + } + + // Step 13. + Instant tomorrowInstant; + if (!GetInstantFor(cx, timeZone, tomorrow, TemporalDisambiguation::Compatible, + &tomorrowInstant)) { + return false; + } + + // Step 14. + auto diffNs = tomorrowInstant - todayInstant; + MOZ_ASSERT(IsValidInstantSpan(diffNs)); + + // Step 15. + constexpr int32_t secPerHour = 60 * 60; + constexpr int64_t nsPerSec = ToNanoseconds(TemporalUnit::Second); + constexpr double nsPerHour = ToNanoseconds(TemporalUnit::Hour); + + int64_t hours = diffNs.seconds / secPerHour; + int64_t seconds = diffNs.seconds % secPerHour; + int64_t nanoseconds = seconds * nsPerSec + diffNs.nanoseconds; + + double result = double(hours) + double(nanoseconds) / nsPerHour; + args.rval().setNumber(result); + return true; +} + +/** + * get Temporal.ZonedDateTime.prototype.hoursInDay + */ +static bool ZonedDateTime_hoursInDay(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_hoursInDay>(cx, + args); +} + +/** + * get Temporal.ZonedDateTime.prototype.daysInWeek + */ +static bool ZonedDateTime_daysInWeek(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + return CalendarDaysInWeek(cx, zonedDateTime.calendar(), dateTime, + args.rval()); +} + +/** + * get Temporal.ZonedDateTime.prototype.daysInWeek + */ +static bool ZonedDateTime_daysInWeek(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_daysInWeek>(cx, + args); +} + +/** + * get Temporal.ZonedDateTime.prototype.daysInMonth + */ +static bool ZonedDateTime_daysInMonth(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + return CalendarDaysInMonth(cx, zonedDateTime.calendar(), dateTime, + args.rval()); +} + +/** + * get Temporal.ZonedDateTime.prototype.daysInMonth + */ +static bool ZonedDateTime_daysInMonth(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_daysInMonth>(cx, + args); +} + +/** + * get Temporal.ZonedDateTime.prototype.daysInYear + */ +static bool ZonedDateTime_daysInYear(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + return CalendarDaysInYear(cx, zonedDateTime.calendar(), dateTime, + args.rval()); +} + +/** + * get Temporal.ZonedDateTime.prototype.daysInYear + */ +static bool ZonedDateTime_daysInYear(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_daysInYear>(cx, + args); +} + +/** + * get Temporal.ZonedDateTime.prototype.monthsInYear + */ +static bool ZonedDateTime_monthsInYear(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + return CalendarMonthsInYear(cx, zonedDateTime.calendar(), dateTime, + args.rval()); +} + +/** + * get Temporal.ZonedDateTime.prototype.monthsInYear + */ +static bool ZonedDateTime_monthsInYear(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_monthsInYear>( + cx, args); +} + +/** + * get Temporal.ZonedDateTime.prototype.inLeapYear + */ +static bool ZonedDateTime_inLeapYear(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime dateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &dateTime)) { + return false; + } + + // Step 7. + return CalendarInLeapYear(cx, zonedDateTime.calendar(), dateTime, + args.rval()); +} + +/** + * get Temporal.ZonedDateTime.prototype.inLeapYear + */ +static bool ZonedDateTime_inLeapYear(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_inLeapYear>(cx, + args); +} + +/** + * get Temporal.ZonedDateTime.prototype.offsetNanoseconds + */ +static bool ZonedDateTime_offsetNanoseconds(JSContext* cx, + const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Step 3. + auto timeZone = zonedDateTime.timeZone(); + + // Step 4. + auto instant = zonedDateTime.instant(); + + // Step 5. + int64_t offsetNanoseconds; + if (!GetOffsetNanosecondsFor(cx, timeZone, instant, &offsetNanoseconds)) { + return false; + } + MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day)); + + args.rval().setNumber(offsetNanoseconds); + return true; +} + +/** + * get Temporal.ZonedDateTime.prototype.offsetNanoseconds + */ +static bool ZonedDateTime_offsetNanoseconds(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_offsetNanoseconds>( + cx, args); +} + +/** + * get Temporal.ZonedDateTime.prototype.offset + */ +static bool ZonedDateTime_offset(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Step 3. + auto timeZone = zonedDateTime.timeZone(); + + // Step 4. + auto instant = zonedDateTime.instant(); + + // Step 5. + JSString* str = GetOffsetStringFor(cx, timeZone, instant); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +/** + * get Temporal.ZonedDateTime.prototype.offset + */ +static bool ZonedDateTime_offset(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_offset>(cx, args); +} + +/** + * Temporal.ZonedDateTime.prototype.with ( temporalZonedDateTimeLike [ , options + * ] ) + */ +static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Step 3. + Rooted<JSObject*> temporalZonedDateTimeLike( + cx, + RequireObjectArg(cx, "temporalZonedDateTimeLike", "with", args.get(0))); + if (!temporalZonedDateTimeLike) { + return false; + } + + // Step 4. + if (!RejectTemporalLikeObject(cx, temporalZonedDateTimeLike)) { + return false; + } + + // Step 5. + Rooted<PlainObject*> resolvedOptions(cx); + if (args.hasDefined(1)) { + Rooted<JSObject*> options(cx, + RequireObjectArg(cx, "options", "with", args[1])); + if (!options) { + return false; + } + resolvedOptions = SnapshotOwnProperties(cx, options); + } else { + resolvedOptions = NewPlainObjectWithProto(cx, nullptr); + } + if (!resolvedOptions) { + return false; + } + + // Step 6. + Rooted<CalendarRecord> calendar(cx); + if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(), + { + CalendarMethod::DateFromFields, + CalendarMethod::Fields, + CalendarMethod::MergeFields, + }, + &calendar)) { + return false; + } + + // Step 7. + Rooted<TimeZoneRecord> timeZone(cx); + if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(), + { + TimeZoneMethod::GetOffsetNanosecondsFor, + TimeZoneMethod::GetPossibleInstantsFor, + }, + &timeZone)) { + return false; + } + + // Step 8. + auto instant = zonedDateTime.instant(); + + // Step 9. + int64_t offsetNanoseconds; + if (!GetOffsetNanosecondsFor(cx, timeZone, instant, &offsetNanoseconds)) { + return false; + } + + // Step 10. + Rooted<PlainDateTimeObject*> dateTime( + cx, + GetPlainDateTimeFor(cx, instant, calendar.receiver(), offsetNanoseconds)); + if (!dateTime) { + return false; + } + + // Step 11. + JS::RootedVector<PropertyKey> fieldNames(cx); + if (!CalendarFields(cx, calendar, + {CalendarField::Day, CalendarField::Month, + CalendarField::MonthCode, CalendarField::Year}, + &fieldNames)) { + return false; + } + + // Step 12. + Rooted<PlainObject*> fields(cx, + PrepareTemporalFields(cx, dateTime, fieldNames)); + if (!fields) { + return false; + } + + // Steps 13-18. + struct TimeField { + using FieldName = ImmutableTenuredPtr<PropertyName*> JSAtomState::*; + + FieldName name; + int32_t value; + } timeFields[] = { + {&JSAtomState::hour, dateTime->isoHour()}, + {&JSAtomState::minute, dateTime->isoMinute()}, + {&JSAtomState::second, dateTime->isoSecond()}, + {&JSAtomState::millisecond, dateTime->isoMillisecond()}, + {&JSAtomState::microsecond, dateTime->isoMicrosecond()}, + {&JSAtomState::nanosecond, dateTime->isoNanosecond()}, + }; + + Rooted<Value> timeFieldValue(cx); + for (const auto& timeField : timeFields) { + Handle<PropertyName*> name = cx->names().*(timeField.name); + timeFieldValue.setInt32(timeField.value); + + if (!DefineDataProperty(cx, fields, name, timeFieldValue)) { + return false; + } + } + + // Step 19. + JSString* fieldsOffset = FormatUTCOffsetNanoseconds(cx, offsetNanoseconds); + if (!fieldsOffset) { + return false; + } + + timeFieldValue.setString(fieldsOffset); + if (!DefineDataProperty(cx, fields, cx->names().offset, timeFieldValue)) { + return false; + } + + // Step 20. + if (!AppendSorted(cx, fieldNames.get(), + { + TemporalField::Hour, + TemporalField::Microsecond, + TemporalField::Millisecond, + TemporalField::Minute, + TemporalField::Nanosecond, + TemporalField::Offset, + TemporalField::Second, + })) { + return false; + } + + // Step 21. + Rooted<PlainObject*> partialZonedDateTime( + cx, + PreparePartialTemporalFields(cx, temporalZonedDateTimeLike, fieldNames)); + if (!partialZonedDateTime) { + return false; + } + + // Step 22. + Rooted<JSObject*> mergedFields( + cx, CalendarMergeFields(cx, calendar, fields, partialZonedDateTime)); + if (!mergedFields) { + return false; + } + + // Step 23. + fields = PrepareTemporalFields(cx, mergedFields, fieldNames, + {TemporalField::Offset}); + if (!fields) { + return false; + } + + // Step 24-25. + auto disambiguation = TemporalDisambiguation::Compatible; + if (!ToTemporalDisambiguation(cx, resolvedOptions, &disambiguation)) { + return false; + } + + // Step 26. + auto offset = TemporalOffset::Prefer; + if (!ToTemporalOffset(cx, resolvedOptions, &offset)) { + return false; + } + + // Step 27. + PlainDateTime dateTimeResult; + if (!InterpretTemporalDateTimeFields(cx, calendar, fields, resolvedOptions, + &dateTimeResult)) { + return false; + } + + // Step 28. + Rooted<Value> offsetString(cx); + if (!GetProperty(cx, fields, fields, cx->names().offset, &offsetString)) { + return false; + } + + // Step 29. + MOZ_ASSERT(offsetString.isString()); + + // Step 30. + Rooted<JSString*> offsetStr(cx, offsetString.toString()); + int64_t newOffsetNanoseconds; + if (!ParseDateTimeUTCOffset(cx, offsetStr, &newOffsetNanoseconds)) { + return false; + } + + // Step 31. + Instant epochNanoseconds; + if (!InterpretISODateTimeOffset( + cx, dateTimeResult, OffsetBehaviour::Option, newOffsetNanoseconds, + timeZone, disambiguation, offset, MatchBehaviour::MatchExactly, + &epochNanoseconds)) { + return false; + } + + // Step 32. + auto* result = CreateTemporalZonedDateTime( + cx, epochNanoseconds, timeZone.receiver(), calendar.receiver()); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.with ( temporalZonedDateTimeLike [ , options + * ] ) + */ +static bool ZonedDateTime_with(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_with>(cx, args); +} + +/** + * Temporal.ZonedDateTime.prototype.withPlainTime ( [ plainTimeLike ] ) + */ +static bool ZonedDateTime_withPlainTime(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-4. + PlainTime time = {}; + if (args.hasDefined(0)) { + if (!ToTemporalTime(cx, args[0], &time)) { + return false; + } + } + + // Step 5. + Rooted<TimeZoneRecord> timeZone(cx); + if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(), + { + TimeZoneMethod::GetOffsetNanosecondsFor, + TimeZoneMethod::GetPossibleInstantsFor, + }, + &timeZone)) { + return false; + } + + // Steps 6 and 8. + PlainDateTime plainDateTime; + if (!GetPlainDateTimeFor(cx, timeZone, zonedDateTime.instant(), + &plainDateTime)) { + return false; + } + + // Step 7. + auto calendar = zonedDateTime.calendar(); + + // Step 9. + Rooted<PlainDateTimeWithCalendar> resultPlainDateTime(cx); + if (!CreateTemporalDateTime(cx, {plainDateTime.date, time}, calendar, + &resultPlainDateTime)) { + return false; + } + + // Step 10. + Instant instant; + if (!GetInstantFor(cx, timeZone, resultPlainDateTime, + TemporalDisambiguation::Compatible, &instant)) { + return false; + } + + // Step 11. + auto* result = + CreateTemporalZonedDateTime(cx, instant, timeZone.receiver(), calendar); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.withPlainTime ( [ plainTimeLike ] ) + */ +static bool ZonedDateTime_withPlainTime(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_withPlainTime>( + cx, args); +} + +/** + * Temporal.ZonedDateTime.prototype.withPlainDate ( plainDateLike ) + */ +static bool ZonedDateTime_withPlainDate(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Step 3. + Rooted<PlainDateWithCalendar> plainDate(cx); + if (!ToTemporalDate(cx, args.get(0), &plainDate)) { + return false; + } + auto date = plainDate.date(); + + // Step 4. + Rooted<TimeZoneRecord> timeZone(cx); + if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(), + { + TimeZoneMethod::GetOffsetNanosecondsFor, + TimeZoneMethod::GetPossibleInstantsFor, + }, + &timeZone)) { + return false; + } + + // Steps 5-6. + PlainDateTime plainDateTime; + if (!GetPlainDateTimeFor(cx, timeZone, zonedDateTime.instant(), + &plainDateTime)) { + return false; + } + + // Step 7. + Rooted<CalendarValue> calendar(cx); + if (!ConsolidateCalendars(cx, zonedDateTime.calendar(), plainDate.calendar(), + &calendar)) { + return false; + } + + // Step 8. + Rooted<PlainDateTimeWithCalendar> resultPlainDateTime(cx); + if (!CreateTemporalDateTime(cx, {date, plainDateTime.time}, calendar, + &resultPlainDateTime)) { + return false; + } + + // Step 9. + Instant instant; + if (!GetInstantFor(cx, timeZone, resultPlainDateTime, + TemporalDisambiguation::Compatible, &instant)) { + return false; + } + + // Step 10. + auto* result = + CreateTemporalZonedDateTime(cx, instant, timeZone.receiver(), calendar); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.withPlainDate ( plainDateLike ) + */ +static bool ZonedDateTime_withPlainDate(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_withPlainDate>( + cx, args); +} + +/** + * Temporal.ZonedDateTime.prototype.withTimeZone ( timeZoneLike ) + */ +static bool ZonedDateTime_withTimeZone(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Step 3. + Rooted<TimeZoneValue> timeZone(cx); + if (!ToTemporalTimeZone(cx, args.get(0), &timeZone)) { + return false; + } + + // Step 4. + auto* result = CreateTemporalZonedDateTime( + cx, zonedDateTime.instant(), timeZone, zonedDateTime.calendar()); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.withTimeZone ( timeZoneLike ) + */ +static bool ZonedDateTime_withTimeZone(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_withTimeZone>( + cx, args); +} + +/** + * Temporal.ZonedDateTime.prototype.withCalendar ( calendarLike ) + */ +static bool ZonedDateTime_withCalendar(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Step 3. + Rooted<CalendarValue> calendar(cx); + if (!ToTemporalCalendar(cx, args.get(0), &calendar)) { + return false; + } + + // Step 4. + auto* result = CreateTemporalZonedDateTime( + cx, zonedDateTime.instant(), zonedDateTime.timeZone(), calendar); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.withCalendar ( calendarLike ) + */ +static bool ZonedDateTime_withCalendar(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_withCalendar>( + cx, args); +} + +/** + * Temporal.ZonedDateTime.prototype.add ( temporalDurationLike [ , options ] ) + */ +static bool ZonedDateTime_add(JSContext* cx, const CallArgs& args) { + return AddDurationToOrSubtractDurationFromZonedDateTime( + cx, ZonedDateTimeDuration::Add, args); +} + +/** + * Temporal.ZonedDateTime.prototype.add ( temporalDurationLike [ , options ] ) + */ +static bool ZonedDateTime_add(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_add>(cx, args); +} + +/** + * Temporal.ZonedDateTime.prototype.subtract ( temporalDurationLike [ , options + * ] ) + */ +static bool ZonedDateTime_subtract(JSContext* cx, const CallArgs& args) { + return AddDurationToOrSubtractDurationFromZonedDateTime( + cx, ZonedDateTimeDuration::Subtract, args); +} + +/** + * Temporal.ZonedDateTime.prototype.subtract ( temporalDurationLike [ , options + * ] ) + */ +static bool ZonedDateTime_subtract(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_subtract>(cx, + args); +} + +/** + * Temporal.ZonedDateTime.prototype.until ( other [ , options ] ) + */ +static bool ZonedDateTime_until(JSContext* cx, const CallArgs& args) { + // Step 3. + return DifferenceTemporalZonedDateTime(cx, TemporalDifference::Until, args); +} + +/** + * Temporal.ZonedDateTime.prototype.until ( other [ , options ] ) + */ +static bool ZonedDateTime_until(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_until>(cx, args); +} + +/** + * Temporal.ZonedDateTime.prototype.since ( other [ , options ] ) + */ +static bool ZonedDateTime_since(JSContext* cx, const CallArgs& args) { + // Step 3. + return DifferenceTemporalZonedDateTime(cx, TemporalDifference::Since, args); +} + +/** + * Temporal.ZonedDateTime.prototype.since ( other [ , options ] ) + */ +static bool ZonedDateTime_since(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_since>(cx, args); +} + +/** + * Temporal.ZonedDateTime.prototype.round ( roundTo ) + */ +static bool ZonedDateTime_round(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-12. + auto smallestUnit = TemporalUnit::Auto; + auto roundingMode = TemporalRoundingMode::HalfExpand; + auto roundingIncrement = Increment{1}; + if (args.get(0).isString()) { + // Step 4. (Not applicable in our implementation.) + + // Step 9. + Rooted<JSString*> paramString(cx, args[0].toString()); + if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::SmallestUnit, + TemporalUnitGroup::DayTime, &smallestUnit)) { + return false; + } + + // Steps 6-8 and 10-12. (Implicit) + } else { + // Steps 3 and 5.a + Rooted<JSObject*> roundTo( + cx, RequireObjectArg(cx, "roundTo", "round", args.get(0))); + if (!roundTo) { + return false; + } + + // Steps 6-7. + if (!ToTemporalRoundingIncrement(cx, roundTo, &roundingIncrement)) { + return false; + } + + // Step 8. + if (!ToTemporalRoundingMode(cx, roundTo, &roundingMode)) { + return false; + } + + // Step 9. + if (!GetTemporalUnit(cx, roundTo, TemporalUnitKey::SmallestUnit, + TemporalUnitGroup::DayTime, &smallestUnit)) { + return false; + } + + if (smallestUnit == TemporalUnit::Auto) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_MISSING_OPTION, "smallestUnit"); + return false; + } + + MOZ_ASSERT(TemporalUnit::Day <= smallestUnit && + smallestUnit <= TemporalUnit::Nanosecond); + + // Steps 10-11. + auto maximum = Increment{1}; + bool inclusive = true; + if (smallestUnit > TemporalUnit::Day) { + maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit); + inclusive = false; + } + + // Step 12. + if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum, + inclusive)) { + return false; + } + } + + // Step 13. + if (smallestUnit == TemporalUnit::Nanosecond && + roundingIncrement == Increment{1}) { + // Step 13.a. + auto* result = CreateTemporalZonedDateTime(cx, zonedDateTime.instant(), + zonedDateTime.timeZone(), + zonedDateTime.calendar()); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; + } + + // Step 14. + Rooted<TimeZoneRecord> timeZone(cx); + if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(), + { + TimeZoneMethod::GetOffsetNanosecondsFor, + TimeZoneMethod::GetPossibleInstantsFor, + }, + &timeZone)) { + return false; + } + + // Step 16. (Reordered) + auto calendar = zonedDateTime.calendar(); + + // Steps 15 and 17. + int64_t offsetNanoseconds; + if (!GetOffsetNanosecondsFor(cx, timeZone, zonedDateTime.instant(), + &offsetNanoseconds)) { + return false; + } + MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day)); + + // Step 18. + auto temporalDateTime = + GetPlainDateTimeFor(zonedDateTime.instant(), offsetNanoseconds); + + // Step 19. + Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601)); + Rooted<PlainDateTimeWithCalendar> dtStart(cx); + if (!CreateTemporalDateTime(cx, {temporalDateTime.date, {}}, isoCalendar, + &dtStart)) { + return false; + } + + // Steps 20-21. + Instant startNs; + if (!GetInstantFor(cx, timeZone, dtStart, TemporalDisambiguation::Compatible, + &startNs)) { + return false; + } + + // Step 22. + Instant endNs; + if (!AddDaysToZonedDateTime(cx, startNs, ToPlainDateTime(dtStart), timeZone, + calendar, 1, &endNs)) { + return false; + } + MOZ_ASSERT(IsValidEpochInstant(endNs)); + + // Step 23. + auto dayLengthNs = endNs - startNs; + MOZ_ASSERT(IsValidInstantSpan(dayLengthNs)); + + // Step 24. + if (dayLengthNs <= InstantSpan{}) { + JS_ReportErrorNumberASCII( + cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_ZONED_DATE_TIME_NON_POSITIVE_DAY_LENGTH); + return false; + } + + // Step 25. + PlainDateTime roundResult; + if (!RoundISODateTime(cx, temporalDateTime, roundingIncrement, smallestUnit, + roundingMode, dayLengthNs, &roundResult)) { + return false; + } + + // Step 26. + Instant epochNanoseconds; + if (!InterpretISODateTimeOffset( + cx, roundResult, OffsetBehaviour::Option, offsetNanoseconds, timeZone, + TemporalDisambiguation::Compatible, TemporalOffset::Prefer, + MatchBehaviour::MatchExactly, &epochNanoseconds)) { + return false; + } + + // Step 27. + auto* result = CreateTemporalZonedDateTime(cx, epochNanoseconds, + timeZone.receiver(), calendar); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.round ( roundTo ) + */ +static bool ZonedDateTime_round(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_round>(cx, args); +} + +/** + * Temporal.ZonedDateTime.prototype.equals ( other ) + */ +static bool ZonedDateTime_equals(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Step 3. + Rooted<ZonedDateTime> other(cx); + if (!ToTemporalZonedDateTime(cx, args.get(0), &other)) { + return false; + } + + // Steps 4-6. + bool equals = zonedDateTime.instant() == other.instant(); + if (equals && !TimeZoneEquals(cx, zonedDateTime.timeZone(), other.timeZone(), + &equals)) { + return false; + } + if (equals && !CalendarEquals(cx, zonedDateTime.calendar(), other.calendar(), + &equals)) { + return false; + } + + args.rval().setBoolean(equals); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.equals ( other ) + */ +static bool ZonedDateTime_equals(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_equals>(cx, args); +} + +/** + * Temporal.ZonedDateTime.prototype.toString ( [ options ] ) + */ +static bool ZonedDateTime_toString(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + SecondsStringPrecision precision = {Precision::Auto(), + TemporalUnit::Nanosecond, Increment{1}}; + auto roundingMode = TemporalRoundingMode::Trunc; + auto showCalendar = CalendarOption::Auto; + auto showTimeZone = TimeZoneNameOption::Auto; + auto showOffset = ShowOffsetOption::Auto; + if (args.hasDefined(0)) { + // Step 3. + Rooted<JSObject*> options( + cx, RequireObjectArg(cx, "options", "toString", args[0])); + if (!options) { + return false; + } + + // Steps 4-5. + if (!ToCalendarNameOption(cx, options, &showCalendar)) { + return false; + } + + // Step 6. + auto digits = Precision::Auto(); + if (!ToFractionalSecondDigits(cx, options, &digits)) { + return false; + } + + // Step 7. + if (!ToShowOffsetOption(cx, options, &showOffset)) { + return false; + } + + // Step 8. + if (!ToTemporalRoundingMode(cx, options, &roundingMode)) { + return false; + } + + // Step 9. + auto smallestUnit = TemporalUnit::Auto; + if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit, + TemporalUnitGroup::Time, &smallestUnit)) { + return false; + } + + // Step 10. + if (smallestUnit == TemporalUnit::Hour) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_INVALID_UNIT_OPTION, "hour", + "smallestUnit"); + return false; + } + + // Step 11. + if (!ToTimeZoneNameOption(cx, options, &showTimeZone)) { + return false; + } + + // Step 12. + precision = ToSecondsStringPrecision(smallestUnit, digits); + } + + // Step 13. + JSString* str = TemporalZonedDateTimeToString( + cx, zonedDateTime, precision.precision, showCalendar, showTimeZone, + showOffset, precision.increment, precision.unit, roundingMode); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.toString ( [ options ] ) + */ +static bool ZonedDateTime_toString(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toString>(cx, + args); +} + +/** + * Temporal.ZonedDateTime.prototype.toLocaleString ( [ locales [ , options ] ] ) + */ +static bool ZonedDateTime_toLocaleString(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Step 3. + JSString* str = TemporalZonedDateTimeToString( + cx, zonedDateTime, Precision::Auto(), CalendarOption::Auto, + TimeZoneNameOption::Auto, ShowOffsetOption::Auto); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.toLocaleString ( [ locales [ , options ] ] ) + */ +static bool ZonedDateTime_toLocaleString(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toLocaleString>( + cx, args); +} + +/** + * Temporal.ZonedDateTime.prototype.toJSON ( ) + */ +static bool ZonedDateTime_toJSON(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Step 3. + JSString* str = TemporalZonedDateTimeToString( + cx, zonedDateTime, Precision::Auto(), CalendarOption::Auto, + TimeZoneNameOption::Auto, ShowOffsetOption::Auto); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.toJSON ( ) + */ +static bool ZonedDateTime_toJSON(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toJSON>(cx, args); +} + +/** + * Temporal.ZonedDateTime.prototype.valueOf ( ) + */ +static bool ZonedDateTime_valueOf(JSContext* cx, unsigned argc, Value* vp) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, + "ZonedDateTime", "primitive type"); + return false; +} + +/** + * Temporal.ZonedDateTime.prototype.startOfDay ( ) + */ +static bool ZonedDateTime_startOfDay(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Step 3. + Rooted<TimeZoneRecord> timeZone(cx); + if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(), + { + TimeZoneMethod::GetOffsetNanosecondsFor, + TimeZoneMethod::GetPossibleInstantsFor, + }, + &timeZone)) { + return false; + } + + // Step 4. + auto calendar = zonedDateTime.calendar(); + + // Step 5. + auto instant = zonedDateTime.instant(); + + // Steps 5-6. + PlainDateTime temporalDateTime; + if (!GetPlainDateTimeFor(cx, timeZone, instant, &temporalDateTime)) { + return false; + } + + // Step 7. + Rooted<PlainDateTimeWithCalendar> startDateTime(cx); + if (!CreateTemporalDateTime(cx, {temporalDateTime.date, {}}, calendar, + &startDateTime)) { + return false; + } + + // Step 8. + Instant startInstant; + if (!GetInstantFor(cx, timeZone, startDateTime, + TemporalDisambiguation::Compatible, &startInstant)) { + return false; + } + + // Step 9. + auto* result = CreateTemporalZonedDateTime(cx, startInstant, + timeZone.receiver(), calendar); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.startOfDay ( ) + */ +static bool ZonedDateTime_startOfDay(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_startOfDay>(cx, + args); +} + +/** + * Temporal.ZonedDateTime.prototype.toInstant ( ) + */ +static bool ZonedDateTime_toInstant(JSContext* cx, const CallArgs& args) { + auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>(); + auto instant = ToInstant(zonedDateTime); + + // Step 3. + auto* result = CreateTemporalInstant(cx, instant); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.toInstant ( ) + */ +static bool ZonedDateTime_toInstant(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toInstant>(cx, + args); +} + +/** + * Temporal.ZonedDateTime.prototype.toPlainDate ( ) + */ +static bool ZonedDateTime_toPlainDate(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime temporalDateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &temporalDateTime)) { + return false; + } + + // Step 7. + auto* result = + CreateTemporalDate(cx, temporalDateTime.date, zonedDateTime.calendar()); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.toPlainDate ( ) + */ +static bool ZonedDateTime_toPlainDate(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toPlainDate>(cx, + args); +} + +/** + * Temporal.ZonedDateTime.prototype.toPlainTime ( ) + */ +static bool ZonedDateTime_toPlainTime(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-6. + PlainDateTime temporalDateTime; + if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), + zonedDateTime.instant(), &temporalDateTime)) { + return false; + } + + // Step 7. + auto* result = CreateTemporalTime(cx, temporalDateTime.time); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.toPlainTime ( ) + */ +static bool ZonedDateTime_toPlainTime(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toPlainTime>(cx, + args); +} + +/** + * Temporal.ZonedDateTime.prototype.toPlainDateTime ( ) + */ +static bool ZonedDateTime_toPlainDateTime(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Steps 3-5. + auto* result = + GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), zonedDateTime.instant(), + zonedDateTime.calendar()); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.toPlainDateTime ( ) + */ +static bool ZonedDateTime_toPlainDateTime(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toPlainDateTime>( + cx, args); +} + +/** + * Temporal.ZonedDateTime.prototype.toPlainYearMonth ( ) + */ +static bool ZonedDateTime_toPlainYearMonth(JSContext* cx, + const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Step 3. + Rooted<CalendarRecord> calendar(cx); + if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(), + { + CalendarMethod::Fields, + CalendarMethod::YearMonthFromFields, + }, + &calendar)) { + return false; + } + + // Steps 4-6. + Rooted<PlainDateTimeObject*> temporalDateTime( + cx, + GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), zonedDateTime.instant(), + zonedDateTime.calendar())); + if (!temporalDateTime) { + return false; + } + + // Step 7. + JS::RootedVector<PropertyKey> fieldNames(cx); + if (!CalendarFields(cx, calendar, + {CalendarField::MonthCode, CalendarField::Year}, + &fieldNames)) { + return false; + } + + // Step 8. + Rooted<PlainObject*> fields( + cx, PrepareTemporalFields(cx, temporalDateTime, fieldNames)); + if (!fields) { + return false; + } + + // Steps 9-10. + auto result = CalendarYearMonthFromFields(cx, calendar, fields); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.toPlainYearMonth ( ) + */ +static bool ZonedDateTime_toPlainYearMonth(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toPlainYearMonth>( + cx, args); +} + +/** + * Temporal.ZonedDateTime.prototype.toPlainMonthDay ( ) + */ +static bool ZonedDateTime_toPlainMonthDay(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Step 3. + Rooted<CalendarRecord> calendar(cx); + if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(), + { + CalendarMethod::Fields, + CalendarMethod::MonthDayFromFields, + }, + &calendar)) { + return false; + } + + // Steps 4-6. + Rooted<PlainDateTimeObject*> temporalDateTime( + cx, + GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), zonedDateTime.instant(), + zonedDateTime.calendar())); + if (!temporalDateTime) { + return false; + } + + // Step 7. + JS::RootedVector<PropertyKey> fieldNames(cx); + if (!CalendarFields(cx, calendar, + {CalendarField::Day, CalendarField::MonthCode}, + &fieldNames)) { + return false; + } + + // Step 8. + Rooted<PlainObject*> fields( + cx, PrepareTemporalFields(cx, temporalDateTime, fieldNames)); + if (!fields) { + return false; + } + + // Steps 9-10. + auto result = CalendarMonthDayFromFields(cx, calendar, fields); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.toPlainMonthDay ( ) + */ +static bool ZonedDateTime_toPlainMonthDay(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toPlainMonthDay>( + cx, args); +} + +/** + * Temporal.ZonedDateTime.prototype.getISOFields ( ) + */ +static bool ZonedDateTime_getISOFields(JSContext* cx, const CallArgs& args) { + Rooted<ZonedDateTime> zonedDateTime( + cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()}); + + // Step 3. + Rooted<IdValueVector> fields(cx, IdValueVector(cx)); + + // Step 4. + auto instant = zonedDateTime.instant(); + + // Step 5. + auto calendar = zonedDateTime.calendar(); + + // Step 6. + auto timeZone = zonedDateTime.timeZone(); + + // Step 7. + int64_t offsetNanoseconds; + if (!GetOffsetNanosecondsFor(cx, timeZone, instant, &offsetNanoseconds)) { + return false; + } + + // Step 8. + auto temporalDateTime = GetPlainDateTimeFor(instant, offsetNanoseconds); + + // Step 9. + Rooted<JSString*> offset(cx, + FormatUTCOffsetNanoseconds(cx, offsetNanoseconds)); + if (!offset) { + return false; + } + + // Step 10. + if (!fields.emplaceBack(NameToId(cx->names().calendar), calendar.toValue())) { + return false; + } + + // Step 11. + if (!fields.emplaceBack(NameToId(cx->names().isoDay), + Int32Value(temporalDateTime.date.day))) { + return false; + } + + // Step 12. + if (!fields.emplaceBack(NameToId(cx->names().isoHour), + Int32Value(temporalDateTime.time.hour))) { + return false; + } + + // Step 13. + if (!fields.emplaceBack(NameToId(cx->names().isoMicrosecond), + Int32Value(temporalDateTime.time.microsecond))) { + return false; + } + + // Step 14. + if (!fields.emplaceBack(NameToId(cx->names().isoMillisecond), + Int32Value(temporalDateTime.time.millisecond))) { + return false; + } + + // Step 15. + if (!fields.emplaceBack(NameToId(cx->names().isoMinute), + Int32Value(temporalDateTime.time.minute))) { + return false; + } + + // Step 16. + if (!fields.emplaceBack(NameToId(cx->names().isoMonth), + Int32Value(temporalDateTime.date.month))) { + return false; + } + + // Step 17. + if (!fields.emplaceBack(NameToId(cx->names().isoNanosecond), + Int32Value(temporalDateTime.time.nanosecond))) { + return false; + } + + // Step 18. + if (!fields.emplaceBack(NameToId(cx->names().isoSecond), + Int32Value(temporalDateTime.time.second))) { + return false; + } + + // Step 19. + if (!fields.emplaceBack(NameToId(cx->names().isoYear), + Int32Value(temporalDateTime.date.year))) { + return false; + } + + // Step 20. + if (!fields.emplaceBack(NameToId(cx->names().offset), StringValue(offset))) { + return false; + } + + // Step 21. + if (!fields.emplaceBack(NameToId(cx->names().timeZone), timeZone.toValue())) { + return false; + } + + // Step 22. + auto* obj = + NewPlainObjectWithUniqueNames(cx, fields.begin(), fields.length()); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.getISOFields ( ) + */ +static bool ZonedDateTime_getISOFields(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_getISOFields>( + cx, args); +} + +/** + * Temporal.ZonedDateTime.prototype.getCalendar ( ) + */ +static bool ZonedDateTime_getCalendar(JSContext* cx, const CallArgs& args) { + auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>(); + Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar()); + + // Step 3. + auto* obj = ToTemporalCalendarObject(cx, calendar); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.getCalendar ( ) + */ +static bool ZonedDateTime_getCalendar(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_getCalendar>(cx, + args); +} + +/** + * Temporal.ZonedDateTime.prototype.getTimeZone ( ) + */ +static bool ZonedDateTime_getTimeZone(JSContext* cx, const CallArgs& args) { + auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>(); + Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone()); + + // Step 3. + auto* obj = ToTemporalTimeZoneObject(cx, timeZone); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.ZonedDateTime.prototype.getTimeZone ( ) + */ +static bool ZonedDateTime_getTimeZone(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_getTimeZone>(cx, + args); +} + +const JSClass ZonedDateTimeObject::class_ = { + "Temporal.ZonedDateTime", + JSCLASS_HAS_RESERVED_SLOTS(ZonedDateTimeObject::SLOT_COUNT) | + JSCLASS_HAS_CACHED_PROTO(JSProto_ZonedDateTime), + JS_NULL_CLASS_OPS, + &ZonedDateTimeObject::classSpec_, +}; + +const JSClass& ZonedDateTimeObject::protoClass_ = PlainObject::class_; + +static const JSFunctionSpec ZonedDateTime_methods[] = { + JS_FN("from", ZonedDateTime_from, 1, 0), + JS_FN("compare", ZonedDateTime_compare, 2, 0), + JS_FS_END, +}; + +static const JSFunctionSpec ZonedDateTime_prototype_methods[] = { + JS_FN("with", ZonedDateTime_with, 1, 0), + JS_FN("withPlainTime", ZonedDateTime_withPlainTime, 0, 0), + JS_FN("withPlainDate", ZonedDateTime_withPlainDate, 1, 0), + JS_FN("withTimeZone", ZonedDateTime_withTimeZone, 1, 0), + JS_FN("withCalendar", ZonedDateTime_withCalendar, 1, 0), + JS_FN("add", ZonedDateTime_add, 1, 0), + JS_FN("subtract", ZonedDateTime_subtract, 1, 0), + JS_FN("until", ZonedDateTime_until, 1, 0), + JS_FN("since", ZonedDateTime_since, 1, 0), + JS_FN("round", ZonedDateTime_round, 1, 0), + JS_FN("equals", ZonedDateTime_equals, 1, 0), + JS_FN("toString", ZonedDateTime_toString, 0, 0), + JS_FN("toLocaleString", ZonedDateTime_toLocaleString, 0, 0), + JS_FN("toJSON", ZonedDateTime_toJSON, 0, 0), + JS_FN("valueOf", ZonedDateTime_valueOf, 0, 0), + JS_FN("startOfDay", ZonedDateTime_startOfDay, 0, 0), + JS_FN("toInstant", ZonedDateTime_toInstant, 0, 0), + JS_FN("toPlainDate", ZonedDateTime_toPlainDate, 0, 0), + JS_FN("toPlainTime", ZonedDateTime_toPlainTime, 0, 0), + JS_FN("toPlainDateTime", ZonedDateTime_toPlainDateTime, 0, 0), + JS_FN("toPlainYearMonth", ZonedDateTime_toPlainYearMonth, 0, 0), + JS_FN("toPlainMonthDay", ZonedDateTime_toPlainMonthDay, 0, 0), + JS_FN("getISOFields", ZonedDateTime_getISOFields, 0, 0), + JS_FN("getCalendar", ZonedDateTime_getCalendar, 0, 0), + JS_FN("getTimeZone", ZonedDateTime_getTimeZone, 0, 0), + JS_FS_END, +}; + +static const JSPropertySpec ZonedDateTime_prototype_properties[] = { + JS_PSG("calendarId", ZonedDateTime_calendarId, 0), + JS_PSG("timeZoneId", ZonedDateTime_timeZoneId, 0), + JS_PSG("year", ZonedDateTime_year, 0), + JS_PSG("month", ZonedDateTime_month, 0), + JS_PSG("monthCode", ZonedDateTime_monthCode, 0), + JS_PSG("day", ZonedDateTime_day, 0), + JS_PSG("hour", ZonedDateTime_hour, 0), + JS_PSG("minute", ZonedDateTime_minute, 0), + JS_PSG("second", ZonedDateTime_second, 0), + JS_PSG("millisecond", ZonedDateTime_millisecond, 0), + JS_PSG("microsecond", ZonedDateTime_microsecond, 0), + JS_PSG("nanosecond", ZonedDateTime_nanosecond, 0), + JS_PSG("epochSeconds", ZonedDateTime_epochSeconds, 0), + JS_PSG("epochMilliseconds", ZonedDateTime_epochMilliseconds, 0), + JS_PSG("epochMicroseconds", ZonedDateTime_epochMicroseconds, 0), + JS_PSG("epochNanoseconds", ZonedDateTime_epochNanoseconds, 0), + JS_PSG("dayOfWeek", ZonedDateTime_dayOfWeek, 0), + JS_PSG("dayOfYear", ZonedDateTime_dayOfYear, 0), + JS_PSG("weekOfYear", ZonedDateTime_weekOfYear, 0), + JS_PSG("yearOfWeek", ZonedDateTime_yearOfWeek, 0), + JS_PSG("hoursInDay", ZonedDateTime_hoursInDay, 0), + JS_PSG("daysInWeek", ZonedDateTime_daysInWeek, 0), + JS_PSG("daysInMonth", ZonedDateTime_daysInMonth, 0), + JS_PSG("daysInYear", ZonedDateTime_daysInYear, 0), + JS_PSG("monthsInYear", ZonedDateTime_monthsInYear, 0), + JS_PSG("inLeapYear", ZonedDateTime_inLeapYear, 0), + JS_PSG("offsetNanoseconds", ZonedDateTime_offsetNanoseconds, 0), + JS_PSG("offset", ZonedDateTime_offset, 0), + JS_STRING_SYM_PS(toStringTag, "Temporal.ZonedDateTime", JSPROP_READONLY), + JS_PS_END, +}; + +const ClassSpec ZonedDateTimeObject::classSpec_ = { + GenericCreateConstructor<ZonedDateTimeConstructor, 2, + gc::AllocKind::FUNCTION>, + GenericCreatePrototype<ZonedDateTimeObject>, + ZonedDateTime_methods, + nullptr, + ZonedDateTime_prototype_methods, + ZonedDateTime_prototype_properties, + nullptr, + ClassSpec::DontDefineConstructor, +}; |