diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /js/src/builtin/temporal/PlainDateTime.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/builtin/temporal/PlainDateTime.cpp')
-rw-r--r-- | js/src/builtin/temporal/PlainDateTime.cpp | 2856 |
1 files changed, 2856 insertions, 0 deletions
diff --git a/js/src/builtin/temporal/PlainDateTime.cpp b/js/src/builtin/temporal/PlainDateTime.cpp new file mode 100644 index 0000000000..8861b484bd --- /dev/null +++ b/js/src/builtin/temporal/PlainDateTime.cpp @@ -0,0 +1,2856 @@ +/* -*- 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/PlainDateTime.h" + +#include "mozilla/Assertions.h" + +#include <algorithm> +#include <type_traits> +#include <utility> + +#include "jsnum.h" +#include "jspubtd.h" +#include "NamespaceImports.h" + +#include "builtin/temporal/Calendar.h" +#include "builtin/temporal/Duration.h" +#include "builtin/temporal/PlainDate.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 "builtin/temporal/ZonedDateTime.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/ErrorReport.h" +#include "js/friend/ErrorMessages.h" +#include "js/GCVector.h" +#include "js/Id.h" +#include "js/PropertyDescriptor.h" +#include "js/PropertySpec.h" +#include "js/RootingAPI.h" +#include "js/TypeDecls.h" +#include "js/Value.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/JSObject-inl.h" +#include "vm/NativeObject-inl.h" + +using namespace js; +using namespace js::temporal; + +static inline bool IsPlainDateTime(Handle<Value> v) { + return v.isObject() && v.toObject().is<PlainDateTimeObject>(); +} + +#ifdef DEBUG +/** + * IsValidISODateTime ( year, month, day, hour, minute, second, millisecond, + * microsecond, nanosecond ) + */ +bool js::temporal::IsValidISODateTime(const PlainDateTime& dateTime) { + return IsValidISODate(dateTime.date) && IsValidTime(dateTime.time); +} +#endif + +/** + * IsValidISODateTime ( year, month, day, hour, minute, second, millisecond, + * microsecond, nanosecond ) + */ +static bool ThrowIfInvalidISODateTime(JSContext* cx, + const PlainDateTime& dateTime) { + return ThrowIfInvalidISODate(cx, dateTime.date) && + ThrowIfInvalidTime(cx, dateTime.time); +} + +/** + * ISODateTimeWithinLimits ( year, month, day, hour, minute, second, + * millisecond, microsecond, nanosecond ) + */ +template <typename T> +static bool ISODateTimeWithinLimits(T year, T month, T day, T hour, T minute, + T second, T millisecond, T microsecond, + T nanosecond) { + static_assert(std::is_same_v<T, int32_t> || std::is_same_v<T, double>); + + // Step 1. + MOZ_ASSERT(IsInteger(year)); + MOZ_ASSERT(IsInteger(month)); + MOZ_ASSERT(IsInteger(day)); + MOZ_ASSERT(IsInteger(hour)); + MOZ_ASSERT(IsInteger(minute)); + MOZ_ASSERT(IsInteger(second)); + MOZ_ASSERT(IsInteger(millisecond)); + MOZ_ASSERT(IsInteger(microsecond)); + MOZ_ASSERT(IsInteger(nanosecond)); + + MOZ_ASSERT(IsValidISODate(year, month, day)); + MOZ_ASSERT( + IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond)); + + // js> new Date(-8_64000_00000_00000).toISOString() + // "-271821-04-20T00:00:00.000Z" + // + // js> new Date(+8_64000_00000_00000).toISOString() + // "+275760-09-13T00:00:00.000Z" + + constexpr int32_t minYear = -271821; + constexpr int32_t maxYear = 275760; + + // Definitely in range. + if (minYear < year && year < maxYear) { + return true; + } + + // -271821 April, 20 + if (year < 0) { + if (year != minYear) { + return false; + } + if (month != 4) { + return month > 4; + } + if (day != (20 - 1)) { + return day > (20 - 1); + } + // Needs to be past midnight on April, 19. + return !(hour == 0 && minute == 0 && second == 0 && millisecond == 0 && + microsecond == 0 && nanosecond == 0); + } + + // 275760 September, 13 + if (year != maxYear) { + return false; + } + if (month != 9) { + return month < 9; + } + if (day > 13) { + return false; + } + return true; +} + +/** + * ISODateTimeWithinLimits ( year, month, day, hour, minute, second, + * millisecond, microsecond, nanosecond ) + */ +template <typename T> +static bool ISODateTimeWithinLimits(T year, T month, T day) { + static_assert(std::is_same_v<T, int32_t> || std::is_same_v<T, double>); + + MOZ_ASSERT(IsValidISODate(year, month, day)); + + // js> new Date(-8_64000_00000_00000).toISOString() + // "-271821-04-20T00:00:00.000Z" + // + // js> new Date(+8_64000_00000_00000).toISOString() + // "+275760-09-13T00:00:00.000Z" + + constexpr int32_t minYear = -271821; + constexpr int32_t maxYear = 275760; + + // ISODateTimeWithinLimits is called with hour=12 and the remaining time + // components set to zero. That means the maximum value is exclusive, whereas + // the minimum value is inclusive. + + // FIXME: spec bug - GetUTCEpochNanoseconds when called with large |year| may + // cause MakeDay to return NaN, which makes MakeDate return NaN, which is + // unexpected in GetUTCEpochNanoseconds, step 4. + // https://github.com/tc39/proposal-temporal/issues/2315 + + // Definitely in range. + if (minYear < year && year < maxYear) { + return true; + } + + // -271821 April, 20 + if (year < 0) { + if (year != minYear) { + return false; + } + if (month != 4) { + return month > 4; + } + if (day < (20 - 1)) { + return false; + } + return true; + } + + // 275760 September, 13 + if (year != maxYear) { + return false; + } + if (month != 9) { + return month < 9; + } + if (day > 13) { + return false; + } + return true; +} + +/** + * ISODateTimeWithinLimits ( year, month, day, hour, minute, second, + * millisecond, microsecond, nanosecond ) + */ +bool js::temporal::ISODateTimeWithinLimits(double year, double month, + double day) { + return ::ISODateTimeWithinLimits(year, month, day); +} + +/** + * ISODateTimeWithinLimits ( year, month, day, hour, minute, second, + * millisecond, microsecond, nanosecond ) + */ +bool js::temporal::ISODateTimeWithinLimits(const PlainDateTime& dateTime) { + auto& [date, time] = dateTime; + return ::ISODateTimeWithinLimits(date.year, date.month, date.day, time.hour, + time.minute, time.second, time.millisecond, + time.microsecond, time.nanosecond); +} + +/** + * ISODateTimeWithinLimits ( year, month, day, hour, minute, second, + * millisecond, microsecond, nanosecond ) + */ +bool js::temporal::ISODateTimeWithinLimits(const PlainDate& date) { + return ::ISODateTimeWithinLimits(date.year, date.month, date.day); +} + +/** + * CreateTemporalDateTime ( isoYear, isoMonth, isoDay, hour, minute, second, + * millisecond, microsecond, nanosecond, calendar [ , newTarget ] ) + */ +static PlainDateTimeObject* CreateTemporalDateTime( + JSContext* cx, const CallArgs& args, double isoYear, double isoMonth, + double isoDay, double hour, double minute, double second, + double millisecond, double microsecond, double nanosecond, + Handle<CalendarValue> calendar) { + MOZ_ASSERT(IsInteger(isoYear)); + MOZ_ASSERT(IsInteger(isoMonth)); + MOZ_ASSERT(IsInteger(isoDay)); + MOZ_ASSERT(IsInteger(hour)); + MOZ_ASSERT(IsInteger(minute)); + MOZ_ASSERT(IsInteger(second)); + MOZ_ASSERT(IsInteger(millisecond)); + MOZ_ASSERT(IsInteger(microsecond)); + MOZ_ASSERT(IsInteger(nanosecond)); + + // Step 1. + if (!ThrowIfInvalidISODate(cx, isoYear, isoMonth, isoDay)) { + return nullptr; + } + + // Step 2. + if (!ThrowIfInvalidTime(cx, hour, minute, second, millisecond, microsecond, + nanosecond)) { + return nullptr; + } + + // Step 3. + if (!ISODateTimeWithinLimits(isoYear, isoMonth, isoDay, hour, minute, second, + millisecond, microsecond, nanosecond)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID); + return nullptr; + } + + // Steps 4-5. + Rooted<JSObject*> proto(cx); + if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_PlainDateTime, + &proto)) { + return nullptr; + } + + auto* dateTime = NewObjectWithClassProto<PlainDateTimeObject>(cx, proto); + if (!dateTime) { + return nullptr; + } + + // Step 6. + dateTime->setFixedSlot(PlainDateTimeObject::ISO_YEAR_SLOT, + Int32Value(isoYear)); + + // Step 7. + dateTime->setFixedSlot(PlainDateTimeObject::ISO_MONTH_SLOT, + Int32Value(isoMonth)); + + // Step 8. + dateTime->setFixedSlot(PlainDateTimeObject::ISO_DAY_SLOT, Int32Value(isoDay)); + + // Step 9. + dateTime->setFixedSlot(PlainDateTimeObject::ISO_HOUR_SLOT, Int32Value(hour)); + + // Step 10. + dateTime->setFixedSlot(PlainDateTimeObject::ISO_MINUTE_SLOT, + Int32Value(minute)); + + // Step 11. + dateTime->setFixedSlot(PlainDateTimeObject::ISO_SECOND_SLOT, + Int32Value(second)); + + // Step 12. + dateTime->setFixedSlot(PlainDateTimeObject::ISO_MILLISECOND_SLOT, + Int32Value(millisecond)); + + // Step 13. + dateTime->setFixedSlot(PlainDateTimeObject::ISO_MICROSECOND_SLOT, + Int32Value(microsecond)); + + // Step 14. + dateTime->setFixedSlot(PlainDateTimeObject::ISO_NANOSECOND_SLOT, + Int32Value(nanosecond)); + + // Step 15. + dateTime->setFixedSlot(PlainDateTimeObject::CALENDAR_SLOT, + calendar.toValue()); + + // Step 16. + return dateTime; +} + +/** + * CreateTemporalDateTime ( isoYear, isoMonth, isoDay, hour, minute, second, + * millisecond, microsecond, nanosecond, calendar [ , newTarget ] ) + */ +PlainDateTimeObject* js::temporal::CreateTemporalDateTime( + JSContext* cx, const PlainDateTime& dateTime, + Handle<CalendarValue> calendar) { + auto& [date, time] = dateTime; + auto& [isoYear, isoMonth, isoDay] = date; + auto& [hour, minute, second, millisecond, microsecond, nanosecond] = time; + + // Steps 1-2. + if (!ThrowIfInvalidISODateTime(cx, dateTime)) { + return nullptr; + } + + // Step 3. + if (!ISODateTimeWithinLimits(dateTime)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID); + return nullptr; + } + + // Steps 4-5. + auto* object = NewBuiltinClassInstance<PlainDateTimeObject>(cx); + if (!object) { + return nullptr; + } + + // Step 6. + object->setFixedSlot(PlainDateTimeObject::ISO_YEAR_SLOT, Int32Value(isoYear)); + + // Step 7. + object->setFixedSlot(PlainDateTimeObject::ISO_MONTH_SLOT, + Int32Value(isoMonth)); + + // Step 8. + object->setFixedSlot(PlainDateTimeObject::ISO_DAY_SLOT, Int32Value(isoDay)); + + // Step 9. + object->setFixedSlot(PlainDateTimeObject::ISO_HOUR_SLOT, Int32Value(hour)); + + // Step 10. + object->setFixedSlot(PlainDateTimeObject::ISO_MINUTE_SLOT, + Int32Value(minute)); + + // Step 11. + object->setFixedSlot(PlainDateTimeObject::ISO_SECOND_SLOT, + Int32Value(second)); + + // Step 12. + object->setFixedSlot(PlainDateTimeObject::ISO_MILLISECOND_SLOT, + Int32Value(millisecond)); + + // Step 13. + object->setFixedSlot(PlainDateTimeObject::ISO_MICROSECOND_SLOT, + Int32Value(microsecond)); + + // Step 14. + object->setFixedSlot(PlainDateTimeObject::ISO_NANOSECOND_SLOT, + Int32Value(nanosecond)); + + // Step 15. + object->setFixedSlot(PlainDateTimeObject::CALENDAR_SLOT, calendar.toValue()); + + // Step 16. + return object; +} + +/** + * CreateTemporalDateTime ( isoYear, isoMonth, isoDay, hour, minute, second, + * millisecond, microsecond, nanosecond, calendar [ , newTarget ] ) + */ +bool js::temporal::CreateTemporalDateTime( + JSContext* cx, const PlainDateTime& dateTime, + Handle<CalendarValue> calendar, + MutableHandle<PlainDateTimeWithCalendar> result) { + // Steps 1-2. + if (!ThrowIfInvalidISODateTime(cx, dateTime)) { + return false; + } + + // Step 3. + if (!ISODateTimeWithinLimits(dateTime)) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID); + return false; + } + + result.set(PlainDateTimeWithCalendar{dateTime, calendar}); + return true; +} + +/** + * InterpretTemporalDateTimeFields ( calendarRec, fields, options ) + */ +bool js::temporal::InterpretTemporalDateTimeFields( + JSContext* cx, Handle<CalendarRecord> calendar, Handle<PlainObject*> fields, + Handle<PlainObject*> options, PlainDateTime* result) { + // Step 1. (Not applicable in our implementation.) + + // Step 2. + MOZ_ASSERT(CalendarMethodsRecordHasLookedUp(calendar, + CalendarMethod::DateFromFields)); + + // Step 3. + TimeRecord timeResult; + if (!ToTemporalTimeRecord(cx, fields, &timeResult)) { + return false; + } + + // Step 4. + auto overflow = TemporalOverflow::Constrain; + if (!ToTemporalOverflow(cx, options, &overflow)) { + return false; + } + + // Steps 5-6. + Rooted<Value> overflowValue(cx); + if (overflow == TemporalOverflow::Constrain) { + overflowValue.setString(cx->names().constrain); + } else { + MOZ_ASSERT(overflow == TemporalOverflow::Reject); + overflowValue.setString(cx->names().reject); + } + if (!DefineDataProperty(cx, options, cx->names().overflow, overflowValue)) { + return false; + } + + // Step 7. + auto temporalDate = + js::temporal::CalendarDateFromFields(cx, calendar, fields, options); + if (!temporalDate) { + return false; + } + auto date = ToPlainDate(&temporalDate.unwrap()); + + // Step 8. + PlainTime time; + if (!RegulateTime(cx, timeResult, overflow, &time)) { + return false; + } + + // Step 9. + *result = {date, time}; + return true; +} + +/** + * InterpretTemporalDateTimeFields ( calendarRec, fields, options ) + */ +bool js::temporal::InterpretTemporalDateTimeFields( + JSContext* cx, Handle<CalendarRecord> calendar, Handle<PlainObject*> fields, + PlainDateTime* result) { + // TODO: Avoid creating the options object when CalendarDateFromFields calls + // the built-in Calendar.prototype.dateFromFields method. + Rooted<PlainObject*> options(cx, NewPlainObjectWithProto(cx, nullptr)); + if (!options) { + return false; + } + + return InterpretTemporalDateTimeFields(cx, calendar, fields, options, result); +} + +/** + * ToTemporalDateTime ( item [ , options ] ) + */ +static Wrapped<PlainDateTimeObject*> ToTemporalDateTime( + JSContext* cx, Handle<Value> item, Handle<JSObject*> maybeOptions) { + // Step 1. (Not applicable) + + // Step 2. + Rooted<PlainObject*> maybeResolvedOptions(cx); + if (maybeOptions) { + maybeResolvedOptions = SnapshotOwnProperties(cx, maybeOptions); + if (!maybeResolvedOptions) { + return nullptr; + } + } + + // Steps 3-4. + Rooted<CalendarValue> calendar(cx); + PlainDateTime result; + if (item.isObject()) { + Rooted<JSObject*> itemObj(cx, &item.toObject()); + + // Step 3.a. + if (itemObj->canUnwrapAs<PlainDateTimeObject>()) { + return itemObj; + } + + // Step 3.b. + if (auto* zonedDateTime = itemObj->maybeUnwrapIf<ZonedDateTimeObject>()) { + auto epochInstant = ToInstant(zonedDateTime); + Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone()); + Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar()); + + if (!timeZone.wrap(cx)) { + return nullptr; + } + if (!calendar.wrap(cx)) { + return nullptr; + } + + // Step 3.b.i. + if (maybeResolvedOptions) { + TemporalOverflow ignored; + if (!ToTemporalOverflow(cx, maybeResolvedOptions, &ignored)) { + return nullptr; + } + } + + // Steps 3.b.ii-iv. + return GetPlainDateTimeFor(cx, timeZone, epochInstant, calendar); + } + + // Step 3.c. + if (auto* date = itemObj->maybeUnwrapIf<PlainDateObject>()) { + PlainDateTime dateTime = {ToPlainDate(date), {}}; + Rooted<CalendarValue> calendar(cx, date->calendar()); + if (!calendar.wrap(cx)) { + return nullptr; + } + + // Step 3.c.i. + if (maybeResolvedOptions) { + TemporalOverflow ignored; + if (!ToTemporalOverflow(cx, maybeResolvedOptions, &ignored)) { + return nullptr; + } + } + + // Step 3.c.ii. + return CreateTemporalDateTime(cx, dateTime, calendar); + } + + // Step 3.d. + if (!GetTemporalCalendarWithISODefault(cx, itemObj, &calendar)) { + return nullptr; + } + + // Step 3.e. + Rooted<CalendarRecord> calendarRec(cx); + if (!CreateCalendarMethodsRecord(cx, calendar, + { + CalendarMethod::DateFromFields, + CalendarMethod::Fields, + }, + &calendarRec)) { + return nullptr; + } + + // Step 3.f. + JS::RootedVector<PropertyKey> fieldNames(cx); + if (!CalendarFields(cx, calendarRec, + {CalendarField::Day, CalendarField::Month, + CalendarField::MonthCode, CalendarField::Year}, + &fieldNames)) { + return nullptr; + } + + // Step 3.g. + if (!AppendSorted(cx, fieldNames.get(), + { + TemporalField::Hour, + TemporalField::Microsecond, + TemporalField::Millisecond, + TemporalField::Minute, + TemporalField::Nanosecond, + TemporalField::Second, + })) { + return nullptr; + } + + // Step 3.h. + Rooted<PlainObject*> fields(cx, + PrepareTemporalFields(cx, itemObj, fieldNames)); + if (!fields) { + return nullptr; + } + + // Step 3.i. + if (maybeResolvedOptions) { + if (!InterpretTemporalDateTimeFields(cx, calendarRec, fields, + maybeResolvedOptions, &result)) { + return nullptr; + } + } else { + if (!InterpretTemporalDateTimeFields(cx, calendarRec, fields, &result)) { + return nullptr; + } + } + } else { + // Step 4.a. + if (!item.isString()) { + ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, item, + nullptr, "not a string"); + return nullptr; + } + Rooted<JSString*> string(cx, item.toString()); + + // Step 4.b. + Rooted<JSString*> calendarString(cx); + if (!ParseTemporalDateTimeString(cx, string, &result, &calendarString)) { + return nullptr; + } + + // Step 4.c. + MOZ_ASSERT(IsValidISODate(result.date)); + + // Step 4.d. + MOZ_ASSERT(IsValidTime(result.time)); + + // Steps 4.e-h. + if (calendarString) { + if (!ToBuiltinCalendar(cx, calendarString, &calendar)) { + return nullptr; + } + } else { + calendar.set(CalendarValue(cx->names().iso8601)); + } + + // Step 4.i. + if (maybeResolvedOptions) { + TemporalOverflow ignored; + if (!ToTemporalOverflow(cx, maybeResolvedOptions, &ignored)) { + return nullptr; + } + } + } + + // Step 5. + return CreateTemporalDateTime(cx, result, calendar); +} + +/** + * ToTemporalDateTime ( item [ , options ] ) + */ +Wrapped<PlainDateTimeObject*> js::temporal::ToTemporalDateTime( + JSContext* cx, Handle<Value> item) { + return ::ToTemporalDateTime(cx, item, nullptr); +} + +/** + * ToTemporalDateTime ( item [ , options ] ) + */ +bool js::temporal::ToTemporalDateTime(JSContext* cx, Handle<Value> item, + PlainDateTime* result) { + auto obj = ::ToTemporalDateTime(cx, item, nullptr); + if (!obj) { + return false; + } + + *result = ToPlainDateTime(&obj.unwrap()); + return true; +} + +/** + * ToTemporalDateTime ( item [ , options ] ) + */ +static bool ToTemporalDateTime( + JSContext* cx, Handle<Value> item, + MutableHandle<PlainDateTimeWithCalendar> result) { + HandleObject options = nullptr; + + auto* obj = ::ToTemporalDateTime(cx, item, options).unwrapOrNull(); + if (!obj) { + return false; + } + + auto dateTime = ToPlainDateTime(obj); + Rooted<CalendarValue> calendar(cx, obj->calendar()); + if (!calendar.wrap(cx)) { + return false; + } + + result.set(PlainDateTimeWithCalendar{dateTime, calendar}); + return true; +} + +/** + * CompareISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, + * d2, h2, min2, s2, ms2, mus2, ns2 ) + */ +static int32_t CompareISODateTime(const PlainDateTime& one, + const PlainDateTime& two) { + // Step 1. (Not applicable in our implementation.) + + // Steps 2-3. + if (int32_t dateResult = CompareISODate(one.date, two.date)) { + return dateResult; + } + + // Steps 4. + return CompareTemporalTime(one.time, two.time); +} + +/** + * AddDateTime ( year, month, day, hour, minute, second, millisecond, + * microsecond, nanosecond, calendarRec, years, months, weeks, days, hours, + * minutes, seconds, milliseconds, microseconds, nanoseconds, options ) + */ +static bool AddDateTime(JSContext* cx, const PlainDateTime& dateTime, + Handle<CalendarRecord> calendar, + const Duration& duration, Handle<JSObject*> options, + PlainDateTime* result) { + MOZ_ASSERT(IsValidDuration(duration)); + + // Step 1. + MOZ_ASSERT(IsValidISODateTime(dateTime)); + MOZ_ASSERT(ISODateTimeWithinLimits(dateTime)); + + // Step 2. + PlainTime timeResult; + double daysResult; + if (!AddTime(cx, dateTime.time, duration, &timeResult, &daysResult)) { + return false; + } + + // Step 3. + const auto& datePart = dateTime.date; + + // Step 4. + Duration dateDuration = {duration.years, duration.months, duration.weeks, + daysResult}; + MOZ_ASSERT(IsValidDuration(duration)); + + // Step 5. + PlainDate addedDate; + if (!AddDate(cx, calendar, datePart, dateDuration, options, &addedDate)) { + return false; + } + + // Step 6. + *result = {addedDate, timeResult}; + return true; +} + +/** + * DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, + * d2, h2, min2, s2, ms2, mus2, ns2, calendarRec, largestUnit, options ) + */ +static bool DifferenceISODateTime(JSContext* cx, const PlainDateTime& one, + const PlainDateTime& two, + Handle<CalendarRecord> calendar, + TemporalUnit largestUnit, + Handle<PlainObject*> maybeOptions, + Duration* result) { + // Steps 1-2. + MOZ_ASSERT(IsValidISODateTime(one)); + MOZ_ASSERT(IsValidISODateTime(two)); + MOZ_ASSERT(ISODateTimeWithinLimits(one)); + MOZ_ASSERT(ISODateTimeWithinLimits(two)); + + // Step 3. + MOZ_ASSERT_IF( + one.date != two.date && largestUnit < TemporalUnit::Day, + CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil)); + + // Step 4. + auto timeDifference = DifferenceTime(one.time, two.time); + + // Step 5. + int32_t timeSign = DurationSign(timeDifference.toDuration()); + + // Step 6. + int32_t dateSign = CompareISODate(two.date, one.date); + + // Step 7. + auto adjustedDate = one.date; + + // Step 8. + if (timeSign == -dateSign) { + // Step 8.a. + adjustedDate = BalanceISODate(adjustedDate.year, adjustedDate.month, + adjustedDate.day - timeSign); + + // Step 8.b. + if (!BalanceTimeDuration(cx, + { + 0, + 0, + 0, + double(-timeSign), + timeDifference.hours, + timeDifference.minutes, + timeDifference.seconds, + timeDifference.milliseconds, + timeDifference.microseconds, + timeDifference.nanoseconds, + }, + largestUnit, &timeDifference)) { + return false; + } + } + + MOZ_ASSERT(IsValidISODate(adjustedDate)); + MOZ_ASSERT(ISODateTimeWithinLimits(adjustedDate)); + + // TODO: Avoid allocating CreateTemporalDate. + + // Step 9. + Rooted<PlainDateObject*> date1( + cx, CreateTemporalDate(cx, adjustedDate, calendar.receiver())); + if (!date1) { + return false; + } + + // Step 10. + Rooted<PlainDateObject*> date2( + cx, CreateTemporalDate(cx, two.date, calendar.receiver())); + if (!date2) { + return false; + } + + // Step 11. + auto dateLargestUnit = std::min(TemporalUnit::Day, largestUnit); + + Duration dateDifference; + if (maybeOptions) { + // FIXME: spec issue - this copy is no longer needed, all callers have + // already copied the user input object. + // https://github.com/tc39/proposal-temporal/issues/2525 + + // Step 12. + Rooted<PlainObject*> untilOptions(cx, + SnapshotOwnProperties(cx, maybeOptions)); + if (!untilOptions) { + return false; + } + + // Step 13. + Rooted<Value> largestUnitValue( + cx, StringValue(TemporalUnitToString(cx, dateLargestUnit))); + if (!DefineDataProperty(cx, untilOptions, cx->names().largestUnit, + largestUnitValue)) { + return false; + } + + // Step 14. + if (!DifferenceDate(cx, calendar, date1, date2, untilOptions, + &dateDifference)) { + return false; + } + } else { + // Steps 12-14. + if (!DifferenceDate(cx, calendar, date1, date2, dateLargestUnit, + &dateDifference)) { + return false; + } + } + + // Step 15. + TimeDuration balanceResult; + if (!BalanceTimeDuration(cx, + { + 0, + 0, + 0, + dateDifference.days, + timeDifference.hours, + timeDifference.minutes, + timeDifference.seconds, + timeDifference.milliseconds, + timeDifference.microseconds, + timeDifference.nanoseconds, + }, + largestUnit, &balanceResult)) { + return false; + } + + // Step 16. + *result = {dateDifference.years, dateDifference.months, + dateDifference.weeks, balanceResult.days, + balanceResult.hours, balanceResult.minutes, + balanceResult.seconds, balanceResult.milliseconds, + balanceResult.microseconds, balanceResult.nanoseconds}; + MOZ_ASSERT(IsValidDuration(*result)); + return true; +} + +/** + * DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, + * d2, h2, min2, s2, ms2, mus2, ns2, calendarRec, largestUnit, options ) + */ +bool js::temporal::DifferenceISODateTime(JSContext* cx, + const PlainDateTime& one, + const PlainDateTime& two, + Handle<CalendarRecord> calendar, + TemporalUnit largestUnit, + Duration* result) { + return ::DifferenceISODateTime(cx, one, two, calendar, largestUnit, nullptr, + result); +} + +/** + * DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, + * d2, h2, min2, s2, ms2, mus2, ns2, calendarRec, largestUnit, options ) + */ +bool js::temporal::DifferenceISODateTime( + JSContext* cx, const PlainDateTime& one, const PlainDateTime& two, + Handle<CalendarRecord> calendar, TemporalUnit largestUnit, + Handle<PlainObject*> options, Duration* result) { + return ::DifferenceISODateTime(cx, one, two, calendar, largestUnit, options, + result); +} + +/** + * RoundISODateTime ( year, month, day, hour, minute, second, millisecond, + * microsecond, nanosecond, increment, unit, roundingMode [ , dayLength ] ) + */ +static PlainDateTime RoundISODateTime(const PlainDateTime& dateTime, + Increment increment, TemporalUnit unit, + TemporalRoundingMode roundingMode) { + 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); + MOZ_ASSERT(0 <= roundedTime.days && roundedTime.days <= 1); + + // Step 4. + auto balanceResult = + BalanceISODate(date.year, date.month, date.day + roundedTime.days); + + // Step 5. + return {balanceResult, roundedTime.time}; +} + +/** + * DifferenceTemporalPlainDateTime ( operation, dateTime, other, options ) + */ +static bool DifferenceTemporalPlainDateTime(JSContext* cx, + TemporalDifference operation, + const CallArgs& args) { + Rooted<PlainDateTimeWithCalendar> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + + // Step 1. (Not applicable in our implementation.) + + // Step 2. + Rooted<PlainDateTimeWithCalendar> other(cx); + if (!::ToTemporalDateTime(cx, args.get(0), &other)) { + return false; + } + + // Step 3. + if (!CalendarEqualsOrThrow(cx, dateTime.calendar(), other.calendar())) { + return false; + } + + // Steps 4-5. + DifferenceSettings settings; + Rooted<PlainObject*> resolvedOptions(cx); + 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::Day, &settings)) { + return false; + } + } else { + // Steps 4-5. + settings = { + TemporalUnit::Nanosecond, + TemporalUnit::Day, + TemporalRoundingMode::Trunc, + Increment{1}, + }; + } + + // Steps 6-7. + bool datePartsIdentical = dateTime.date() == other.date(); + + // Step 8. + if (datePartsIdentical && dateTime.time() == other.time()) { + auto* obj = CreateTemporalDuration(cx, {}); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; + } + + // Step 9. + Rooted<CalendarRecord> calendar(cx); + if (!CreateCalendarMethodsRecord(cx, dateTime.calendar(), + { + CalendarMethod::DateAdd, + CalendarMethod::DateUntil, + }, + &calendar)) { + return false; + } + + // Step 10. + Duration diff; + if (!::DifferenceISODateTime(cx, dateTime, other, calendar, + settings.largestUnit, resolvedOptions, &diff)) { + return false; + } + + // Step 11. + bool roundingGranularityIsNoop = + settings.smallestUnit == TemporalUnit::Nanosecond && + settings.roundingIncrement == Increment{1}; + + // Step 12. + if (roundingGranularityIsNoop) { + if (operation == TemporalDifference::Since) { + diff = diff.negate(); + } + + auto* obj = CreateTemporalDuration(cx, diff); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; + } + + // Step 13. + Rooted<PlainDateObject*> relativeTo( + cx, CreateTemporalDate(cx, dateTime.date(), dateTime.calendar())); + if (!relativeTo) { + return false; + } + + // Steps 14-15. + Duration roundResult; + if (!temporal::RoundDuration(cx, diff, settings.roundingIncrement, + settings.smallestUnit, settings.roundingMode, + relativeTo, calendar, &roundResult)) { + return false; + } + + // Step 16. + TimeDuration result; + if (!BalanceTimeDuration(cx, roundResult, settings.largestUnit, &result)) { + return false; + } + + // Step 17. + auto toBalance = Duration{ + roundResult.years, + roundResult.months, + roundResult.weeks, + result.days, + }; + DateDuration balanceResult; + if (!temporal::BalanceDateDurationRelative( + cx, toBalance, settings.largestUnit, settings.smallestUnit, + relativeTo, calendar, &balanceResult)) { + return false; + } + + // Step 18. + Duration duration = { + balanceResult.years, balanceResult.months, balanceResult.weeks, + balanceResult.days, result.hours, result.minutes, + result.seconds, result.milliseconds, result.microseconds, + result.nanoseconds, + }; + if (operation == TemporalDifference::Since) { + duration = duration.negate(); + } + + auto* obj = CreateTemporalDuration(cx, duration); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +enum class PlainDateTimeDuration { Add, Subtract }; + +/** + * AddDurationToOrSubtractDurationFromPlainDateTime ( operation, dateTime, + * temporalDurationLike, options ) + */ +static bool AddDurationToOrSubtractDurationFromPlainDateTime( + JSContext* cx, PlainDateTimeDuration operation, const CallArgs& args) { + Rooted<PlainDateTimeWithCalendar> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + + // 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 == PlainDateTimeDuration::Add ? "add" : "subtract"; + options = RequireObjectArg(cx, "options", name, args[1]); + } else { + options = NewPlainObjectWithProto(cx, nullptr); + } + if (!options) { + return false; + } + + // Step 4. + Rooted<CalendarRecord> calendar(cx); + if (!CreateCalendarMethodsRecord(cx, dateTime.calendar(), + { + CalendarMethod::DateAdd, + }, + &calendar)) { + return false; + } + + // Step 5. + if (operation == PlainDateTimeDuration::Subtract) { + duration = duration.negate(); + } + + PlainDateTime result; + if (!AddDateTime(cx, dateTime, calendar, duration, options, &result)) { + return false; + } + + // Steps 6-7. + MOZ_ASSERT(IsValidISODateTime(result)); + + // Step 8. + auto* obj = CreateTemporalDateTime(cx, result, dateTime.calendar()); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.PlainDateTime ( isoYear, isoMonth, isoDay [ , hour [ , minute [ , + * second [ , millisecond [ , microsecond [ , nanosecond [ , calendarLike ] ] ] + * ] ] ] ] ) + */ +static bool PlainDateTimeConstructor(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + if (!ThrowIfNotConstructing(cx, args, "Temporal.PlainDateTime")) { + return false; + } + + // Step 2. + double isoYear; + if (!ToIntegerWithTruncation(cx, args.get(0), "year", &isoYear)) { + return false; + } + + // Step 3. + double isoMonth; + if (!ToIntegerWithTruncation(cx, args.get(1), "month", &isoMonth)) { + return false; + } + + // Step 4. + double isoDay; + if (!ToIntegerWithTruncation(cx, args.get(2), "day", &isoDay)) { + return false; + } + + // Step 5. + double hour = 0; + if (args.hasDefined(3)) { + if (!ToIntegerWithTruncation(cx, args[3], "hour", &hour)) { + return false; + } + } + + // Step 6. + double minute = 0; + if (args.hasDefined(4)) { + if (!ToIntegerWithTruncation(cx, args[4], "minute", &minute)) { + return false; + } + } + + // Step 7. + double second = 0; + if (args.hasDefined(5)) { + if (!ToIntegerWithTruncation(cx, args[5], "second", &second)) { + return false; + } + } + + // Step 8. + double millisecond = 0; + if (args.hasDefined(6)) { + if (!ToIntegerWithTruncation(cx, args[6], "millisecond", &millisecond)) { + return false; + } + } + + // Step 9. + double microsecond = 0; + if (args.hasDefined(7)) { + if (!ToIntegerWithTruncation(cx, args[7], "microsecond", µsecond)) { + return false; + } + } + + // Step 10. + double nanosecond = 0; + if (args.hasDefined(8)) { + if (!ToIntegerWithTruncation(cx, args[8], "nanosecond", &nanosecond)) { + return false; + } + } + + // Step 11. + Rooted<CalendarValue> calendar(cx); + if (!ToTemporalCalendarWithISODefault(cx, args.get(9), &calendar)) { + return false; + } + + // Step 12. + auto* temporalDateTime = CreateTemporalDateTime( + cx, args, isoYear, isoMonth, isoDay, hour, minute, second, millisecond, + microsecond, nanosecond, calendar); + if (!temporalDateTime) { + return false; + } + + args.rval().setObject(*temporalDateTime); + return true; +} + +/** + * Temporal.PlainDateTime.from ( item [ , options ] ) + */ +static bool PlainDateTime_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* temporalDateTime = item->maybeUnwrapIf<PlainDateTimeObject>()) { + auto dateTime = ToPlainDateTime(temporalDateTime); + + Rooted<CalendarValue> calendar(cx, temporalDateTime->calendar()); + if (!calendar.wrap(cx)) { + return false; + } + + if (options) { + // Step 2.a. + TemporalOverflow ignored; + if (!ToTemporalOverflow(cx, options, &ignored)) { + return false; + } + } + + // Step 2.b. + auto* result = CreateTemporalDateTime(cx, dateTime, calendar); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; + } + } + + // Step 3. + auto result = ToTemporalDateTime(cx, args.get(0), options); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.PlainDateTime.compare ( one, two ) + */ +static bool PlainDateTime_compare(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + PlainDateTime one; + if (!ToTemporalDateTime(cx, args.get(0), &one)) { + return false; + } + + // Step 2. + PlainDateTime two; + if (!ToTemporalDateTime(cx, args.get(1), &two)) { + return false; + } + + // Step 3. + args.rval().setInt32(CompareISODateTime(one, two)); + return true; +} + +/** + * get Temporal.PlainDateTime.prototype.calendarId + */ +static bool PlainDateTime_calendarId(JSContext* cx, const CallArgs& args) { + auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + + // Step 3. + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + auto* calendarId = ToTemporalCalendarIdentifier(cx, calendar); + if (!calendarId) { + return false; + } + + args.rval().setString(calendarId); + return true; +} + +/** + * get Temporal.PlainDateTime.prototype.calendarId + */ +static bool PlainDateTime_calendarId(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_calendarId>(cx, + args); +} + +/** + * get Temporal.PlainDateTime.prototype.year + */ +static bool PlainDateTime_year(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted<PlainDateTimeObject*> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + // Step 4. + return CalendarYear(cx, calendar, dateTime, args.rval()); +} + +/** + * get Temporal.PlainDateTime.prototype.year + */ +static bool PlainDateTime_year(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_year>(cx, args); +} + +/** + * get Temporal.PlainDateTime.prototype.month + */ +static bool PlainDateTime_month(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted<PlainDateTimeObject*> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + // Step 4. + return CalendarMonth(cx, calendar, dateTime, args.rval()); +} + +/** + * get Temporal.PlainDateTime.prototype.month + */ +static bool PlainDateTime_month(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_month>(cx, args); +} + +/** + * get Temporal.PlainDateTime.prototype.monthCode + */ +static bool PlainDateTime_monthCode(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted<PlainDateTimeObject*> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + // Step 4. + return CalendarMonthCode(cx, calendar, dateTime, args.rval()); +} + +/** + * get Temporal.PlainDateTime.prototype.monthCode + */ +static bool PlainDateTime_monthCode(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_monthCode>(cx, + args); +} + +/** + * get Temporal.PlainDateTime.prototype.day + */ +static bool PlainDateTime_day(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted<PlainDateTimeObject*> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + // Step 4. + return CalendarDay(cx, calendar, dateTime, args.rval()); +} + +/** + * get Temporal.PlainDateTime.prototype.day + */ +static bool PlainDateTime_day(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_day>(cx, args); +} + +/** + * get Temporal.PlainDateTime.prototype.hour + */ +static bool PlainDateTime_hour(JSContext* cx, const CallArgs& args) { + // Step 3. + auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + args.rval().setInt32(dateTime->isoHour()); + return true; +} + +/** + * get Temporal.PlainDateTime.prototype.hour + */ +static bool PlainDateTime_hour(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_hour>(cx, args); +} + +/** + * get Temporal.PlainDateTime.prototype.minute + */ +static bool PlainDateTime_minute(JSContext* cx, const CallArgs& args) { + // Step 3. + auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + args.rval().setInt32(dateTime->isoMinute()); + return true; +} + +/** + * get Temporal.PlainDateTime.prototype.minute + */ +static bool PlainDateTime_minute(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_minute>(cx, args); +} + +/** + * get Temporal.PlainDateTime.prototype.second + */ +static bool PlainDateTime_second(JSContext* cx, const CallArgs& args) { + // Step 3. + auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + args.rval().setInt32(dateTime->isoSecond()); + return true; +} + +/** + * get Temporal.PlainDateTime.prototype.second + */ +static bool PlainDateTime_second(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_second>(cx, args); +} + +/** + * get Temporal.PlainDateTime.prototype.millisecond + */ +static bool PlainDateTime_millisecond(JSContext* cx, const CallArgs& args) { + // Step 3. + auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + args.rval().setInt32(dateTime->isoMillisecond()); + return true; +} + +/** + * get Temporal.PlainDateTime.prototype.millisecond + */ +static bool PlainDateTime_millisecond(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_millisecond>(cx, + args); +} + +/** + * get Temporal.PlainDateTime.prototype.microsecond + */ +static bool PlainDateTime_microsecond(JSContext* cx, const CallArgs& args) { + // Step 3. + auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + args.rval().setInt32(dateTime->isoMicrosecond()); + return true; +} + +/** + * get Temporal.PlainDateTime.prototype.microsecond + */ +static bool PlainDateTime_microsecond(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_microsecond>(cx, + args); +} + +/** + * get Temporal.PlainDateTime.prototype.nanosecond + */ +static bool PlainDateTime_nanosecond(JSContext* cx, const CallArgs& args) { + // Step 3. + auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + args.rval().setInt32(dateTime->isoNanosecond()); + return true; +} + +/** + * get Temporal.PlainDateTime.prototype.nanosecond + */ +static bool PlainDateTime_nanosecond(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_nanosecond>(cx, + args); +} + +/** + * get Temporal.PlainDateTime.prototype.dayOfWeek + */ +static bool PlainDateTime_dayOfWeek(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted<PlainDateTimeObject*> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + // Step 4. + return CalendarDayOfWeek(cx, calendar, dateTime, args.rval()); +} + +/** + * get Temporal.PlainDateTime.prototype.dayOfWeek + */ +static bool PlainDateTime_dayOfWeek(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_dayOfWeek>(cx, + args); +} + +/** + * get Temporal.PlainDateTime.prototype.dayOfYear + */ +static bool PlainDateTime_dayOfYear(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted<PlainDateTimeObject*> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + // Step 4. + return CalendarDayOfYear(cx, calendar, dateTime, args.rval()); +} + +/** + * get Temporal.PlainDateTime.prototype.dayOfYear + */ +static bool PlainDateTime_dayOfYear(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_dayOfYear>(cx, + args); +} + +/** + * get Temporal.PlainDateTime.prototype.weekOfYear + */ +static bool PlainDateTime_weekOfYear(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted<PlainDateTimeObject*> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + // Step 4. + return CalendarWeekOfYear(cx, calendar, dateTime, args.rval()); +} + +/** + * get Temporal.PlainDateTime.prototype.weekOfYear + */ +static bool PlainDateTime_weekOfYear(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_weekOfYear>(cx, + args); +} + +/** + * get Temporal.PlainDateTime.prototype.yearOfWeek + */ +static bool PlainDateTime_yearOfWeek(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted<PlainDateTimeObject*> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + // Step 4. + return CalendarYearOfWeek(cx, calendar, dateTime, args.rval()); +} + +/** + * get Temporal.PlainDateTime.prototype.yearOfWeek + */ +static bool PlainDateTime_yearOfWeek(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_yearOfWeek>(cx, + args); +} + +/** + * get Temporal.PlainDateTime.prototype.daysInWeek + */ +static bool PlainDateTime_daysInWeek(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted<PlainDateTimeObject*> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + // Step 4. + return CalendarDaysInWeek(cx, calendar, dateTime, args.rval()); +} + +/** + * get Temporal.PlainDateTime.prototype.daysInWeek + */ +static bool PlainDateTime_daysInWeek(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_daysInWeek>(cx, + args); +} + +/** + * get Temporal.PlainDateTime.prototype.daysInMonth + */ +static bool PlainDateTime_daysInMonth(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted<PlainDateTimeObject*> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + // Step 4. + return CalendarDaysInMonth(cx, calendar, dateTime, args.rval()); +} + +/** + * get Temporal.PlainDateTime.prototype.daysInMonth + */ +static bool PlainDateTime_daysInMonth(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_daysInMonth>(cx, + args); +} + +/** + * get Temporal.PlainDateTime.prototype.daysInYear + */ +static bool PlainDateTime_daysInYear(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted<PlainDateTimeObject*> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + // Step 4. + return CalendarDaysInYear(cx, calendar, dateTime, args.rval()); +} + +/** + * get Temporal.PlainDateTime.prototype.daysInYear + */ +static bool PlainDateTime_daysInYear(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_daysInYear>(cx, + args); +} + +/** + * get Temporal.PlainDateTime.prototype.monthsInYear + */ +static bool PlainDateTime_monthsInYear(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted<PlainDateTimeObject*> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + // Step 4. + return CalendarMonthsInYear(cx, calendar, dateTime, args.rval()); +} + +/** + * get Temporal.PlainDateTime.prototype.monthsInYear + */ +static bool PlainDateTime_monthsInYear(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_monthsInYear>( + cx, args); +} + +/** + * get Temporal.PlainDateTime.prototype.inLeapYear + */ +static bool PlainDateTime_inLeapYear(JSContext* cx, const CallArgs& args) { + // Step 3. + Rooted<PlainDateTimeObject*> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + // Step 4. + return CalendarInLeapYear(cx, calendar, dateTime, args.rval()); +} + +/** + * get Temporal.PlainDateTime.prototype.inLeapYear + */ +static bool PlainDateTime_inLeapYear(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_inLeapYear>(cx, + args); +} + +/** + * Temporal.PlainDateTime.prototype.with ( temporalDateTimeLike [ , options ] ) + */ +static bool PlainDateTime_with(JSContext* cx, const CallArgs& args) { + Rooted<PlainDateTimeObject*> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + + // Step 3. + Rooted<JSObject*> temporalDateTimeLike( + cx, RequireObjectArg(cx, "temporalDateTimeLike", "with", args.get(0))); + if (!temporalDateTimeLike) { + return false; + } + + // Step 4. + if (!RejectTemporalLikeObject(cx, temporalDateTimeLike)) { + 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<CalendarValue> calendarValue(cx, dateTime->calendar()); + Rooted<CalendarRecord> calendar(cx); + if (!CreateCalendarMethodsRecord(cx, calendarValue, + { + CalendarMethod::DateFromFields, + CalendarMethod::Fields, + CalendarMethod::MergeFields, + }, + &calendar)) { + return false; + } + + // Step 7. + JS::RootedVector<PropertyKey> fieldNames(cx); + if (!CalendarFields(cx, calendar, + {CalendarField::Day, CalendarField::Month, + CalendarField::MonthCode, CalendarField::Year}, + &fieldNames)) { + return false; + } + + // Step 8. + Rooted<PlainObject*> fields(cx, + PrepareTemporalFields(cx, dateTime, fieldNames)); + if (!fields) { + return false; + } + + // Steps 9-14. + 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 15. + if (!AppendSorted(cx, fieldNames.get(), + { + TemporalField::Hour, + TemporalField::Microsecond, + TemporalField::Millisecond, + TemporalField::Minute, + TemporalField::Nanosecond, + TemporalField::Second, + })) { + return false; + } + + // Step 16. + Rooted<PlainObject*> partialDateTime( + cx, PreparePartialTemporalFields(cx, temporalDateTimeLike, fieldNames)); + if (!partialDateTime) { + return false; + } + + // Step 17. + Rooted<JSObject*> mergedFields( + cx, CalendarMergeFields(cx, calendar, fields, partialDateTime)); + if (!mergedFields) { + return false; + } + + // Step 18. + fields = PrepareTemporalFields(cx, mergedFields, fieldNames); + if (!fields) { + return false; + } + + // Step 19. + PlainDateTime result; + if (!InterpretTemporalDateTimeFields(cx, calendar, fields, resolvedOptions, + &result)) { + return false; + } + + // Steps 20-21. + MOZ_ASSERT(IsValidISODateTime(result)); + + // Step 22. + auto* obj = CreateTemporalDateTime(cx, result, calendar.receiver()); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.PlainDateTime.prototype.with ( temporalDateTimeLike [ , options ] ) + */ +static bool PlainDateTime_with(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_with>(cx, args); +} + +/** + * Temporal.PlainDateTime.prototype.withPlainTime ( [ plainTimeLike ] ) + */ +static bool PlainDateTime_withPlainTime(JSContext* cx, const CallArgs& args) { + auto* temporalDateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + auto date = ToPlainDate(temporalDateTime); + Rooted<CalendarValue> calendar(cx, temporalDateTime->calendar()); + + // Step 4. + PlainTime time = {}; + if (args.hasDefined(0)) { + if (!ToTemporalTime(cx, args[0], &time)) { + return false; + } + } + + // Steps 3 and 5. + auto* obj = CreateTemporalDateTime(cx, {date, time}, calendar); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.PlainDateTime.prototype.withPlainTime ( [ plainTimeLike ] ) + */ +static bool PlainDateTime_withPlainTime(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_withPlainTime>( + cx, args); +} + +/** + * Temporal.PlainDateTime.prototype.withPlainDate ( plainDateLike ) + */ +static bool PlainDateTime_withPlainDate(JSContext* cx, const CallArgs& args) { + auto* temporalDateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + auto time = ToPlainTime(temporalDateTime); + Rooted<CalendarValue> calendar(cx, temporalDateTime->calendar()); + + // Step 3. + Rooted<PlainDateWithCalendar> plainDate(cx); + if (!ToTemporalDate(cx, args.get(0), &plainDate)) { + return false; + } + auto date = plainDate.date(); + + // Step 4. + if (!ConsolidateCalendars(cx, calendar, plainDate.calendar(), &calendar)) { + return false; + } + + // Step 5. + auto* obj = CreateTemporalDateTime(cx, {date, time}, calendar); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.PlainDateTime.prototype.withPlainDate ( plainDateLike ) + */ +static bool PlainDateTime_withPlainDate(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_withPlainDate>( + cx, args); +} + +/** + * Temporal.PlainDateTime.prototype.withCalendar ( calendar ) + */ +static bool PlainDateTime_withCalendar(JSContext* cx, const CallArgs& args) { + auto* temporalDateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + auto dateTime = ToPlainDateTime(temporalDateTime); + + // Step 3. + Rooted<CalendarValue> calendar(cx); + if (!ToTemporalCalendar(cx, args.get(0), &calendar)) { + return false; + } + + // Step 4. + auto* result = CreateTemporalDateTime(cx, dateTime, calendar); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.PlainDateTime.prototype.withCalendar ( calendar ) + */ +static bool PlainDateTime_withCalendar(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_withCalendar>( + cx, args); +} + +/** + * Temporal.PlainDateTime.prototype.add ( temporalDurationLike [ , options ] ) + */ +static bool PlainDateTime_add(JSContext* cx, const CallArgs& args) { + // Step 3. + return AddDurationToOrSubtractDurationFromPlainDateTime( + cx, PlainDateTimeDuration::Add, args); +} + +/** + * Temporal.PlainDateTime.prototype.add ( temporalDurationLike [ , options ] ) + */ +static bool PlainDateTime_add(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_add>(cx, args); +} + +/** + * Temporal.PlainDateTime.prototype.subtract ( temporalDurationLike [ , options + * ] ) + */ +static bool PlainDateTime_subtract(JSContext* cx, const CallArgs& args) { + // Step 3. + return AddDurationToOrSubtractDurationFromPlainDateTime( + cx, PlainDateTimeDuration::Subtract, args); +} + +/** + * Temporal.PlainDateTime.prototype.subtract ( temporalDurationLike [ , options + * ] ) + */ +static bool PlainDateTime_subtract(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_subtract>(cx, + args); +} + +/** + * Temporal.PlainDateTime.prototype.until ( other [ , options ] ) + */ +static bool PlainDateTime_until(JSContext* cx, const CallArgs& args) { + // Step 3. + return DifferenceTemporalPlainDateTime(cx, TemporalDifference::Until, args); +} + +/** + * Temporal.PlainDateTime.prototype.until ( other [ , options ] ) + */ +static bool PlainDateTime_until(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_until>(cx, args); +} + +/** + * Temporal.PlainDateTime.prototype.since ( other [ , options ] ) + */ +static bool PlainDateTime_since(JSContext* cx, const CallArgs& args) { + // Step 3. + return DifferenceTemporalPlainDateTime(cx, TemporalDifference::Since, args); +} + +/** + * Temporal.PlainDateTime.prototype.since ( other [ , options ] ) + */ +static bool PlainDateTime_since(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_since>(cx, args); +} + +/** + * Temporal.PlainDateTime.prototype.round ( roundTo ) + */ +static bool PlainDateTime_round(JSContext* cx, const CallArgs& args) { + auto* temporalDateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + auto dateTime = ToPlainDateTime(temporalDateTime); + Rooted<CalendarValue> calendar(cx, temporalDateTime->calendar()); + + // 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; + } + + MOZ_ASSERT(TemporalUnit::Day <= smallestUnit && + smallestUnit <= TemporalUnit::Nanosecond); + + // Steps 6-8 and 10-12. (Implicit) + } else { + // Steps 3 and 5. + 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}) { + auto* obj = CreateTemporalDateTime(cx, dateTime, calendar); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; + } + + // Step 14. + auto result = + RoundISODateTime(dateTime, roundingIncrement, smallestUnit, roundingMode); + + // Step 15. + auto* obj = CreateTemporalDateTime(cx, result, calendar); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.PlainDateTime.prototype.round ( roundTo ) + */ +static bool PlainDateTime_round(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_round>(cx, args); +} + +/** + * Temporal.PlainDateTime.prototype.equals ( other ) + */ +static bool PlainDateTime_equals(JSContext* cx, const CallArgs& args) { + auto* temporalDateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + auto dateTime = ToPlainDateTime(temporalDateTime); + Rooted<CalendarValue> calendar(cx, temporalDateTime->calendar()); + + // Step 3. + Rooted<PlainDateTimeWithCalendar> other(cx); + if (!::ToTemporalDateTime(cx, args.get(0), &other)) { + return false; + } + + // Steps 4-13. + bool equals = dateTime == other.dateTime(); + if (equals && !CalendarEquals(cx, calendar, other.calendar(), &equals)) { + return false; + } + + args.rval().setBoolean(equals); + return true; +} + +/** + * Temporal.PlainDateTime.prototype.equals ( other ) + */ +static bool PlainDateTime_equals(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_equals>(cx, args); +} + +/** + * Temporal.PlainDateTime.prototype.toString ( [ options ] ) + */ +static bool PlainDateTime_toString(JSContext* cx, const CallArgs& args) { + auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + auto dt = ToPlainDateTime(dateTime); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + SecondsStringPrecision precision = {Precision::Auto(), + TemporalUnit::Nanosecond, Increment{1}}; + auto roundingMode = TemporalRoundingMode::Trunc; + auto showCalendar = CalendarOption::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 (!ToTemporalRoundingMode(cx, options, &roundingMode)) { + return false; + } + + // Step 8. + auto smallestUnit = TemporalUnit::Auto; + if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit, + TemporalUnitGroup::Time, &smallestUnit)) { + return false; + } + + // Step 9. + if (smallestUnit == TemporalUnit::Hour) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_TEMPORAL_INVALID_UNIT_OPTION, "hour", + "smallestUnit"); + return false; + } + + // Step 10. + precision = ToSecondsStringPrecision(smallestUnit, digits); + } + + // Step 11. + auto result = + RoundISODateTime(dt, precision.increment, precision.unit, roundingMode); + + // Step 12. + JSString* str = ::TemporalDateTimeToString(cx, result, calendar, + precision.precision, showCalendar); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +/** + * Temporal.PlainDateTime.prototype.toString ( [ options ] ) + */ +static bool PlainDateTime_toString(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_toString>(cx, + args); +} + +/** + * Temporal.PlainDateTime.prototype.toLocaleString ( [ locales [ , options ] ] ) + */ +static bool PlainDateTime_toLocaleString(JSContext* cx, const CallArgs& args) { + auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + auto dt = ToPlainDateTime(dateTime); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + // Step 3. + JSString* str = ::TemporalDateTimeToString( + cx, dt, calendar, Precision::Auto(), CalendarOption::Auto); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +/** + * Temporal.PlainDateTime.prototype.toLocaleString ( [ locales [ , options ] ] ) + */ +static bool PlainDateTime_toLocaleString(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_toLocaleString>( + cx, args); +} + +/** + * Temporal.PlainDateTime.prototype.toJSON ( ) + */ +static bool PlainDateTime_toJSON(JSContext* cx, const CallArgs& args) { + auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + auto dt = ToPlainDateTime(dateTime); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + // Step 3. + JSString* str = ::TemporalDateTimeToString( + cx, dt, calendar, Precision::Auto(), CalendarOption::Auto); + if (!str) { + return false; + } + + args.rval().setString(str); + return true; +} + +/** + * Temporal.PlainDateTime.prototype.toJSON ( ) + */ +static bool PlainDateTime_toJSON(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_toJSON>(cx, args); +} + +/** + * Temporal.PlainDateTime.prototype.valueOf ( ) + */ +static bool PlainDateTime_valueOf(JSContext* cx, unsigned argc, Value* vp) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, + "PlainDateTime", "primitive type"); + return false; +} + +/** + * Temporal.PlainDateTime.prototype.getISOFields ( ) + */ +static bool PlainDateTime_getISOFields(JSContext* cx, const CallArgs& args) { + auto* temporalDateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + auto dateTime = ToPlainDateTime(temporalDateTime); + auto calendar = temporalDateTime->calendar(); + + // Step 3. + Rooted<IdValueVector> fields(cx, IdValueVector(cx)); + + // Step 4. + if (!fields.emplaceBack(NameToId(cx->names().calendar), calendar.toValue())) { + return false; + } + + // Step 5. + if (!fields.emplaceBack(NameToId(cx->names().isoDay), + Int32Value(dateTime.date.day))) { + return false; + } + + // Step 6. + if (!fields.emplaceBack(NameToId(cx->names().isoHour), + Int32Value(dateTime.time.hour))) { + return false; + } + + // Step 7. + if (!fields.emplaceBack(NameToId(cx->names().isoMicrosecond), + Int32Value(dateTime.time.microsecond))) { + return false; + } + + // Step 8. + if (!fields.emplaceBack(NameToId(cx->names().isoMillisecond), + Int32Value(dateTime.time.millisecond))) { + return false; + } + + // Step 9. + if (!fields.emplaceBack(NameToId(cx->names().isoMinute), + Int32Value(dateTime.time.minute))) { + return false; + } + + // Step 10. + if (!fields.emplaceBack(NameToId(cx->names().isoMonth), + Int32Value(dateTime.date.month))) { + return false; + } + + // Step 11. + if (!fields.emplaceBack(NameToId(cx->names().isoNanosecond), + Int32Value(dateTime.time.nanosecond))) { + return false; + } + + // Step 12. + if (!fields.emplaceBack(NameToId(cx->names().isoSecond), + Int32Value(dateTime.time.second))) { + return false; + } + + // Step 13. + if (!fields.emplaceBack(NameToId(cx->names().isoYear), + Int32Value(dateTime.date.year))) { + return false; + } + + // Step 14. + auto* obj = + NewPlainObjectWithUniqueNames(cx, fields.begin(), fields.length()); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.PlainDateTime.prototype.getISOFields ( ) + */ +static bool PlainDateTime_getISOFields(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_getISOFields>( + cx, args); +} + +/** + * Temporal.PlainDateTime.prototype.getCalendar ( ) + */ +static bool PlainDateTime_getCalendar(JSContext* cx, const CallArgs& args) { + auto* temporalDateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + Rooted<CalendarValue> calendar(cx, temporalDateTime->calendar()); + + // Step 3. + auto* obj = ToTemporalCalendarObject(cx, calendar); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.PlainDateTime.prototype.getCalendar ( ) + */ +static bool PlainDateTime_getCalendar(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_getCalendar>(cx, + args); +} + +/** + * Temporal.PlainDateTime.prototype.toZonedDateTime ( temporalTimeZoneLike [ , + * options ] ) + */ +static bool PlainDateTime_toZonedDateTime(JSContext* cx, const CallArgs& args) { + Rooted<PlainDateTimeObject*> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + // Step 3. + Rooted<TimeZoneValue> timeZone(cx); + if (!ToTemporalTimeZone(cx, args.get(0), &timeZone)) { + return false; + } + + auto disambiguation = TemporalDisambiguation::Compatible; + if (args.hasDefined(1)) { + // Step 4. + Rooted<JSObject*> options( + cx, RequireObjectArg(cx, "options", "toZonedDateTime", args[1])); + if (!options) { + return false; + } + + // Step 5. + if (!ToTemporalDisambiguation(cx, options, &disambiguation)) { + return false; + } + } + + // Steps 6-7. + Instant instant; + if (!GetInstantFor(cx, timeZone, dateTime, disambiguation, &instant)) { + return false; + } + + // Step 8. + auto* result = CreateTemporalZonedDateTime(cx, instant, timeZone, calendar); + if (!result) { + return false; + } + + args.rval().setObject(*result); + return true; +} + +/** + * Temporal.PlainDateTime.prototype.toZonedDateTime ( temporalTimeZoneLike [ , + * options ] ) + */ +static bool PlainDateTime_toZonedDateTime(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_toZonedDateTime>( + cx, args); +} + +/** + * Temporal.PlainDateTime.prototype.toPlainDate ( ) + */ +static bool PlainDateTime_toPlainDate(JSContext* cx, const CallArgs& args) { + auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + Rooted<CalendarValue> calendar(cx, dateTime->calendar()); + + // Step 3. + auto* obj = CreateTemporalDate(cx, ToPlainDate(dateTime), calendar); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.PlainDateTime.prototype.toPlainDate ( ) + */ +static bool PlainDateTime_toPlainDate(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_toPlainDate>(cx, + args); +} + +/** + * Temporal.PlainDateTime.prototype.toPlainYearMonth ( ) + */ +static bool PlainDateTime_toPlainYearMonth(JSContext* cx, + const CallArgs& args) { + Rooted<PlainDateTimeObject*> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + Rooted<CalendarValue> calendarValue(cx, dateTime->calendar()); + + // Step 3. + Rooted<CalendarRecord> calendar(cx); + if (!CreateCalendarMethodsRecord(cx, calendarValue, + { + CalendarMethod::Fields, + CalendarMethod::YearMonthFromFields, + }, + &calendar)) { + return false; + } + + // Step 4. + JS::RootedVector<PropertyKey> fieldNames(cx); + if (!CalendarFields(cx, calendar, + {CalendarField::MonthCode, CalendarField::Year}, + &fieldNames)) { + return false; + } + + // Step 4. + Rooted<PlainObject*> fields(cx, + PrepareTemporalFields(cx, dateTime, fieldNames)); + if (!fields) { + return false; + } + + // Step 5. + auto obj = CalendarYearMonthFromFields(cx, calendar, fields); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.PlainDateTime.prototype.toPlainYearMonth ( ) + */ +static bool PlainDateTime_toPlainYearMonth(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_toPlainYearMonth>( + cx, args); +} + +/** + * Temporal.PlainDateTime.prototype.toPlainMonthDay ( ) + */ +static bool PlainDateTime_toPlainMonthDay(JSContext* cx, const CallArgs& args) { + Rooted<PlainDateTimeObject*> dateTime( + cx, &args.thisv().toObject().as<PlainDateTimeObject>()); + Rooted<CalendarValue> calendarValue(cx, dateTime->calendar()); + + // Step 3. + Rooted<CalendarRecord> calendar(cx); + if (!CreateCalendarMethodsRecord(cx, calendarValue, + { + CalendarMethod::Fields, + CalendarMethod::MonthDayFromFields, + }, + &calendar)) { + return false; + } + + // Step 4. + JS::RootedVector<PropertyKey> fieldNames(cx); + if (!CalendarFields(cx, calendar, + {CalendarField::Day, CalendarField::MonthCode}, + &fieldNames)) { + return false; + } + + // Step 5. + Rooted<PlainObject*> fields(cx, + PrepareTemporalFields(cx, dateTime, fieldNames)); + if (!fields) { + return false; + } + + // Steps 6-7. + auto obj = CalendarMonthDayFromFields(cx, calendar, fields); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.PlainDateTime.prototype.toPlainMonthDay ( ) + */ +static bool PlainDateTime_toPlainMonthDay(JSContext* cx, unsigned argc, + Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_toPlainMonthDay>( + cx, args); +} + +/** + * Temporal.PlainDateTime.prototype.toPlainTime ( ) + */ +static bool PlainDateTime_toPlainTime(JSContext* cx, const CallArgs& args) { + auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>(); + + // Step 3. + auto* obj = CreateTemporalTime(cx, ToPlainTime(dateTime)); + if (!obj) { + return false; + } + + args.rval().setObject(*obj); + return true; +} + +/** + * Temporal.PlainDateTime.prototype.toPlainTime ( ) + */ +static bool PlainDateTime_toPlainTime(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2. + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_toPlainTime>(cx, + args); +} + +const JSClass PlainDateTimeObject::class_ = { + "Temporal.PlainDateTime", + JSCLASS_HAS_RESERVED_SLOTS(PlainDateTimeObject::SLOT_COUNT) | + JSCLASS_HAS_CACHED_PROTO(JSProto_PlainDateTime), + JS_NULL_CLASS_OPS, + &PlainDateTimeObject::classSpec_, +}; + +const JSClass& PlainDateTimeObject::protoClass_ = PlainObject::class_; + +static const JSFunctionSpec PlainDateTime_methods[] = { + JS_FN("from", PlainDateTime_from, 1, 0), + JS_FN("compare", PlainDateTime_compare, 2, 0), + JS_FS_END, +}; + +static const JSFunctionSpec PlainDateTime_prototype_methods[] = { + JS_FN("with", PlainDateTime_with, 1, 0), + JS_FN("withPlainTime", PlainDateTime_withPlainTime, 0, 0), + JS_FN("withPlainDate", PlainDateTime_withPlainDate, 1, 0), + JS_FN("withCalendar", PlainDateTime_withCalendar, 1, 0), + JS_FN("add", PlainDateTime_add, 1, 0), + JS_FN("subtract", PlainDateTime_subtract, 1, 0), + JS_FN("until", PlainDateTime_until, 1, 0), + JS_FN("since", PlainDateTime_since, 1, 0), + JS_FN("round", PlainDateTime_round, 1, 0), + JS_FN("equals", PlainDateTime_equals, 1, 0), + JS_FN("toString", PlainDateTime_toString, 0, 0), + JS_FN("toLocaleString", PlainDateTime_toLocaleString, 0, 0), + JS_FN("toJSON", PlainDateTime_toJSON, 0, 0), + JS_FN("valueOf", PlainDateTime_valueOf, 0, 0), + JS_FN("toZonedDateTime", PlainDateTime_toZonedDateTime, 1, 0), + JS_FN("toPlainDate", PlainDateTime_toPlainDate, 0, 0), + JS_FN("toPlainYearMonth", PlainDateTime_toPlainYearMonth, 0, 0), + JS_FN("toPlainMonthDay", PlainDateTime_toPlainMonthDay, 0, 0), + JS_FN("toPlainTime", PlainDateTime_toPlainTime, 0, 0), + JS_FN("getISOFields", PlainDateTime_getISOFields, 0, 0), + JS_FN("getCalendar", PlainDateTime_getCalendar, 0, 0), + JS_FS_END, +}; + +static const JSPropertySpec PlainDateTime_prototype_properties[] = { + JS_PSG("calendarId", PlainDateTime_calendarId, 0), + JS_PSG("year", PlainDateTime_year, 0), + JS_PSG("month", PlainDateTime_month, 0), + JS_PSG("monthCode", PlainDateTime_monthCode, 0), + JS_PSG("day", PlainDateTime_day, 0), + JS_PSG("hour", PlainDateTime_hour, 0), + JS_PSG("minute", PlainDateTime_minute, 0), + JS_PSG("second", PlainDateTime_second, 0), + JS_PSG("millisecond", PlainDateTime_millisecond, 0), + JS_PSG("microsecond", PlainDateTime_microsecond, 0), + JS_PSG("nanosecond", PlainDateTime_nanosecond, 0), + JS_PSG("dayOfWeek", PlainDateTime_dayOfWeek, 0), + JS_PSG("dayOfYear", PlainDateTime_dayOfYear, 0), + JS_PSG("weekOfYear", PlainDateTime_weekOfYear, 0), + JS_PSG("yearOfWeek", PlainDateTime_yearOfWeek, 0), + JS_PSG("daysInWeek", PlainDateTime_daysInWeek, 0), + JS_PSG("daysInMonth", PlainDateTime_daysInMonth, 0), + JS_PSG("daysInYear", PlainDateTime_daysInYear, 0), + JS_PSG("monthsInYear", PlainDateTime_monthsInYear, 0), + JS_PSG("inLeapYear", PlainDateTime_inLeapYear, 0), + JS_STRING_SYM_PS(toStringTag, "Temporal.PlainDateTime", JSPROP_READONLY), + JS_PS_END, +}; + +const ClassSpec PlainDateTimeObject::classSpec_ = { + GenericCreateConstructor<PlainDateTimeConstructor, 3, + gc::AllocKind::FUNCTION>, + GenericCreatePrototype<PlainDateTimeObject>, + PlainDateTime_methods, + nullptr, + PlainDateTime_prototype_methods, + PlainDateTime_prototype_properties, + nullptr, + ClassSpec::DontDefineConstructor, +}; |