summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/temporal/PlainDate.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/builtin/temporal/PlainDate.cpp')
-rw-r--r--js/src/builtin/temporal/PlainDate.cpp2999
1 files changed, 2999 insertions, 0 deletions
diff --git a/js/src/builtin/temporal/PlainDate.cpp b/js/src/builtin/temporal/PlainDate.cpp
new file mode 100644
index 0000000000..759456c9cc
--- /dev/null
+++ b/js/src/builtin/temporal/PlainDate.cpp
@@ -0,0 +1,2999 @@
+/* -*- 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/PlainDate.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Maybe.h"
+
+#include <algorithm>
+#include <cmath>
+#include <cstdlib>
+#include <initializer_list>
+#include <stdint.h>
+#include <type_traits>
+#include <utility>
+
+#include "jsnum.h"
+#include "jspubtd.h"
+#include "jstypes.h"
+#include "NamespaceImports.h"
+
+#include "builtin/temporal/Calendar.h"
+#include "builtin/temporal/Duration.h"
+#include "builtin/temporal/PlainDateTime.h"
+#include "builtin/temporal/PlainMonthDay.h"
+#include "builtin/temporal/PlainTime.h"
+#include "builtin/temporal/PlainYearMonth.h"
+#include "builtin/temporal/Temporal.h"
+#include "builtin/temporal/TemporalFields.h"
+#include "builtin/temporal/TemporalParser.h"
+#include "builtin/temporal/TemporalRoundingMode.h"
+#include "builtin/temporal/TemporalTypes.h"
+#include "builtin/temporal/TemporalUnit.h"
+#include "builtin/temporal/TimeZone.h"
+#include "builtin/temporal/ToString.h"
+#include "builtin/temporal/Wrapped.h"
+#include "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/Date.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/PlainObject.h"
+#include "vm/PropertyInfo.h"
+#include "vm/Realm.h"
+#include "vm/Shape.h"
+#include "vm/StringType.h"
+
+#include "vm/JSObject-inl.h"
+#include "vm/NativeObject-inl.h"
+#include "vm/ObjectOperations-inl.h"
+
+using namespace js;
+using namespace js::temporal;
+
+static inline bool IsPlainDate(Handle<Value> v) {
+ return v.isObject() && v.toObject().is<PlainDateObject>();
+}
+
+#ifdef DEBUG
+/**
+ * IsValidISODate ( year, month, day )
+ */
+template <typename T>
+static bool IsValidISODate(T year, T month, T day) {
+ 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));
+
+ // Step 2.
+ if (month < 1 || month > 12) {
+ return false;
+ }
+
+ // Step 3.
+ int32_t daysInMonth = js::temporal::ISODaysInMonth(year, int32_t(month));
+
+ // Step 4.
+ if (day < 1 || day > daysInMonth) {
+ return false;
+ }
+
+ // Step 5.
+ return true;
+}
+
+/**
+ * IsValidISODate ( year, month, day )
+ */
+bool js::temporal::IsValidISODate(const PlainDate& date) {
+ auto& [year, month, day] = date;
+ return ::IsValidISODate(year, month, day);
+}
+
+/**
+ * IsValidISODate ( year, month, day )
+ */
+bool js::temporal::IsValidISODate(double year, double month, double day) {
+ return ::IsValidISODate(year, month, day);
+}
+#endif
+
+static void ReportInvalidDateValue(JSContext* cx, const char* name, int32_t min,
+ int32_t max, double num) {
+ Int32ToCStringBuf minCbuf;
+ const char* minStr = Int32ToCString(&minCbuf, min);
+
+ Int32ToCStringBuf maxCbuf;
+ const char* maxStr = Int32ToCString(&maxCbuf, max);
+
+ ToCStringBuf numCbuf;
+ const char* numStr = NumberToCString(&numCbuf, num);
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PLAIN_DATE_INVALID_VALUE, name,
+ minStr, maxStr, numStr);
+}
+
+template <typename T>
+static inline bool ThrowIfInvalidDateValue(JSContext* cx, const char* name,
+ int32_t min, int32_t max, T num) {
+ if (min <= num && num <= max) {
+ return true;
+ }
+ ReportInvalidDateValue(cx, name, min, max, num);
+ return false;
+}
+
+/**
+ * IsValidISODate ( year, month, day )
+ */
+template <typename T>
+static bool ThrowIfInvalidISODate(JSContext* cx, T year, T month, T day) {
+ 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));
+
+ // Step 2.
+ if (!ThrowIfInvalidDateValue(cx, "month", 1, 12, month)) {
+ return false;
+ }
+
+ // Step 3.
+ int32_t daysInMonth = js::temporal::ISODaysInMonth(year, int32_t(month));
+
+ // Step 4.
+ if (!ThrowIfInvalidDateValue(cx, "day", 1, daysInMonth, day)) {
+ return false;
+ }
+
+ // Step 5.
+ return true;
+}
+
+/**
+ * IsValidISODate ( year, month, day )
+ */
+bool js::temporal::ThrowIfInvalidISODate(JSContext* cx, const PlainDate& date) {
+ auto& [year, month, day] = date;
+ return ::ThrowIfInvalidISODate(cx, year, month, day);
+}
+
+/**
+ * IsValidISODate ( year, month, day )
+ */
+bool js::temporal::ThrowIfInvalidISODate(JSContext* cx, double year,
+ double month, double day) {
+ return ::ThrowIfInvalidISODate(cx, year, month, day);
+}
+
+/**
+ * RegulateISODate ( year, month, day, overflow )
+ *
+ * With |overflow = "constrain"|.
+ */
+static PlainDate ConstrainISODate(const PlainDate& date) {
+ auto& [year, month, day] = date;
+
+ // Step 1.a.
+ int32_t m = std::clamp(month, 1, 12);
+
+ // Step 1.b.
+ int32_t daysInMonth = temporal::ISODaysInMonth(year, m);
+
+ // Step 1.c.
+ int32_t d = std::clamp(day, 1, daysInMonth);
+
+ // Step 1.d.
+ return {year, m, d};
+}
+
+/**
+ * RegulateISODate ( year, month, day, overflow )
+ */
+bool js::temporal::RegulateISODate(JSContext* cx, const PlainDate& date,
+ TemporalOverflow overflow,
+ PlainDate* result) {
+ // Step 1.
+ if (overflow == TemporalOverflow::Constrain) {
+ *result = ::ConstrainISODate(date);
+ return true;
+ }
+
+ // Step 2.a.
+ MOZ_ASSERT(overflow == TemporalOverflow::Reject);
+
+ // Step 2.b.
+ if (!ThrowIfInvalidISODate(cx, date)) {
+ return false;
+ }
+
+ // Step 2.b. (Inlined call to CreateISODateRecord.)
+ *result = date;
+ return true;
+}
+
+/**
+ * RegulateISODate ( year, month, day, overflow )
+ */
+bool js::temporal::RegulateISODate(JSContext* cx, double year, double month,
+ double day, TemporalOverflow overflow,
+ RegulatedISODate* result) {
+ MOZ_ASSERT(IsInteger(year));
+ MOZ_ASSERT(IsInteger(month));
+ MOZ_ASSERT(IsInteger(day));
+
+ // Step 1.
+ if (overflow == TemporalOverflow::Constrain) {
+ // Step 1.a.
+ int32_t m = int32_t(std::clamp(month, 1.0, 12.0));
+
+ // Step 1.b.
+ double daysInMonth = double(ISODaysInMonth(year, m));
+
+ // Step 1.c.
+ int32_t d = int32_t(std::clamp(day, 1.0, daysInMonth));
+
+ // Step 1.d.
+ *result = {year, m, d};
+ return true;
+ }
+
+ // Step 2.a.
+ MOZ_ASSERT(overflow == TemporalOverflow::Reject);
+
+ // Step 2.b.
+ if (!ThrowIfInvalidISODate(cx, year, month, day)) {
+ return false;
+ }
+
+ // Step 2.b. (Inlined call to CreateISODateRecord.)
+ *result = {year, int32_t(month), int32_t(day)};
+ return true;
+}
+
+/**
+ * CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )
+ */
+static PlainDateObject* CreateTemporalDate(JSContext* cx, const CallArgs& args,
+ double isoYear, double isoMonth,
+ double isoDay,
+ Handle<CalendarValue> calendar) {
+ MOZ_ASSERT(IsInteger(isoYear));
+ MOZ_ASSERT(IsInteger(isoMonth));
+ MOZ_ASSERT(IsInteger(isoDay));
+
+ // Step 1.
+ if (!ThrowIfInvalidISODate(cx, isoYear, isoMonth, isoDay)) {
+ return nullptr;
+ }
+
+ // Step 2.
+ if (!ISODateTimeWithinLimits(isoYear, isoMonth, isoDay)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
+ return nullptr;
+ }
+
+ // Steps 3-4.
+ Rooted<JSObject*> proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_PlainDate,
+ &proto)) {
+ return nullptr;
+ }
+
+ auto* object = NewObjectWithClassProto<PlainDateObject>(cx, proto);
+ if (!object) {
+ return nullptr;
+ }
+
+ // Step 5.
+ object->setFixedSlot(PlainDateObject::ISO_YEAR_SLOT, Int32Value(isoYear));
+
+ // Step 6.
+ object->setFixedSlot(PlainDateObject::ISO_MONTH_SLOT, Int32Value(isoMonth));
+
+ // Step 7.
+ object->setFixedSlot(PlainDateObject::ISO_DAY_SLOT, Int32Value(isoDay));
+
+ // Step 8.
+ object->setFixedSlot(PlainDateObject::CALENDAR_SLOT, calendar.toValue());
+
+ // Step 9.
+ return object;
+}
+
+/**
+ * CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )
+ */
+PlainDateObject* js::temporal::CreateTemporalDate(
+ JSContext* cx, const PlainDate& date, Handle<CalendarValue> calendar) {
+ auto& [isoYear, isoMonth, isoDay] = date;
+
+ // Step 1.
+ if (!ThrowIfInvalidISODate(cx, date)) {
+ return nullptr;
+ }
+
+ // Step 2.
+ if (!ISODateTimeWithinLimits(date)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
+ return nullptr;
+ }
+
+ // Steps 3-4.
+ auto* object = NewBuiltinClassInstance<PlainDateObject>(cx);
+ if (!object) {
+ return nullptr;
+ }
+
+ // Step 5.
+ object->setFixedSlot(PlainDateObject::ISO_YEAR_SLOT, Int32Value(isoYear));
+
+ // Step 6.
+ object->setFixedSlot(PlainDateObject::ISO_MONTH_SLOT, Int32Value(isoMonth));
+
+ // Step 7.
+ object->setFixedSlot(PlainDateObject::ISO_DAY_SLOT, Int32Value(isoDay));
+
+ // Step 8.
+ object->setFixedSlot(PlainDateObject::CALENDAR_SLOT, calendar.toValue());
+
+ // Step 9.
+ return object;
+}
+
+/**
+ * CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )
+ */
+bool js::temporal::CreateTemporalDate(
+ JSContext* cx, const PlainDate& date, Handle<CalendarValue> calendar,
+ MutableHandle<PlainDateWithCalendar> result) {
+ // Step 1.
+ if (!ThrowIfInvalidISODate(cx, date)) {
+ return false;
+ }
+
+ // Step 2.
+ if (!ISODateTimeWithinLimits(date)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
+ return false;
+ }
+
+ // Steps 3-9.
+ result.set(PlainDateWithCalendar{date, calendar});
+ return true;
+}
+
+/**
+ * ToTemporalDate ( item [ , options ] )
+ */
+static Wrapped<PlainDateObject*> ToTemporalDate(
+ JSContext* cx, Handle<JSObject*> item, Handle<PlainObject*> maybeOptions) {
+ // Step 1-2. (Not applicable in our implementation.)
+
+ // Step 3.a.
+ if (item->canUnwrapAs<PlainDateObject>()) {
+ return item;
+ }
+
+ // Step 3.b.
+ if (auto* zonedDateTime = item->maybeUnwrapIf<ZonedDateTimeObject>()) {
+ auto epochInstant = ToInstant(zonedDateTime);
+ Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
+ Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
+
+ if (!timeZone.wrap(cx)) {
+ return nullptr;
+ }
+ if (!calendar.wrap(cx)) {
+ return nullptr;
+ }
+
+ // Step 3.b.i.
+ if (maybeOptions) {
+ TemporalOverflow ignored;
+ if (!ToTemporalOverflow(cx, maybeOptions, &ignored)) {
+ return nullptr;
+ }
+ }
+
+ // Steps 3.b.ii-iv.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, timeZone, epochInstant, &dateTime)) {
+ return nullptr;
+ }
+
+ // Step 3.b.v.
+ return CreateTemporalDate(cx, dateTime.date, calendar);
+ }
+
+ // Step 3.c.
+ if (auto* dateTime = item->maybeUnwrapIf<PlainDateTimeObject>()) {
+ auto date = ToPlainDate(dateTime);
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+ if (!calendar.wrap(cx)) {
+ return nullptr;
+ }
+
+ // Step 3.c.i.
+ if (maybeOptions) {
+ TemporalOverflow ignored;
+ if (!ToTemporalOverflow(cx, maybeOptions, &ignored)) {
+ return nullptr;
+ }
+ }
+
+ // Step 3.c.ii.
+ return CreateTemporalDate(cx, date, calendar);
+ }
+
+ // Step 3.d.
+ Rooted<CalendarValue> calendarValue(cx);
+ if (!GetTemporalCalendarWithISODefault(cx, item, &calendarValue)) {
+ return nullptr;
+ }
+
+ // Step 3.e.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendarValue,
+ {
+ CalendarMethod::DateFromFields,
+ CalendarMethod::Fields,
+ },
+ &calendar)) {
+ return nullptr;
+ }
+
+ // Step 3.f.
+ JS::RootedVector<PropertyKey> fieldNames(cx);
+ if (!CalendarFields(cx, calendar,
+ {CalendarField::Day, CalendarField::Month,
+ CalendarField::MonthCode, CalendarField::Year},
+ &fieldNames)) {
+ return nullptr;
+ }
+
+ // Step 3.g.
+ Rooted<PlainObject*> fields(cx, PrepareTemporalFields(cx, item, fieldNames));
+ if (!fields) {
+ return nullptr;
+ }
+
+ // Step 3.h.
+ if (maybeOptions) {
+ return temporal::CalendarDateFromFields(cx, calendar, fields, maybeOptions);
+ }
+ return temporal::CalendarDateFromFields(cx, calendar, fields);
+}
+
+/**
+ * ToTemporalDate ( item [ , options ] )
+ */
+static Wrapped<PlainDateObject*> ToTemporalDate(
+ JSContext* cx, Handle<Value> item, Handle<JSObject*> maybeOptions) {
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ Rooted<PlainObject*> maybeResolvedOptions(cx);
+ if (maybeOptions) {
+ maybeResolvedOptions = SnapshotOwnProperties(cx, maybeOptions);
+ if (!maybeResolvedOptions) {
+ return nullptr;
+ }
+ }
+
+ // Step 3.
+ if (item.isObject()) {
+ Rooted<JSObject*> itemObj(cx, &item.toObject());
+ return ::ToTemporalDate(cx, itemObj, maybeResolvedOptions);
+ }
+
+ // Step 4.
+ 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 5.
+ PlainDate result;
+ Rooted<JSString*> calendarString(cx);
+ if (!ParseTemporalDateString(cx, string, &result, &calendarString)) {
+ return nullptr;
+ }
+
+ // Step 6.
+ MOZ_ASSERT(IsValidISODate(result));
+
+ // Steps 7-10.
+ Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
+ if (calendarString) {
+ if (!ToBuiltinCalendar(cx, calendarString, &calendar)) {
+ return nullptr;
+ }
+ }
+
+ // Step 11.
+ if (maybeResolvedOptions) {
+ TemporalOverflow ignored;
+ if (!ToTemporalOverflow(cx, maybeResolvedOptions, &ignored)) {
+ return nullptr;
+ }
+ }
+
+ // Step 12.
+ return CreateTemporalDate(cx, result, calendar);
+}
+
+/**
+ * ToTemporalDate ( item [ , options ] )
+ */
+static Wrapped<PlainDateObject*> ToTemporalDate(JSContext* cx,
+ Handle<Value> item) {
+ return ::ToTemporalDate(cx, item, nullptr);
+}
+
+/**
+ * ToTemporalDate ( item [ , options ] )
+ */
+bool js::temporal::ToTemporalDate(JSContext* cx, Handle<Value> item,
+ PlainDate* result) {
+ auto obj = ::ToTemporalDate(cx, item, nullptr);
+ if (!obj) {
+ return false;
+ }
+
+ *result = ToPlainDate(&obj.unwrap());
+ return true;
+}
+
+/**
+ * ToTemporalDate ( item [ , options ] )
+ */
+bool js::temporal::ToTemporalDate(JSContext* cx, Handle<Value> item,
+ MutableHandle<PlainDateWithCalendar> result) {
+ auto* obj = ::ToTemporalDate(cx, item, nullptr).unwrapOrNull();
+ if (!obj) {
+ return false;
+ }
+
+ auto date = ToPlainDate(obj);
+ Rooted<CalendarValue> calendar(cx, obj->calendar());
+ if (!calendar.wrap(cx)) {
+ return false;
+ }
+
+ result.set(PlainDateWithCalendar{date, calendar});
+ return true;
+}
+
+/**
+ * Mathematical Operations, "modulo" notation.
+ */
+static int32_t NonNegativeModulo(double x, int32_t y) {
+ MOZ_ASSERT(IsInteger(x));
+ MOZ_ASSERT(y > 0);
+
+ double r = std::fmod(x, y);
+
+ int32_t result;
+ MOZ_ALWAYS_TRUE(mozilla::NumberEqualsInt32(r, &result));
+
+ return (result < 0) ? (result + y) : result;
+}
+
+struct BalancedYearMonth final {
+ double year = 0;
+ int32_t month = 0;
+};
+
+/**
+ * BalanceISOYearMonth ( year, month )
+ */
+static BalancedYearMonth BalanceISOYearMonth(double year, double month) {
+ // Step 1.
+ MOZ_ASSERT(IsInteger(year));
+ MOZ_ASSERT(IsInteger(month));
+
+ // Note: If either abs(year) or abs(month) is greater than 2^53 (the double
+ // integral precision limit), the additions resp. subtractions below are
+ // imprecise. This doesn't matter for us, because the single caller to this
+ // function (AddISODate) will throw an error for large values anyway.
+
+ // Step 2.
+ year = year + std::floor((month - 1) / 12);
+ MOZ_ASSERT(IsInteger(year) || std::isinf(year));
+
+ // Step 3.
+ int32_t mon = NonNegativeModulo(month - 1, 12) + 1;
+ MOZ_ASSERT(1 <= mon && mon <= 12);
+
+ // Step 4.
+ return {year, mon};
+}
+
+static bool CanBalanceISOYear(double year) {
+ // TODO: Export these values somewhere.
+ constexpr int32_t minYear = -271821;
+ constexpr int32_t maxYear = 275760;
+
+ // If the year is below resp. above the min-/max-year, no value of |day| will
+ // make the resulting date valid.
+ return minYear <= year && year <= maxYear;
+}
+
+static bool CanBalanceISODay(double day) {
+ // The maximum number of seconds from the epoch is 8.64 * 10^12.
+ constexpr int64_t maxInstantSeconds = 8'640'000'000'000;
+
+ // In days that makes 10^8.
+ constexpr int64_t maxInstantDays = maxInstantSeconds / 60 / 60 / 24;
+
+ // Multiply by two to take both directions into account and add twenty to
+ // account for the day number of the minimum date "-271821-02-20".
+ constexpr int64_t maximumDayDifference = 2 * maxInstantDays + 20;
+
+ // When |day| is below |maximumDayDifference|, it can be represented as int32.
+ static_assert(maximumDayDifference <= INT32_MAX);
+
+ // When the day difference exceeds the maximum valid day difference, the
+ // overall result won't be a valid date. Detect this early so we don't have to
+ // struggle with floating point precision issues in BalanceISODate.
+ //
+ // This also means BalanceISODate, step 1 doesn't apply to our implementation.
+ return std::abs(day) <= maximumDayDifference;
+}
+
+/**
+ * BalanceISODate ( year, month, day )
+ */
+PlainDate js::temporal::BalanceISODateNew(int32_t year, int32_t month,
+ int32_t day) {
+ MOZ_ASSERT(1 <= month && month <= 12);
+
+ // Steps 1-3.
+ int64_t ms = MakeDate(year, month, day);
+
+ // FIXME: spec issue - |ms| can be non-finite
+ // https://github.com/tc39/proposal-temporal/issues/2315
+
+ // TODO: This approach isn't efficient, because MonthFromTime and DayFromTime
+ // both recompute YearFromTime.
+
+ // Step 4.
+ return {int32_t(JS::YearFromTime(ms)), int32_t(JS::MonthFromTime(ms) + 1),
+ int32_t(JS::DayFromTime(ms))};
+}
+
+/**
+ * BalanceISODate ( year, month, day )
+ */
+bool js::temporal::BalanceISODate(JSContext* cx, int32_t year, int32_t month,
+ int64_t day, PlainDate* result) {
+ if (!CanBalanceISODay(day)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
+ return false;
+ }
+
+ *result = BalanceISODate(year, month, int32_t(day));
+ return true;
+}
+
+/**
+ * BalanceISODate ( year, month, day )
+ */
+PlainDate js::temporal::BalanceISODate(int32_t year, int32_t month,
+ int32_t day) {
+ // Check no inputs can lead to floating point precision issues below. This
+ // also ensures all loops can finish in reasonable time, so we don't need to
+ // worry about interrupts here. And it ensures there won't be overflows when
+ // using int32_t values.
+ MOZ_ASSERT(CanBalanceISOYear(year));
+ MOZ_ASSERT(1 <= month && month <= 12);
+ MOZ_ASSERT(CanBalanceISODay(day));
+
+ // TODO: BalanceISODate now works using MakeDate
+ // TODO: Can't use JS::MakeDate, because it expects valid month/day values.
+ // https://github.com/tc39/proposal-temporal/issues/2315
+
+ // Step 1. (Not applicable in our implementation.)
+
+ // Steps 3-4. (Not applicable in our implementation.)
+
+ constexpr int32_t daysInNonLeapYear = 365;
+
+ // Skip steps 5-11 for the common case when abs(day) doesn't exceed 365.
+ if (std::abs(day) > daysInNonLeapYear) {
+ // Step 5. (Note)
+
+ // Steps 6-7.
+ int32_t testYear = month > 2 ? year : year - 1;
+
+ // Step 8.
+ while (day < -ISODaysInYear(testYear)) {
+ // Step 8.a.
+ day += ISODaysInYear(testYear);
+
+ // Step 8.b.
+ year -= 1;
+
+ // Step 8.c.
+ testYear -= 1;
+ }
+
+ // Step 9. (Note)
+
+ // Step 10.
+ testYear += 1;
+
+ // Step 11.
+ while (day > ISODaysInYear(testYear)) {
+ // Step 11.a.
+ day -= ISODaysInYear(testYear);
+
+ // Step 11.b.
+ year += 1;
+
+ // Step 11.c.
+ testYear += 1;
+ }
+ }
+
+ // Step 12. (Note)
+
+ // Step 13.
+ while (day < 1) {
+ // Steps 13.a-b. (Inlined call to BalanceISOYearMonth.)
+ if (--month == 0) {
+ month = 12;
+ year -= 1;
+ }
+
+ // Step 13.d
+ day += ISODaysInMonth(year, month);
+ }
+
+ // Step 14. (Note)
+
+ // Step 15.
+ while (day > ISODaysInMonth(year, month)) {
+ // Step 15.a.
+ day -= ISODaysInMonth(year, month);
+
+ // Steps 15.b-d. (Inlined call to BalanceISOYearMonth.)
+ if (++month == 13) {
+ month = 1;
+ year += 1;
+ }
+ }
+
+ MOZ_ASSERT(1 <= month && month <= 12);
+ MOZ_ASSERT(1 <= day && day <= 31);
+
+ // Step 16.
+ return {year, month, day};
+}
+
+/**
+ * AddISODate ( year, month, day, years, months, weeks, days, overflow )
+ */
+bool js::temporal::AddISODate(JSContext* cx, const PlainDate& date,
+ const Duration& duration,
+ TemporalOverflow overflow, PlainDate* result) {
+ MOZ_ASSERT(IsValidISODate(date));
+ MOZ_ASSERT(ISODateTimeWithinLimits(date));
+
+ // TODO: Not quite sure if this holds for all callers. But if it does hold,
+ // then we can directly reject any numbers which can't be represented with
+ // int32_t. That in turn avoids the precision loss issue noted in
+ // BalanceISODate.
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ // Step 1.
+ MOZ_ASSERT(IsInteger(duration.years));
+ MOZ_ASSERT(IsInteger(duration.months));
+ MOZ_ASSERT(IsInteger(duration.weeks));
+ MOZ_ASSERT(IsInteger(duration.days));
+
+ // Step 2. (Not applicable in our implementation.)
+
+ // Step 3.
+ auto yearMonth = BalanceISOYearMonth(date.year + duration.years,
+ date.month + duration.months);
+ MOZ_ASSERT(IsInteger(yearMonth.year) || std::isinf(yearMonth.year));
+ MOZ_ASSERT(1 <= yearMonth.month && yearMonth.month <= 12);
+
+ // FIXME: spec issue?
+ // new Temporal.PlainDate(2021, 5, 31).subtract({months:1, days:1}).toString()
+ // returns "2021-04-29", but "2021-04-30" seems more likely expected.
+ // Note: "2021-04-29" agrees with java.time, though.
+ //
+ // Example where this creates inconsistent results:
+ //
+ // clang-format off
+ //
+ // js> Temporal.PlainDate.from("2021-05-31").since("2021-04-30", {largestUnit:"months"}).toString()
+ // "P1M1D"
+ // js> Temporal.PlainDate.from("2021-05-31").subtract("P1M1D").toString()
+ // "2021-04-29"
+ //
+ // clang-format on
+ //
+ // Later: This now returns "P1M" instead "P1M1D", so the results are at least
+ // consistent. Let's add a test case for this behaviour.
+ //
+ // Revisit when <https://github.com/tc39/proposal-temporal/issues/2535> has
+ // been addressed.
+
+ // |yearMonth.year| can only exceed the valid years range when called from
+ // `Temporal.Calendar.prototype.dateAdd`. And because `dateAdd` uses the
+ // result of AddISODate to create a new Temporal.PlainDate, we can directly
+ // throw an error if the result isn't within the valid date-time limits. This
+ // in turn allows to work on integer values and we don't have to worry about
+ // imprecise double value computations.
+ if (!CanBalanceISOYear(yearMonth.year)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
+ return false;
+ }
+
+ // Step 4.
+ PlainDate regulated;
+ if (!RegulateISODate(cx, {int32_t(yearMonth.year), yearMonth.month, date.day},
+ overflow, &regulated)) {
+ return false;
+ }
+
+ // NB: BalanceISODate will reject too large days, so we don't have to worry
+ // about imprecise number arithmetic here.
+
+ // Steps 5-6.
+ double d = regulated.day + (duration.days + duration.weeks * 7);
+
+ // Just as with |yearMonth.year|, also directly throw an error if the |days|
+ // value is too large.
+ if (!CanBalanceISODay(d)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
+ return false;
+ }
+
+ // Step 7.
+ auto balanced = BalanceISODate(regulated.year, regulated.month, int32_t(d));
+ MOZ_ASSERT(IsValidISODate(balanced));
+
+ *result = balanced;
+ return true;
+}
+
+struct YearMonthDuration {
+ int32_t years = 0;
+ int32_t months = 0;
+};
+
+/**
+ * AddISODate ( year, month, day, years, months, weeks, days, overflow )
+ *
+ * With |overflow = "constrain"|.
+ */
+static PlainDate AddISODate(const PlainDate& date,
+ const YearMonthDuration& duration) {
+ MOZ_ASSERT(IsValidISODate(date));
+ MOZ_ASSERT(ISODateTimeWithinLimits(date));
+
+ MOZ_ASSERT_IF(duration.years < 0, duration.months <= 0);
+ MOZ_ASSERT_IF(duration.years > 0, duration.months >= 0);
+
+ // TODO: Export these values somewhere.
+ [[maybe_unused]] constexpr int32_t minYear = -271821;
+ [[maybe_unused]] constexpr int32_t maxYear = 275760;
+
+ MOZ_ASSERT(std::abs(duration.years) <= (maxYear - minYear),
+ "years doesn't exceed the maximum duration between valid years");
+ MOZ_ASSERT(std::abs(duration.months) <= 12,
+ "months duration is at most one year");
+
+ // Steps 1-2. (Not applicable)
+
+ // Step 3. (Inlined BalanceISOYearMonth)
+ int32_t year = date.year + duration.years;
+ int32_t month = date.month + duration.months;
+ MOZ_ASSERT(-11 <= month && month <= 24);
+
+ if (month > 12) {
+ month -= 12;
+ year += 1;
+ } else if (month <= 0) {
+ month += 12;
+ year -= 1;
+ }
+
+ MOZ_ASSERT(1 <= month && month <= 12);
+ MOZ_ASSERT(CanBalanceISOYear(year));
+
+ // Steps 4-7.
+ return ::ConstrainISODate({year, month, date.day});
+}
+
+static bool HasYearsMonthsOrWeeks(const Duration& duration) {
+ return duration.years != 0 || duration.months != 0 || duration.weeks != 0;
+}
+
+static bool AddDate(JSContext* cx, const PlainDate& date,
+ const Duration& duration, Handle<JSObject*> maybeOptions,
+ PlainDate* result) {
+ MOZ_ASSERT(!HasYearsMonthsOrWeeks(duration));
+
+ // Steps 1-3. (Not applicable)
+
+ // Step 4.
+ auto overflow = TemporalOverflow::Constrain;
+ if (maybeOptions) {
+ if (!ToTemporalOverflow(cx, maybeOptions, &overflow)) {
+ return false;
+ }
+ }
+
+ // Step 5.
+ TimeDuration daysDuration;
+ if (!BalanceTimeDuration(cx, duration, TemporalUnit::Day, &daysDuration)) {
+ return false;
+ }
+
+ // Step 6.
+ return AddISODate(cx, date, {0, 0, 0, daysDuration.days}, overflow, result);
+}
+
+static bool AddDate(JSContext* cx, Handle<Wrapped<PlainDateObject*>> date,
+ const Duration& duration, Handle<JSObject*> maybeOptions,
+ PlainDate* result) {
+ auto* unwrappedDate = date.unwrap(cx);
+ if (!unwrappedDate) {
+ return false;
+ }
+ return ::AddDate(cx, ToPlainDate(unwrappedDate), duration, maybeOptions,
+ result);
+}
+
+static PlainDateObject* AddDate(JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> date,
+ const Duration& duration,
+ Handle<JSObject*> maybeOptions) {
+ // Steps 1-3. (Not applicable)
+
+ // Steps 4-6.
+ PlainDate resultDate;
+ if (!::AddDate(cx, date, duration, maybeOptions, &resultDate)) {
+ return nullptr;
+ }
+
+ // Step 7.
+ return CreateTemporalDate(cx, resultDate, calendar.receiver());
+}
+
+/**
+ * AddDate ( calendarRec, plainDate, duration [ , options ] )
+ */
+Wrapped<PlainDateObject*> js::temporal::AddDate(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> date, const Duration& duration,
+ Handle<JSObject*> options) {
+ // Step 1.
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+
+ // Step 2. (Not applicable in our implementation.)
+
+ // Step 3.
+ if (HasYearsMonthsOrWeeks(duration)) {
+ return temporal::CalendarDateAdd(cx, calendar, date, duration, options);
+ }
+
+ // Steps 4-7.
+ return ::AddDate(cx, calendar, date, duration, options);
+}
+
+/**
+ * AddDate ( calendarRec, plainDate, duration [ , options ] )
+ */
+Wrapped<PlainDateObject*> js::temporal::AddDate(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> date, const Duration& duration) {
+ // Step 1.
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+
+ // Step 2. (Not applicable in our implementation.)
+
+ // Step 3.
+ if (HasYearsMonthsOrWeeks(duration)) {
+ return CalendarDateAdd(cx, calendar, date, duration);
+ }
+
+ // Steps 4-7.
+ return ::AddDate(cx, calendar, date, duration, nullptr);
+}
+
+/**
+ * AddDate ( calendarRec, plainDate, duration [ , options ] )
+ */
+Wrapped<PlainDateObject*> js::temporal::AddDate(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> date,
+ Handle<Wrapped<DurationObject*>> durationObj) {
+ auto* unwrappedDuration = durationObj.unwrap(cx);
+ if (!unwrappedDuration) {
+ return nullptr;
+ }
+ auto duration = ToDuration(unwrappedDuration);
+
+ // Step 1.
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+
+ // Step 2. (Not applicable in our implementation.)
+
+ // Step 3.
+ if (HasYearsMonthsOrWeeks(duration)) {
+ return CalendarDateAdd(cx, calendar, date, durationObj);
+ }
+
+ // Steps 4-7.
+ return ::AddDate(cx, calendar, date, duration, nullptr);
+}
+
+/**
+ * AddDate ( calendarRec, plainDate, duration [ , options ] )
+ */
+Wrapped<PlainDateObject*> js::temporal::AddDate(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> date,
+ Handle<Wrapped<DurationObject*>> durationObj, Handle<JSObject*> options) {
+ auto* unwrappedDuration = durationObj.unwrap(cx);
+ if (!unwrappedDuration) {
+ return nullptr;
+ }
+ auto duration = ToDuration(unwrappedDuration);
+
+ // Step 1.
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+
+ // Step 2. (Not applicable in our implementation.)
+
+ // Step 3.
+ if (HasYearsMonthsOrWeeks(duration)) {
+ return temporal::CalendarDateAdd(cx, calendar, date, durationObj, options);
+ }
+
+ // Steps 4-7.
+ return ::AddDate(cx, calendar, date, duration, options);
+}
+
+/**
+ * AddDate ( calendarRec, plainDate, duration [ , options ] )
+ */
+bool js::temporal::AddDate(JSContext* cx, Handle<CalendarRecord> calendar,
+ const PlainDate& date, const Duration& duration,
+ Handle<JSObject*> options, PlainDate* result) {
+ // Step 1.
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+
+ // Step 2. (Not applicable in our implementation.)
+
+ // Step 3.
+ if (HasYearsMonthsOrWeeks(duration)) {
+ return temporal::CalendarDateAdd(cx, calendar, date, duration, options,
+ result);
+ }
+
+ // Steps 4-7.
+ return ::AddDate(cx, date, duration, options, result);
+}
+
+/**
+ * AddDate ( calendarRec, plainDate, duration [ , options ] )
+ */
+bool js::temporal::AddDate(JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> date,
+ const Duration& duration, PlainDate* result) {
+ // Step 1.
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+
+ // Step 2. (Not applicable in our implementation.)
+
+ // Step 3.
+ if (HasYearsMonthsOrWeeks(duration)) {
+ return CalendarDateAdd(cx, calendar, date, duration, result);
+ }
+
+ // Steps 4-7.
+ return ::AddDate(cx, date, duration, nullptr, result);
+}
+
+/**
+ * DifferenceDate ( calendarRec, one, two, options )
+ */
+bool js::temporal::DifferenceDate(JSContext* cx,
+ Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> one,
+ Handle<Wrapped<PlainDateObject*>> two,
+ Handle<PlainObject*> options,
+ Duration* result) {
+ auto* unwrappedOne = one.unwrap(cx);
+ if (!unwrappedOne) {
+ return false;
+ }
+ auto oneDate = ToPlainDate(unwrappedOne);
+
+ auto* unwrappedTwo = two.unwrap(cx);
+ if (!unwrappedTwo) {
+ return false;
+ }
+ auto twoDate = ToPlainDate(unwrappedTwo);
+
+ // Steps 1-2. (Not applicable in our implementation.)
+
+ // Step 3.
+ MOZ_ASSERT(options->staticPrototype() == nullptr);
+
+ // Step 4.
+ MOZ_ASSERT(options->containsPure(cx->names().largestUnit));
+
+ // Step 5.
+ if (oneDate == twoDate) {
+ *result = {};
+ return true;
+ }
+
+ // Step 6.
+ Rooted<JS::Value> largestUnit(cx);
+ if (!GetProperty(cx, options, options, cx->names().largestUnit,
+ &largestUnit)) {
+ return false;
+ }
+
+ if (largestUnit.isString()) {
+ bool isDay;
+ if (!EqualStrings(cx, largestUnit.toString(), cx->names().day, &isDay)) {
+ return false;
+ }
+
+ if (isDay) {
+ // Step 6.a.
+ int32_t days = DaysUntil(oneDate, twoDate);
+
+ // Step 6.b.
+ *result = {0, 0, 0, double(days)};
+ return true;
+ }
+ }
+
+ // Step 7.
+ return CalendarDateUntil(cx, calendar, one, two, options, result);
+}
+
+/**
+ * DifferenceDate ( calendarRec, one, two, options )
+ */
+bool js::temporal::DifferenceDate(JSContext* cx,
+ Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> one,
+ Handle<Wrapped<PlainDateObject*>> two,
+ TemporalUnit largestUnit, Duration* result) {
+ auto* unwrappedOne = one.unwrap(cx);
+ if (!unwrappedOne) {
+ return false;
+ }
+ auto oneDate = ToPlainDate(unwrappedOne);
+
+ auto* unwrappedTwo = two.unwrap(cx);
+ if (!unwrappedTwo) {
+ return false;
+ }
+ auto twoDate = ToPlainDate(unwrappedTwo);
+
+ // Steps 1-4. (Not applicable in our implementation.)
+
+ // Step 5.
+ if (oneDate == twoDate) {
+ *result = {};
+ return true;
+ }
+
+ // Step 6.
+ if (largestUnit == TemporalUnit::Day) {
+ // Step 6.a.
+ int32_t days = DaysUntil(oneDate, twoDate);
+
+ // Step 6.b.
+ *result = {0, 0, 0, double(days)};
+ return true;
+ }
+
+ // Step 7.
+ return CalendarDateUntil(cx, calendar, one, two, largestUnit, result);
+}
+
+/**
+ * CompareISODate ( y1, m1, d1, y2, m2, d2 )
+ */
+int32_t js::temporal::CompareISODate(const PlainDate& one,
+ const PlainDate& two) {
+ // Steps 1-2.
+ if (one.year != two.year) {
+ return one.year < two.year ? -1 : 1;
+ }
+
+ // Steps 3-4.
+ if (one.month != two.month) {
+ return one.month < two.month ? -1 : 1;
+ }
+
+ // Steps 5-6.
+ if (one.day != two.day) {
+ return one.day < two.day ? -1 : 1;
+ }
+
+ // Step 7.
+ return 0;
+}
+
+/**
+ * CreateDateDurationRecord ( years, months, weeks, days )
+ */
+static DateDuration CreateDateDurationRecord(int32_t years, int32_t months,
+ int32_t weeks, int32_t days) {
+ MOZ_ASSERT(IsValidDuration(
+ {double(years), double(months), double(weeks), double(days)}));
+ return {double(years), double(months), double(weeks), double(days)};
+}
+
+/**
+ * DifferenceISODate ( y1, m1, d1, y2, m2, d2, largestUnit )
+ */
+DateDuration js::temporal::DifferenceISODate(const PlainDate& start,
+ const PlainDate& end,
+ TemporalUnit largestUnit) {
+ // Steps 1-2.
+ MOZ_ASSERT(IsValidISODate(start));
+ MOZ_ASSERT(IsValidISODate(end));
+
+ // Both inputs are also within the date-time limits.
+ MOZ_ASSERT(ISODateTimeWithinLimits(start));
+ MOZ_ASSERT(ISODateTimeWithinLimits(end));
+
+ // Because both inputs are valid dates, we don't need to worry about integer
+ // overflow in any of the computations below.
+
+ MOZ_ASSERT(TemporalUnit::Year <= largestUnit &&
+ largestUnit <= TemporalUnit::Day);
+
+ // Step 3.
+ if (largestUnit == TemporalUnit::Year || largestUnit == TemporalUnit::Month) {
+ // Step 3.a.
+ int32_t sign = -CompareISODate(start, end);
+
+ // Step 3.b.
+ if (sign == 0) {
+ return CreateDateDurationRecord(0, 0, 0, 0);
+ }
+
+ // FIXME: spec issue - results can be ambiguous, is this intentional?
+ // https://github.com/tc39/proposal-temporal/issues/2535
+ //
+ // clang-format off
+ // js> var end = new Temporal.PlainDate(1970, 2, 28)
+ // js> var start = new Temporal.PlainDate(1970, 1, 28)
+ // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
+ // "P1M"
+ // js> var start = new Temporal.PlainDate(1970, 1, 29)
+ // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
+ // "P1M"
+ // js> var start = new Temporal.PlainDate(1970, 1, 30)
+ // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
+ // "P1M"
+ // js> var start = new Temporal.PlainDate(1970, 1, 31)
+ // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
+ // "P1M"
+ //
+ // Compare to java.time.temporal
+ //
+ // jshell> import java.time.LocalDate
+ // jshell> var end = LocalDate.of(1970, 2, 28)
+ // end ==> 1970-02-28
+ // jshell> var start = LocalDate.of(1970, 1, 28)
+ // start ==> 1970-01-28
+ // jshell> start.until(end)
+ // $27 ==> P1M
+ // jshell> var start = LocalDate.of(1970, 1, 29)
+ // start ==> 1970-01-29
+ // jshell> start.until(end)
+ // $29 ==> P30D
+ // jshell> var start = LocalDate.of(1970, 1, 30)
+ // start ==> 1970-01-30
+ // jshell> start.until(end)
+ // $31 ==> P29D
+ // jshell> var start = LocalDate.of(1970, 1, 31)
+ // start ==> 1970-01-31
+ // jshell> start.until(end)
+ // $33 ==> P28D
+ //
+ // Also compare to:
+ //
+ // js> var end = new Temporal.PlainDate(1970, 2, 27)
+ // js> var start = new Temporal.PlainDate(1970, 1, 27)
+ // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
+ // "P1M"
+ // js> var start = new Temporal.PlainDate(1970, 1, 28)
+ // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
+ // "P30D"
+ // js> var start = new Temporal.PlainDate(1970, 1, 29)
+ // js> start.calendar.dateUntil(start, end, {largestUnit:"months"}).toString()
+ // "P29D"
+ //
+ // clang-format on
+
+ // Steps 3.c-d. (Not applicable in our implementation.)
+
+ // FIXME: spec issue - consistently use either |end.[[Year]]| or |y2|.
+
+ // Step 3.e.
+ int32_t years = end.year - start.year;
+
+ // TODO: We could inline this, because the AddISODate call is just a more
+ // complicated way to perform:
+ // mid = ConstrainISODate(end.year, start.month, start.day)
+ //
+ // The remaining computations can probably simplified similarily.
+
+ // Step 3.f.
+ auto mid = ::AddISODate(start, {years, 0});
+
+ // Step 3.g.
+ int32_t midSign = -CompareISODate(mid, end);
+
+ // Step 3.h.
+ if (midSign == 0) {
+ // Step 3.h.i.
+ if (largestUnit == TemporalUnit::Year) {
+ return CreateDateDurationRecord(years, 0, 0, 0);
+ }
+
+ // Step 3.h.ii.
+ return CreateDateDurationRecord(0, years * 12, 0, 0);
+ }
+
+ // Step 3.i.
+ int32_t months = end.month - start.month;
+
+ // Step 3.j.
+ if (midSign != sign) {
+ // Step 3.j.i.
+ years -= sign;
+
+ // Step 3.j.ii.
+ months += sign * 12;
+ }
+
+ // Step 3.k.
+ mid = ::AddISODate(start, {years, months});
+
+ // Step 3.l.
+ midSign = -CompareISODate(mid, end);
+
+ // Step 3.m.
+ if (midSign == 0) {
+ // Step 3.m.i.
+ if (largestUnit == TemporalUnit::Year) {
+ return CreateDateDurationRecord(years, months, 0, 0);
+ }
+
+ // Step 3.m.ii.
+ return CreateDateDurationRecord(0, months + years * 12, 0, 0);
+ }
+
+ // Step 3.n.
+ if (midSign != sign) {
+ // Step 3.n.i.
+ months -= sign;
+
+ // Step 3.n.ii.
+ mid = ::AddISODate(start, {years, months});
+ }
+
+ // Steps 3.o-q.
+ int32_t days;
+ if (mid.month == end.month) {
+ MOZ_ASSERT(mid.year == end.year);
+
+ days = end.day - mid.day;
+ } else if (sign < 0) {
+ days = -mid.day - (ISODaysInMonth(end.year, end.month) - end.day);
+ } else {
+ days = end.day + (ISODaysInMonth(mid.year, mid.month) - mid.day);
+ }
+
+ // Step 3.r.
+ if (largestUnit == TemporalUnit::Month) {
+ // Step 3.r.i.
+ months += years * 12;
+
+ // Step 3.r.ii.
+ years = 0;
+ }
+
+ // Step 3.s.
+ return CreateDateDurationRecord(years, months, 0, days);
+ }
+
+ // Step 4.a.
+ MOZ_ASSERT(largestUnit == TemporalUnit::Week ||
+ largestUnit == TemporalUnit::Day);
+
+ // Step 4.b.
+ int32_t epochDaysStart = MakeDay(start);
+
+ // Step 4.c.
+ int32_t epochDaysEnd = MakeDay(end);
+
+ // Step 4.d.
+ int32_t days = epochDaysEnd - epochDaysStart;
+
+ // Step 4.e.
+ int32_t weeks = 0;
+
+ // Step 4.f.
+ if (largestUnit == TemporalUnit::Week) {
+ // Step 4.f.i
+ weeks = days / 7;
+
+ // Step 4.f.ii.
+ days = days % 7;
+ }
+
+ // Step 4.g.
+ return CreateDateDurationRecord(0, 0, weeks, days);
+}
+
+/**
+ * DifferenceTemporalPlainDate ( operation, temporalDate, other, options )
+ */
+static bool DifferenceTemporalPlainDate(JSContext* cx,
+ TemporalDifference operation,
+ const CallArgs& args) {
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendarValue(cx, temporalDate->calendar());
+
+ // Step 1. (Not applicable in our implementation)
+
+ // Step 2.
+ auto wrappedOther = ::ToTemporalDate(cx, args.get(0));
+ if (!wrappedOther) {
+ return false;
+ }
+ auto* unwrappedOther = &wrappedOther.unwrap();
+ auto otherDate = ToPlainDate(unwrappedOther);
+
+ Rooted<Wrapped<PlainDateObject*>> other(cx, wrappedOther);
+ Rooted<CalendarValue> otherCalendar(cx, unwrappedOther->calendar());
+ if (!otherCalendar.wrap(cx)) {
+ return false;
+ }
+
+ // Step 3.
+ if (!CalendarEqualsOrThrow(cx, calendarValue, otherCalendar)) {
+ 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::Date, TemporalUnit::Day,
+ TemporalUnit::Day, &settings)) {
+ return false;
+ }
+ } else {
+ // Steps 4-5.
+ settings = {
+ TemporalUnit::Day,
+ TemporalUnit::Day,
+ TemporalRoundingMode::Trunc,
+ Increment{1},
+ };
+ }
+
+ // Step 6.
+ if (ToPlainDate(temporalDate) == otherDate) {
+ auto* obj = CreateTemporalDuration(cx, {});
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+ }
+
+ // Step 7.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendarValue,
+ {
+ CalendarMethod::DateAdd,
+ CalendarMethod::DateUntil,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Steps 8-9.
+ Duration duration;
+ if (resolvedOptions) {
+ // Step 8.
+ Rooted<Value> largestUnitValue(
+ cx, StringValue(TemporalUnitToString(cx, settings.largestUnit)));
+ if (!DefineDataProperty(cx, resolvedOptions, cx->names().largestUnit,
+ largestUnitValue)) {
+ return false;
+ }
+
+ // Step 9.
+ Duration result;
+ if (!DifferenceDate(cx, calendar, temporalDate, other, resolvedOptions,
+ &result)) {
+ return false;
+ }
+ duration = result.date();
+ } else {
+ // Steps 8-9.
+ Duration result;
+ if (!DifferenceDate(cx, calendar, temporalDate, other, settings.largestUnit,
+ &result)) {
+ return false;
+ }
+ duration = result.date();
+ }
+
+ // Step 10.
+ bool roundingGranularityIsNoop = settings.smallestUnit == TemporalUnit::Day &&
+ settings.roundingIncrement == Increment{1};
+
+ // Step 11.
+ if (!roundingGranularityIsNoop) {
+ // Steps 11.a-b.
+ Duration roundResult;
+ if (!temporal::RoundDuration(cx, duration.date(),
+ settings.roundingIncrement,
+ settings.smallestUnit, settings.roundingMode,
+ temporalDate, calendar, &roundResult)) {
+ return false;
+ }
+
+ // Step 11.c.
+ DateDuration balanceResult;
+ if (!temporal::BalanceDateDurationRelative(
+ cx, roundResult.date(), settings.largestUnit, settings.smallestUnit,
+ temporalDate, calendar, &balanceResult)) {
+ return false;
+ }
+ duration = balanceResult.toDuration();
+ }
+
+ // Step 12.
+ if (operation == TemporalDifference::Since) {
+ duration = duration.negate();
+ }
+
+ auto* obj = CreateTemporalDuration(cx, duration.date());
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainDate ( isoYear, isoMonth, isoDay [ , calendarLike ] )
+ */
+static bool PlainDateConstructor(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ if (!ThrowIfNotConstructing(cx, args, "Temporal.PlainDate")) {
+ 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.
+ Rooted<CalendarValue> calendar(cx);
+ if (!ToTemporalCalendarWithISODefault(cx, args.get(3), &calendar)) {
+ return false;
+ }
+
+ // Step 6.
+ auto* temporalDate =
+ CreateTemporalDate(cx, args, isoYear, isoMonth, isoDay, calendar);
+ if (!temporalDate) {
+ return false;
+ }
+
+ args.rval().setObject(*temporalDate);
+ return true;
+}
+
+/**
+ * Temporal.PlainDate.from ( item [ , options ] )
+ */
+static bool PlainDate_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* temporalDate = item->maybeUnwrapIf<PlainDateObject>()) {
+ auto date = ToPlainDate(temporalDate);
+
+ Rooted<CalendarValue> calendar(cx, temporalDate->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 = CreateTemporalDate(cx, date, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+ }
+ }
+
+ // Step 3.
+ auto result = ToTemporalDate(cx, args.get(0), options);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.PlainDate.compare ( one, two )
+ */
+static bool PlainDate_compare(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ PlainDate one;
+ if (!ToTemporalDate(cx, args.get(0), &one)) {
+ return false;
+ }
+
+ // Step 2.
+ PlainDate two;
+ if (!ToTemporalDate(cx, args.get(1), &two)) {
+ return false;
+ }
+
+ // Step 3.
+ args.rval().setInt32(CompareISODate(one, two));
+ return true;
+}
+
+/**
+ * get Temporal.PlainDate.prototype.calendarId
+ */
+static bool PlainDate_calendarId(JSContext* cx, const CallArgs& args) {
+ auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Step 3.
+ auto* calendarId = ToTemporalCalendarIdentifier(cx, calendar);
+ if (!calendarId) {
+ return false;
+ }
+
+ args.rval().setString(calendarId);
+ return true;
+}
+
+/**
+ * get Temporal.PlainDate.prototype.calendarId
+ */
+static bool PlainDate_calendarId(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_calendarId>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDate.prototype.year
+ */
+static bool PlainDate_year(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Step 4.
+ return CalendarYear(cx, calendar, temporalDate, args.rval());
+}
+
+/**
+ * get Temporal.PlainDate.prototype.year
+ */
+static bool PlainDate_year(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_year>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDate.prototype.month
+ */
+static bool PlainDate_month(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Step 4.
+ return CalendarMonth(cx, calendar, temporalDate, args.rval());
+}
+
+/**
+ * get Temporal.PlainDate.prototype.month
+ */
+static bool PlainDate_month(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_month>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDate.prototype.monthCode
+ */
+static bool PlainDate_monthCode(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Step 4.
+ return CalendarMonthCode(cx, calendar, temporalDate, args.rval());
+}
+
+/**
+ * get Temporal.PlainDate.prototype.monthCode
+ */
+static bool PlainDate_monthCode(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_monthCode>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDate.prototype.day
+ */
+static bool PlainDate_day(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Step 4.
+ return CalendarDay(cx, calendar, temporalDate, args.rval());
+}
+
+/**
+ * get Temporal.PlainDate.prototype.day
+ */
+static bool PlainDate_day(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_day>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDate.prototype.dayOfWeek
+ */
+static bool PlainDate_dayOfWeek(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Step 4.
+ return CalendarDayOfWeek(cx, calendar, temporalDate, args.rval());
+}
+
+/**
+ * get Temporal.PlainDate.prototype.dayOfWeek
+ */
+static bool PlainDate_dayOfWeek(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_dayOfWeek>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDate.prototype.dayOfYear
+ */
+static bool PlainDate_dayOfYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Step 4.
+ return CalendarDayOfYear(cx, calendar, temporalDate, args.rval());
+}
+
+/**
+ * get Temporal.PlainDate.prototype.dayOfYear
+ */
+static bool PlainDate_dayOfYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_dayOfYear>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDate.prototype.weekOfYear
+ */
+static bool PlainDate_weekOfYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Step 4.
+ return CalendarWeekOfYear(cx, calendar, temporalDate, args.rval());
+}
+
+/**
+ * get Temporal.PlainDate.prototype.weekOfYear
+ */
+static bool PlainDate_weekOfYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_weekOfYear>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDate.prototype.yearOfWeek
+ */
+static bool PlainDate_yearOfWeek(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Step 4.
+ return CalendarYearOfWeek(cx, calendar, temporalDate, args.rval());
+}
+
+/**
+ * get Temporal.PlainDate.prototype.yearOfWeek
+ */
+static bool PlainDate_yearOfWeek(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_yearOfWeek>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDate.prototype.daysInWeek
+ */
+static bool PlainDate_daysInWeek(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Step 4.
+ return CalendarDaysInWeek(cx, calendar, temporalDate, args.rval());
+}
+
+/**
+ * get Temporal.PlainDate.prototype.daysInWeek
+ */
+static bool PlainDate_daysInWeek(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_daysInWeek>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDate.prototype.daysInMonth
+ */
+static bool PlainDate_daysInMonth(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Step 4.
+ return CalendarDaysInMonth(cx, calendar, temporalDate, args.rval());
+}
+
+/**
+ * get Temporal.PlainDate.prototype.daysInMonth
+ */
+static bool PlainDate_daysInMonth(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_daysInMonth>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDate.prototype.daysInYear
+ */
+static bool PlainDate_daysInYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Step 4.
+ return CalendarDaysInYear(cx, calendar, temporalDate, args.rval());
+}
+
+/**
+ * get Temporal.PlainDate.prototype.daysInYear
+ */
+static bool PlainDate_daysInYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_daysInYear>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDate.prototype.monthsInYear
+ */
+static bool PlainDate_monthsInYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Step 4.
+ return CalendarMonthsInYear(cx, calendar, temporalDate, args.rval());
+}
+
+/**
+ * get Temporal.PlainDate.prototype.monthsInYear
+ */
+static bool PlainDate_monthsInYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_monthsInYear>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDate.prototype.inLeapYear
+ */
+static bool PlainDate_inLeapYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Step 4.
+ return CalendarInLeapYear(cx, calendar, temporalDate, args.rval());
+}
+
+/**
+ * get Temporal.PlainDate.prototype.inLeapYear
+ */
+static bool PlainDate_inLeapYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_inLeapYear>(cx, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.toPlainYearMonth ( )
+ */
+static bool PlainDate_toPlainYearMonth(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendarValue(cx, temporalDate->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 5.
+ Rooted<PlainObject*> fields(
+ cx, PrepareTemporalFields(cx, temporalDate, fieldNames));
+ if (!fields) {
+ return false;
+ }
+
+ // Step 6.
+ auto obj = CalendarYearMonthFromFields(cx, calendar, fields);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainDate.prototype.toPlainYearMonth ( )
+ */
+static bool PlainDate_toPlainYearMonth(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_toPlainYearMonth>(cx,
+ args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.toPlainMonthDay ( )
+ */
+static bool PlainDate_toPlainMonthDay(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendarValue(cx, temporalDate->calendar());
+
+ // Example for the optimisation described in TemporalFields.cpp
+
+ // Optimization for built-in objects.
+ do {
+ // Step 4.
+ static constexpr std::initializer_list<CalendarField> fieldNames = {
+ CalendarField::Day, CalendarField::MonthCode};
+
+ // Step 5.
+ if (calendarValue.isObject()) {
+ Rooted<JSObject*> calendarObj(cx, calendarValue.toObject());
+ if (!calendarObj->is<CalendarObject>()) {
+ break;
+ }
+ auto builtinCalendar = calendarObj.as<CalendarObject>();
+
+ // Step 5.
+ if (!IsBuiltinAccess(cx, builtinCalendar, fieldNames)) {
+ break;
+ }
+ }
+ if (!IsBuiltinAccess(cx, temporalDate, fieldNames)) {
+ break;
+ }
+
+ // Step 6.
+ auto date = ToPlainDate(temporalDate);
+ auto result = PlainDate{1972 /* referenceISOYear */, date.month, date.day};
+
+ auto* obj = CreateTemporalMonthDay(cx, result, calendarValue);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+ } while (false);
+
+ // 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, temporalDate, 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.PlainDate.prototype.toPlainMonthDay ( )
+ */
+static bool PlainDate_toPlainMonthDay(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_toPlainMonthDay>(cx, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.toPlainDateTime ( [ temporalTime ] )
+ */
+static bool PlainDate_toPlainDateTime(JSContext* cx, const CallArgs& args) {
+ auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Default initialize the time component to all zero.
+ PlainDateTime dateTime = {ToPlainDate(temporalDate), {}};
+
+ // Step 4. (Reordered)
+ if (args.hasDefined(0)) {
+ if (!ToTemporalTime(cx, args[0], &dateTime.time)) {
+ return false;
+ }
+ }
+
+ // Steps 3 and 5.
+ auto* obj = CreateTemporalDateTime(cx, dateTime, calendar);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainDate.prototype.toPlainDateTime ( [ temporalTime ] )
+ */
+static bool PlainDate_toPlainDateTime(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_toPlainDateTime>(cx, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.getISOFields ( )
+ */
+static bool PlainDate_getISOFields(JSContext* cx, const CallArgs& args) {
+ auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
+ auto date = ToPlainDate(temporalDate);
+ auto calendar = temporalDate->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(date.day))) {
+ return false;
+ }
+
+ // Step 6.
+ if (!fields.emplaceBack(NameToId(cx->names().isoMonth),
+ Int32Value(date.month))) {
+ return false;
+ }
+
+ // Step 7.
+ if (!fields.emplaceBack(NameToId(cx->names().isoYear),
+ Int32Value(date.year))) {
+ return false;
+ }
+
+ // Step 8.
+ auto* obj =
+ NewPlainObjectWithUniqueNames(cx, fields.begin(), fields.length());
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainDate.prototype.getISOFields ( )
+ */
+static bool PlainDate_getISOFields(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_getISOFields>(cx, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.getCalendar ( )
+ */
+static bool PlainDate_getCalendar(JSContext* cx, const CallArgs& args) {
+ auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Step 3.
+ auto* obj = ToTemporalCalendarObject(cx, calendar);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainDate.prototype.getCalendar ( )
+ */
+static bool PlainDate_getCalendar(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_getCalendar>(cx, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.add ( temporalDurationLike [ , options ] )
+ */
+static bool PlainDate_add(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendarValue(cx, temporalDate->calendar());
+
+ // Step 3.
+ Rooted<Wrapped<DurationObject*>> duration(
+ cx, ToTemporalDuration(cx, args.get(0)));
+ if (!duration) {
+ return false;
+ }
+
+ // Step 4.
+ Rooted<JSObject*> options(cx);
+ if (args.hasDefined(1)) {
+ options = RequireObjectArg(cx, "options", "add", args[1]);
+ } else {
+ options = NewPlainObjectWithProto(cx, nullptr);
+ }
+ if (!options) {
+ return false;
+ }
+
+ // Step 5.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendarValue,
+ {
+ CalendarMethod::DateAdd,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Step 6.
+ auto result = AddDate(cx, calendar, temporalDate, duration, options);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.PlainDate.prototype.add ( temporalDurationLike [ , options ] )
+ */
+static bool PlainDate_add(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_add>(cx, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.subtract ( temporalDurationLike [ , options ] )
+ */
+static bool PlainDate_subtract(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+ Rooted<CalendarValue> calendarValue(cx, temporalDate->calendar());
+
+ // Step 3.
+ Duration duration;
+ if (!ToTemporalDuration(cx, args.get(0), &duration)) {
+ return false;
+ }
+
+ // Step 4.
+ Rooted<JSObject*> options(cx);
+ if (args.hasDefined(1)) {
+ options = RequireObjectArg(cx, "options", "subtract", args[1]);
+ } else {
+ options = NewPlainObjectWithProto(cx, nullptr);
+ }
+ if (!options) {
+ return false;
+ }
+
+ // Step 5.
+ auto negatedDuration = duration.negate();
+
+ // Step 6.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendarValue,
+ {
+ CalendarMethod::DateAdd,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Step 7.
+ auto result =
+ temporal::AddDate(cx, calendar, temporalDate, negatedDuration, options);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.PlainDate.prototype.subtract ( temporalDurationLike [ , options ] )
+ */
+static bool PlainDate_subtract(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_subtract>(cx, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.with ( temporalDateLike [ , options ] )
+ */
+static bool PlainDate_with(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+
+ // Step 3.
+ Rooted<JSObject*> temporalDateLike(
+ cx, RequireObjectArg(cx, "temporalDateLike", "with", args.get(0)));
+ if (!temporalDateLike) {
+ return false;
+ }
+
+ // Step 4.
+ if (!RejectTemporalLikeObject(cx, temporalDateLike)) {
+ 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, temporalDate->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, temporalDate, fieldNames));
+ if (!fields) {
+ return false;
+ }
+
+ // Step 9.
+ Rooted<PlainObject*> partialDate(
+ cx, PreparePartialTemporalFields(cx, temporalDateLike, fieldNames));
+ if (!partialDate) {
+ return false;
+ }
+
+ // Step 10.
+ Rooted<JSObject*> mergedFields(
+ cx, CalendarMergeFields(cx, calendar, fields, partialDate));
+ if (!mergedFields) {
+ return false;
+ }
+
+ // Step 11.
+ fields = PrepareTemporalFields(cx, mergedFields, fieldNames);
+ if (!fields) {
+ return false;
+ }
+
+ // Step 12.
+ auto result =
+ temporal::CalendarDateFromFields(cx, calendar, fields, resolvedOptions);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.PlainDate.prototype.with ( temporalDateLike [ , options ] )
+ */
+static bool PlainDate_with(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_with>(cx, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.withCalendar ( calendar )
+ */
+static bool PlainDate_withCalendar(JSContext* cx, const CallArgs& args) {
+ auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
+ auto date = ToPlainDate(temporalDate);
+
+ // Step 3.
+ Rooted<CalendarValue> calendar(cx);
+ if (!ToTemporalCalendar(cx, args.get(0), &calendar)) {
+ return false;
+ }
+
+ // Step 4.
+ auto* result = CreateTemporalDate(cx, date, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.PlainDate.prototype.withCalendar ( calendar )
+ */
+static bool PlainDate_withCalendar(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_withCalendar>(cx, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.until ( other [ , options ] )
+ */
+static bool PlainDate_until(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ return DifferenceTemporalPlainDate(cx, TemporalDifference::Until, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.until ( other [ , options ] )
+ */
+static bool PlainDate_until(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_until>(cx, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.since ( other [ , options ] )
+ */
+static bool PlainDate_since(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ return DifferenceTemporalPlainDate(cx, TemporalDifference::Since, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.since ( other [ , options ] )
+ */
+static bool PlainDate_since(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_since>(cx, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.equals ( other )
+ */
+static bool PlainDate_equals(JSContext* cx, const CallArgs& args) {
+ auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
+ auto date = ToPlainDate(temporalDate);
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Step 3.
+ Rooted<PlainDateWithCalendar> other(cx);
+ if (!ToTemporalDate(cx, args.get(0), &other)) {
+ return false;
+ }
+
+ // Steps 4-7.
+ bool equals = date == other.date();
+ if (equals && !CalendarEquals(cx, calendar, other.calendar(), &equals)) {
+ return false;
+ }
+
+ args.rval().setBoolean(equals);
+ return true;
+}
+
+/**
+ * Temporal.PlainDate.prototype.equals ( other )
+ */
+static bool PlainDate_equals(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_equals>(cx, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.toZonedDateTime ( item )
+ *
+ * The |item| argument represents either a time zone or an options object. The
+ * following cases are supported:
+ * - |item| is a `Temporal.TimeZone` object.
+ * - |item| is a user-defined time zone object.
+ * - |item| is an options object with `timeZone` and `plainTime` properties.
+ * - |item| is a time zone identifier string.
+ *
+ * User-defined time zone objects are distinguished from options objects by the
+ * `timeZone` property, i.e. if a `timeZone` property is present, the object is
+ * treated as an options object, otherwise an object is treated as a
+ * user-defined time zone.
+ */
+static bool PlainDate_toZonedDateTime(JSContext* cx, const CallArgs& args) {
+ auto* temporalDate = &args.thisv().toObject().as<PlainDateObject>();
+ auto date = ToPlainDate(temporalDate);
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+
+ // Steps 3-4
+ Rooted<TimeZoneValue> timeZone(cx);
+ Rooted<Value> temporalTime(cx);
+ if (args.get(0).isObject()) {
+ Rooted<JSObject*> item(cx, &args[0].toObject());
+
+ // Steps 3.a-b.
+ if (item->canUnwrapAs<TimeZoneObject>()) {
+ // Step 3.a.i.
+ timeZone.set(TimeZoneValue(item));
+
+ // Step 3.a.ii.
+ temporalTime.setUndefined();
+ } else {
+ // Step 3.b.i.
+ Rooted<Value> timeZoneLike(cx);
+ if (!GetProperty(cx, item, item, cx->names().timeZone, &timeZoneLike)) {
+ return false;
+ }
+
+ // Steps 3.b.ii-iii.
+ if (timeZoneLike.isUndefined()) {
+ // Step 3.b.ii.1.
+ if (!ToTemporalTimeZone(cx, args[0], &timeZone)) {
+ return false;
+ }
+
+ // Step 3.b.ii.2.
+ temporalTime.setUndefined();
+ } else {
+ // Step 3.b.iii.1.
+ if (!ToTemporalTimeZone(cx, timeZoneLike, &timeZone)) {
+ return false;
+ }
+
+ // Step 3.b.iii.2.
+ if (!GetProperty(cx, item, item, cx->names().plainTime,
+ &temporalTime)) {
+ return false;
+ }
+ }
+ }
+ } else {
+ // Step 4.a.
+ if (!ToTemporalTimeZone(cx, args.get(0), &timeZone)) {
+ return false;
+ }
+
+ // Step 4.b.
+ temporalTime.setUndefined();
+ }
+
+ // Step 6.a.
+ PlainTime time = {};
+ if (!temporalTime.isUndefined()) {
+ if (!ToTemporalTime(cx, temporalTime, &time)) {
+ return false;
+ }
+ }
+
+ // Steps 5.a and 6.b
+ Rooted<PlainDateTimeWithCalendar> temporalDateTime(cx);
+ if (!CreateTemporalDateTime(cx, {date, time}, calendar, &temporalDateTime)) {
+ return false;
+ }
+
+ // Steps 7-8.
+ Instant instant;
+ if (!GetInstantFor(cx, timeZone, temporalDateTime,
+ TemporalDisambiguation::Compatible, &instant)) {
+ return false;
+ }
+
+ // Step 9.
+ auto* result = CreateTemporalZonedDateTime(cx, instant, timeZone, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.PlainDate.prototype.toZonedDateTime ( item )
+ */
+static bool PlainDate_toZonedDateTime(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_toZonedDateTime>(cx, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.toString ( [ options ] )
+ */
+static bool PlainDate_toString(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+
+ auto showCalendar = CalendarOption::Auto;
+ if (args.hasDefined(0)) {
+ // Step 3.
+ Rooted<JSObject*> options(
+ cx, RequireObjectArg(cx, "options", "toString", args[0]));
+ if (!options) {
+ return false;
+ }
+
+ // Step 4.
+ if (!ToCalendarNameOption(cx, options, &showCalendar)) {
+ return false;
+ }
+ }
+
+ // Step 5.
+ JSString* str = TemporalDateToString(cx, temporalDate, showCalendar);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.PlainDate.prototype.toString ( [ options ] )
+ */
+static bool PlainDate_toString(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_toString>(cx, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.toLocaleString ( [ locales [ , options ] ] )
+ */
+static bool PlainDate_toLocaleString(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+
+ // Step 3.
+ JSString* str = TemporalDateToString(cx, temporalDate, CalendarOption::Auto);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.PlainDate.prototype.toLocaleString ( [ locales [ , options ] ] )
+ */
+static bool PlainDate_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_toLocaleString>(cx, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.toJSON ( )
+ */
+static bool PlainDate_toJSON(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainDateObject*> temporalDate(
+ cx, &args.thisv().toObject().as<PlainDateObject>());
+
+ // Step 3.
+ JSString* str = TemporalDateToString(cx, temporalDate, CalendarOption::Auto);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.PlainDate.prototype.toJSON ( )
+ */
+static bool PlainDate_toJSON(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDate, PlainDate_toJSON>(cx, args);
+}
+
+/**
+ * Temporal.PlainDate.prototype.valueOf ( )
+ */
+static bool PlainDate_valueOf(JSContext* cx, unsigned argc, Value* vp) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
+ "PlainDate", "primitive type");
+ return false;
+}
+
+const JSClass PlainDateObject::class_ = {
+ "Temporal.PlainDate",
+ JSCLASS_HAS_RESERVED_SLOTS(PlainDateObject::SLOT_COUNT) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_PlainDate),
+ JS_NULL_CLASS_OPS,
+ &PlainDateObject::classSpec_,
+};
+
+const JSClass& PlainDateObject::protoClass_ = PlainObject::class_;
+
+static const JSFunctionSpec PlainDate_methods[] = {
+ JS_FN("from", PlainDate_from, 1, 0),
+ JS_FN("compare", PlainDate_compare, 2, 0),
+ JS_FS_END,
+};
+
+static const JSFunctionSpec PlainDate_prototype_methods[] = {
+ JS_FN("toPlainMonthDay", PlainDate_toPlainMonthDay, 0, 0),
+ JS_FN("toPlainYearMonth", PlainDate_toPlainYearMonth, 0, 0),
+ JS_FN("toPlainDateTime", PlainDate_toPlainDateTime, 0, 0),
+ JS_FN("getISOFields", PlainDate_getISOFields, 0, 0),
+ JS_FN("getCalendar", PlainDate_getCalendar, 0, 0),
+ JS_FN("add", PlainDate_add, 1, 0),
+ JS_FN("subtract", PlainDate_subtract, 1, 0),
+ JS_FN("with", PlainDate_with, 1, 0),
+ JS_FN("withCalendar", PlainDate_withCalendar, 1, 0),
+ JS_FN("until", PlainDate_until, 1, 0),
+ JS_FN("since", PlainDate_since, 1, 0),
+ JS_FN("equals", PlainDate_equals, 1, 0),
+ JS_FN("toZonedDateTime", PlainDate_toZonedDateTime, 1, 0),
+ JS_FN("toString", PlainDate_toString, 0, 0),
+ JS_FN("toLocaleString", PlainDate_toLocaleString, 0, 0),
+ JS_FN("toJSON", PlainDate_toJSON, 0, 0),
+ JS_FN("valueOf", PlainDate_valueOf, 0, 0),
+ JS_FS_END,
+};
+
+static const JSPropertySpec PlainDate_prototype_properties[] = {
+ JS_PSG("calendarId", PlainDate_calendarId, 0),
+ JS_PSG("year", PlainDate_year, 0),
+ JS_PSG("month", PlainDate_month, 0),
+ JS_PSG("monthCode", PlainDate_monthCode, 0),
+ JS_PSG("day", PlainDate_day, 0),
+ JS_PSG("dayOfWeek", PlainDate_dayOfWeek, 0),
+ JS_PSG("dayOfYear", PlainDate_dayOfYear, 0),
+ JS_PSG("weekOfYear", PlainDate_weekOfYear, 0),
+ JS_PSG("yearOfWeek", PlainDate_yearOfWeek, 0),
+ JS_PSG("daysInWeek", PlainDate_daysInWeek, 0),
+ JS_PSG("daysInMonth", PlainDate_daysInMonth, 0),
+ JS_PSG("daysInYear", PlainDate_daysInYear, 0),
+ JS_PSG("monthsInYear", PlainDate_monthsInYear, 0),
+ JS_PSG("inLeapYear", PlainDate_inLeapYear, 0),
+ JS_STRING_SYM_PS(toStringTag, "Temporal.PlainDate", JSPROP_READONLY),
+ JS_PS_END,
+};
+
+const ClassSpec PlainDateObject::classSpec_ = {
+ GenericCreateConstructor<PlainDateConstructor, 3, gc::AllocKind::FUNCTION>,
+ GenericCreatePrototype<PlainDateObject>,
+ PlainDate_methods,
+ nullptr,
+ PlainDate_prototype_methods,
+ PlainDate_prototype_properties,
+ nullptr,
+ ClassSpec::DontDefineConstructor,
+};
+
+struct PlainDateNameAndNative final {
+ PropertyName* name;
+ JSNative native;
+};
+
+static PlainDateNameAndNative GetPlainDateNameAndNative(
+ JSContext* cx, CalendarField fieldName) {
+ switch (fieldName) {
+ case CalendarField::Year:
+ return {cx->names().year, PlainDate_year};
+ case CalendarField::Month:
+ return {cx->names().month, PlainDate_month};
+ case CalendarField::MonthCode:
+ return {cx->names().monthCode, PlainDate_monthCode};
+ case CalendarField::Day:
+ return {cx->names().day, PlainDate_day};
+ }
+ MOZ_CRASH("invalid temporal field name");
+}
+
+bool js::temporal::IsBuiltinAccess(
+ JSContext* cx, Handle<PlainDateObject*> date,
+ std::initializer_list<CalendarField> fieldNames) {
+ // Don't optimize when the object has any own properties which may shadow the
+ // built-in methods.
+ if (date->shape()->propMapLength() > 0) {
+ return false;
+ }
+
+ JSObject* proto = cx->global()->maybeGetPrototype(JSProto_PlainDate);
+
+ // Don't attempt to optimize when the class isn't yet initialized.
+ if (!proto) {
+ return false;
+ }
+
+ // Don't optimize when the prototype isn't the built-in prototype.
+ if (date->staticPrototype() != proto) {
+ return false;
+ }
+
+ auto* nproto = &proto->as<NativeObject>();
+ for (auto fieldName : fieldNames) {
+ auto [name, native] = GetPlainDateNameAndNative(cx, fieldName);
+ auto prop = nproto->lookupPure(name);
+
+ // Return if the property isn't a data property.
+ if (!prop || !prop->isDataProperty()) {
+ return false;
+ }
+
+ // Return if the property isn't the initial method.
+ if (!IsNativeFunction(nproto->getSlot(prop->slot()), native)) {
+ return false;
+ }
+ }
+
+ // Success! The access can be optimized.
+ return true;
+}