/* -*- 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 "mozilla/Casting.h" #include #include "jspubtd.h" #include "NamespaceImports.h" #include "builtin/intl/DateTimeFormat.h" #include "builtin/temporal/Calendar.h" #include "builtin/temporal/CalendarFields.h" #include "builtin/temporal/Duration.h" #include "builtin/temporal/Instant.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/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/ZonedDateTime.h" #include "gc/AllocKind.h" #include "gc/Barrier.h" #include "gc/GCEnum.h" #include "js/CallArgs.h" #include "js/CallNonGenericMethod.h" #include "js/Class.h" #include "js/ErrorReport.h" #include "js/friend/ErrorMessages.h" #include "js/PropertyDescriptor.h" #include "js/PropertySpec.h" #include "js/RootingAPI.h" #include "js/TypeDecls.h" #include "js/Value.h" #include "vm/BytecodeUtil.h" #include "vm/GlobalObject.h" #include "vm/JSAtomState.h" #include "vm/JSContext.h" #include "vm/JSObject.h" #include "vm/PlainObject.h" #include "vm/StringType.h" #include "vm/JSObject-inl.h" #include "vm/NativeObject-inl.h" using namespace js; using namespace js::temporal; static inline bool IsPlainDateTime(Handle v) { return v.isObject() && v.toObject().is(); } #ifdef DEBUG /** * IsValidISODate ( year, month, day ) * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond ) */ bool js::temporal::IsValidISODateTime(const ISODateTime& isoDateTime) { return IsValidISODate(isoDateTime.date) && IsValidTime(isoDateTime.time); } #endif /** * ISODateTimeWithinLimits ( isoDateTime ) */ bool js::temporal::ISODateTimeWithinLimits(const ISODateTime& isoDateTime) { MOZ_ASSERT(IsValidISODateTime(isoDateTime)); constexpr auto min = ISODate::min(); constexpr auto max = ISODate::max(); const auto& year = isoDateTime.date.year; // Fast-path when the input is definitely in range. if (min.year < year && year < max.year) { return true; } // Check |isoDateTime| is within the valid limits. if (year < 0) { if (isoDateTime.date != min) { return isoDateTime.date > min; } // Needs to be past midnight. return isoDateTime.time != Time{}; } return isoDateTime.date <= max; } /** * CreateTemporalDateTime ( isoDateTime, calendar [ , newTarget ] ) */ static PlainDateTimeObject* CreateTemporalDateTime( JSContext* cx, const CallArgs& args, const ISODateTime& isoDateTime, Handle calendar) { MOZ_ASSERT(IsValidISODateTime(isoDateTime)); // Step 1. if (!ISODateTimeWithinLimits(isoDateTime)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID); return nullptr; } // Steps 2-3. Rooted proto(cx); if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_PlainDateTime, &proto)) { return nullptr; } auto* object = NewObjectWithClassProto(cx, proto); if (!object) { return nullptr; } // Step 4. auto packedDate = PackedDate::pack(isoDateTime.date); auto packedTime = PackedTime::pack(isoDateTime.time); object->initFixedSlot(PlainDateTimeObject::PACKED_DATE_SLOT, PrivateUint32Value(packedDate.value)); object->initFixedSlot( PlainDateTimeObject::PACKED_TIME_SLOT, DoubleValue(mozilla::BitwiseCast(packedTime.value))); // Step 5. object->initFixedSlot(PlainDateTimeObject::CALENDAR_SLOT, calendar.toSlotValue()); // Step 6. return object; } /** * CreateTemporalDateTime ( isoDateTime, calendar [ , newTarget ] ) */ PlainDateTimeObject* js::temporal::CreateTemporalDateTime( JSContext* cx, const ISODateTime& isoDateTime, Handle calendar) { MOZ_ASSERT(IsValidISODateTime(isoDateTime)); // Step 1. if (!ISODateTimeWithinLimits(isoDateTime)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID); return nullptr; } // Steps 2-3. auto* object = NewBuiltinClassInstance(cx); if (!object) { return nullptr; } // Step 4. auto packedDate = PackedDate::pack(isoDateTime.date); auto packedTime = PackedTime::pack(isoDateTime.time); object->initFixedSlot(PlainDateTimeObject::PACKED_DATE_SLOT, PrivateUint32Value(packedDate.value)); object->initFixedSlot( PlainDateTimeObject::PACKED_TIME_SLOT, DoubleValue(mozilla::BitwiseCast(packedTime.value))); // Step 5. object->initFixedSlot(PlainDateTimeObject::CALENDAR_SLOT, calendar.toSlotValue()); // Step 6. return object; } /** * CreateTemporalDateTime ( isoDateTime, calendar [ , newTarget ] ) */ static PlainDateTimeObject* CreateTemporalDateTime( JSContext* cx, Handle dateTime) { MOZ_ASSERT(ISODateTimeWithinLimits(dateTime)); return CreateTemporalDateTime(cx, dateTime, dateTime.calendar()); } /** * CreateTemporalDateTime ( isoDateTime, calendar [ , newTarget ] ) */ static bool CreateTemporalDateTime(JSContext* cx, const ISODateTime& dateTime, Handle calendar, MutableHandle result) { MOZ_ASSERT(IsValidISODateTime(dateTime)); // Step 1. if (!ISODateTimeWithinLimits(dateTime)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID); return false; } // Step 2-6. result.set(PlainDateTime{dateTime, calendar}); return true; } /** * InterpretTemporalDateTimeFields ( calendar, fields, overflow ) */ bool js::temporal::InterpretTemporalDateTimeFields( JSContext* cx, Handle calendar, Handle fields, TemporalOverflow overflow, ISODateTime* result) { // Step 1. Rooted temporalDate(cx); if (!CalendarDateFromFields(cx, calendar, fields, overflow, &temporalDate)) { return false; } // Step 2. auto timeLike = TemporalTimeLike{ fields.hour(), fields.minute(), fields.second(), fields.millisecond(), fields.microsecond(), fields.nanosecond(), }; Time time; if (!RegulateTime(cx, timeLike, overflow, &time)) { return false; } // Step 3. *result = {temporalDate.date(), time}; return true; } struct DateTimeOptions { TemporalOverflow overflow = TemporalOverflow::Constrain; }; /** * ToTemporalDateTime ( item [ , options ] ) */ static bool ToTemporalDateTimeOptions(JSContext* cx, Handle options, DateTimeOptions* result) { if (options.isUndefined()) { *result = {}; return true; } // NOTE: |options| are only passed from `Temporal.PlainDateTime.from`. Rooted resolvedOptions( cx, RequireObjectArg(cx, "options", "from", options)); if (!resolvedOptions) { return false; } auto overflow = TemporalOverflow::Constrain; if (!GetTemporalOverflowOption(cx, resolvedOptions, &overflow)) { return false; } *result = {overflow}; return true; } /** * ToTemporalDateTime ( item [ , options ] ) */ static bool ToTemporalDateTime(JSContext* cx, Handle item, Handle options, MutableHandle result) { // Step 1. (Not applicable in our implementation.) // Step 2.a. if (auto* plainDateTime = item->maybeUnwrapIf()) { auto dateTime = plainDateTime->dateTime(); Rooted calendar(cx, plainDateTime->calendar()); if (!calendar.wrap(cx)) { return false; } // Steps 2.a.i-ii. DateTimeOptions ignoredOptions; if (!ToTemporalDateTimeOptions(cx, options, &ignoredOptions)) { return false; } // Step 2.a.iii. result.set(PlainDateTime{dateTime, calendar}); return true; } // Step 2.b. if (auto* zonedDateTime = item->maybeUnwrapIf()) { auto epochNs = zonedDateTime->epochNanoseconds(); Rooted timeZone(cx, zonedDateTime->timeZone()); Rooted calendar(cx, zonedDateTime->calendar()); if (!timeZone.wrap(cx)) { return false; } if (!calendar.wrap(cx)) { return false; } // Step 2.b.i. ISODateTime dateTime; if (!GetISODateTimeFor(cx, timeZone, epochNs, &dateTime)) { return false; } // Steps 2.b.ii-iii. DateTimeOptions ignoredOptions; if (!ToTemporalDateTimeOptions(cx, options, &ignoredOptions)) { return false; } // Step 2.b.iv. result.set(PlainDateTime{dateTime, calendar}); return true; } // Step 2.c. if (auto* plainDate = item->maybeUnwrapIf()) { auto date = plainDate->date(); Rooted calendar(cx, plainDate->calendar()); if (!calendar.wrap(cx)) { return false; } // Steps 2.c.i-ii. DateTimeOptions ignoredOptions; if (!ToTemporalDateTimeOptions(cx, options, &ignoredOptions)) { return false; } // Steps 2.c.iii-iv. return CreateTemporalDateTime(cx, ISODateTime{date}, calendar, result); } // Step 2.d. Rooted calendar(cx); if (!GetTemporalCalendarWithISODefault(cx, item, &calendar)) { return false; } // Step 2.e. Rooted fields(cx); if (!PrepareCalendarFields(cx, calendar, item, { CalendarField::Year, CalendarField::Month, CalendarField::MonthCode, CalendarField::Day, CalendarField::Hour, CalendarField::Minute, CalendarField::Second, CalendarField::Millisecond, CalendarField::Microsecond, CalendarField::Nanosecond, }, &fields)) { return false; } // Steps 2.f-g. DateTimeOptions resolvedOptions; if (!ToTemporalDateTimeOptions(cx, options, &resolvedOptions)) { return false; } auto [overflow] = resolvedOptions; // Step 2.h. ISODateTime dateTime; if (!InterpretTemporalDateTimeFields(cx, calendar, fields, overflow, &dateTime)) { return false; } // Step 2.i. return CreateTemporalDateTime(cx, dateTime, calendar, result); } /** * ToTemporalDateTime ( item [ , options ] ) */ static bool ToTemporalDateTime(JSContext* cx, Handle item, Handle options, MutableHandle result) { // Step 1. (Not applicable) // Step 2. if (item.isObject()) { Rooted itemObj(cx, &item.toObject()); return ToTemporalDateTime(cx, itemObj, options, result); } // Step 3. if (!item.isString()) { ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, item, nullptr, "not a string"); return false; } Rooted string(cx, item.toString()); // Steps 4-5. ISODateTime dateTime; Rooted calendarString(cx); if (!ParseTemporalDateTimeString(cx, string, &dateTime, &calendarString)) { return false; } MOZ_ASSERT(IsValidISODateTime(dateTime)); // Steps 6-8. Rooted calendar(cx, CalendarValue(CalendarId::ISO8601)); if (calendarString) { if (!CanonicalizeCalendar(cx, calendarString, &calendar)) { return false; } } // Steps 9-10. DateTimeOptions ignoredOptions; if (!ToTemporalDateTimeOptions(cx, options, &ignoredOptions)) { return false; } // Steps 11-13. return CreateTemporalDateTime(cx, dateTime, calendar, result); } /** * ToTemporalDateTime ( item [ , options ] ) */ static bool ToTemporalDateTime(JSContext* cx, Handle item, MutableHandle result) { return ToTemporalDateTime(cx, item, UndefinedHandleValue, result); } /** * CompareISODateTime ( isoDateTime1, isoDateTime2 ) */ static int32_t CompareISODateTime(const ISODateTime& isoDateTime1, const ISODateTime& isoDateTime2) { // Steps 1-2. if (int32_t dateResult = CompareISODate(isoDateTime1.date, isoDateTime2.date)) { return dateResult; } // Steps 3. return CompareTimeRecord(isoDateTime1.time, isoDateTime2.time); } /** * Add24HourDaysToTimeDuration ( d, days ) */ static TimeDuration Add24HourDaysToTimeDuration(const TimeDuration& d, int32_t days) { // Step 1. auto result = d + TimeDuration::fromDays(days); // Step 2. MOZ_ASSERT(result.abs() <= TimeDuration::max()); // Step 3. return result; } /** * DifferenceISODateTime ( isoDateTime1, isoDateTime2, calendar, largestUnit ) */ static bool DifferenceISODateTime(JSContext* cx, const ISODateTime& isoDateTime1, const ISODateTime& isoDateTime2, Handle calendar, TemporalUnit largestUnit, InternalDuration* result) { MOZ_ASSERT(isoDateTime1 != isoDateTime2, "fast-path for same date-time case handled in caller"); // Steps 1-2. MOZ_ASSERT(IsValidISODateTime(isoDateTime1)); MOZ_ASSERT(IsValidISODateTime(isoDateTime2)); MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime1)); MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime2)); // Step 3. auto timeDuration = DifferenceTime(isoDateTime1.time, isoDateTime2.time); // Step 4. int32_t timeSign = TimeDurationSign(timeDuration); // Step 5. int32_t dateSign = CompareISODate(isoDateTime1.date, isoDateTime2.date); // Step 6. auto adjustedDate = isoDateTime2.date; // Step 7. if (timeSign == dateSign) { // Step 7.a. adjustedDate = BalanceISODate(adjustedDate, timeSign); // Step 7.b. timeDuration = Add24HourDaysToTimeDuration(timeDuration, -timeSign); } MOZ_ASSERT(IsValidISODate(adjustedDate)); MOZ_ASSERT(ISODateWithinLimits(adjustedDate)); // Step 8. auto dateLargestUnit = std::min(TemporalUnit::Day, largestUnit); // Step 9. DateDuration dateDifference; if (!CalendarDateUntil(cx, calendar, isoDateTime1.date, adjustedDate, dateLargestUnit, &dateDifference)) { return false; } // Steps 10. if (largestUnit > TemporalUnit::Day) { // Step 10.a. auto days = mozilla::AssertedCast(dateDifference.days); timeDuration = Add24HourDaysToTimeDuration(timeDuration, days); // Step 10.b. dateDifference.days = 0; } // Step 11. MOZ_ASSERT( DateDurationSign(dateDifference) * TimeDurationSign(timeDuration) >= 0); *result = {dateDifference, timeDuration}; return true; } /** * RoundISODateTime ( isoDateTime, increment, unit, roundingMode ) */ ISODateTime js::temporal::RoundISODateTime(const ISODateTime& isoDateTime, Increment increment, TemporalUnit unit, TemporalRoundingMode roundingMode) { MOZ_ASSERT(IsValidISODateTime(isoDateTime)); // Step 1. MOZ_ASSERT(ISODateTimeWithinLimits(isoDateTime)); // Step 2. auto roundedTime = RoundTime(isoDateTime.time, increment, unit, roundingMode); MOZ_ASSERT(0 <= roundedTime.days && roundedTime.days <= 1); // Step 3. auto balanceResult = BalanceISODate(isoDateTime.date, static_cast(roundedTime.days)); // Step 4. return {balanceResult, roundedTime.time}; } /** * DifferencePlainDateTimeWithRounding ( isoDateTime1, isoDateTime2, calendar, * largestUnit, roundingIncrement, smallestUnit, roundingMode ) */ bool js::temporal::DifferencePlainDateTimeWithRounding( JSContext* cx, const ISODateTime& isoDateTime1, const ISODateTime& isoDateTime2, Handle calendar, const DifferenceSettings& settings, InternalDuration* result) { // Step 1. if (isoDateTime1 == isoDateTime2) { // Step 1.a. *result = {}; return true; } // Step 2. if (!ISODateTimeWithinLimits(isoDateTime1) || !ISODateTimeWithinLimits(isoDateTime2)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID); return false; } // Step 3. InternalDuration diff; if (!DifferenceISODateTime(cx, isoDateTime1, isoDateTime2, calendar, settings.largestUnit, &diff)) { return false; } // Step 4. if (settings.smallestUnit == TemporalUnit::Nanosecond && settings.roundingIncrement == Increment{1}) { *result = diff; return true; } // Step 5. auto originEpochNs = GetUTCEpochNanoseconds(isoDateTime1); // Step 6. auto destEpochNs = GetUTCEpochNanoseconds(isoDateTime2); // Step 7. Rooted timeZone(cx, TimeZoneValue{}); return RoundRelativeDuration( cx, diff, originEpochNs, destEpochNs, isoDateTime1, timeZone, calendar, settings.largestUnit, settings.roundingIncrement, settings.smallestUnit, settings.roundingMode, result); } /** * DifferencePlainDateTimeWithTotal ( isoDateTime1, isoDateTime2, calendar, unit * ) */ bool js::temporal::DifferencePlainDateTimeWithTotal( JSContext* cx, const ISODateTime& isoDateTime1, const ISODateTime& isoDateTime2, Handle calendar, TemporalUnit unit, double* result) { // Step 1. if (isoDateTime1 == isoDateTime2) { // Step 1.a. *result = 0; return true; } // Step 2. if (!ISODateTimeWithinLimits(isoDateTime1) || !ISODateTimeWithinLimits(isoDateTime2)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID); return false; } // Step 3. InternalDuration diff; if (!DifferenceISODateTime(cx, isoDateTime1, isoDateTime2, calendar, unit, &diff)) { return false; } // Step 4. (Optimized to avoid GetUTCEpochNanoseconds for non-calendar units.) if (unit > TemporalUnit::Day) { MOZ_ASSERT(diff.date == DateDuration{}); // TotalRelativeDuration, steps 1-2. (Not applicable) // TotalRelativeDuration, step 3. *result = TotalTimeDuration(diff.time, unit); return true; } if (unit == TemporalUnit::Day) { // TotalRelativeDuration, step 1. (Not applicable) // TotalRelativeDuration, step 2. auto days = mozilla::AssertedCast(diff.date.days); auto timeDuration = Add24HourDaysToTimeDuration(diff.time, days); // TotalRelativeDuration, step 3. *result = TotalTimeDuration(timeDuration, unit); return true; } // Step 5. auto originEpochNs = GetUTCEpochNanoseconds(isoDateTime1); // Step 6. auto destEpochNs = GetUTCEpochNanoseconds(isoDateTime2); // Step 7. Rooted timeZone(cx, TimeZoneValue{}); return TotalRelativeDuration(cx, diff, originEpochNs, destEpochNs, isoDateTime1, timeZone, calendar, unit, result); } /** * DifferenceTemporalPlainDateTime ( operation, dateTime, other, options ) */ static bool DifferenceTemporalPlainDateTime(JSContext* cx, TemporalDifference operation, const CallArgs& args) { Rooted dateTime( cx, &args.thisv().toObject().as()); // Step 1. Rooted other(cx); if (!ToTemporalDateTime(cx, args.get(0), &other)) { return false; } // Step 2. if (!CalendarEquals(dateTime.calendar(), other.calendar())) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE, CalendarIdentifier(dateTime.calendar()).data(), CalendarIdentifier(other.calendar()).data()); return false; } // Steps 3-4. DifferenceSettings settings; if (args.hasDefined(1)) { // Step 3. Rooted options( cx, RequireObjectArg(cx, "options", ToName(operation), args[1])); if (!options) { return false; } // Step 4. if (!GetDifferenceSettings( cx, operation, options, TemporalUnitGroup::DateTime, TemporalUnit::Nanosecond, TemporalUnit::Day, &settings)) { return false; } } else { // Steps 3-4. settings = { TemporalUnit::Nanosecond, TemporalUnit::Day, TemporalRoundingMode::Trunc, Increment{1}, }; } // Step 5. if (dateTime.dateTime() == other.dateTime()) { auto* obj = CreateTemporalDuration(cx, {}); if (!obj) { return false; } args.rval().setObject(*obj); return true; } // Step 6. InternalDuration internalDuration; if (!DifferencePlainDateTimeWithRounding(cx, dateTime, other, dateTime.calendar(), settings, &internalDuration)) { return false; } MOZ_ASSERT(IsValidDuration(internalDuration)); // Step 7. Duration result; if (!TemporalDurationFromInternal(cx, internalDuration, settings.largestUnit, &result)) { return false; } MOZ_ASSERT(IsValidDuration(result)); // Step 8. if (operation == TemporalDifference::Since) { result = result.negate(); } // Step 9. auto* obj = CreateTemporalDuration(cx, result); if (!obj) { return false; } args.rval().setObject(*obj); return true; } /** * AddDurationToDateTime ( operation, dateTime, temporalDurationLike, options ) */ static bool AddDurationToDateTime(JSContext* cx, TemporalAddDuration operation, const CallArgs& args) { Rooted dateTime( cx, &args.thisv().toObject().as()); // Step 1. Duration duration; if (!ToTemporalDuration(cx, args.get(0), &duration)) { return false; } // Step 2. if (operation == TemporalAddDuration::Subtract) { duration = duration.negate(); } // Steps 3-4. auto overflow = TemporalOverflow::Constrain; if (args.hasDefined(1)) { // Step 3. Rooted options( cx, RequireObjectArg(cx, "options", ToName(operation), args[1])); if (!options) { return false; } // Step 4. if (!GetTemporalOverflowOption(cx, options, &overflow)) { return false; } } // Step 5. auto internalDuration = ToInternalDurationRecordWith24HourDays(duration); MOZ_ASSERT(IsValidDuration(internalDuration)); // Step 6. auto timeResult = AddTime(dateTime.time(), internalDuration.time); // Step 7. (Inlined AdjustDateDurationRecord) if (std::abs(timeResult.days) > TimeDuration::max().toDays()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_DURATION_INVALID_NORMALIZED_TIME); return false; } auto dateDuration = DateDuration{ internalDuration.date.years, internalDuration.date.months, internalDuration.date.weeks, timeResult.days, }; MOZ_ASSERT(IsValidDuration(dateDuration)); // Step 8. ISODate addedDate; if (!CalendarDateAdd(cx, dateTime.calendar(), dateTime.date(), dateDuration, overflow, &addedDate)) { return false; } // Step 9. auto result = ISODateTime{addedDate, timeResult.time}; MOZ_ASSERT(IsValidISODateTime(result)); // Step 10. 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 [ , calendar ] ] ] ] ] * ] ] ) */ 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; } } // Steps 11-13. Rooted calendar(cx, CalendarValue(CalendarId::ISO8601)); if (args.hasDefined(9)) { // Step 12. if (!args[9].isString()) { ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, args[9], nullptr, "not a string"); return false; } // Step 13. Rooted calendarString(cx, args[9].toString()); if (!CanonicalizeCalendar(cx, calendarString, &calendar)) { return false; } } // Step 14. if (!ThrowIfInvalidISODate(cx, isoYear, isoMonth, isoDay)) { return false; } // Step 15. auto isoDate = ISODate{int32_t(isoYear), int32_t(isoMonth), int32_t(isoDay)}; // Step 16. if (!ThrowIfInvalidTime(cx, hour, minute, second, millisecond, microsecond, nanosecond)) { return false; } // Step 17. auto time = Time{int32_t(hour), int32_t(minute), int32_t(second), int32_t(millisecond), int32_t(microsecond), int32_t(nanosecond)}; // Step 18. auto isoDateTime = ISODateTime{isoDate, time}; // Step 19. auto* temporalDateTime = CreateTemporalDateTime(cx, args, isoDateTime, 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 dateTime(cx); if (!ToTemporalDateTime(cx, args.get(0), args.get(1), &dateTime)) { return false; } auto* result = CreateTemporalDateTime(cx, dateTime); 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. Rooted one(cx); if (!ToTemporalDateTime(cx, args.get(0), &one)) { return false; } // Step 2. Rooted two(cx); 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(); // Step 3. auto* str = NewStringCopy(cx, CalendarIdentifier(dateTime->calendar())); if (!str) { return false; } args.rval().setString(str); 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.era */ static bool PlainDateTime_era(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); Rooted calendar(cx, dateTime->calendar()); // Step 3. return CalendarEra(cx, calendar, dateTime->date(), args.rval()); } /** * get Temporal.PlainDateTime.prototype.era */ static bool PlainDateTime_era(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /** * get Temporal.PlainDateTime.prototype.eraYear */ static bool PlainDateTime_eraYear(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); Rooted calendar(cx, dateTime->calendar()); // Steps 3-5. return CalendarEraYear(cx, calendar, dateTime->date(), args.rval()); } /** * get Temporal.PlainDateTime.prototype.eraYear */ static bool PlainDateTime_eraYear(JSContext* cx, unsigned argc, Value* vp) { // Steps 1-2. CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } /** * get Temporal.PlainDateTime.prototype.year */ static bool PlainDateTime_year(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); Rooted calendar(cx, dateTime->calendar()); // Step 3. return CalendarYear(cx, calendar, dateTime->date(), 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.month */ static bool PlainDateTime_month(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); Rooted calendar(cx, dateTime->calendar()); // Step 3. return CalendarMonth(cx, calendar, dateTime->date(), 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.monthCode */ static bool PlainDateTime_monthCode(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); Rooted calendar(cx, dateTime->calendar()); // Step 3. return CalendarMonthCode(cx, calendar, dateTime->date(), 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.day */ static bool PlainDateTime_day(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); Rooted calendar(cx, dateTime->calendar()); // Step 3. return CalendarDay(cx, calendar, dateTime->date(), 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.hour */ static bool PlainDateTime_hour(JSContext* cx, const CallArgs& args) { // Step 3. auto* dateTime = &args.thisv().toObject().as(); args.rval().setInt32(dateTime->time().hour); 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.minute */ static bool PlainDateTime_minute(JSContext* cx, const CallArgs& args) { // Step 3. auto* dateTime = &args.thisv().toObject().as(); args.rval().setInt32(dateTime->time().minute); 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.second */ static bool PlainDateTime_second(JSContext* cx, const CallArgs& args) { // Step 3. auto* dateTime = &args.thisv().toObject().as(); args.rval().setInt32(dateTime->time().second); 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.millisecond */ static bool PlainDateTime_millisecond(JSContext* cx, const CallArgs& args) { // Step 3. auto* dateTime = &args.thisv().toObject().as(); args.rval().setInt32(dateTime->time().millisecond); 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.microsecond */ static bool PlainDateTime_microsecond(JSContext* cx, const CallArgs& args) { // Step 3. auto* dateTime = &args.thisv().toObject().as(); args.rval().setInt32(dateTime->time().microsecond); 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.nanosecond */ static bool PlainDateTime_nanosecond(JSContext* cx, const CallArgs& args) { // Step 3. auto* dateTime = &args.thisv().toObject().as(); args.rval().setInt32(dateTime->time().nanosecond); 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.dayOfWeek */ static bool PlainDateTime_dayOfWeek(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); Rooted calendar(cx, dateTime->calendar()); // Step 3. return CalendarDayOfWeek(cx, calendar, dateTime->date(), 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.dayOfYear */ static bool PlainDateTime_dayOfYear(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); Rooted calendar(cx, dateTime->calendar()); // Step 3. return CalendarDayOfYear(cx, calendar, dateTime->date(), 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.weekOfYear */ static bool PlainDateTime_weekOfYear(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); Rooted calendar(cx, dateTime->calendar()); // Steps 3-5. return CalendarWeekOfYear(cx, calendar, dateTime->date(), 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.yearOfWeek */ static bool PlainDateTime_yearOfWeek(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); Rooted calendar(cx, dateTime->calendar()); // Steps 3-5. return CalendarYearOfWeek(cx, calendar, dateTime->date(), 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.daysInWeek */ static bool PlainDateTime_daysInWeek(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); Rooted calendar(cx, dateTime->calendar()); // Step 3. return CalendarDaysInWeek(cx, calendar, dateTime->date(), 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.daysInMonth */ static bool PlainDateTime_daysInMonth(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); Rooted calendar(cx, dateTime->calendar()); // Step 3. return CalendarDaysInMonth(cx, calendar, dateTime->date(), 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.daysInYear */ static bool PlainDateTime_daysInYear(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); Rooted calendar(cx, dateTime->calendar()); // Step 3. return CalendarDaysInYear(cx, calendar, dateTime->date(), 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(cx, args); } /** * get Temporal.PlainDateTime.prototype.monthsInYear */ static bool PlainDateTime_monthsInYear(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); Rooted calendar(cx, dateTime->calendar()); // Step 3. return CalendarMonthsInYear(cx, calendar, dateTime->date(), 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( cx, args); } /** * get Temporal.PlainDateTime.prototype.inLeapYear */ static bool PlainDateTime_inLeapYear(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); Rooted calendar(cx, dateTime->calendar()); // Step 3. return CalendarInLeapYear(cx, calendar, dateTime->date(), 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(cx, args); } /** * Temporal.PlainDateTime.prototype.with ( temporalDateTimeLike [ , options ] ) */ static bool PlainDateTime_with(JSContext* cx, const CallArgs& args) { Rooted dateTime( cx, &args.thisv().toObject().as()); // Step 3. Rooted temporalDateTimeLike( cx, RequireObjectArg(cx, "temporalDateTimeLike", "with", args.get(0))); if (!temporalDateTimeLike) { return false; } if (!ThrowIfTemporalLikeObject(cx, temporalDateTimeLike)) { return false; } // Step 4. auto calendar = dateTime.calendar(); // Step 5. Rooted fields(cx); if (!ISODateToFields(cx, dateTime, &fields)) { return false; } // Steps 6-11. fields.setHour(dateTime.time().hour); fields.setMinute(dateTime.time().minute); fields.setSecond(dateTime.time().second); fields.setMillisecond(dateTime.time().millisecond); fields.setMicrosecond(dateTime.time().microsecond); fields.setNanosecond(dateTime.time().nanosecond); // Step 12. Rooted partialDateTime(cx); if (!PreparePartialCalendarFields(cx, calendar, temporalDateTimeLike, { CalendarField::Year, CalendarField::Month, CalendarField::MonthCode, CalendarField::Day, CalendarField::Hour, CalendarField::Minute, CalendarField::Second, CalendarField::Millisecond, CalendarField::Microsecond, CalendarField::Nanosecond, }, &partialDateTime)) { return false; } MOZ_ASSERT(!partialDateTime.keys().isEmpty()); // Step 13. fields = CalendarMergeFields(calendar, fields, partialDateTime); // Steps 14-15. auto overflow = TemporalOverflow::Constrain; if (args.hasDefined(1)) { // Step 14. Rooted options(cx, RequireObjectArg(cx, "options", "with", args[1])); if (!options) { return false; } // Step 15. if (!GetTemporalOverflowOption(cx, options, &overflow)) { return false; } } // Step 16. ISODateTime result; if (!InterpretTemporalDateTimeFields(cx, calendar, fields, overflow, &result)) { return false; } MOZ_ASSERT(IsValidISODateTime(result)); // Step 21. auto* obj = CreateTemporalDateTime(cx, result, calendar); 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(cx, args); } /** * Temporal.PlainDateTime.prototype.withPlainTime ( [ plainTimeLike ] ) */ static bool PlainDateTime_withPlainTime(JSContext* cx, const CallArgs& args) { auto* temporalDateTime = &args.thisv().toObject().as(); auto date = temporalDateTime->date(); Rooted calendar(cx, temporalDateTime->calendar()); // Step 3. (Inlined ToTemporalTimeOrMidnight) Time time = {}; if (args.hasDefined(0)) { if (!ToTemporalTime(cx, args[0], &time)) { return false; } } // Step 4. auto isoDateTime = ISODateTime{date, time}; // Step 5. auto* obj = CreateTemporalDateTime(cx, isoDateTime, 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( cx, args); } /** * Temporal.PlainDateTime.prototype.withCalendar ( calendar ) */ static bool PlainDateTime_withCalendar(JSContext* cx, const CallArgs& args) { auto* temporalDateTime = &args.thisv().toObject().as(); auto dateTime = temporalDateTime->dateTime(); // Step 3. Rooted 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( cx, args); } /** * Temporal.PlainDateTime.prototype.add ( temporalDurationLike [ , options ] ) */ static bool PlainDateTime_add(JSContext* cx, const CallArgs& args) { // Step 3. return AddDurationToDateTime(cx, TemporalAddDuration::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(cx, args); } /** * Temporal.PlainDateTime.prototype.subtract ( temporalDurationLike [ , options * ] ) */ static bool PlainDateTime_subtract(JSContext* cx, const CallArgs& args) { // Step 3. return AddDurationToDateTime(cx, TemporalAddDuration::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(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(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(cx, args); } /** * Temporal.PlainDateTime.prototype.round ( roundTo ) */ static bool PlainDateTime_round(JSContext* cx, const CallArgs& args) { auto* temporalDateTime = &args.thisv().toObject().as(); auto dateTime = temporalDateTime->dateTime(); Rooted calendar(cx, temporalDateTime->calendar()); // Steps 3-13. auto smallestUnit = TemporalUnit::Unset; auto roundingMode = TemporalRoundingMode::HalfExpand; auto roundingIncrement = Increment{1}; if (args.get(0).isString()) { // Step 4. (Not applicable in our implementation.) // Step 9. Rooted paramString(cx, args[0].toString()); if (!GetTemporalUnitValuedOption( cx, paramString, TemporalUnitKey::SmallestUnit, &smallestUnit)) { return false; } // Step 10. if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::SmallestUnit, smallestUnit, TemporalUnitGroup::DayTime)) { return false; } MOZ_ASSERT(TemporalUnit::Day <= smallestUnit && smallestUnit <= TemporalUnit::Nanosecond); // Steps 6-8 and 11-13. (Implicit) } else { // Steps 3 and 5. Rooted roundTo( cx, RequireObjectArg(cx, "roundTo", "round", args.get(0))); if (!roundTo) { return false; } // Steps 6-7. if (!GetRoundingIncrementOption(cx, roundTo, &roundingIncrement)) { return false; } // Step 8. if (!GetRoundingModeOption(cx, roundTo, &roundingMode)) { return false; } // Step 9. if (!GetTemporalUnitValuedOption(cx, roundTo, TemporalUnitKey::SmallestUnit, &smallestUnit)) { return false; } if (smallestUnit == TemporalUnit::Unset) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_MISSING_OPTION, "smallestUnit"); return false; } // Step 10. if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::SmallestUnit, smallestUnit, TemporalUnitGroup::DayTime)) { return false; } MOZ_ASSERT(TemporalUnit::Day <= smallestUnit && smallestUnit <= TemporalUnit::Nanosecond); // Steps 11-12. auto maximum = Increment{1}; bool inclusive = true; if (smallestUnit > TemporalUnit::Day) { maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit); inclusive = false; } // Step 13. if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum, inclusive)) { return false; } } // Step 14. if (smallestUnit == TemporalUnit::Nanosecond && roundingIncrement == Increment{1}) { auto* obj = CreateTemporalDateTime(cx, dateTime, calendar); if (!obj) { return false; } args.rval().setObject(*obj); return true; } // Step 15. auto result = RoundISODateTime(dateTime, roundingIncrement, smallestUnit, roundingMode); // Step 16. 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(cx, args); } /** * Temporal.PlainDateTime.prototype.equals ( other ) */ static bool PlainDateTime_equals(JSContext* cx, const CallArgs& args) { auto* temporalDateTime = &args.thisv().toObject().as(); auto dateTime = temporalDateTime->dateTime(); Rooted calendar(cx, temporalDateTime->calendar()); // Step 3. Rooted other(cx); if (!ToTemporalDateTime(cx, args.get(0), &other)) { return false; } // Steps 4-5. bool equals = dateTime == other.dateTime() && CalendarEquals(calendar, other.calendar()); 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(cx, args); } /** * Temporal.PlainDateTime.prototype.toString ( [ options ] ) */ static bool PlainDateTime_toString(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); auto dt = dateTime->dateTime(); Rooted calendar(cx, dateTime->calendar()); SecondsStringPrecision precision = {Precision::Auto(), TemporalUnit::Nanosecond, Increment{1}}; auto roundingMode = TemporalRoundingMode::Trunc; auto showCalendar = ShowCalendar::Auto; if (args.hasDefined(0)) { // Step 3. Rooted options( cx, RequireObjectArg(cx, "options", "toString", args[0])); if (!options) { return false; } // Steps 4-5. if (!GetTemporalShowCalendarNameOption(cx, options, &showCalendar)) { return false; } // Step 6. auto digits = Precision::Auto(); if (!GetTemporalFractionalSecondDigitsOption(cx, options, &digits)) { return false; } // Step 7. if (!GetRoundingModeOption(cx, options, &roundingMode)) { return false; } // Step 8. auto smallestUnit = TemporalUnit::Unset; if (!GetTemporalUnitValuedOption(cx, options, TemporalUnitKey::SmallestUnit, &smallestUnit)) { return false; } // Step 9. if (!ValidateTemporalUnitValue(cx, TemporalUnitKey::SmallestUnit, smallestUnit, TemporalUnitGroup::Time)) { return false; } // Step 10. if (smallestUnit == TemporalUnit::Hour) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INVALID_UNIT_OPTION, "hour", "smallestUnit"); return false; } // Step 11. precision = ToSecondsStringPrecision(smallestUnit, digits); } // Step 12. auto result = RoundISODateTime(dt, precision.increment, precision.unit, roundingMode); // Step 13. if (!ISODateTimeWithinLimits(result)) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID); return false; } // Step 14. JSString* str = ISODateTimeToString(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(cx, args); } /** * Temporal.PlainDateTime.prototype.toLocaleString ( [ locales [ , options ] ] ) */ static bool PlainDateTime_toLocaleString(JSContext* cx, const CallArgs& args) { // Steps 3-4. return intl::TemporalObjectToLocaleString(cx, args, intl::DateTimeFormatKind::All); } /** * 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( cx, args); } /** * Temporal.PlainDateTime.prototype.toJSON ( ) */ static bool PlainDateTime_toJSON(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); auto dt = dateTime->dateTime(); Rooted calendar(cx, dateTime->calendar()); // Step 3. JSString* str = ISODateTimeToString(cx, dt, calendar, Precision::Auto(), ShowCalendar::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(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.toZonedDateTime ( temporalTimeZoneLike [ , * options ] ) */ static bool PlainDateTime_toZonedDateTime(JSContext* cx, const CallArgs& args) { auto* temporalDateTime = &args.thisv().toObject().as(); auto isoDateTime = temporalDateTime->dateTime(); Rooted calendar(cx, temporalDateTime->calendar()); // Step 3. Rooted timeZone(cx); if (!ToTemporalTimeZone(cx, args.get(0), &timeZone)) { return false; } auto disambiguation = TemporalDisambiguation::Compatible; if (args.hasDefined(1)) { // Step 4. Rooted options( cx, RequireObjectArg(cx, "options", "toZonedDateTime", args[1])); if (!options) { return false; } // Step 5. if (!GetTemporalDisambiguationOption(cx, options, &disambiguation)) { return false; } } // Step 6. EpochNanoseconds epochNs; if (!GetEpochNanosecondsFor(cx, timeZone, isoDateTime, disambiguation, &epochNs)) { return false; } // Step 7. auto* result = CreateTemporalZonedDateTime(cx, epochNs, 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( cx, args); } /** * Temporal.PlainDateTime.prototype.toPlainDate ( ) */ static bool PlainDateTime_toPlainDate(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); Rooted calendar(cx, dateTime->calendar()); // Step 3. auto* obj = CreateTemporalDate(cx, dateTime->date(), 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(cx, args); } /** * Temporal.PlainDateTime.prototype.toPlainTime ( ) */ static bool PlainDateTime_toPlainTime(JSContext* cx, const CallArgs& args) { auto* dateTime = &args.thisv().toObject().as(); // Step 3. auto* obj = CreateTemporalTime(cx, dateTime->time()); 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(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("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("toPlainTime", PlainDateTime_toPlainTime, 0, 0), JS_FS_END, }; static const JSPropertySpec PlainDateTime_prototype_properties[] = { JS_PSG("calendarId", PlainDateTime_calendarId, 0), JS_PSG("era", PlainDateTime_era, 0), JS_PSG("eraYear", PlainDateTime_eraYear, 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, GenericCreatePrototype, PlainDateTime_methods, nullptr, PlainDateTime_prototype_methods, PlainDateTime_prototype_properties, nullptr, ClassSpec::DontDefineConstructor, };