summaryrefslogtreecommitdiffstats
path: root/js/src/builtin/temporal
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /js/src/builtin/temporal
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/builtin/temporal')
-rw-r--r--js/src/builtin/temporal/Calendar.cpp4758
-rw-r--r--js/src/builtin/temporal/Calendar.h956
-rw-r--r--js/src/builtin/temporal/Duration.cpp6802
-rw-r--r--js/src/builtin/temporal/Duration.h210
-rw-r--r--js/src/builtin/temporal/Instant.cpp1792
-rw-r--r--js/src/builtin/temporal/Instant.h159
-rw-r--r--js/src/builtin/temporal/Int96.cpp81
-rw-r--r--js/src/builtin/temporal/Int96.h161
-rw-r--r--js/src/builtin/temporal/PlainDate.cpp2999
-rw-r--r--js/src/builtin/temporal/PlainDate.h292
-rw-r--r--js/src/builtin/temporal/PlainDateTime.cpp2856
-rw-r--r--js/src/builtin/temporal/PlainDateTime.h263
-rw-r--r--js/src/builtin/temporal/PlainMonthDay.cpp1000
-rw-r--r--js/src/builtin/temporal/PlainMonthDay.h66
-rw-r--r--js/src/builtin/temporal/PlainTime.cpp2641
-rw-r--r--js/src/builtin/temporal/PlainTime.h187
-rw-r--r--js/src/builtin/temporal/PlainYearMonth.cpp1642
-rw-r--r--js/src/builtin/temporal/PlainYearMonth.h65
-rw-r--r--js/src/builtin/temporal/Temporal.cpp1850
-rw-r--r--js/src/builtin/temporal/Temporal.h397
-rw-r--r--js/src/builtin/temporal/TemporalFields.cpp939
-rw-r--r--js/src/builtin/temporal/TemporalFields.h192
-rw-r--r--js/src/builtin/temporal/TemporalNow.cpp539
-rw-r--r--js/src/builtin/temporal/TemporalNow.h30
-rw-r--r--js/src/builtin/temporal/TemporalParser.cpp3464
-rw-r--r--js/src/builtin/temporal/TemporalParser.h166
-rw-r--r--js/src/builtin/temporal/TemporalRoundingMode.h433
-rw-r--r--js/src/builtin/temporal/TemporalTypes.h546
-rw-r--r--js/src/builtin/temporal/TemporalUnit.h135
-rw-r--r--js/src/builtin/temporal/TimeZone.cpp2729
-rw-r--r--js/src/builtin/temporal/TimeZone.h625
-rw-r--r--js/src/builtin/temporal/ToString.cpp679
-rw-r--r--js/src/builtin/temporal/ToString.h86
-rw-r--r--js/src/builtin/temporal/Wrapped.cpp22
-rw-r--r--js/src/builtin/temporal/Wrapped.h169
-rw-r--r--js/src/builtin/temporal/ZonedDateTime.cpp4124
-rw-r--r--js/src/builtin/temporal/ZonedDateTime.h280
-rw-r--r--js/src/builtin/temporal/moz.build34
38 files changed, 44369 insertions, 0 deletions
diff --git a/js/src/builtin/temporal/Calendar.cpp b/js/src/builtin/temporal/Calendar.cpp
new file mode 100644
index 0000000000..8a06877093
--- /dev/null
+++ b/js/src/builtin/temporal/Calendar.cpp
@@ -0,0 +1,4758 @@
+/* -*- 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/Calendar.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Range.h"
+#include "mozilla/TextUtils.h"
+
+#include <algorithm>
+#include <array>
+#include <cmath>
+#include <cstring>
+#include <iterator>
+#include <stddef.h>
+#include <stdint.h>
+#include <utility>
+
+#include "jsfriendapi.h"
+#include "jsnum.h"
+#include "jspubtd.h"
+#include "jstypes.h"
+#include "NamespaceImports.h"
+
+#include "builtin/Array.h"
+#include "builtin/String.h"
+#include "builtin/temporal/Duration.h"
+#include "builtin/temporal/PlainDate.h"
+#include "builtin/temporal/PlainDateTime.h"
+#include "builtin/temporal/PlainMonthDay.h"
+#include "builtin/temporal/PlainTime.h"
+#include "builtin/temporal/PlainYearMonth.h"
+#include "builtin/temporal/Temporal.h"
+#include "builtin/temporal/TemporalFields.h"
+#include "builtin/temporal/TemporalParser.h"
+#include "builtin/temporal/TemporalTypes.h"
+#include "builtin/temporal/TemporalUnit.h"
+#include "builtin/temporal/Wrapped.h"
+#include "builtin/temporal/ZonedDateTime.h"
+#include "gc/AllocKind.h"
+#include "gc/Barrier.h"
+#include "gc/GCEnum.h"
+#include "gc/Tracer.h"
+#include "js/AllocPolicy.h"
+#include "js/CallArgs.h"
+#include "js/CallNonGenericMethod.h"
+#include "js/Class.h"
+#include "js/Conversions.h"
+#include "js/ErrorReport.h"
+#include "js/ForOfIterator.h"
+#include "js/friend/ErrorMessages.h"
+#include "js/GCAPI.h"
+#include "js/GCHashTable.h"
+#include "js/GCVector.h"
+#include "js/Id.h"
+#include "js/Printer.h"
+#include "js/PropertyDescriptor.h"
+#include "js/PropertySpec.h"
+#include "js/RootingAPI.h"
+#include "js/TracingAPI.h"
+#include "js/Value.h"
+#include "util/Text.h"
+#include "vm/ArrayObject.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/Compartment.h"
+#include "vm/GlobalObject.h"
+#include "vm/Interpreter.h"
+#include "vm/JSAtomState.h"
+#include "vm/JSContext.h"
+#include "vm/JSObject.h"
+#include "vm/PlainObject.h"
+#include "vm/PropertyInfo.h"
+#include "vm/PropertyKey.h"
+#include "vm/Realm.h"
+#include "vm/Shape.h"
+#include "vm/Stack.h"
+#include "vm/StringType.h"
+
+#include "vm/Compartment-inl.h"
+#include "vm/JSAtomUtils-inl.h"
+#include "vm/JSObject-inl.h"
+#include "vm/NativeObject-inl.h"
+#include "vm/ObjectOperations-inl.h"
+
+using namespace js;
+using namespace js::temporal;
+
+static inline bool IsCalendar(Handle<Value> v) {
+ return v.isObject() && v.toObject().is<CalendarObject>();
+}
+
+void js::temporal::CalendarValue::trace(JSTracer* trc) {
+ TraceRoot(trc, &value_, "CalendarValue::value");
+}
+
+void js::temporal::CalendarRecord::trace(JSTracer* trc) {
+ receiver_.trace(trc);
+ TraceNullableRoot(trc, &dateAdd_, "CalendarRecord::dateAdd");
+ TraceNullableRoot(trc, &dateFromFields_, "CalendarRecord::dateFromFields");
+ TraceNullableRoot(trc, &dateUntil_, "CalendarRecord::dateUntil");
+ TraceNullableRoot(trc, &day_, "CalendarRecord::day");
+ TraceNullableRoot(trc, &fields_, "CalendarRecord::fields");
+ TraceNullableRoot(trc, &mergeFields_, "CalendarRecord::mergeFields");
+ TraceNullableRoot(trc, &monthDayFromFields_,
+ "CalendarRecord::monthDayFromFields");
+ TraceNullableRoot(trc, &yearMonthFromFields_,
+ "CalendarRecord::yearMonthFromFields");
+}
+
+bool js::temporal::WrapCalendarValue(JSContext* cx,
+ MutableHandle<JS::Value> calendar) {
+ MOZ_ASSERT(calendar.isString() || calendar.isObject());
+ return cx->compartment()->wrap(cx, calendar);
+}
+
+/**
+ * IteratorToListOfType ( iteratorRecord, elementTypes )
+ *
+ * With `elementTypes = ยซ String ยป`.
+ *
+ * This implementation accepts an iterable instead of an iterator record.
+ */
+static bool IterableToListOfStrings(JSContext* cx, Handle<Value> items,
+ MutableHandle<CalendarFieldNames> list) {
+ JS::ForOfIterator iterator(cx);
+ if (!iterator.init(items)) {
+ return false;
+ }
+
+ // Step 1. (Not applicable in our implementation.)
+
+ // Steps 2-3.
+ Rooted<Value> nextValue(cx);
+ Rooted<PropertyKey> value(cx);
+ while (true) {
+ bool done;
+ if (!iterator.next(&nextValue, &done)) {
+ return false;
+ }
+ if (done) {
+ break;
+ }
+
+ if (nextValue.isString()) {
+ if (!PrimitiveValueToId<CanGC>(cx, nextValue, &value)) {
+ return false;
+ }
+ if (!list.append(value)) {
+ return false;
+ }
+ continue;
+ }
+
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, nextValue,
+ nullptr, "not a string");
+
+ iterator.closeThrow();
+ return false;
+ }
+
+ // Step 4.
+ return true;
+}
+
+/**
+ * IsISOLeapYear ( year )
+ */
+static constexpr bool IsISOLeapYear(int32_t year) {
+ // Steps 1-5.
+ return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
+}
+
+/**
+ * IsISOLeapYear ( year )
+ */
+static bool IsISOLeapYear(double year) {
+ // Step 1.
+ MOZ_ASSERT(IsInteger(year));
+
+ // Steps 2-5.
+ return std::fmod(year, 4) == 0 &&
+ (std::fmod(year, 100) != 0 || std::fmod(year, 400) == 0);
+}
+
+/**
+ * ISODaysInYear ( year )
+ */
+int32_t js::temporal::ISODaysInYear(int32_t year) {
+ // Steps 1-3.
+ return IsISOLeapYear(year) ? 366 : 365;
+}
+
+/**
+ * ISODaysInMonth ( year, month )
+ */
+static constexpr int32_t ISODaysInMonth(int32_t year, int32_t month) {
+ MOZ_ASSERT(1 <= month && month <= 12);
+
+ constexpr uint8_t daysInMonth[2][13] = {
+ {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+ {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
+
+ // Steps 1-4.
+ return daysInMonth[IsISOLeapYear(year)][month];
+}
+
+/**
+ * ISODaysInMonth ( year, month )
+ */
+int32_t js::temporal::ISODaysInMonth(int32_t year, int32_t month) {
+ return ::ISODaysInMonth(year, month);
+}
+
+/**
+ * ISODaysInMonth ( year, month )
+ */
+int32_t js::temporal::ISODaysInMonth(double year, int32_t month) {
+ MOZ_ASSERT(1 <= month && month <= 12);
+
+ static constexpr uint8_t daysInMonth[2][13] = {
+ {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
+ {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};
+
+ // Steps 1-4.
+ return daysInMonth[IsISOLeapYear(year)][month];
+}
+
+/**
+ * 21.4.1.6 Week Day
+ *
+ * Compute the week day from |day| without first expanding |day| into a full
+ * date through |MakeDate(day, 0)|:
+ *
+ * WeekDay(MakeDate(day, 0))
+ * = WeekDay(day ร— msPerDay + 0)
+ * = WeekDay(day ร— msPerDay)
+ * = ๐”ฝ(โ„(Day(day ร— msPerDay) + 4๐”ฝ) modulo 7)
+ * = ๐”ฝ(โ„(๐”ฝ(floor(โ„((day ร— msPerDay) / msPerDay))) + 4๐”ฝ) modulo 7)
+ * = ๐”ฝ(โ„(๐”ฝ(floor(โ„(day))) + 4๐”ฝ) modulo 7)
+ * = ๐”ฝ(โ„(๐”ฝ(day) + 4๐”ฝ) modulo 7)
+ */
+static int32_t WeekDay(int32_t day) {
+ int32_t result = (day + 4) % 7;
+ if (result < 0) {
+ result += 7;
+ }
+ return result;
+}
+
+/**
+ * ToISODayOfWeek ( year, month, day )
+ */
+static int32_t ToISODayOfWeek(const PlainDate& date) {
+ MOZ_ASSERT(ISODateTimeWithinLimits(date));
+
+ // Steps 1-3. (Not applicable in our implementation.)
+
+ // TODO: Check if ES MakeDate + WeekDay is efficient enough.
+ //
+ // https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week#Methods_in_computer_code
+
+ // Step 4.
+ int32_t day = MakeDay(date);
+
+ // Step 5.
+ int32_t weekday = WeekDay(day);
+ return weekday != 0 ? weekday : 7;
+}
+
+static constexpr auto FirstDayOfMonth(int32_t year) {
+ // The following array contains the day of year for the first day of each
+ // month, where index 0 is January, and day 0 is January 1.
+ std::array<int32_t, 13> days = {};
+ for (int32_t month = 1; month <= 12; ++month) {
+ days[month] = days[month - 1] + ::ISODaysInMonth(year, month);
+ }
+ return days;
+}
+
+/**
+ * ToISODayOfYear ( year, month, day )
+ */
+static int32_t ToISODayOfYear(int32_t year, int32_t month, int32_t day) {
+ MOZ_ASSERT(1 <= month && month <= 12);
+
+ // First day of month arrays for non-leap and leap years.
+ constexpr decltype(FirstDayOfMonth(0)) firstDayOfMonth[2] = {
+ FirstDayOfMonth(1), FirstDayOfMonth(0)};
+
+ // Steps 1-3. (Not applicable in our implementation.)
+
+ // Steps 4-5.
+ //
+ // Instead of first computing the date and then using DayWithinYear to map the
+ // date to the day within the year, directly lookup the first day of the month
+ // and then add the additional days.
+ return firstDayOfMonth[IsISOLeapYear(year)][month - 1] + day;
+}
+
+/**
+ * ToISODayOfYear ( year, month, day )
+ */
+int32_t js::temporal::ToISODayOfYear(const PlainDate& date) {
+ MOZ_ASSERT(ISODateTimeWithinLimits(date));
+
+ // Steps 1-5.
+ auto& [year, month, day] = date;
+ return ::ToISODayOfYear(year, month, day);
+}
+
+static int32_t FloorDiv(int32_t dividend, int32_t divisor) {
+ MOZ_ASSERT(divisor > 0);
+
+ int32_t quotient = dividend / divisor;
+ int32_t remainder = dividend % divisor;
+ if (remainder < 0) {
+ quotient -= 1;
+ }
+ return quotient;
+}
+
+/**
+ * 21.4.1.3 Year Number, DayFromYear
+ */
+static int32_t DayFromYear(int32_t year) {
+ return 365 * (year - 1970) + FloorDiv(year - 1969, 4) -
+ FloorDiv(year - 1901, 100) + FloorDiv(year - 1601, 400);
+}
+
+/**
+ * 21.4.1.11 MakeTime ( hour, min, sec, ms )
+ */
+static int64_t MakeTime(const PlainTime& time) {
+ MOZ_ASSERT(IsValidTime(time));
+
+ // Step 1 (Not applicable).
+
+ // Step 2.
+ int64_t h = time.hour;
+
+ // Step 3.
+ int64_t m = time.minute;
+
+ // Step 4.
+ int64_t s = time.second;
+
+ // Step 5.
+ int64_t milli = time.millisecond;
+
+ // Steps 6-7.
+ return h * ToMilliseconds(TemporalUnit::Hour) +
+ m * ToMilliseconds(TemporalUnit::Minute) +
+ s * ToMilliseconds(TemporalUnit::Second) + milli;
+}
+
+/**
+ * 21.4.1.12 MakeDay ( year, month, date )
+ */
+int32_t js::temporal::MakeDay(const PlainDate& date) {
+ MOZ_ASSERT(ISODateTimeWithinLimits(date));
+
+ return DayFromYear(date.year) + ToISODayOfYear(date) - 1;
+}
+
+/**
+ * 21.4.1.13 MakeDate ( day, time )
+ */
+int64_t js::temporal::MakeDate(const PlainDateTime& dateTime) {
+ MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
+
+ // Step 1 (Not applicable).
+
+ // Steps 2-3.
+ int64_t tv = MakeDay(dateTime.date) * ToMilliseconds(TemporalUnit::Day) +
+ MakeTime(dateTime.time);
+
+ // Step 4.
+ return tv;
+}
+
+/**
+ * 21.4.1.12 MakeDay ( year, month, date )
+ */
+static int32_t MakeDay(int32_t year, int32_t month, int32_t day) {
+ MOZ_ASSERT(1 <= month && month <= 12);
+
+ // FIXME: spec issue - what should happen for invalid years/days?
+ return DayFromYear(year) + ::ToISODayOfYear(year, month, day) - 1;
+}
+
+/**
+ * 21.4.1.13 MakeDate ( day, time )
+ */
+int64_t js::temporal::MakeDate(int32_t year, int32_t month, int32_t day) {
+ // NOTE: This version accepts values outside the valid date-time limits.
+ MOZ_ASSERT(1 <= month && month <= 12);
+
+ // Step 1 (Not applicable).
+
+ // Steps 2-3.
+ int64_t tv = ::MakeDay(year, month, day) * ToMilliseconds(TemporalUnit::Day);
+
+ // Step 4.
+ return tv;
+}
+
+struct YearWeek final {
+ int32_t year = 0;
+ int32_t week = 0;
+};
+
+/**
+ * ToISOWeekOfYear ( year, month, day )
+ */
+static YearWeek ToISOWeekOfYear(const PlainDate& date) {
+ MOZ_ASSERT(ISODateTimeWithinLimits(date));
+
+ auto& [year, month, day] = date;
+
+ // TODO: https://en.wikipedia.org/wiki/Week#The_ISO_week_date_system
+ // TODO: https://en.wikipedia.org/wiki/ISO_week_date#Algorithms
+
+ // Steps 1-3. (Not applicable in our implementation.)
+
+ // Steps 4-5.
+ int32_t doy = ToISODayOfYear(date);
+ int32_t dow = ToISODayOfWeek(date);
+
+ int32_t woy = (10 + doy - dow) / 7;
+ MOZ_ASSERT(0 <= woy && woy <= 53);
+
+ // An ISO year has 53 weeks if the year starts on a Thursday or if it's a
+ // leap year which starts on a Wednesday.
+ auto isLongYear = [](int32_t year) {
+ int32_t startOfYear = ToISODayOfWeek({year, 1, 1});
+ return startOfYear == 4 || (startOfYear == 3 && IsISOLeapYear(year));
+ };
+
+ // Part of last year's last week, which is either week 52 or week 53.
+ if (woy == 0) {
+ return {year - 1, 52 + int32_t(isLongYear(year - 1))};
+ }
+
+ // Part of next year's first week if the current year isn't a long year.
+ if (woy == 53 && !isLongYear(year)) {
+ return {year + 1, 1};
+ }
+
+ return {year, woy};
+}
+
+/**
+ * ISOMonthCode ( month )
+ */
+static JSString* ISOMonthCode(JSContext* cx, int32_t month) {
+ MOZ_ASSERT(1 <= month && month <= 12);
+
+ // Steps 1-2.
+ char monthCode[3] = {'M', char('0' + (month / 10)), char('0' + (month % 10))};
+ return NewStringCopyN<CanGC>(cx, monthCode, std::size(monthCode));
+}
+
+template <typename T, typename... Ts>
+static bool ToPlainDate(JSObject* temporalDateLike, PlainDate* result) {
+ if (auto* obj = temporalDateLike->maybeUnwrapIf<T>()) {
+ *result = ToPlainDate(obj);
+ return true;
+ }
+ if constexpr (sizeof...(Ts) > 0) {
+ return ToPlainDate<Ts...>(temporalDateLike, result);
+ }
+ return false;
+}
+
+template <typename... Ts>
+static bool ToPlainDate(JSContext* cx, Handle<Value> temporalDateLike,
+ PlainDate* result) {
+ if (temporalDateLike.isObject()) {
+ if (ToPlainDate<Ts...>(&temporalDateLike.toObject(), result)) {
+ return true;
+ }
+ }
+
+ return ToTemporalDate(cx, temporalDateLike, result);
+}
+
+#ifdef DEBUG
+template <typename CharT>
+static bool StringIsAsciiLowerCase(mozilla::Range<CharT> str) {
+ return std::all_of(str.begin().get(), str.end().get(), [](CharT ch) {
+ return mozilla::IsAscii(ch) && !mozilla::IsAsciiUppercaseAlpha(ch);
+ });
+}
+
+static bool StringIsAsciiLowerCase(JSLinearString* str) {
+ JS::AutoCheckCannotGC nogc;
+ return str->hasLatin1Chars()
+ ? StringIsAsciiLowerCase(str->latin1Range(nogc))
+ : StringIsAsciiLowerCase(str->twoByteRange(nogc));
+}
+#endif
+
+static bool IsISO8601Calendar(JSLinearString* id) {
+ return StringEqualsLiteral(id, "iso8601");
+}
+
+#ifdef DEBUG
+static bool IsISO8601Calendar(CalendarObject* calendar) {
+ return IsISO8601Calendar(calendar->identifier());
+}
+#endif
+
+static bool IsISO8601Calendar(JSContext* cx, JSString* id, bool* result) {
+ JSLinearString* linear = id->ensureLinear(cx);
+ if (!linear) {
+ return false;
+ }
+ *result = IsISO8601Calendar(linear);
+ return true;
+}
+
+/**
+ * IsBuiltinCalendar ( id )
+ */
+static bool IsBuiltinCalendar(JSLinearString* id) {
+ // Callers must convert to lower case.
+ MOZ_ASSERT(StringIsAsciiLowerCase(id));
+
+ // Steps 1-3.
+ return StringEqualsLiteral(id, "iso8601");
+}
+
+static JSLinearString* ThrowIfNotBuiltinCalendar(JSContext* cx,
+ Handle<JSLinearString*> id) {
+ if (!StringIsAscii(id)) {
+ if (auto chars = QuoteString(cx, id)) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_CALENDAR_INVALID_ID, chars.get());
+ }
+ return nullptr;
+ }
+
+ JSString* lower = StringToLowerCase(cx, id);
+ if (!lower) {
+ return nullptr;
+ }
+
+ JSLinearString* linear = lower->ensureLinear(cx);
+ if (!linear) {
+ return nullptr;
+ }
+
+ if (!IsBuiltinCalendar(linear)) {
+ if (auto chars = QuoteString(cx, id)) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_CALENDAR_INVALID_ID, chars.get());
+ }
+ return nullptr;
+ }
+
+ return linear;
+}
+
+bool js::temporal::ToBuiltinCalendar(JSContext* cx, Handle<JSString*> id,
+ MutableHandle<CalendarValue> result) {
+ Rooted<JSLinearString*> linear(cx, id->ensureLinear(cx));
+ if (!linear) {
+ return false;
+ }
+
+ auto* identifier = ThrowIfNotBuiltinCalendar(cx, linear);
+ if (!identifier) {
+ return false;
+ }
+
+ result.set(CalendarValue(identifier));
+ return true;
+}
+
+/**
+ * CreateTemporalCalendar ( identifier [ , newTarget ] )
+ */
+static CalendarObject* CreateTemporalCalendar(
+ JSContext* cx, const CallArgs& args, Handle<JSLinearString*> identifier) {
+ // Step 1.
+ MOZ_ASSERT(IsBuiltinCalendar(identifier));
+
+ // Steps 2-3.
+ Rooted<JSObject*> proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Calendar, &proto)) {
+ return nullptr;
+ }
+
+ auto* obj = NewObjectWithClassProto<CalendarObject>(cx, proto);
+ if (!obj) {
+ return nullptr;
+ }
+
+ // Step 4.
+ obj->setFixedSlot(CalendarObject::IDENTIFIER_SLOT, StringValue(identifier));
+
+ // Step 5.
+ return obj;
+}
+
+/**
+ * CreateTemporalCalendar ( identifier [ , newTarget ] )
+ */
+static CalendarObject* CreateTemporalCalendar(
+ JSContext* cx, Handle<JSLinearString*> identifier) {
+ // Step 1.
+ MOZ_ASSERT(IsBuiltinCalendar(identifier));
+
+ // Steps 2-3.
+ auto* obj = NewBuiltinClassInstance<CalendarObject>(cx);
+ if (!obj) {
+ return nullptr;
+ }
+
+ // Step 4.
+ obj->setFixedSlot(CalendarObject::IDENTIFIER_SLOT, StringValue(identifier));
+
+ // Step 5.
+ return obj;
+}
+
+/**
+ * ObjectImplementsTemporalCalendarProtocol ( object )
+ */
+static bool ObjectImplementsTemporalCalendarProtocol(JSContext* cx,
+ Handle<JSObject*> object,
+ bool* result) {
+ // Step 1. (Not applicable in our implementation.)
+ MOZ_ASSERT(!object->canUnwrapAs<CalendarObject>(),
+ "Calendar objects handled in the caller");
+
+ // Step 2.
+ for (auto key : {
+ &JSAtomState::dateAdd, &JSAtomState::dateFromFields,
+ &JSAtomState::dateUntil, &JSAtomState::day,
+ &JSAtomState::dayOfWeek, &JSAtomState::dayOfYear,
+ &JSAtomState::daysInMonth, &JSAtomState::daysInWeek,
+ &JSAtomState::daysInYear, &JSAtomState::fields,
+ &JSAtomState::id, &JSAtomState::inLeapYear,
+ &JSAtomState::mergeFields, &JSAtomState::month,
+ &JSAtomState::monthCode, &JSAtomState::monthDayFromFields,
+ &JSAtomState::monthsInYear, &JSAtomState::weekOfYear,
+ &JSAtomState::year, &JSAtomState::yearMonthFromFields,
+ &JSAtomState::yearOfWeek,
+ }) {
+ // Step 2.a.
+ bool has;
+ if (!HasProperty(cx, object, cx->names().*key, &has)) {
+ return false;
+ }
+ if (!has) {
+ *result = false;
+ return true;
+ }
+ }
+
+ // Step 3.
+ *result = true;
+ return true;
+}
+
+template <typename T, typename... Ts>
+static bool ToTemporalCalendar(JSContext* cx, Handle<JSObject*> object,
+ MutableHandle<CalendarValue> result) {
+ if (auto* unwrapped = object->maybeUnwrapIf<T>()) {
+ result.set(unwrapped->calendar());
+ return result.wrap(cx);
+ }
+
+ if constexpr (sizeof...(Ts) > 0) {
+ return ToTemporalCalendar<Ts...>(cx, object, result);
+ }
+
+ result.set(CalendarValue());
+ return true;
+}
+
+/**
+ * ToTemporalCalendarSlotValue ( temporalCalendarLike [ , default ] )
+ */
+bool js::temporal::ToTemporalCalendar(JSContext* cx,
+ Handle<Value> temporalCalendarLike,
+ MutableHandle<CalendarValue> result) {
+ // Step 1. (Not applicable)
+
+ // Step 2.
+ Rooted<Value> calendarLike(cx, temporalCalendarLike);
+ if (calendarLike.isObject()) {
+ Rooted<JSObject*> obj(cx, &calendarLike.toObject());
+
+ // Step 2.b. (Partial)
+ if (obj->canUnwrapAs<CalendarObject>()) {
+ result.set(CalendarValue(obj));
+ return true;
+ }
+
+ // Step 2.a.
+ Rooted<CalendarValue> calendar(cx);
+ if (!::ToTemporalCalendar<PlainDateObject, PlainDateTimeObject,
+ PlainMonthDayObject, PlainYearMonthObject,
+ ZonedDateTimeObject>(cx, obj, &calendar)) {
+ return false;
+ }
+ if (calendar) {
+ result.set(calendar);
+ return true;
+ }
+
+ // Step 2.b.
+ bool implementsCalendarProtocol;
+ if (!ObjectImplementsTemporalCalendarProtocol(
+ cx, obj, &implementsCalendarProtocol)) {
+ return false;
+ }
+ if (!implementsCalendarProtocol) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_OBJECT,
+ "Temporal.Calendar", obj->getClass()->name);
+ return false;
+ }
+
+ // Step 2.c.
+ result.set(CalendarValue(obj));
+ return true;
+ }
+
+ // Step 3.
+ if (!calendarLike.isString()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
+ calendarLike, nullptr, "not a string");
+ return false;
+ }
+ Rooted<JSString*> str(cx, calendarLike.toString());
+
+ // Step 4.
+ Rooted<JSLinearString*> identifier(cx, ParseTemporalCalendarString(cx, str));
+ if (!identifier) {
+ return false;
+ }
+
+ // Step 5.
+ identifier = ThrowIfNotBuiltinCalendar(cx, identifier);
+ if (!identifier) {
+ return false;
+ }
+
+ // Step 6.
+ result.set(CalendarValue(identifier));
+ return true;
+}
+
+/**
+ * ToTemporalCalendarSlotValue ( temporalCalendarLike [ , default ] )
+ *
+ * When called with `default = "iso8601"`.
+ */
+bool js::temporal::ToTemporalCalendarWithISODefault(
+ JSContext* cx, Handle<Value> temporalCalendarLike,
+ MutableHandle<CalendarValue> result) {
+ // Step 1.
+ if (temporalCalendarLike.isUndefined()) {
+ result.set(CalendarValue(cx->names().iso8601));
+ return true;
+ }
+
+ // Steps 2-6.
+ return ToTemporalCalendar(cx, temporalCalendarLike, result);
+}
+
+/**
+ * GetTemporalCalendarSlotValueWithISODefault ( item )
+ */
+bool js::temporal::GetTemporalCalendarWithISODefault(
+ JSContext* cx, Handle<JSObject*> item,
+ MutableHandle<CalendarValue> result) {
+ // Step 1.
+ Rooted<CalendarValue> calendar(cx);
+ if (!::ToTemporalCalendar<PlainDateObject, PlainDateTimeObject,
+ PlainMonthDayObject, PlainYearMonthObject,
+ ZonedDateTimeObject>(cx, item, &calendar)) {
+ return false;
+ }
+ if (calendar) {
+ result.set(calendar);
+ return true;
+ }
+
+ // Step 2.
+ Rooted<Value> calendarValue(cx);
+ if (!GetProperty(cx, item, item, cx->names().calendar, &calendarValue)) {
+ return false;
+ }
+
+ // Step 3.
+ return ToTemporalCalendarWithISODefault(cx, calendarValue, result);
+}
+
+/**
+ * ToTemporalCalendarIdentifier ( calendarSlotValue )
+ */
+JSString* js::temporal::ToTemporalCalendarIdentifier(
+ JSContext* cx, Handle<CalendarValue> calendar) {
+ // Step 1.
+ if (calendar.isString()) {
+ // Step 1.a.
+ MOZ_ASSERT(IsBuiltinCalendar(calendar.toString()));
+
+ // Step 1.b.
+ return calendar.toString();
+ }
+
+ // Step 2.
+ Rooted<JSObject*> calendarObj(cx, calendar.toObject());
+ Rooted<Value> identifier(cx);
+ if (!GetProperty(cx, calendarObj, calendarObj, cx->names().id, &identifier)) {
+ return nullptr;
+ }
+
+ // Step 3.
+ if (!identifier.isString()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, identifier,
+ nullptr, "not a string");
+ return nullptr;
+ }
+
+ // Step 4.
+ return identifier.toString();
+}
+
+/**
+ * ToTemporalCalendarObject ( calendarSlotValue )
+ */
+JSObject* js::temporal::ToTemporalCalendarObject(
+ JSContext* cx, Handle<CalendarValue> calendar) {
+ // Step 1.
+ if (calendar.isObject()) {
+ return calendar.toObject();
+ }
+
+ // Step 2.
+ Rooted<JSLinearString*> calendarId(cx, calendar.toString());
+ return CreateTemporalCalendar(cx, calendarId);
+}
+
+static bool Calendar_dateAdd(JSContext* cx, unsigned argc, Value* vp);
+static bool Calendar_dateFromFields(JSContext* cx, unsigned argc, Value* vp);
+static bool Calendar_dateUntil(JSContext* cx, unsigned argc, Value* vp);
+static bool Calendar_day(JSContext* cx, unsigned argc, Value* vp);
+static bool Calendar_fields(JSContext* cx, unsigned argc, Value* vp);
+static bool Calendar_mergeFields(JSContext* cx, unsigned argc, Value* vp);
+static bool Calendar_monthDayFromFields(JSContext* cx, unsigned argc,
+ Value* vp);
+static bool Calendar_yearMonthFromFields(JSContext* cx, unsigned argc,
+ Value* vp);
+
+/**
+ * CalendarMethodsRecordLookup ( calendarRec, methodName )
+ */
+static bool CalendarMethodsRecordLookup(JSContext* cx,
+ MutableHandle<CalendarRecord> calendar,
+ CalendarMethod methodName) {
+ // Step 1. (Not applicable in our implementation.)
+
+ // Steps 2-10.
+ Rooted<JSObject*> object(cx, calendar.receiver().toObject());
+
+ auto lookup = [&](Handle<PropertyName*> name, JSNative native,
+ MutableHandle<JSObject*> result) {
+ auto* method = GetMethod(cx, object, name);
+ if (!method) {
+ return false;
+ }
+
+ // As an optimization we only store the method if the receiver is either
+ // a custom calendar object or if the method isn't the default, built-in
+ // calender method.
+ if (!object->is<CalendarObject>() || !IsNativeFunction(method, native)) {
+ result.set(method);
+ }
+ return true;
+ };
+
+ switch (methodName) {
+ // Steps 2 and 10.
+ case CalendarMethod::DateAdd:
+ return lookup(cx->names().dateAdd, Calendar_dateAdd, calendar.dateAdd());
+
+ // Steps 3 and 10.
+ case CalendarMethod::DateFromFields:
+ return lookup(cx->names().dateFromFields, Calendar_dateFromFields,
+ calendar.dateFromFields());
+
+ // Steps 4 and 10.
+ case CalendarMethod::DateUntil:
+ return lookup(cx->names().dateUntil, Calendar_dateUntil,
+ calendar.dateUntil());
+
+ // Steps 5 and 10.
+ case CalendarMethod::Day:
+ return lookup(cx->names().day, Calendar_day, calendar.day());
+
+ // Steps 6 and 10.
+ case CalendarMethod::Fields:
+ return lookup(cx->names().fields, Calendar_fields, calendar.fields());
+
+ // Steps 7 and 10.
+ case CalendarMethod::MergeFields:
+ return lookup(cx->names().mergeFields, Calendar_mergeFields,
+ calendar.mergeFields());
+
+ // Steps 8 and 10.
+ case CalendarMethod::MonthDayFromFields:
+ return lookup(cx->names().monthDayFromFields, Calendar_monthDayFromFields,
+ calendar.monthDayFromFields());
+
+ // Steps 9 and 10.
+ case CalendarMethod::YearMonthFromFields:
+ return lookup(cx->names().yearMonthFromFields,
+ Calendar_yearMonthFromFields,
+ calendar.yearMonthFromFields());
+ }
+
+ MOZ_CRASH("invalid calendar method");
+}
+
+/**
+ * CreateCalendarMethodsRecord ( calendar, methods )
+ */
+bool js::temporal::CreateCalendarMethodsRecord(
+ JSContext* cx, Handle<CalendarValue> calendar,
+ mozilla::EnumSet<CalendarMethod> methods,
+ MutableHandle<CalendarRecord> result) {
+ MOZ_ASSERT(!methods.isEmpty());
+
+ // Step 1.
+ result.set(CalendarRecord{calendar});
+
+#ifdef DEBUG
+ // Remember the set of looked-up methods for assertions.
+ result.get().lookedUp() += methods;
+#endif
+
+ // Built-in calendars don't perform observable lookups.
+ if (calendar.isString()) {
+ return true;
+ }
+
+ // Step 2.
+ for (auto method : methods) {
+ if (!CalendarMethodsRecordLookup(cx, result, method)) {
+ return false;
+ }
+ }
+
+ // Step 3.
+ return true;
+}
+
+static bool ToCalendarField(JSContext* cx, JSLinearString* linear,
+ CalendarField* result) {
+ if (StringEqualsLiteral(linear, "year")) {
+ *result = CalendarField::Year;
+ return true;
+ }
+ if (StringEqualsLiteral(linear, "month")) {
+ *result = CalendarField::Month;
+ return true;
+ }
+ if (StringEqualsLiteral(linear, "monthCode")) {
+ *result = CalendarField::MonthCode;
+ return true;
+ }
+ if (StringEqualsLiteral(linear, "day")) {
+ *result = CalendarField::Day;
+ return true;
+ }
+ if (auto chars = QuoteString(cx, linear, '"')) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_CALENDAR_INVALID_FIELD,
+ chars.get());
+ }
+ return false;
+}
+
+static PropertyName* ToPropertyName(JSContext* cx, CalendarField field) {
+ switch (field) {
+ case CalendarField::Year:
+ return cx->names().year;
+ case CalendarField::Month:
+ return cx->names().month;
+ case CalendarField::MonthCode:
+ return cx->names().monthCode;
+ case CalendarField::Day:
+ return cx->names().day;
+ }
+ MOZ_CRASH("invalid calendar field name");
+}
+
+#ifdef DEBUG
+static const char* ToCString(CalendarField field) {
+ switch (field) {
+ case CalendarField::Year:
+ return "year";
+ case CalendarField::Month:
+ return "month";
+ case CalendarField::MonthCode:
+ return "monthCode";
+ case CalendarField::Day:
+ return "day";
+ }
+ MOZ_CRASH("invalid calendar field name");
+}
+#endif
+
+/**
+ * Temporal.Calendar.prototype.fields ( fields )
+ */
+static bool BuiltinCalendarFields(
+ JSContext* cx, std::initializer_list<CalendarField> fieldNames,
+ CalendarFieldNames& result) {
+ MOZ_ASSERT(result.empty());
+
+ // Steps 1-5. (Not applicable.)
+
+ // Reserve space for the append operation.
+ if (!result.reserve(fieldNames.size())) {
+ return false;
+ }
+
+ // Steps 6-7.
+ for (auto fieldName : fieldNames) {
+ auto* name = ToPropertyName(cx, fieldName);
+
+ // Steps 7.a and 7.b.i-iv. (Not applicable)
+
+ // Step 7.b.v.
+ result.infallibleAppend(NameToId(name));
+ }
+
+ // Step 8. (Not applicable)
+ return true;
+}
+
+#ifdef DEBUG
+static bool IsSorted(std::initializer_list<CalendarField> fieldNames) {
+ return std::is_sorted(fieldNames.begin(), fieldNames.end(),
+ [](auto x, auto y) {
+ auto* a = ToCString(x);
+ auto* b = ToCString(y);
+ return std::strcmp(a, b) < 0;
+ });
+}
+#endif
+
+/**
+ * CalendarFields ( calendarRec, fieldNames )
+ */
+bool js::temporal::CalendarFields(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ std::initializer_list<CalendarField> fieldNames,
+ MutableHandle<CalendarFieldNames> result) {
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::Fields));
+
+ // FIXME: spec issue - the input is already sorted, let's assert this, too.
+ MOZ_ASSERT(IsSorted(fieldNames));
+
+ // FIXME: spec issue - the input shouldn't have duplicate elements. Let's
+ // assert this, too.
+ MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) ==
+ fieldNames.end());
+
+ // Step 1.
+ auto fields = calendar.fields();
+ if (!fields) {
+ bool arrayIterationSane;
+ if (calendar.receiver().isString()) {
+ // "String" calendars don't perform observable array iteration.
+ arrayIterationSane = true;
+ } else {
+ // "Object" calendars need to ensure array iteration is still sane.
+ if (!IsArrayIterationSane(cx, &arrayIterationSane)) {
+ return false;
+ }
+ }
+
+ if (arrayIterationSane) {
+ // Steps 1.a-b. (Not applicable in our implementation.)
+
+ // Step 1.c.
+ return BuiltinCalendarFields(cx, fieldNames, result.get());
+
+ // Steps 1.d-f. (Not applicable in our implementation.)
+ }
+ }
+
+ // Step 2. (Inlined call to CalendarMethodsRecordCall.)
+
+ auto* array = NewDenseFullyAllocatedArray(cx, fieldNames.size());
+ if (!array) {
+ return false;
+ }
+ array->setDenseInitializedLength(fieldNames.size());
+
+ for (size_t i = 0; i < fieldNames.size(); i++) {
+ auto* name = ToPropertyName(cx, fieldNames.begin()[i]);
+ array->initDenseElement(i, StringValue(name));
+ }
+
+ Rooted<Value> fieldsFn(cx, ObjectValue(*fields));
+ auto thisv = calendar.receiver().toValue();
+ Rooted<Value> fieldsArray(cx, ObjectValue(*array));
+ if (!Call(cx, fieldsFn, thisv, fieldsArray, &fieldsArray)) {
+ return false;
+ }
+
+ // Steps 3-4.
+ if (!IterableToListOfStrings(cx, fieldsArray, result)) {
+ return false;
+ }
+
+ // The spec sorts the field names in PrepareTemporalFields. Sorting is only
+ // needed for user-defined calendars, so our implementation performs this step
+ // here instead of in PrepareTemporalFields.
+ return SortTemporalFieldNames(cx, result.get());
+}
+
+static bool RequireIntegralNumber(JSContext* cx, Handle<Value> value,
+ Handle<PropertyName*> name,
+ MutableHandle<Value> result) {
+ if (MOZ_LIKELY(value.isInt32())) {
+ result.set(value);
+ return true;
+ }
+
+ if (value.isDouble()) {
+ double d = value.toDouble();
+ if (js::IsInteger(d)) {
+ result.setNumber(d);
+ return true;
+ }
+
+ if (auto str = QuoteString(cx, name)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_INTEGER, str.get());
+ }
+ return false;
+ }
+
+ if (auto str = QuoteString(cx, name)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_UNEXPECTED_TYPE, str.get(), "not a number");
+ }
+ return false;
+}
+
+static bool RequireIntegralPositiveNumber(JSContext* cx, Handle<Value> value,
+ Handle<PropertyName*> name,
+ MutableHandle<Value> result) {
+ if (!RequireIntegralNumber(cx, value, name, result)) {
+ return false;
+ }
+
+ if (result.toNumber() <= 0) {
+ if (auto str = QuoteString(cx, name)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_NUMBER, str.get());
+ }
+ return false;
+ }
+ return true;
+}
+
+static bool RequireString(JSContext* cx, Handle<Value> value,
+ Handle<PropertyName*> name,
+ MutableHandle<Value> result) {
+ if (MOZ_LIKELY(value.isString())) {
+ result.set(value);
+ return true;
+ }
+
+ if (auto str = QuoteString(cx, name)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_UNEXPECTED_TYPE, str.get(), "not a string");
+ }
+ return false;
+}
+
+static bool RequireBoolean(JSContext* cx, Handle<Value> value,
+ Handle<PropertyName*> name,
+ MutableHandle<Value> result) {
+ if (MOZ_LIKELY(value.isBoolean())) {
+ result.set(value);
+ return true;
+ }
+
+ if (auto str = QuoteString(cx, name)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_UNEXPECTED_TYPE, str.get(),
+ "not a boolean");
+ }
+ return false;
+}
+
+using BuiltinCalendarMethod = bool (*)(JSContext* cx, const PlainDate&,
+ MutableHandle<Value>);
+
+using CalendarConversion = bool (*)(JSContext*, Handle<Value>,
+ Handle<PropertyName*>,
+ MutableHandle<Value>);
+
+template <BuiltinCalendarMethod builtin, CalendarConversion conversion>
+static bool CallCalendarMethod(JSContext* cx, Handle<PropertyName*> name,
+ JSNative native, Handle<CalendarValue> calendar,
+ Handle<JSObject*> dateLike,
+ const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Step 1.
+ if (calendar.isString()) {
+ return builtin(cx, date, result);
+ }
+
+ // Step 2.
+ Rooted<JSObject*> calendarObj(cx, calendar.toObject());
+ JSObject* fn = GetMethod(cx, calendarObj, name);
+ if (!fn) {
+ return false;
+ }
+
+ // Fast-path for the default implementation.
+ if (calendarObj->is<CalendarObject>() && IsNativeFunction(fn, native)) {
+ return builtin(cx, date, result);
+ }
+
+ Rooted<JS::Value> fnVal(cx, ObjectValue(*fn));
+ Rooted<JS::Value> dateLikeValue(cx, ObjectValue(*dateLike));
+ if (!Call(cx, fnVal, calendarObj, dateLikeValue, result)) {
+ return false;
+ }
+
+ // Steps 3-5.
+ return conversion(cx, result, name, result);
+}
+
+/**
+ * Temporal.Calendar.prototype.year ( temporalDateLike )
+ */
+static bool BuiltinCalendarYear(JSContext* cx, const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-4. (Not applicable.)
+
+ // Steps 5-6.
+ result.setInt32(date.year);
+ return true;
+}
+
+static bool Calendar_year(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * CalendarYear ( calendar, dateLike )
+ */
+static bool CalendarYear(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<JSObject*> dateLike, const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-5.
+ return CallCalendarMethod<BuiltinCalendarYear, RequireIntegralNumber>(
+ cx, cx->names().year, Calendar_year, calendar, dateLike, date, result);
+}
+
+/**
+ * CalendarYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarYear(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<PlainDateObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarYear(cx, calendar, dateLike, ToPlainDate(dateLike), result);
+}
+
+/**
+ * CalendarYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarYear(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<PlainDateTimeObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarYear(cx, calendar, dateLike, ToPlainDate(dateLike), result);
+}
+
+/**
+ * CalendarYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarYear(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<PlainYearMonthObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarYear(cx, calendar, dateLike, ToPlainDate(dateLike), result);
+}
+
+/**
+ * CalendarYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarYear(JSContext* cx, Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ MutableHandle<Value> result) {
+ Rooted<PlainDateTimeObject*> dateLike(
+ cx, CreateTemporalDateTime(cx, dateTime, calendar));
+ if (!dateLike) {
+ return false;
+ }
+
+ return ::CalendarYear(cx, calendar, dateLike, dateTime.date, result);
+}
+
+/**
+ * Temporal.Calendar.prototype.month ( temporalDateLike )
+ */
+static bool BuiltinCalendarMonth(JSContext* cx, const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-5. (Not applicable.)
+
+ // Steps 6-7.
+ result.setInt32(date.month);
+ return true;
+}
+
+static bool Calendar_month(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * CalendarMonth ( calendar, dateLike )
+ */
+static bool CalendarMonth(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<JSObject*> dateLike, const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-6.
+ return CallCalendarMethod<BuiltinCalendarMonth,
+ RequireIntegralPositiveNumber>(
+ cx, cx->names().month, Calendar_month, calendar, dateLike, date, result);
+}
+
+/**
+ * CalendarMonth ( calendar, dateLike )
+ */
+bool js::temporal::CalendarMonth(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<PlainDateObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarMonth(cx, calendar, dateLike, ToPlainDate(dateLike), result);
+}
+
+/**
+ * CalendarMonth ( calendar, dateLike )
+ */
+bool js::temporal::CalendarMonth(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<PlainDateTimeObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarMonth(cx, calendar, dateLike, ToPlainDate(dateLike), result);
+}
+
+/**
+ * CalendarMonth ( calendar, dateLike )
+ */
+bool js::temporal::CalendarMonth(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<PlainYearMonthObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarMonth(cx, calendar, dateLike, ToPlainDate(dateLike), result);
+}
+
+/**
+ * CalendarMonth ( calendar, dateLike )
+ */
+bool js::temporal::CalendarMonth(JSContext* cx, Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ MutableHandle<Value> result) {
+ Rooted<PlainDateTimeObject*> dateLike(
+ cx, CreateTemporalDateTime(cx, dateTime, calendar));
+ if (!dateLike) {
+ return false;
+ }
+
+ return ::CalendarMonth(cx, calendar, dateLike, dateTime.date, result);
+}
+
+/**
+ * Temporal.Calendar.prototype.monthCode ( temporalDateLike )
+ */
+static bool BuiltinCalendarMonthCode(JSContext* cx, const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-4. (Not applicable.)
+
+ // Steps 5-6.
+ JSString* str = ISOMonthCode(cx, date.month);
+ if (!str) {
+ return false;
+ }
+
+ result.setString(str);
+ return true;
+}
+
+static bool Calendar_monthCode(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * CalendarMonthCode ( calendar, dateLike )
+ */
+static bool CalendarMonthCode(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<JSObject*> dateLike, const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-4.
+ return CallCalendarMethod<BuiltinCalendarMonthCode, RequireString>(
+ cx, cx->names().monthCode, Calendar_monthCode, calendar, dateLike, date,
+ result);
+}
+
+/**
+ * CalendarMonthCode ( calendar, dateLike )
+ */
+bool js::temporal::CalendarMonthCode(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarMonthCode(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarMonthCode ( calendar, dateLike )
+ */
+bool js::temporal::CalendarMonthCode(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateTimeObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarMonthCode(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarMonthCode ( calendar, dateLike )
+ */
+bool js::temporal::CalendarMonthCode(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainMonthDayObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarMonthCode(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarMonthCode ( calendar, dateLike )
+ */
+bool js::temporal::CalendarMonthCode(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainYearMonthObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarMonthCode(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarMonthCode ( calendar, dateLike )
+ */
+bool js::temporal::CalendarMonthCode(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ MutableHandle<Value> result) {
+ Rooted<PlainDateTimeObject*> dateLike(
+ cx, CreateTemporalDateTime(cx, dateTime, calendar));
+ if (!dateLike) {
+ return false;
+ }
+
+ return ::CalendarMonthCode(cx, calendar, dateLike, dateTime.date, result);
+}
+
+/**
+ * Temporal.Calendar.prototype.day ( temporalDateLike )
+ */
+static bool BuiltinCalendarDay(const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-4. (Not applicable.)
+
+ // Steps 5-6.
+ result.setInt32(date.day);
+ return true;
+}
+
+/**
+ * CalendarDay ( calendarRec, dateLike )
+ */
+static bool CalendarDay(JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<JSObject*> dateLike, const PlainDate& date,
+ MutableHandle<Value> result) {
+ MOZ_ASSERT(CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::Day));
+
+ // Step 2. (Reordered)
+ auto day = calendar.day();
+ if (!day) {
+ return BuiltinCalendarDay(date, result);
+ }
+
+ // Step 1. (Inlined call to CalendarMethodsRecordCall.)
+ Rooted<Value> fn(cx, ObjectValue(*day));
+ auto thisv = calendar.receiver().toValue();
+ Rooted<JS::Value> dateLikeValue(cx, ObjectValue(*dateLike));
+ if (!Call(cx, fn, thisv, dateLikeValue, result)) {
+ return false;
+ }
+
+ // Steps 3-6.
+ return RequireIntegralPositiveNumber(cx, result, cx->names().day, result);
+}
+
+/**
+ * CalendarDay ( calendarRec, dateLike )
+ */
+bool js::temporal::CalendarDay(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<PlainDateObject*> dateLike,
+ MutableHandle<Value> result) {
+ Rooted<CalendarRecord> calendarRec(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendar,
+ {
+ CalendarMethod::Day,
+ },
+ &calendarRec)) {
+ return false;
+ }
+
+ return CalendarDay(cx, calendarRec, dateLike, ToPlainDate(dateLike), result);
+}
+
+/**
+ * CalendarDay ( calendarRec, dateLike )
+ */
+bool js::temporal::CalendarDay(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<PlainDateTimeObject*> dateLike,
+ MutableHandle<Value> result) {
+ Rooted<CalendarRecord> calendarRec(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendar,
+ {
+ CalendarMethod::Day,
+ },
+ &calendarRec)) {
+ return false;
+ }
+
+ return CalendarDay(cx, calendarRec, dateLike, ToPlainDate(dateLike), result);
+}
+
+/**
+ * CalendarDay ( calendarRec, dateLike )
+ */
+bool js::temporal::CalendarDay(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<PlainMonthDayObject*> dateLike,
+ MutableHandle<Value> result) {
+ Rooted<CalendarRecord> calendarRec(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendar,
+ {
+ CalendarMethod::Day,
+ },
+ &calendarRec)) {
+ return false;
+ }
+
+ return CalendarDay(cx, calendarRec, dateLike, ToPlainDate(dateLike), result);
+}
+
+/**
+ * CalendarDay ( calendarRec, dateLike )
+ */
+bool js::temporal::CalendarDay(JSContext* cx, Handle<CalendarRecord> calendar,
+ const PlainDate& date,
+ MutableHandle<Value> result) {
+ Rooted<PlainDateObject*> dateLike(
+ cx, CreateTemporalDate(cx, date, calendar.receiver()));
+ if (!dateLike) {
+ return false;
+ }
+
+ return ::CalendarDay(cx, calendar, dateLike, date, result);
+}
+
+/**
+ * CalendarDay ( calendarRec, dateLike )
+ */
+bool js::temporal::CalendarDay(JSContext* cx, Handle<CalendarRecord> calendar,
+ const PlainDateTime& dateTime,
+ MutableHandle<Value> result) {
+ Rooted<PlainDateTimeObject*> dateLike(
+ cx, CreateTemporalDateTime(cx, dateTime, calendar.receiver()));
+ if (!dateLike) {
+ return false;
+ }
+
+ return ::CalendarDay(cx, calendar, dateLike, dateTime.date, result);
+}
+
+/**
+ * Temporal.Calendar.prototype.dayOfWeek ( temporalDateLike )
+ */
+static bool BuiltinCalendarDayOfWeek(JSContext* cx, const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-4. (Not applicable.)
+
+ // Steps 5-9.
+ result.setInt32(ToISODayOfWeek(date));
+ return true;
+}
+
+static bool Calendar_dayOfWeek(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * CalendarDayOfWeek ( calendar, dateLike )
+ */
+static bool CalendarDayOfWeek(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<JSObject*> dateLike, const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-6.
+ return CallCalendarMethod<BuiltinCalendarDayOfWeek,
+ RequireIntegralPositiveNumber>(
+ cx, cx->names().dayOfWeek, Calendar_dayOfWeek, calendar, dateLike, date,
+ result);
+}
+
+/**
+ * CalendarDayOfWeek ( calendar, dateLike )
+ */
+bool js::temporal::CalendarDayOfWeek(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarDayOfWeek(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarDayOfWeek ( calendar, dateLike )
+ */
+bool js::temporal::CalendarDayOfWeek(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateTimeObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarDayOfWeek(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarDayOfWeek ( calendar, dateLike )
+ */
+bool js::temporal::CalendarDayOfWeek(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ MutableHandle<Value> result) {
+ Rooted<PlainDateTimeObject*> dateLike(
+ cx, CreateTemporalDateTime(cx, dateTime, calendar));
+ if (!dateLike) {
+ return false;
+ }
+
+ return ::CalendarDayOfWeek(cx, calendar, dateLike, dateTime.date, result);
+}
+
+/**
+ * Temporal.Calendar.prototype.dayOfYear ( temporalDateLike )
+ */
+static bool BuiltinCalendarDayOfYear(JSContext* cx, const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-4. (Not applicable.)
+
+ // Steps 5-7.
+ result.setInt32(ToISODayOfYear(date));
+ return true;
+}
+
+static bool Calendar_dayOfYear(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * CalendarDayOfYear ( calendar, dateLike )
+ */
+static bool CalendarDayOfYear(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<JSObject*> dateLike, const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-6.
+ return CallCalendarMethod<BuiltinCalendarDayOfYear,
+ RequireIntegralPositiveNumber>(
+ cx, cx->names().dayOfYear, Calendar_dayOfYear, calendar, dateLike, date,
+ result);
+}
+
+/**
+ * CalendarDayOfYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarDayOfYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarDayOfYear(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarDayOfYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarDayOfYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateTimeObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarDayOfYear(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarDayOfYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarDayOfYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ MutableHandle<Value> result) {
+ Rooted<PlainDateTimeObject*> dateLike(
+ cx, CreateTemporalDateTime(cx, dateTime, calendar));
+ if (!dateLike) {
+ return false;
+ }
+
+ return ::CalendarDayOfYear(cx, calendar, dateLike, dateTime.date, result);
+}
+
+/**
+ * Temporal.Calendar.prototype.weekOfYear ( temporalDateLike )
+ */
+static bool BuiltinCalendarWeekOfYear(JSContext* cx, const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-4. (Not applicable.)
+
+ // Steps 5-6.
+ result.setInt32(ToISOWeekOfYear(date).week);
+ return true;
+}
+
+static bool Calendar_weekOfYear(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * CalendarWeekOfYear ( calendar, dateLike )
+ */
+static bool CalendarWeekOfYear(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<JSObject*> dateLike,
+ const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-6.
+ return CallCalendarMethod<BuiltinCalendarWeekOfYear,
+ RequireIntegralPositiveNumber>(
+ cx, cx->names().weekOfYear, Calendar_weekOfYear, calendar, dateLike, date,
+ result);
+}
+
+/**
+ * CalendarWeekOfYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarWeekOfYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarWeekOfYear(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarWeekOfYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarWeekOfYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateTimeObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarWeekOfYear(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarWeekOfYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarWeekOfYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ MutableHandle<Value> result) {
+ Rooted<PlainDateTimeObject*> dateLike(
+ cx, CreateTemporalDateTime(cx, dateTime, calendar));
+ if (!dateLike) {
+ return false;
+ }
+
+ return ::CalendarWeekOfYear(cx, calendar, dateLike, dateTime.date, result);
+}
+
+/**
+ * Temporal.Calendar.prototype.yearOfWeek ( temporalDateLike )
+ */
+static bool BuiltinCalendarYearOfWeek(JSContext* cx, const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-4. (Not applicable.)
+
+ // Steps 5-6.
+ result.setInt32(ToISOWeekOfYear(date).year);
+ return true;
+}
+
+static bool Calendar_yearOfWeek(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * CalendarYearOfWeek ( calendar, dateLike )
+ */
+static bool CalendarYearOfWeek(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<JSObject*> dateLike,
+ const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-5.
+ return CallCalendarMethod<BuiltinCalendarYearOfWeek, RequireIntegralNumber>(
+ cx, cx->names().yearOfWeek, Calendar_yearOfWeek, calendar, dateLike, date,
+ result);
+}
+
+/**
+ * CalendarYearOfWeek ( calendar, dateLike )
+ */
+bool js::temporal::CalendarYearOfWeek(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarYearOfWeek(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarYearOfWeek ( calendar, dateLike )
+ */
+bool js::temporal::CalendarYearOfWeek(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateTimeObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarYearOfWeek(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarYearOfWeek ( calendar, dateLike )
+ */
+bool js::temporal::CalendarYearOfWeek(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ MutableHandle<Value> result) {
+ Rooted<PlainDateTimeObject*> dateLike(
+ cx, CreateTemporalDateTime(cx, dateTime, calendar));
+ if (!dateLike) {
+ return false;
+ }
+
+ return ::CalendarYearOfWeek(cx, calendar, dateLike, dateTime.date, result);
+}
+
+/**
+ * Temporal.Calendar.prototype.daysInWeek ( temporalDateLike )
+ */
+static bool BuiltinCalendarDaysInWeek(JSContext* cx, const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-4. (Not applicable.)
+
+ // Step 5.
+ result.setInt32(7);
+ return true;
+}
+
+static bool Calendar_daysInWeek(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * CalendarDaysInWeek ( calendar, dateLike )
+ */
+static bool CalendarDaysInWeek(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<JSObject*> dateLike,
+ const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-6.
+ return CallCalendarMethod<BuiltinCalendarDaysInWeek,
+ RequireIntegralPositiveNumber>(
+ cx, cx->names().daysInWeek, Calendar_daysInWeek, calendar, dateLike, date,
+ result);
+}
+
+/**
+ * CalendarDaysInWeek ( calendar, dateLike )
+ */
+bool js::temporal::CalendarDaysInWeek(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarDaysInWeek(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarDaysInWeek ( calendar, dateLike )
+ */
+bool js::temporal::CalendarDaysInWeek(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateTimeObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarDaysInWeek(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarDaysInWeek ( calendar, dateLike )
+ */
+bool js::temporal::CalendarDaysInWeek(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ MutableHandle<Value> result) {
+ Rooted<PlainDateTimeObject*> dateLike(
+ cx, CreateTemporalDateTime(cx, dateTime, calendar));
+ if (!dateLike) {
+ return false;
+ }
+
+ return ::CalendarDaysInWeek(cx, calendar, dateLike, dateTime.date, result);
+}
+
+/**
+ * Temporal.Calendar.prototype.daysInMonth ( temporalDateLike )
+ */
+static bool BuiltinCalendarDaysInMonth(JSContext* cx, const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-4. (Not applicable.)
+
+ // Step 5.
+ result.setInt32(::ISODaysInMonth(date.year, date.month));
+ return true;
+}
+
+static bool Calendar_daysInMonth(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * CalendarDaysInMonth ( calendar, dateLike )
+ */
+static bool CalendarDaysInMonth(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<JSObject*> dateLike,
+ const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Step 1-6.
+ return CallCalendarMethod<BuiltinCalendarDaysInMonth,
+ RequireIntegralPositiveNumber>(
+ cx, cx->names().daysInMonth, Calendar_daysInMonth, calendar, dateLike,
+ date, result);
+}
+
+/**
+ * CalendarDaysInMonth ( calendar, dateLike )
+ */
+bool js::temporal::CalendarDaysInMonth(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarDaysInMonth(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarDaysInMonth ( calendar, dateLike )
+ */
+bool js::temporal::CalendarDaysInMonth(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateTimeObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarDaysInMonth(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarDaysInMonth ( calendar, dateLike )
+ */
+bool js::temporal::CalendarDaysInMonth(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainYearMonthObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarDaysInMonth(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarDaysInMonth ( calendar, dateLike )
+ */
+bool js::temporal::CalendarDaysInMonth(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ MutableHandle<Value> result) {
+ Rooted<PlainDateTimeObject*> dateLike(
+ cx, CreateTemporalDateTime(cx, dateTime, calendar));
+ if (!dateLike) {
+ return false;
+ }
+
+ return ::CalendarDaysInMonth(cx, calendar, dateLike, dateTime.date, result);
+}
+
+/**
+ * Temporal.Calendar.prototype.daysInYear ( temporalDateLike )
+ */
+static bool BuiltinCalendarDaysInYear(JSContext* cx, const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-4. (Not applicable.)
+
+ // Step 5.
+ result.setInt32(ISODaysInYear(date.year));
+ return true;
+}
+
+static bool Calendar_daysInYear(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * CalendarDaysInYear ( calendar, dateLike )
+ */
+static bool CalendarDaysInYear(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<JSObject*> dateLike,
+ const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Step 1-6.
+ return CallCalendarMethod<BuiltinCalendarDaysInYear,
+ RequireIntegralPositiveNumber>(
+ cx, cx->names().daysInYear, Calendar_daysInYear, calendar, dateLike, date,
+ result);
+}
+
+/**
+ * CalendarDaysInYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarDaysInYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarDaysInYear(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarDaysInYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarDaysInYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateTimeObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarDaysInYear(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarDaysInYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarDaysInYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainYearMonthObject*> dateLike,
+ MutableHandle<Value> result) {
+ return CalendarDaysInYear(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarDaysInYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarDaysInYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ MutableHandle<Value> result) {
+ Rooted<PlainDateTimeObject*> dateLike(
+ cx, CreateTemporalDateTime(cx, dateTime, calendar));
+ if (!dateLike) {
+ return false;
+ }
+
+ return ::CalendarDaysInYear(cx, calendar, dateLike, dateTime.date, result);
+}
+
+/**
+ * Temporal.Calendar.prototype.monthsInYear ( temporalDateLike )
+ */
+static bool BuiltinCalendarMonthsInYear(JSContext* cx, const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-4. (Not applicable.)
+
+ // Step 5.
+ result.setInt32(12);
+ return true;
+}
+
+static bool Calendar_monthsInYear(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * CalendarMonthsInYear ( calendar, dateLike )
+ */
+static bool CalendarMonthsInYear(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<JSObject*> dateLike,
+ const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Step 1-6.
+ return CallCalendarMethod<BuiltinCalendarMonthsInYear,
+ RequireIntegralPositiveNumber>(
+ cx, cx->names().monthsInYear, Calendar_monthsInYear, calendar, dateLike,
+ date, result);
+}
+
+/**
+ * CalendarMonthsInYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarMonthsInYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateObject*> dateLike,
+ MutableHandle<Value> result) {
+ return ::CalendarMonthsInYear(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarMonthsInYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarMonthsInYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateTimeObject*> dateLike,
+ MutableHandle<Value> result) {
+ return ::CalendarMonthsInYear(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarMonthsInYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarMonthsInYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainYearMonthObject*> dateLike,
+ MutableHandle<Value> result) {
+ return ::CalendarMonthsInYear(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarMonthsInYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarMonthsInYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ MutableHandle<Value> result) {
+ Rooted<PlainDateTimeObject*> dateLike(
+ cx, CreateTemporalDateTime(cx, dateTime, calendar));
+ if (!dateLike) {
+ return false;
+ }
+
+ return ::CalendarMonthsInYear(cx, calendar, dateLike, dateTime.date, result);
+}
+
+/**
+ * Temporal.Calendar.prototype.inLeapYear ( temporalDateLike )
+ */
+static bool BuiltinCalendarInLeapYear(JSContext* cx, const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Steps 1-4. (Not applicable.)
+
+ // Steps 5-6.
+ result.setBoolean(IsISOLeapYear(date.year));
+ return true;
+}
+
+static bool Calendar_inLeapYear(JSContext* cx, unsigned argc, Value* vp);
+
+/**
+ * CalendarInLeapYear ( calendar, dateLike )
+ */
+static bool CalendarInLeapYear(JSContext* cx, Handle<CalendarValue> calendar,
+ Handle<JSObject*> dateLike,
+ const PlainDate& date,
+ MutableHandle<Value> result) {
+ // Step 1-4.
+ return CallCalendarMethod<BuiltinCalendarInLeapYear, RequireBoolean>(
+ cx, cx->names().inLeapYear, Calendar_inLeapYear, calendar, dateLike, date,
+ result);
+}
+
+/**
+ * CalendarInLeapYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarInLeapYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateObject*> dateLike,
+ MutableHandle<Value> result) {
+ return ::CalendarInLeapYear(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarInLeapYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarInLeapYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainDateTimeObject*> dateLike,
+ MutableHandle<Value> result) {
+ return ::CalendarInLeapYear(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarInLeapYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarInLeapYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ Handle<PlainYearMonthObject*> dateLike,
+ MutableHandle<Value> result) {
+ return ::CalendarInLeapYear(cx, calendar, dateLike, ToPlainDate(dateLike),
+ result);
+}
+
+/**
+ * CalendarInLeapYear ( calendar, dateLike )
+ */
+bool js::temporal::CalendarInLeapYear(JSContext* cx,
+ Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ MutableHandle<Value> result) {
+ Rooted<PlainDateTimeObject*> dateLike(
+ cx, CreateTemporalDateTime(cx, dateTime, calendar));
+ if (!dateLike) {
+ return false;
+ }
+
+ return ::CalendarInLeapYear(cx, calendar, dateLike, dateTime.date, result);
+}
+
+/**
+ * ISOResolveMonth ( fields )
+ */
+static bool ISOResolveMonth(JSContext* cx,
+ MutableHandle<TemporalFields> fields) {
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ double month = fields.month();
+
+ // Step 3.
+ MOZ_ASSERT((IsInteger(month) && month > 0) || std::isnan(month));
+
+ // Step 4.
+ Handle<JSString*> monthCode = fields.monthCode();
+
+ // Step 5.
+ if (!monthCode) {
+ // Step 5.a.
+ if (std::isnan(month)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_CALENDAR_MISSING_FIELD,
+ "monthCode");
+ return false;
+ }
+
+ // Step 5.b.
+ return true;
+ }
+
+ // Steps 6-7. (Not applicable in our implementation.)
+
+ // Step 8.
+ if (monthCode->length() != 3) {
+ if (auto code = QuoteString(cx, monthCode)) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_CALENDAR_INVALID_MONTHCODE,
+ code.get());
+ }
+ return false;
+ }
+
+ JSLinearString* linear = monthCode->ensureLinear(cx);
+ if (!linear) {
+ return false;
+ }
+
+ char16_t chars[3] = {
+ linear->latin1OrTwoByteChar(0),
+ linear->latin1OrTwoByteChar(1),
+ linear->latin1OrTwoByteChar(2),
+ };
+
+ // Steps 9-11. (Partial)
+ if (chars[0] != 'M' || !mozilla::IsAsciiDigit(chars[1]) ||
+ !mozilla::IsAsciiDigit(chars[2])) {
+ if (auto code = QuoteString(cx, linear)) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_CALENDAR_INVALID_MONTHCODE,
+ code.get());
+ }
+ return false;
+ }
+
+ // Step 12.
+ int32_t monthCodeInteger =
+ AsciiDigitToNumber(chars[1]) * 10 + AsciiDigitToNumber(chars[2]);
+
+ // Step 11. (Partial)
+ if (monthCodeInteger < 1 || monthCodeInteger > 12) {
+ if (auto code = QuoteString(cx, linear)) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_CALENDAR_INVALID_MONTHCODE,
+ code.get());
+ }
+ return false;
+ }
+
+ // Step 13. (Not applicable in our implementation.)
+
+ // Step 14.
+ if (!std::isnan(month) && month != monthCodeInteger) {
+ if (auto code = QuoteString(cx, linear)) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_CALENDAR_INVALID_MONTHCODE,
+ code.get());
+ }
+ return false;
+ }
+
+ // Step 15.
+ fields.month() = monthCodeInteger;
+
+ // Step 16.
+ return true;
+}
+
+/**
+ * ISODateFromFields ( fields, overflow )
+ */
+static bool ISODateFromFields(JSContext* cx, Handle<TemporalFields> fields,
+ TemporalOverflow overflow, PlainDate* result) {
+ // Steps 1-2. (Not applicable in our implementation.)
+
+ // Step 3.
+ double year = fields.year();
+
+ // Step 4.
+ double month = fields.month();
+
+ // Step 5.
+ double day = fields.day();
+
+ // Step 6.
+ MOZ_ASSERT(!std::isnan(year) && !std::isnan(month) && !std::isnan(day));
+
+ // Step 7.
+ RegulatedISODate regulated;
+ if (!RegulateISODate(cx, year, month, day, overflow, &regulated)) {
+ return false;
+ }
+
+ // The result is used to create a new PlainDateObject, so it's okay to
+ // directly throw an error for invalid years. That way we don't have to worry
+ // about representing doubles in PlainDate structs.
+ int32_t intYear;
+ if (!mozilla::NumberEqualsInt32(regulated.year, &intYear)) {
+ // CreateTemporalDate, steps 1-2.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PLAIN_DATE_INVALID);
+ return false;
+ }
+
+ *result = {intYear, regulated.month, regulated.day};
+ return true;
+}
+
+/**
+ * Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] )
+ */
+static PlainDateObject* BuiltinCalendarDateFromFields(
+ JSContext* cx, Handle<JSObject*> fields, Handle<JSObject*> maybeOptions) {
+ // Steps 1-5. (Not applicable)
+
+ // Step 6.
+ Rooted<TemporalFields> dateFields(cx);
+ if (!PrepareTemporalFields(cx, fields,
+ {TemporalField::Day, TemporalField::Month,
+ TemporalField::MonthCode, TemporalField::Year},
+ {TemporalField::Day, TemporalField::Year},
+ &dateFields)) {
+ return nullptr;
+ }
+
+ // Step 7.
+ auto overflow = TemporalOverflow::Constrain;
+ if (maybeOptions) {
+ if (!ToTemporalOverflow(cx, maybeOptions, &overflow)) {
+ return nullptr;
+ }
+ }
+
+ // Step 8.
+ if (!ISOResolveMonth(cx, &dateFields)) {
+ return nullptr;
+ }
+
+ // Step 9.
+ PlainDate result;
+ if (!ISODateFromFields(cx, dateFields, overflow, &result)) {
+ return nullptr;
+ }
+
+ // Step 10.
+ Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
+ return CreateTemporalDate(cx, result, calendar);
+}
+
+/**
+ * CalendarDateFromFields ( calendarRec, fields [ , options ] )
+ */
+static Wrapped<PlainDateObject*> CalendarDateFromFields(
+ JSContext* cx, Handle<CalendarRecord> calendar, Handle<JSObject*> fields,
+ Handle<PlainObject*> maybeOptions) {
+ MOZ_ASSERT(CalendarMethodsRecordHasLookedUp(calendar,
+ CalendarMethod::DateFromFields));
+
+ // Step 1. (Not applicable in our implemetation.)
+
+ // Step 3. (Reordered)
+ auto dateFromFields = calendar.dateFromFields();
+ if (!dateFromFields) {
+ return BuiltinCalendarDateFromFields(cx, fields, maybeOptions);
+ }
+
+ // Step 2. (Inlined call to CalendarMethodsRecordCall.)
+
+ Rooted<Value> dateFromFieldsFn(cx, ObjectValue(*dateFromFields));
+ auto thisv = calendar.receiver().toValue();
+ Rooted<Value> rval(cx);
+
+ FixedInvokeArgs<2> args(cx);
+ args[0].setObject(*fields);
+ if (maybeOptions) {
+ args[1].setObject(*maybeOptions);
+ } else {
+ args[1].setUndefined();
+ }
+
+ if (!Call(cx, dateFromFieldsFn, thisv, args, &rval)) {
+ return nullptr;
+ }
+
+ // Step 4.
+ if (!rval.isObject() || !rval.toObject().canUnwrapAs<PlainDateObject>()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, rval,
+ nullptr, "not a PlainDate object");
+ return nullptr;
+ }
+
+ // Step 5.
+ return &rval.toObject();
+}
+
+/**
+ * CalendarDateFromFields ( calendarRec, fields [ , options ] )
+ */
+Wrapped<PlainDateObject*> js::temporal::CalendarDateFromFields(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<PlainObject*> fields) {
+ // Steps 1-6.
+ return ::CalendarDateFromFields(cx, calendar, fields, nullptr);
+}
+
+/**
+ * CalendarDateFromFields ( calendarRec, fields [ , options ] )
+ */
+Wrapped<PlainDateObject*> js::temporal::CalendarDateFromFields(
+ JSContext* cx, Handle<CalendarRecord> calendar, Handle<PlainObject*> fields,
+ Handle<PlainObject*> options) {
+ // Steps 1-6.
+ return ::CalendarDateFromFields(cx, calendar, fields, options);
+}
+
+struct RegulatedISOYearMonth final {
+ double year;
+ int32_t month;
+};
+
+/**
+ * RegulateISOYearMonth ( year, month, overflow )
+ */
+static bool RegulateISOYearMonth(JSContext* cx, double year, double month,
+ TemporalOverflow overflow,
+ RegulatedISOYearMonth* result) {
+ // Step 1.
+ MOZ_ASSERT(IsInteger(year));
+ MOZ_ASSERT(IsInteger(month));
+
+ // Step 2. (Not applicable in our implementation.)
+
+ // Step 3.
+ if (overflow == TemporalOverflow::Constrain) {
+ // Step 3.a.
+ month = std::clamp(month, 1.0, 12.0);
+
+ // Step 3.b.
+ *result = {year, int32_t(month)};
+ return true;
+ }
+
+ // Step 4.a.
+ MOZ_ASSERT(overflow == TemporalOverflow::Reject);
+
+ // Step 4.b.
+ if (month < 1 || month > 12) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PLAIN_YEAR_MONTH_INVALID);
+ return false;
+ }
+
+ // Step 4.c.
+ *result = {year, int32_t(month)};
+ return true;
+}
+
+/**
+ * ISOYearMonthFromFields ( fields, overflow )
+ */
+static bool ISOYearMonthFromFields(JSContext* cx, Handle<TemporalFields> fields,
+ TemporalOverflow overflow,
+ PlainDate* result) {
+ // Steps 1-2. (Not applicable in our implementation.)
+
+ // Step 3.
+ double year = fields.year();
+
+ // Step 4.
+ double month = fields.month();
+
+ // Step 5.
+ MOZ_ASSERT(!std::isnan(year) && !std::isnan(month));
+
+ // Step 6.
+ RegulatedISOYearMonth regulated;
+ if (!RegulateISOYearMonth(cx, year, month, overflow, &regulated)) {
+ return false;
+ }
+
+ // Step 7.
+
+ // The result is used to create a new PlainYearMonthObject, so it's okay to
+ // directly throw an error for invalid years. That way we don't have to worry
+ // about representing doubles in PlainDate structs.
+ int32_t intYear;
+ if (!mozilla::NumberEqualsInt32(regulated.year, &intYear)) {
+ // CreateTemporalYearMonth, steps 1-2.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PLAIN_YEAR_MONTH_INVALID);
+ return false;
+ }
+
+ *result = {intYear, regulated.month, 1};
+ return true;
+}
+
+/**
+ * Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )
+ */
+static PlainYearMonthObject* BuiltinCalendarYearMonthFromFields(
+ JSContext* cx, Handle<JSObject*> fields, Handle<JSObject*> maybeOptions) {
+ // Steps 1-5. (Not applicable)
+
+ // Step 6.
+ Rooted<TemporalFields> dateFields(cx);
+ if (!PrepareTemporalFields(
+ cx, fields,
+ {TemporalField::Month, TemporalField::MonthCode, TemporalField::Year},
+ {TemporalField::Year}, &dateFields)) {
+ return nullptr;
+ }
+
+ // Step 7.
+ auto overflow = TemporalOverflow::Constrain;
+ if (maybeOptions) {
+ if (!ToTemporalOverflow(cx, maybeOptions, &overflow)) {
+ return nullptr;
+ }
+ }
+
+ // Step 8.
+ if (!ISOResolveMonth(cx, &dateFields)) {
+ return nullptr;
+ }
+
+ // Step 9.
+ PlainDate result;
+ if (!ISOYearMonthFromFields(cx, dateFields, overflow, &result)) {
+ return nullptr;
+ }
+
+ // Step 10.
+ Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
+ return CreateTemporalYearMonth(cx, result, calendar);
+}
+
+/**
+ * CalendarYearMonthFromFields ( calendarRec, fields [ , options ] )
+ */
+static Wrapped<PlainYearMonthObject*> CalendarYearMonthFromFields(
+ JSContext* cx, Handle<CalendarRecord> calendar, Handle<JSObject*> fields,
+ Handle<PlainObject*> maybeOptions) {
+ MOZ_ASSERT(CalendarMethodsRecordHasLookedUp(
+ calendar, CalendarMethod::YearMonthFromFields));
+
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 3. (Reordered)
+ auto yearMonthFromFields = calendar.yearMonthFromFields();
+ if (!yearMonthFromFields) {
+ return BuiltinCalendarYearMonthFromFields(cx, fields, maybeOptions);
+ }
+
+ // Step 2. (Inlined call to CalendarMethodsRecordCall.)
+
+ Rooted<Value> yearMonthFromFieldsFn(cx, ObjectValue(*yearMonthFromFields));
+ auto thisv = calendar.receiver().toValue();
+ Rooted<Value> rval(cx);
+
+ FixedInvokeArgs<2> args(cx);
+ args[0].setObject(*fields);
+ if (maybeOptions) {
+ args[1].setObject(*maybeOptions);
+ } else {
+ args[1].setUndefined();
+ }
+
+ if (!Call(cx, yearMonthFromFieldsFn, thisv, args, &rval)) {
+ return nullptr;
+ }
+
+ // Step 4.
+ if (!rval.isObject() ||
+ !rval.toObject().canUnwrapAs<PlainYearMonthObject>()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, rval,
+ nullptr, "not a PlainYearMonth object");
+ return nullptr;
+ }
+
+ // Step 5.
+ return &rval.toObject();
+}
+
+/**
+ * CalendarYearMonthFromFields ( calendarRec, fields [ , options ] )
+ */
+Wrapped<PlainYearMonthObject*> js::temporal::CalendarYearMonthFromFields(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<PlainObject*> fields) {
+ // Steps 1-4.
+ return ::CalendarYearMonthFromFields(cx, calendar, fields, nullptr);
+}
+
+/**
+ * CalendarYearMonthFromFields ( calendarRec, fields [ , options ] )
+ */
+Wrapped<PlainYearMonthObject*> js::temporal::CalendarYearMonthFromFields(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<PlainYearMonthObject*> fields) {
+ // Steps 1-4.
+ return ::CalendarYearMonthFromFields(cx, calendar, fields, nullptr);
+}
+
+/**
+ * CalendarYearMonthFromFields ( calendarRec, fields [ , options ] )
+ */
+Wrapped<PlainYearMonthObject*> js::temporal::CalendarYearMonthFromFields(
+ JSContext* cx, Handle<CalendarRecord> calendar, Handle<PlainObject*> fields,
+ Handle<PlainObject*> options) {
+ // Steps 1-4.
+ return ::CalendarYearMonthFromFields(cx, calendar, fields, options);
+}
+
+/**
+ * ISOMonthDayFromFields ( fields, overflow )
+ */
+static bool ISOMonthDayFromFields(JSContext* cx, Handle<TemporalFields> fields,
+ TemporalOverflow overflow,
+ PlainDate* result) {
+ // Steps 1-2. (Not applicable in our implementation.)
+
+ // Step 3.
+ double month = fields.month();
+
+ // Step 4.
+ double day = fields.day();
+
+ // Step 5.
+ MOZ_ASSERT(!std::isnan(month));
+ MOZ_ASSERT(!std::isnan(day));
+
+ // Step 6.
+ double year = fields.year();
+
+ // Step 7.
+ int32_t referenceISOYear = 1972;
+
+ // Steps 8-9.
+ double y = std::isnan(year) ? referenceISOYear : year;
+ RegulatedISODate regulated;
+ if (!RegulateISODate(cx, y, month, day, overflow, &regulated)) {
+ return false;
+ }
+
+ // Step 10.
+ *result = {referenceISOYear, regulated.month, regulated.day};
+ return true;
+}
+
+/**
+ * Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )
+ */
+static PlainMonthDayObject* BuiltinCalendarMonthDayFromFields(
+ JSContext* cx, Handle<JSObject*> fields, Handle<JSObject*> maybeOptions) {
+ // Steps 1-5. (Not applicable)
+
+ // Step 6.
+ Rooted<TemporalFields> dateFields(cx);
+ if (!PrepareTemporalFields(cx, fields,
+ {TemporalField::Day, TemporalField::Month,
+ TemporalField::MonthCode, TemporalField::Year},
+ {TemporalField::Day}, &dateFields)) {
+ return nullptr;
+ }
+
+ // Step 7.
+ auto overflow = TemporalOverflow::Constrain;
+ if (maybeOptions) {
+ if (!ToTemporalOverflow(cx, maybeOptions, &overflow)) {
+ return nullptr;
+ }
+ }
+
+ // Step 8.
+ if (!ISOResolveMonth(cx, &dateFields)) {
+ return nullptr;
+ }
+
+ // Step 9.
+ PlainDate result;
+ if (!ISOMonthDayFromFields(cx, dateFields, overflow, &result)) {
+ return nullptr;
+ }
+
+ // Step 10.
+ Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
+ return CreateTemporalMonthDay(cx, result, calendar);
+}
+
+/**
+ * CalendarMonthDayFromFields ( calendarRec, fields [ , options ] )
+ */
+static Wrapped<PlainMonthDayObject*> CalendarMonthDayFromFields(
+ JSContext* cx, Handle<CalendarRecord> calendar, Handle<JSObject*> fields,
+ Handle<PlainObject*> maybeOptions) {
+ MOZ_ASSERT(CalendarMethodsRecordHasLookedUp(
+ calendar, CalendarMethod::MonthDayFromFields));
+
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 3. (Reordered)
+ auto monthDayFromFields = calendar.monthDayFromFields();
+ if (!monthDayFromFields) {
+ return BuiltinCalendarMonthDayFromFields(cx, fields, maybeOptions);
+ }
+
+ // Step 2. (Inlined call to CalendarMethodsRecordCall.)
+
+ Rooted<Value> monthDayFromFieldsFn(cx, ObjectValue(*monthDayFromFields));
+ auto thisv = calendar.receiver().toValue();
+ Rooted<Value> rval(cx);
+
+ FixedInvokeArgs<2> args(cx);
+ args[0].setObject(*fields);
+ if (maybeOptions) {
+ args[1].setObject(*maybeOptions);
+ } else {
+ args[1].setUndefined();
+ }
+
+ if (!Call(cx, monthDayFromFieldsFn, thisv, args, &rval)) {
+ return nullptr;
+ }
+
+ // Step 4.
+ if (!rval.isObject() || !rval.toObject().canUnwrapAs<PlainMonthDayObject>()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, rval,
+ nullptr, "not a PlainMonthDay object");
+ return nullptr;
+ }
+
+ // Step 5.
+ return &rval.toObject();
+}
+
+/**
+ * CalendarMonthDayFromFields ( calendarRec, fields [ , options ] )
+ */
+Wrapped<PlainMonthDayObject*> js::temporal::CalendarMonthDayFromFields(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<PlainObject*> fields) {
+ // Steps 1-4.
+ return ::CalendarMonthDayFromFields(cx, calendar, fields, nullptr);
+}
+
+/**
+ * CalendarMonthDayFromFields ( calendarRec, fields [ , options ] )
+ */
+Wrapped<PlainMonthDayObject*> js::temporal::CalendarMonthDayFromFields(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<PlainMonthDayObject*> fields) {
+ // Steps 1-4.
+ return ::CalendarMonthDayFromFields(cx, calendar, fields, nullptr);
+}
+
+/**
+ * CalendarMonthDayFromFields ( calendarRec, fields [ , options ] )
+ */
+Wrapped<PlainMonthDayObject*> js::temporal::CalendarMonthDayFromFields(
+ JSContext* cx, Handle<CalendarRecord> calendar, Handle<PlainObject*> fields,
+ Handle<PlainObject*> options) {
+ // Steps 1-4.
+ return ::CalendarMonthDayFromFields(cx, calendar, fields, options);
+}
+
+using PropertyHashSet = JS::GCHashSet<JS::PropertyKey>;
+using PropertyVector = JS::StackGCVector<JS::PropertyKey>;
+
+/**
+ * ISOFieldKeysToIgnore ( keys )
+ */
+static bool ISOFieldKeysToIgnore(JSContext* cx, const PropertyVector& keys,
+ PropertyHashSet& ignoredKeys) {
+ MOZ_ASSERT(ignoredKeys.empty(), "expected an empty output hashset");
+
+ // Step 1. (Not applicable in our implementation.)
+
+ if (!ignoredKeys.reserve(keys.length())) {
+ return false;
+ }
+
+ // Step 2.
+ bool seenMonthOrMonthCode = false;
+ for (auto& key : keys) {
+ // Reorder the substeps in order to use |putNew| instead of |put|, because
+ // the former is slightly faster.
+
+ // Steps 2.a-c.
+ if (key.isAtom(cx->names().month) || key.isAtom(cx->names().monthCode)) {
+ // Steps 2.b-c.
+ if (!seenMonthOrMonthCode) {
+ seenMonthOrMonthCode = true;
+
+ // Add both keys at once.
+ if (!ignoredKeys.putNew(NameToId(cx->names().month)) ||
+ !ignoredKeys.putNew(NameToId(cx->names().monthCode))) {
+ return false;
+ }
+ }
+ } else {
+ // Step 2.a.
+ if (!ignoredKeys.putNew(key)) {
+ return false;
+ }
+ }
+ }
+
+ // Step 3.
+ return true;
+}
+
+/**
+ * Temporal.Calendar.prototype.mergeFields ( fields, additionalFields )
+ */
+static PlainObject* BuiltinCalendarMergeFields(
+ JSContext* cx, Handle<JSObject*> fields,
+ Handle<JSObject*> additionalFields) {
+ // NOTE: This function is only called from CalendarMergeFields and its
+ // result is always passed to PrepareTemporalFields. PrepareTemporalFields
+ // sorts the incoming property keys, so it doesn't matter in which order the
+ // properties are created in this function. This allows to reorder the steps
+ // to process |additionalFields| first.
+
+ // TODO: Consider additionally passing the fieldNames from the succeeding
+ // PrepareTemporalFields call, so we don't have to process unnecessary keys.
+
+ // Steps 1-2. (Not applicable in our implementation.)
+
+ // Step 13.
+ Rooted<PlainObject*> merged(cx, NewPlainObjectWithProto(cx, nullptr));
+ if (!merged) {
+ return nullptr;
+ }
+
+ // Contrary to the spec, add the properties from |additionalFields| first.
+
+ // Step 6. (Not applicable in our implementation.)
+
+ // Steps 7-8.
+ //
+ // PrepareTemporalFields ignores symbol property keys, so we don't need to
+ // pass JSITER_SYMBOLS. |additionalFields| contains no non-enumerable
+ // properties, therefore JSITER_HIDDEN isn't needed, too.
+ JS::RootedVector<PropertyKey> keys(cx);
+ if (!GetPropertyKeys(cx, additionalFields, JSITER_OWNONLY, &keys)) {
+ return nullptr;
+ }
+
+ // Steps 9-11. (Not applicable in our implementation.)
+
+ // Step 12.
+ Rooted<PropertyHashSet> ignoredKeys(cx, PropertyHashSet(cx));
+ if (!ignoredKeys.reserve(keys.length())) {
+ return nullptr;
+ }
+
+ // Steps 7-8, 12, and 15.
+ Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
+ Rooted<Value> propValue(cx);
+ bool seenMonthOrMonthCode = false;
+ for (size_t i = 0; i < keys.length(); i++) {
+ Handle<PropertyKey> key = keys[i];
+
+ if (!GetOwnPropertyDescriptor(cx, additionalFields, key, &desc)) {
+ return nullptr;
+ }
+
+ propValue.set(desc->value());
+
+ // Skip |undefined| properties per step 8.
+ if (propValue.isUndefined()) {
+ continue;
+ }
+
+ // Step 15.
+ if (!DefineDataProperty(cx, merged, key, propValue)) {
+ return nullptr;
+ }
+
+ // Step 12. (Inlined ISOFieldKeysToIgnore)
+ if (key.isAtom(cx->names().month) || key.isAtom(cx->names().monthCode)) {
+ // ISOFieldKeysToIgnore, steps 2.b-c.
+ if (!seenMonthOrMonthCode) {
+ seenMonthOrMonthCode = true;
+
+ if (!ignoredKeys.putNew(NameToId(cx->names().month)) ||
+ !ignoredKeys.putNew(NameToId(cx->names().monthCode))) {
+ return nullptr;
+ }
+ }
+ } else {
+ // ISOFieldKeysToIgnore, step 2.a.
+ if (!ignoredKeys.putNew(key)) {
+ return nullptr;
+ }
+ }
+ }
+
+ // Now add the properties from |field|.
+
+ // Step 3. (Not applicable in our implementation.)
+
+ // Reuse |keys| to avoid extra allocations.
+ keys.clear();
+
+ // Steps 4-5.
+ //
+ // See above why neither JSITER_SYMBOLS nor JSITER_HIDDEN is needed.
+ if (!GetPropertyKeys(cx, fields, JSITER_OWNONLY, &keys)) {
+ return nullptr;
+ }
+
+ // Steps 4-5 and 14.
+ for (size_t i = 0; i < keys.length(); i++) {
+ Handle<PropertyKey> key = keys[i];
+
+ // Skip ignored keys per step 14.
+ if (ignoredKeys.has(key)) {
+ continue;
+ }
+
+ if (!GetOwnPropertyDescriptor(cx, fields, key, &desc)) {
+ return nullptr;
+ }
+
+ propValue.set(desc->value());
+
+ // Skip |undefined| properties per step 5.
+ if (propValue.isUndefined()) {
+ continue;
+ }
+
+ // Step 14.
+ if (!DefineDataProperty(cx, merged, key, propValue)) {
+ return nullptr;
+ }
+ }
+
+ // Step 16.
+ return merged;
+}
+
+#ifdef DEBUG
+static bool IsPlainDataObject(PlainObject* obj) {
+ for (ShapePropertyIter<NoGC> iter(obj->shape()); !iter.done(); iter++) {
+ if (iter->flags() != PropertyFlags::defaultDataPropFlags) {
+ return false;
+ }
+ }
+ return true;
+}
+#endif
+
+/**
+ * CalendarMergeFields ( calendarRec, fields, additionalFields )
+ */
+JSObject* js::temporal::CalendarMergeFields(
+ JSContext* cx, Handle<CalendarRecord> calendar, Handle<PlainObject*> fields,
+ Handle<PlainObject*> additionalFields) {
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::MergeFields));
+
+ MOZ_ASSERT(IsPlainDataObject(fields));
+ MOZ_ASSERT(IsPlainDataObject(additionalFields));
+
+ // Step 2. (Reordered)
+ auto mergeFields = calendar.mergeFields();
+ if (!mergeFields) {
+ return BuiltinCalendarMergeFields(cx, fields, additionalFields);
+ }
+
+ // Step 1. (Inlined call to CalendarMethodsRecordCall.)
+
+ Rooted<Value> mergeFieldsFn(cx, ObjectValue(*mergeFields));
+ auto thisv = calendar.receiver().toValue();
+ Rooted<Value> result(cx);
+
+ FixedInvokeArgs<2> args(cx);
+ args[0].setObject(*fields);
+ args[1].setObject(*additionalFields);
+
+ if (!Call(cx, mergeFieldsFn, thisv, args, &result)) {
+ return nullptr;
+ }
+
+ // Steps 3-4.
+ return RequireObject(cx, result);
+}
+
+/**
+ * Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )
+ */
+static bool BuiltinCalendarAdd(JSContext* cx, const PlainDate& date,
+ const Duration& duration,
+ Handle<JSObject*> options, PlainDate* result) {
+ MOZ_ASSERT(IsValidISODate(date));
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ // Steps 1-6. (Not applicable)
+
+ // Step 7.
+ auto overflow = TemporalOverflow::Constrain;
+ if (options) {
+ if (!ToTemporalOverflow(cx, options, &overflow)) {
+ return false;
+ }
+ }
+
+ // Step 8.
+ TimeDuration balanceResult;
+ if (!BalanceTimeDuration(cx, duration, TemporalUnit::Day, &balanceResult)) {
+ return false;
+ }
+
+ // Step 9.
+ Duration addDuration = {duration.years, duration.months, duration.weeks,
+ balanceResult.days};
+ return AddISODate(cx, date, addDuration, overflow, result);
+}
+
+/**
+ * Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )
+ */
+static PlainDateObject* BuiltinCalendarAdd(JSContext* cx, const PlainDate& date,
+ const Duration& duration,
+ Handle<JSObject*> options) {
+ // Steps 1-6. (Not applicable)
+
+ // Steps 7-9.
+ PlainDate result;
+ if (!BuiltinCalendarAdd(cx, date, duration, options, &result)) {
+ return nullptr;
+ }
+
+ // Step 10.
+ Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
+ return CreateTemporalDate(cx, result, calendar);
+}
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+static Wrapped<PlainDateObject*> CalendarDateAddSlow(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> date,
+ Handle<Wrapped<DurationObject*>> duration, Handle<JSObject*> options) {
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+ MOZ_ASSERT(calendar.receiver().isObject());
+ MOZ_ASSERT(calendar.dateAdd());
+
+ // Step 1. (Not applicable).
+
+ // Step 2. (Inlined call to CalendarMethodsRecordCall.)
+ Rooted<JS::Value> dateAdd(cx, ObjectValue(*calendar.dateAdd()));
+ auto thisv = calendar.receiver().toValue();
+ Rooted<Value> rval(cx);
+
+ FixedInvokeArgs<3> args(cx);
+ args[0].setObject(*date);
+ args[1].setObject(*duration);
+ if (options) {
+ args[2].setObject(*options);
+ } else {
+ args[2].setUndefined();
+ }
+
+ if (!Call(cx, dateAdd, thisv, args, &rval)) {
+ return nullptr;
+ }
+
+ // Step 3. (Not applicable)
+ MOZ_ASSERT(!CalendarMethodsRecordIsBuiltin(calendar));
+
+ // Step 4.
+ if (!rval.isObject() || !rval.toObject().canUnwrapAs<PlainDateObject>()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, rval,
+ nullptr, "not a PlainDate object");
+ return nullptr;
+ }
+
+ // Step 5.
+ return &rval.toObject();
+}
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+static Wrapped<PlainDateObject*> CalendarDateAdd(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> date, const Duration& duration,
+ Handle<JSObject*> options) {
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+
+ // Step 1. (Not applicable).
+
+ // Step 3. (Reordered)
+ if (!calendar.dateAdd()) {
+ auto* unwrappedDate = date.unwrap(cx);
+ if (!unwrappedDate) {
+ return nullptr;
+ }
+ auto date = ToPlainDate(unwrappedDate);
+
+ return BuiltinCalendarAdd(cx, date, duration, options);
+ }
+
+ // Steps 2 and 4-5.
+ Rooted<DurationObject*> durationObj(cx, CreateTemporalDuration(cx, duration));
+ if (!durationObj) {
+ return nullptr;
+ }
+ return CalendarDateAddSlow(cx, calendar, date, durationObj, options);
+}
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+static Wrapped<PlainDateObject*> CalendarDateAdd(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> date,
+ Handle<Wrapped<DurationObject*>> duration, Handle<JSObject*> options) {
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+
+ // Step 1. (Not applicable).
+
+ // Step 3. (Reordered)
+ if (!calendar.dateAdd()) {
+ auto* unwrappedDate = date.unwrap(cx);
+ if (!unwrappedDate) {
+ return nullptr;
+ }
+ auto date = ToPlainDate(unwrappedDate);
+
+ auto* unwrappedDuration = duration.unwrap(cx);
+ if (!unwrappedDuration) {
+ return nullptr;
+ }
+ auto duration = ToDuration(unwrappedDuration);
+
+ return BuiltinCalendarAdd(cx, date, duration, options);
+ }
+
+ // Steps 2 and 4-5.
+ return CalendarDateAddSlow(cx, calendar, date, duration, options);
+}
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+static bool CalendarDateAdd(JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> date,
+ const Duration& duration, Handle<JSObject*> options,
+ PlainDate* result) {
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+
+ // Step 1. (Not applicable).
+
+ // Step 3. (Reordered)
+ if (!calendar.dateAdd()) {
+ auto* unwrappedDate = date.unwrap(cx);
+ if (!unwrappedDate) {
+ return false;
+ }
+ auto date = ToPlainDate(unwrappedDate);
+
+ return BuiltinCalendarAdd(cx, date, duration, options, result);
+ }
+
+ // Steps 2 and 4-5.
+
+ Rooted<DurationObject*> durationObj(cx, CreateTemporalDuration(cx, duration));
+ if (!durationObj) {
+ return false;
+ }
+
+ auto obj = CalendarDateAddSlow(cx, calendar, date, durationObj, options);
+ if (!obj) {
+ return false;
+ }
+
+ *result = ToPlainDate(&obj.unwrap());
+ return true;
+}
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+static bool CalendarDateAdd(JSContext* cx, Handle<CalendarRecord> calendar,
+ const PlainDate& date, const Duration& duration,
+ Handle<JSObject*> options, PlainDate* result) {
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+
+ // Step 1. (Not applicable).
+
+ // Step 3. (Reordered)
+ if (!calendar.dateAdd()) {
+ return BuiltinCalendarAdd(cx, date, duration, options, result);
+ }
+
+ // Steps 2 and 4-5.
+
+ Rooted<PlainDateObject*> dateObj(
+ cx, CreateTemporalDate(cx, date, calendar.receiver()));
+ if (!dateObj) {
+ return false;
+ }
+
+ Rooted<DurationObject*> durationObj(cx, CreateTemporalDuration(cx, duration));
+ if (!durationObj) {
+ return false;
+ }
+
+ auto obj = CalendarDateAddSlow(cx, calendar, dateObj, durationObj, options);
+ if (!obj) {
+ return false;
+ }
+
+ *result = ToPlainDate(&obj.unwrap());
+ return true;
+}
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+Wrapped<PlainDateObject*> js::temporal::CalendarDateAdd(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> date, const Duration& duration,
+ Handle<JSObject*> options) {
+ // Step 1. (Not applicable).
+
+ // Steps 2-5.
+ return ::CalendarDateAdd(cx, calendar, date, duration, options);
+}
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+Wrapped<PlainDateObject*> js::temporal::CalendarDateAdd(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> date, const Duration& duration) {
+ // Step 1.
+ Handle<JSObject*> options = nullptr;
+
+ // Steps 2-5.
+ return ::CalendarDateAdd(cx, calendar, date, duration, options);
+}
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+Wrapped<PlainDateObject*> js::temporal::CalendarDateAdd(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> date,
+ Handle<Wrapped<DurationObject*>> duration) {
+ // Step 1.
+ Handle<JSObject*> options = nullptr;
+
+ // Steps 2-5.
+ return ::CalendarDateAdd(cx, calendar, date, duration, options);
+}
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+Wrapped<PlainDateObject*> js::temporal::CalendarDateAdd(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> date,
+ Handle<Wrapped<DurationObject*>> duration, Handle<JSObject*> options) {
+ // Step 1. (Not applicable).
+
+ // Steps 2-5.
+ return ::CalendarDateAdd(cx, calendar, date, duration, options);
+}
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+bool js::temporal::CalendarDateAdd(JSContext* cx,
+ Handle<CalendarRecord> calendar,
+ const PlainDate& date,
+ const Duration& duration,
+ PlainDate* result) {
+ // Step 1.
+ Handle<JSObject*> options = nullptr;
+
+ // Steps 2-5.
+ return ::CalendarDateAdd(cx, calendar, date, duration, options, result);
+}
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+bool js::temporal::CalendarDateAdd(
+ JSContext* cx, Handle<CalendarRecord> calendar, const PlainDate& date,
+ const Duration& duration, Handle<JSObject*> options, PlainDate* result) {
+ // Step 1. (Not applicable)
+
+ // Steps 2-5.
+ return ::CalendarDateAdd(cx, calendar, date, duration, options, result);
+}
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+bool js::temporal::CalendarDateAdd(JSContext* cx,
+ Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> date,
+ const Duration& duration,
+ PlainDate* result) {
+ // Step 1.
+ Handle<JSObject*> options = nullptr;
+
+ // Steps 2-5.
+ return ::CalendarDateAdd(cx, calendar, date, duration, options, result);
+}
+
+/**
+ * Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )
+ */
+static Duration BuiltinCalendarDateUntil(const PlainDate& one,
+ const PlainDate& two,
+ TemporalUnit largestUnit) {
+ // Steps 1-3. (Not applicable)
+
+ // Steps 4-8. (Not applicable)
+
+ // Step 9.
+ auto difference = DifferenceISODate(one, two, largestUnit);
+
+ // Step 10.
+ return difference.toDuration();
+}
+
+/**
+ * Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )
+ */
+static bool BuiltinCalendarDateUntil(JSContext* cx,
+ Handle<Wrapped<PlainDateObject*>> one,
+ Handle<Wrapped<PlainDateObject*>> two,
+ TemporalUnit largestUnit,
+ Duration* result) {
+ MOZ_ASSERT(largestUnit <= TemporalUnit::Day);
+
+ auto* unwrappedOne = one.unwrap(cx);
+ if (!unwrappedOne) {
+ return false;
+ }
+ auto dateOne = ToPlainDate(unwrappedOne);
+
+ auto* unwrappedTwo = two.unwrap(cx);
+ if (!unwrappedTwo) {
+ return false;
+ }
+ auto dateTwo = ToPlainDate(unwrappedTwo);
+
+ // Steps 1-10.
+ *result = BuiltinCalendarDateUntil(dateOne, dateTwo, largestUnit);
+ return true;
+}
+
+/**
+ * Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )
+ */
+static bool BuiltinCalendarDateUntil(JSContext* cx,
+ Handle<Wrapped<PlainDateObject*>> one,
+ Handle<Wrapped<PlainDateObject*>> two,
+ Handle<JSObject*> options,
+ Duration* result) {
+ // Steps 1-6. (Not applicable)
+
+ // Steps 7-8.
+ auto largestUnit = TemporalUnit::Day;
+ if (!GetTemporalUnit(cx, options, TemporalUnitKey::LargestUnit,
+ TemporalUnitGroup::Date, &largestUnit)) {
+ return false;
+ }
+
+ // Steps 9-10.
+ return BuiltinCalendarDateUntil(cx, one, two, largestUnit, result);
+}
+
+static bool CalendarDateUntilSlow(JSContext* cx,
+ Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> one,
+ Handle<Wrapped<PlainDateObject*>> two,
+ Handle<JSObject*> options, Duration* result) {
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
+ MOZ_ASSERT(calendar.receiver().isObject());
+ MOZ_ASSERT(calendar.dateUntil());
+
+ // Step 1. (Inlined call to CalendarMethodsRecordCall.)
+ Rooted<JS::Value> dateUntil(cx, ObjectValue(*calendar.dateUntil()));
+ auto thisv = calendar.receiver().toValue();
+ Rooted<Value> rval(cx);
+
+ FixedInvokeArgs<3> args(cx);
+ args[0].setObject(*one);
+ args[1].setObject(*two);
+ args[2].setObject(*options);
+
+ if (!Call(cx, dateUntil, thisv, args, &rval)) {
+ return false;
+ }
+
+ // Step 2. (Not applicable)
+ MOZ_ASSERT(!CalendarMethodsRecordIsBuiltin(calendar));
+
+ // Step 3.
+ if (!rval.isObject() || !rval.toObject().canUnwrapAs<DurationObject>()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, rval,
+ nullptr, "not a Duration object");
+ return false;
+ }
+
+ // Step 4.
+ *result = ToDuration(&rval.toObject().unwrapAs<DurationObject>());
+ return true;
+}
+
+/**
+ * CalendarDateUntil ( calendarRec, one, two, options )
+ */
+bool js::temporal::CalendarDateUntil(JSContext* cx,
+ Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> one,
+ Handle<Wrapped<PlainDateObject*>> two,
+ Handle<PlainObject*> options,
+ Duration* result) {
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
+
+ // Step 2. (Reordered)
+ if (!calendar.dateUntil()) {
+ return BuiltinCalendarDateUntil(cx, one, two, options, result);
+ }
+
+ // Steps 1 and 3-4.
+ return CalendarDateUntilSlow(cx, calendar, one, two, options, result);
+}
+
+/**
+ * CalendarDateUntil ( calendarRec, one, two, options )
+ */
+bool js::temporal::CalendarDateUntil(JSContext* cx,
+ Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> one,
+ Handle<Wrapped<PlainDateObject*>> two,
+ TemporalUnit largestUnit,
+ Duration* result) {
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
+ MOZ_ASSERT(largestUnit <= TemporalUnit::Day);
+
+ // Step 2. (Reordered)
+ if (!calendar.dateUntil()) {
+ return BuiltinCalendarDateUntil(cx, one, two, largestUnit, result);
+ }
+
+ Rooted<PlainObject*> untilOptions(cx, NewPlainObjectWithProto(cx, nullptr));
+ if (!untilOptions) {
+ return false;
+ }
+
+ Rooted<Value> value(cx, StringValue(TemporalUnitToString(cx, largestUnit)));
+ if (!DefineDataProperty(cx, untilOptions, cx->names().largestUnit, value)) {
+ return false;
+ }
+
+ // Steps 1 and 3-4.
+ return CalendarDateUntilSlow(cx, calendar, one, two, untilOptions, result);
+}
+
+/**
+ * CalendarEquals ( one, two )
+ */
+bool js::temporal::CalendarEquals(JSContext* cx, Handle<CalendarValue> one,
+ Handle<CalendarValue> two, bool* equals) {
+ // Step 1.
+ if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) {
+ *equals = true;
+ return true;
+ }
+
+ // Step 2.
+ Rooted<JSString*> calendarOne(cx, ToTemporalCalendarIdentifier(cx, one));
+ if (!calendarOne) {
+ return false;
+ }
+
+ // Step 3.
+ JSString* calendarTwo = ToTemporalCalendarIdentifier(cx, two);
+ if (!calendarTwo) {
+ return false;
+ }
+
+ // Steps 4-5.
+ return EqualStrings(cx, calendarOne, calendarTwo, equals);
+}
+
+/**
+ * CalendarEquals ( one, two )
+ */
+bool js::temporal::CalendarEqualsOrThrow(JSContext* cx,
+ Handle<CalendarValue> one,
+ Handle<CalendarValue> two) {
+ // Step 1.
+ if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) {
+ return true;
+ }
+
+ // Step 2.
+ Rooted<JSString*> calendarOne(cx, ToTemporalCalendarIdentifier(cx, one));
+ if (!calendarOne) {
+ return false;
+ }
+
+ // Step 3.
+ JSString* calendarTwo = ToTemporalCalendarIdentifier(cx, two);
+ if (!calendarTwo) {
+ return false;
+ }
+
+ // Steps 4-5.
+ bool equals;
+ if (!EqualStrings(cx, calendarOne, calendarTwo, &equals)) {
+ return false;
+ }
+ if (equals) {
+ return true;
+ }
+
+ // Throw an error when the calendar identifiers don't match. Used when unequal
+ // calendars throw a RangeError.
+ if (auto charsOne = QuoteString(cx, calendarOne)) {
+ if (auto charsTwo = QuoteString(cx, calendarTwo)) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE,
+ charsOne.get(), charsTwo.get());
+ }
+ }
+ return false;
+}
+
+/**
+ * ConsolidateCalendars ( one, two )
+ */
+bool js::temporal::ConsolidateCalendars(JSContext* cx,
+ Handle<CalendarValue> one,
+ Handle<CalendarValue> two,
+ MutableHandle<CalendarValue> result) {
+ // Step 1.
+ if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) {
+ result.set(two);
+ return true;
+ }
+
+ // Step 2.
+ Rooted<JSString*> calendarOne(cx, ToTemporalCalendarIdentifier(cx, one));
+ if (!calendarOne) {
+ return false;
+ }
+
+ // Step 3.
+ Rooted<JSString*> calendarTwo(cx, ToTemporalCalendarIdentifier(cx, two));
+ if (!calendarTwo) {
+ return false;
+ }
+
+ // Step 4.
+ bool equals;
+ if (!EqualStrings(cx, calendarOne, calendarTwo, &equals)) {
+ return false;
+ }
+ if (equals) {
+ result.set(two);
+ return true;
+ }
+
+ // Step 5.
+ bool isoCalendarOne;
+ if (!IsISO8601Calendar(cx, calendarOne, &isoCalendarOne)) {
+ return false;
+ }
+ if (isoCalendarOne) {
+ result.set(two);
+ return true;
+ }
+
+ // Step 6.
+ bool isoCalendarTwo;
+ if (!IsISO8601Calendar(cx, calendarTwo, &isoCalendarTwo)) {
+ return false;
+ }
+ if (isoCalendarTwo) {
+ result.set(one);
+ return true;
+ }
+
+ // Step 7.
+ if (auto charsOne = QuoteString(cx, calendarOne)) {
+ if (auto charsTwo = QuoteString(cx, calendarTwo)) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_CALENDAR_INCOMPATIBLE,
+ charsOne.get(), charsTwo.get());
+ }
+ }
+ return false;
+}
+
+/**
+ * Temporal.Calendar ( id )
+ */
+static bool CalendarConstructor(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ if (!ThrowIfNotConstructing(cx, args, "Temporal.Calendar")) {
+ return false;
+ }
+
+ // Step 2.
+ if (!args.requireAtLeast(cx, "Temporal.Calendar", 1)) {
+ return false;
+ }
+
+ if (!args[0].isString()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, args[0],
+ nullptr, "not a string");
+ return false;
+ }
+
+ Rooted<JSLinearString*> identifier(cx, args[0].toString()->ensureLinear(cx));
+ if (!identifier) {
+ return false;
+ }
+
+ // Step 3.
+ identifier = ThrowIfNotBuiltinCalendar(cx, identifier);
+ if (!identifier) {
+ return false;
+ }
+
+ // Step 4.
+ auto* calendar = CreateTemporalCalendar(cx, args, identifier);
+ if (!calendar) {
+ return false;
+ }
+
+ args.rval().setObject(*calendar);
+ return true;
+}
+
+/**
+ * Temporal.Calendar.from ( item )
+ */
+static bool Calendar_from(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ Rooted<CalendarValue> calendar(cx);
+ if (!ToTemporalCalendar(cx, args.get(0), &calendar)) {
+ return false;
+ }
+
+ // Step 2.
+ auto* obj = ToTemporalCalendarObject(cx, calendar);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * get Temporal.Calendar.prototype.id
+ */
+static bool Calendar_id(JSContext* cx, const CallArgs& args) {
+ auto* calendar = &args.thisv().toObject().as<CalendarObject>();
+
+ // Step 3.
+ args.rval().setString(calendar->identifier());
+ return true;
+}
+
+/**
+ * get Temporal.Calendar.prototype.id
+ */
+static bool Calendar_id(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_id>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] )
+ */
+static bool Calendar_dateFromFields(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ Rooted<JSObject*> fields(
+ cx, RequireObjectArg(cx, "fields", "dateFromFields", args.get(0)));
+ if (!fields) {
+ return false;
+ }
+
+ // Step 5.
+ Rooted<JSObject*> options(cx);
+ if (args.hasDefined(1)) {
+ options = RequireObjectArg(cx, "options", "dateFromFields", args[1]);
+ if (!options) {
+ return false;
+ }
+ }
+
+ // Steps 6-10.
+ auto* obj = BuiltinCalendarDateFromFields(cx, fields, options);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.Calendar.prototype.dateFromFields ( fields [ , options ] )
+ */
+static bool Calendar_dateFromFields(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_dateFromFields>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )
+ */
+static bool Calendar_yearMonthFromFields(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ Rooted<JSObject*> fields(
+ cx, RequireObjectArg(cx, "fields", "yearMonthFromFields", args.get(0)));
+ if (!fields) {
+ return false;
+ }
+
+ // Step 5.
+ Rooted<JSObject*> options(cx);
+ if (args.hasDefined(1)) {
+ options = RequireObjectArg(cx, "options", "yearMonthFromFields", args[1]);
+ if (!options) {
+ return false;
+ }
+ }
+
+ // Steps 6-10.
+ auto* obj = BuiltinCalendarYearMonthFromFields(cx, fields, options);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.Calendar.prototype.yearMonthFromFields ( fields [ , options ] )
+ */
+static bool Calendar_yearMonthFromFields(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_yearMonthFromFields>(cx,
+ args);
+}
+
+/**
+ * Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )
+ */
+static bool Calendar_monthDayFromFields(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ Rooted<JSObject*> fields(
+ cx, RequireObjectArg(cx, "fields", "monthDayFromFields", args.get(0)));
+ if (!fields) {
+ return false;
+ }
+
+ // Step 5.
+ Rooted<JSObject*> options(cx);
+ if (args.hasDefined(1)) {
+ options = RequireObjectArg(cx, "options", "monthDayFromFields", args[1]);
+ if (!options) {
+ return false;
+ }
+ }
+
+ // Steps 6-10.
+ auto* obj = BuiltinCalendarMonthDayFromFields(cx, fields, options);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.Calendar.prototype.monthDayFromFields ( fields [ , options ] )
+ */
+static bool Calendar_monthDayFromFields(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_monthDayFromFields>(cx,
+ args);
+}
+
+/**
+ * Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )
+ */
+static bool Calendar_dateAdd(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ PlainDate date;
+ if (!ToTemporalDate(cx, args.get(0), &date)) {
+ return false;
+ }
+
+ // Step 5.
+ Duration duration;
+ if (!ToTemporalDuration(cx, args.get(1), &duration)) {
+ return false;
+ }
+
+ // Step 6.
+ Rooted<JSObject*> options(cx);
+ if (args.hasDefined(2)) {
+ options = RequireObjectArg(cx, "options", "dateAdd", args[2]);
+ if (!options) {
+ return false;
+ }
+ }
+
+ // Steps 7-10.
+ auto* obj = BuiltinCalendarAdd(cx, date, duration, options);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.Calendar.prototype.dateAdd ( date, duration [ , options ] )
+ */
+static bool Calendar_dateAdd(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_dateAdd>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )
+ */
+static bool Calendar_dateUntil(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ PlainDate one;
+ if (!ToTemporalDate(cx, args.get(0), &one)) {
+ return false;
+ }
+
+ // Step 5.
+ PlainDate two;
+ if (!ToTemporalDate(cx, args.get(1), &two)) {
+ return false;
+ }
+
+ // Steps 6-8.
+ auto largestUnit = TemporalUnit::Day;
+ if (args.hasDefined(2)) {
+ Rooted<JSObject*> options(
+ cx, RequireObjectArg(cx, "options", "dateUntil", args[2]));
+ if (!options) {
+ return false;
+ }
+
+ // Steps 7-8.
+ if (!GetTemporalUnit(cx, options, TemporalUnitKey::LargestUnit,
+ TemporalUnitGroup::Date, &largestUnit)) {
+ return false;
+ }
+ }
+
+ // Steps 9-10.
+ auto duration = BuiltinCalendarDateUntil(one, two, largestUnit);
+
+ auto* obj = CreateTemporalDuration(cx, duration);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.Calendar.prototype.dateUntil ( one, two [ , options ] )
+ */
+static bool Calendar_dateUntil(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_dateUntil>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.year ( temporalDateLike )
+ */
+static bool Calendar_year(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ PlainDate date;
+ if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>(
+ cx, args.get(0), &date)) {
+ return false;
+ }
+
+ // Steps 5-6.
+ return BuiltinCalendarYear(cx, date, args.rval());
+}
+
+/**
+ * Temporal.Calendar.prototype.year ( temporalDateLike )
+ */
+static bool Calendar_year(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_year>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.month ( temporalDateLike )
+ */
+static bool Calendar_month(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ Handle<Value> temporalDateLike = args.get(0);
+
+ // Step 4.
+ if (temporalDateLike.isObject() &&
+ temporalDateLike.toObject().canUnwrapAs<PlainMonthDayObject>()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK,
+ temporalDateLike, nullptr, "a PlainMonthDay object");
+ return false;
+ }
+
+ // Step 5.
+ PlainDate date;
+ if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>(
+ cx, temporalDateLike, &date)) {
+ return false;
+ }
+
+ // Steps 6-7.
+ return BuiltinCalendarMonth(cx, date, args.rval());
+}
+
+/**
+ * Temporal.Calendar.prototype.month ( temporalDateLike )
+ */
+static bool Calendar_month(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_month>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.monthCode ( temporalDateLike )
+ */
+static bool Calendar_monthCode(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ PlainDate date;
+ if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainMonthDayObject,
+ PlainYearMonthObject>(cx, args.get(0), &date)) {
+ return false;
+ }
+
+ // Steps 5-6.
+ return BuiltinCalendarMonthCode(cx, date, args.rval());
+}
+
+/**
+ * Temporal.Calendar.prototype.monthCode ( temporalDateLike )
+ */
+static bool Calendar_monthCode(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_monthCode>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.day ( temporalDateLike )
+ */
+static bool Calendar_day(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ PlainDate date;
+ if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainMonthDayObject>(
+ cx, args.get(0), &date)) {
+ return false;
+ }
+
+ // Steps 5-6.
+ return BuiltinCalendarDay(date, args.rval());
+}
+
+/**
+ * Temporal.Calendar.prototype.day ( temporalDateLike )
+ */
+static bool Calendar_day(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_day>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.dayOfWeek ( temporalDateLike )
+ */
+static bool Calendar_dayOfWeek(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ PlainDate date;
+ if (!ToTemporalDate(cx, args.get(0), &date)) {
+ return false;
+ }
+
+ // Steps 5-9.
+ return BuiltinCalendarDayOfWeek(cx, date, args.rval());
+}
+
+/**
+ * Temporal.Calendar.prototype.dayOfWeek ( temporalDateLike )
+ */
+static bool Calendar_dayOfWeek(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_dayOfWeek>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.dayOfYear ( temporalDateLike )
+ */
+static bool Calendar_dayOfYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ PlainDate date;
+ if (!ToTemporalDate(cx, args.get(0), &date)) {
+ return false;
+ }
+
+ // Steps 5-7.
+ return BuiltinCalendarDayOfYear(cx, date, args.rval());
+}
+
+/**
+ * Temporal.Calendar.prototype.dayOfYear ( temporalDateLike )
+ */
+static bool Calendar_dayOfYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_dayOfYear>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.weekOfYear ( temporalDateLike )
+ */
+static bool Calendar_weekOfYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ PlainDate date;
+ if (!ToTemporalDate(cx, args.get(0), &date)) {
+ return false;
+ }
+
+ // Steps 5-6.
+ return BuiltinCalendarWeekOfYear(cx, date, args.rval());
+}
+
+/**
+ * Temporal.Calendar.prototype.weekOfYear ( temporalDateLike )
+ */
+static bool Calendar_weekOfYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_weekOfYear>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.yearOfWeek ( temporalDateLike )
+ */
+static bool Calendar_yearOfWeek(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ PlainDate date;
+ if (!ToTemporalDate(cx, args.get(0), &date)) {
+ return false;
+ }
+
+ // Steps 5-6.
+ return BuiltinCalendarYearOfWeek(cx, date, args.rval());
+}
+
+/**
+ * Temporal.Calendar.prototype.yearOfWeek ( temporalDateLike )
+ */
+static bool Calendar_yearOfWeek(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_yearOfWeek>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.daysInWeek ( temporalDateLike )
+ */
+static bool Calendar_daysInWeek(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ PlainDate date;
+ if (!ToTemporalDate(cx, args.get(0), &date)) {
+ return false;
+ }
+
+ // Step 5.
+ return BuiltinCalendarDaysInWeek(cx, date, args.rval());
+}
+
+/**
+ * Temporal.Calendar.prototype.daysInWeek ( temporalDateLike )
+ */
+static bool Calendar_daysInWeek(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_daysInWeek>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.daysInMonth ( temporalDateLike )
+ */
+static bool Calendar_daysInMonth(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ PlainDate date;
+ if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>(
+ cx, args.get(0), &date)) {
+ return false;
+ }
+
+ // Step 5.
+ return BuiltinCalendarDaysInMonth(cx, date, args.rval());
+}
+
+/**
+ * Temporal.Calendar.prototype.daysInMonth ( temporalDateLike )
+ */
+static bool Calendar_daysInMonth(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_daysInMonth>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.daysInYear ( temporalDateLike )
+ */
+static bool Calendar_daysInYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ PlainDate date;
+ if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>(
+ cx, args.get(0), &date)) {
+ return false;
+ }
+
+ // Step 5.
+ return BuiltinCalendarDaysInYear(cx, date, args.rval());
+}
+
+/**
+ * Temporal.Calendar.prototype.daysInYear ( temporalDateLike )
+ */
+static bool Calendar_daysInYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_daysInYear>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.monthsInYear ( temporalDateLike )
+ */
+static bool Calendar_monthsInYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ PlainDate date;
+ if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>(
+ cx, args.get(0), &date)) {
+ return false;
+ }
+
+ // Step 5.
+ return BuiltinCalendarMonthsInYear(cx, date, args.rval());
+}
+
+/**
+ * Temporal.Calendar.prototype.monthsInYear ( temporalDateLike )
+ */
+static bool Calendar_monthsInYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_monthsInYear>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.inLeapYear ( temporalDateLike )
+ */
+static bool Calendar_inLeapYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ PlainDate date;
+ if (!ToPlainDate<PlainDateObject, PlainDateTimeObject, PlainYearMonthObject>(
+ cx, args.get(0), &date)) {
+ return false;
+ }
+
+ // Steps 5-6.
+ return BuiltinCalendarInLeapYear(cx, date, args.rval());
+}
+
+/**
+ * Temporal.Calendar.prototype.inLeapYear ( temporalDateLike )
+ */
+static bool Calendar_inLeapYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_inLeapYear>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.fields ( fields )
+ */
+static bool Calendar_fields(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 4.
+ JS::ForOfIterator iterator(cx);
+ if (!iterator.init(args.get(0))) {
+ return false;
+ }
+
+ // Step 5.
+ JS::RootedVector<Value> fieldNames(cx);
+ mozilla::EnumSet<CalendarField> seen;
+
+ // Steps 6-7.
+ Rooted<Value> nextValue(cx);
+ Rooted<JSLinearString*> linear(cx);
+ while (true) {
+ // Steps 7.a and 7.b.i.
+ bool done;
+ if (!iterator.next(&nextValue, &done)) {
+ return false;
+ }
+ if (done) {
+ break;
+ }
+
+ // Step 7.b.ii.
+ if (!nextValue.isString()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, nextValue,
+ nullptr, "not a string");
+ iterator.closeThrow();
+ return false;
+ }
+
+ linear = nextValue.toString()->ensureLinear(cx);
+ if (!linear) {
+ return false;
+ }
+
+ // Step 7.b.iv. (Reordered)
+ CalendarField field;
+ if (!ToCalendarField(cx, linear, &field)) {
+ iterator.closeThrow();
+ return false;
+ }
+
+ // Step 7.b.iii.
+ if (seen.contains(field)) {
+ if (auto chars = QuoteString(cx, linear, '"')) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_CALENDAR_DUPLICATE_FIELD,
+ chars.get());
+ }
+ iterator.closeThrow();
+ return false;
+ }
+
+ // Step 7.b.v.
+ if (!fieldNames.append(nextValue)) {
+ return false;
+ }
+ seen += field;
+ }
+
+ // Step 8.
+ auto* array =
+ NewDenseCopiedArray(cx, fieldNames.length(), fieldNames.begin());
+ if (!array) {
+ return false;
+ }
+
+ args.rval().setObject(*array);
+ return true;
+}
+
+/**
+ * Temporal.Calendar.prototype.fields ( fields )
+ */
+static bool Calendar_fields(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_fields>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.mergeFields ( fields, additionalFields )
+ */
+static bool Calendar_mergeFields(JSContext* cx, const CallArgs& args) {
+ // Step 7. (Reordered)
+ MOZ_ASSERT(IsISO8601Calendar(&args.thisv().toObject().as<CalendarObject>()));
+
+ // Step 3.
+ Rooted<JSObject*> fields(cx, JS::ToObject(cx, args.get(0)));
+ if (!fields) {
+ return false;
+ }
+
+ Rooted<PlainObject*> fieldsCopy(
+ cx, SnapshotOwnPropertiesIgnoreUndefined(cx, fields));
+ if (!fieldsCopy) {
+ return false;
+ }
+
+ // Step 4.
+ Rooted<JSObject*> additionalFields(cx, JS::ToObject(cx, args.get(1)));
+ if (!additionalFields) {
+ return false;
+ }
+
+ Rooted<PlainObject*> additionalFieldsCopy(
+ cx, SnapshotOwnPropertiesIgnoreUndefined(cx, additionalFields));
+ if (!additionalFieldsCopy) {
+ return false;
+ }
+
+ // Steps 5-6.
+ //
+ // JSITER_HIDDEN doesn't need to be passed, because CopyDataProperties creates
+ // all properties as enumerable.
+ JS::RootedVector<PropertyKey> additionalKeys(cx);
+ if (!GetPropertyKeys(cx, additionalFieldsCopy,
+ JSITER_OWNONLY | JSITER_SYMBOLS, &additionalKeys)) {
+ return false;
+ }
+
+ // Step 8.
+ Rooted<PropertyHashSet> overriddenKeys(cx, PropertyHashSet(cx));
+ if (!ISOFieldKeysToIgnore(cx, additionalKeys, overriddenKeys.get())) {
+ return false;
+ }
+
+ // Step 9.
+ Rooted<PlainObject*> merged(cx, NewPlainObjectWithProto(cx, nullptr));
+ if (!merged) {
+ return false;
+ }
+
+ // Steps 10-11.
+ //
+ // JSITER_HIDDEN doesn't need to be passed, because CopyDataProperties creates
+ // all properties as enumerable.
+ JS::RootedVector<PropertyKey> fieldsKeys(cx);
+ if (!GetPropertyKeys(cx, fieldsCopy, JSITER_OWNONLY | JSITER_SYMBOLS,
+ &fieldsKeys)) {
+ return false;
+ }
+
+ // Step 12.
+ Rooted<Value> propValue(cx);
+ for (size_t i = 0; i < fieldsKeys.length(); i++) {
+ Handle<PropertyKey> key = fieldsKeys[i];
+
+ // Step 12.a.
+ // FIXME: spec issue - unnecessary initialisation
+ // https://github.com/tc39/proposal-temporal/issues/2549
+
+ // Steps 12.b-c.
+ if (overriddenKeys.has(key)) {
+ if (!GetProperty(cx, additionalFieldsCopy, additionalFieldsCopy, key,
+ &propValue)) {
+ return false;
+ }
+
+ // Step 12.d. (Reordered)
+ if (propValue.isUndefined()) {
+ // The property can be undefined if the key is "month" or "monthCode".
+ MOZ_ASSERT(key.isAtom(cx->names().month) ||
+ key.isAtom(cx->names().monthCode));
+
+ continue;
+ }
+ } else {
+ if (!GetProperty(cx, fieldsCopy, fieldsCopy, key, &propValue)) {
+ return false;
+ }
+
+ // All properties of |fieldsCopy| have a non-undefined value.
+ MOZ_ASSERT(!propValue.isUndefined());
+ }
+
+ // Step 12.d.
+ if (!DefineDataProperty(cx, merged, key, propValue)) {
+ return false;
+ }
+ }
+
+ // Step 13.
+ if (!CopyDataProperties(cx, merged, additionalFieldsCopy)) {
+ return false;
+ }
+
+ // Step 14.
+ args.rval().setObject(*merged);
+ return true;
+}
+
+/**
+ * Temporal.Calendar.prototype.mergeFields ( fields, additionalFields )
+ */
+static bool Calendar_mergeFields(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_mergeFields>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.toString ( )
+ */
+static bool Calendar_toString(JSContext* cx, const CallArgs& args) {
+ auto* calendar = &args.thisv().toObject().as<CalendarObject>();
+
+ // Step 3.
+ args.rval().setString(calendar->identifier());
+ return true;
+}
+
+/**
+ * Temporal.Calendar.prototype.toString ( )
+ */
+static bool Calendar_toString(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_toString>(cx, args);
+}
+
+/**
+ * Temporal.Calendar.prototype.toJSON ( )
+ */
+static bool Calendar_toJSON(JSContext* cx, const CallArgs& args) {
+ auto* calendar = &args.thisv().toObject().as<CalendarObject>();
+
+ // Step 3.
+ args.rval().setString(calendar->identifier());
+ return true;
+}
+
+/**
+ * Temporal.Calendar.prototype.toJSON ( )
+ */
+static bool Calendar_toJSON(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsCalendar, Calendar_toJSON>(cx, args);
+}
+
+const JSClass CalendarObject::class_ = {
+ "Temporal.Calendar",
+ JSCLASS_HAS_RESERVED_SLOTS(CalendarObject::SLOT_COUNT) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_Calendar),
+ JS_NULL_CLASS_OPS,
+ &CalendarObject::classSpec_,
+};
+
+const JSClass& CalendarObject::protoClass_ = PlainObject::class_;
+
+static const JSFunctionSpec Calendar_methods[] = {
+ JS_FN("from", Calendar_from, 1, 0),
+ JS_FS_END,
+};
+
+static const JSFunctionSpec Calendar_prototype_methods[] = {
+ JS_FN("dateFromFields", Calendar_dateFromFields, 1, 0),
+ JS_FN("yearMonthFromFields", Calendar_yearMonthFromFields, 1, 0),
+ JS_FN("monthDayFromFields", Calendar_monthDayFromFields, 1, 0),
+ JS_FN("dateAdd", Calendar_dateAdd, 2, 0),
+ JS_FN("dateUntil", Calendar_dateUntil, 2, 0),
+ JS_FN("year", Calendar_year, 1, 0),
+ JS_FN("month", Calendar_month, 1, 0),
+ JS_FN("monthCode", Calendar_monthCode, 1, 0),
+ JS_FN("day", Calendar_day, 1, 0),
+ JS_FN("dayOfWeek", Calendar_dayOfWeek, 1, 0),
+ JS_FN("dayOfYear", Calendar_dayOfYear, 1, 0),
+ JS_FN("weekOfYear", Calendar_weekOfYear, 1, 0),
+ JS_FN("yearOfWeek", Calendar_yearOfWeek, 1, 0),
+ JS_FN("daysInWeek", Calendar_daysInWeek, 1, 0),
+ JS_FN("daysInMonth", Calendar_daysInMonth, 1, 0),
+ JS_FN("daysInYear", Calendar_daysInYear, 1, 0),
+ JS_FN("monthsInYear", Calendar_monthsInYear, 1, 0),
+ JS_FN("inLeapYear", Calendar_inLeapYear, 1, 0),
+ JS_FN("fields", Calendar_fields, 1, 0),
+ JS_FN("mergeFields", Calendar_mergeFields, 2, 0),
+ JS_FN("toString", Calendar_toString, 0, 0),
+ JS_FN("toJSON", Calendar_toJSON, 0, 0),
+ JS_FS_END,
+};
+
+static const JSPropertySpec Calendar_prototype_properties[] = {
+ JS_PSG("id", Calendar_id, 0),
+ JS_STRING_SYM_PS(toStringTag, "Temporal.Calendar", JSPROP_READONLY),
+ JS_PS_END,
+};
+
+const ClassSpec CalendarObject::classSpec_ = {
+ GenericCreateConstructor<CalendarConstructor, 1, gc::AllocKind::FUNCTION>,
+ GenericCreatePrototype<CalendarObject>,
+ Calendar_methods,
+ nullptr,
+ Calendar_prototype_methods,
+ Calendar_prototype_properties,
+ nullptr,
+ ClassSpec::DontDefineConstructor,
+};
+
+struct MOZ_STACK_CLASS CalendarNameAndNative final {
+ PropertyName* name;
+ JSNative native;
+};
+
+static CalendarNameAndNative GetCalendarNameAndNative(JSContext* cx,
+ CalendarField fieldName) {
+ switch (fieldName) {
+ case CalendarField::Year:
+ return {cx->names().year, Calendar_year};
+ case CalendarField::Month:
+ return {cx->names().month, Calendar_month};
+ case CalendarField::MonthCode:
+ return {cx->names().monthCode, Calendar_monthCode};
+ case CalendarField::Day:
+ return {cx->names().day, Calendar_day};
+ }
+ MOZ_CRASH("invalid temporal field name");
+}
+
+bool js::temporal::IsBuiltinAccess(
+ JSContext* cx, Handle<CalendarObject*> calendar,
+ std::initializer_list<CalendarField> fieldNames) {
+ // Don't optimize when the object has any own properties which may shadow the
+ // built-in methods.
+ if (!calendar->empty()) {
+ return false;
+ }
+
+ JSObject* proto = cx->global()->maybeGetPrototype(JSProto_Calendar);
+
+ // 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 (calendar->staticPrototype() != proto) {
+ return false;
+ }
+
+ auto* nproto = &proto->as<NativeObject>();
+ for (auto fieldName : fieldNames) {
+ auto [name, native] = GetCalendarNameAndNative(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;
+ }
+ }
+
+ // TODO: Pass accessor list from caller to avoid excessive checks.
+
+ // Additionally check the various calendar fields operations.
+ for (const auto& [name, native] : (CalendarNameAndNative[]){
+ {cx->names().fields, Calendar_fields},
+ {cx->names().mergeFields, Calendar_mergeFields},
+ {cx->names().dateFromFields, Calendar_dateFromFields},
+ {cx->names().monthDayFromFields, Calendar_monthDayFromFields},
+ {cx->names().yearMonthFromFields, Calendar_yearMonthFromFields},
+ }) {
+ 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;
+ }
+ }
+
+ // CalendarFields observably uses array iteration.
+ bool arrayIterationSane;
+ if (!IsArrayIterationSane(cx, &arrayIterationSane)) {
+ cx->recoverFromOutOfMemory();
+ return false;
+ }
+ if (!arrayIterationSane) {
+ return false;
+ }
+
+ // Success! The access can be optimized.
+ return true;
+}
diff --git a/js/src/builtin/temporal/Calendar.h b/js/src/builtin/temporal/Calendar.h
new file mode 100644
index 0000000000..f80f528d83
--- /dev/null
+++ b/js/src/builtin/temporal/Calendar.h
@@ -0,0 +1,956 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_Calendar_h
+#define builtin_temporal_Calendar_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/EnumSet.h"
+
+#include <initializer_list>
+#include <stdint.h>
+
+#include "builtin/temporal/Wrapped.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "vm/NativeObject.h"
+#include "vm/StringType.h"
+
+class JS_PUBLIC_API JSTracer;
+
+namespace js {
+struct ClassSpec;
+class PlainObject;
+} // namespace js
+
+namespace js::temporal {
+
+class CalendarObject : public NativeObject {
+ public:
+ static const JSClass class_;
+ static const JSClass& protoClass_;
+
+ static constexpr uint32_t IDENTIFIER_SLOT = 0;
+ static constexpr uint32_t SLOT_COUNT = 1;
+
+ JSLinearString* identifier() const {
+ return &getFixedSlot(IDENTIFIER_SLOT).toString()->asLinear();
+ }
+
+ private:
+ static const ClassSpec classSpec_;
+};
+
+/**
+ * Calendar value, which is either a string containing a canonical calendar
+ * identifier or an object.
+ */
+class CalendarValue final {
+ JS::Value value_{};
+
+ public:
+ /**
+ * Default initialize this CalendarValue.
+ */
+ CalendarValue() = default;
+
+ /**
+ * Default initialize this CalendarValue.
+ */
+ explicit CalendarValue(const JS::Value& value) : value_(value) {
+ MOZ_ASSERT(value.isString() || value.isObject());
+ MOZ_ASSERT_IF(value.isString(), value.toString()->isLinear());
+ }
+
+ /**
+ * Initialize this CalendarValue with a canonical calendar identifier.
+ */
+ explicit CalendarValue(JSLinearString* calendarId)
+ : value_(JS::StringValue(calendarId)) {}
+
+ /**
+ * Initialize this CalendarValue with a calendar object.
+ */
+ explicit CalendarValue(JSObject* calendar)
+ : value_(JS::ObjectValue(*calendar)) {}
+
+ /**
+ * Return true iff this CalendarValue is initialized with either a canonical
+ * calendar identifier or a calendar object.
+ */
+ explicit operator bool() const { return !value_.isUndefined(); }
+
+ /**
+ * Return this CalendarValue as a JS::Value.
+ */
+ JS::Value toValue() const { return value_; }
+
+ /**
+ * Return true if this CalendarValue is a string.
+ */
+ bool isString() const { return value_.isString(); }
+
+ /**
+ * Return true if this CalendarValue is an object.
+ */
+ bool isObject() const { return value_.isObject(); }
+
+ /**
+ * Return the calendar identifier.
+ */
+ JSLinearString* toString() const { return &value_.toString()->asLinear(); }
+
+ /**
+ * Return the calendar object.
+ */
+ JSObject* toObject() const { return &value_.toObject(); }
+
+ void trace(JSTracer* trc);
+
+ JS::Value* valueDoNotUse() { return &value_; }
+ JS::Value const* valueDoNotUse() const { return &value_; }
+};
+
+enum class CalendarMethod {
+ DateAdd,
+ DateFromFields,
+ DateUntil,
+ Day,
+ Fields,
+ MergeFields,
+ MonthDayFromFields,
+ YearMonthFromFields,
+};
+
+class CalendarRecord {
+ CalendarValue receiver_;
+
+ // Null unless non-builtin calendar methods are used.
+ JSObject* dateAdd_ = nullptr;
+ JSObject* dateFromFields_ = nullptr;
+ JSObject* dateUntil_ = nullptr;
+ JSObject* day_ = nullptr;
+ JSObject* fields_ = nullptr;
+ JSObject* mergeFields_ = nullptr;
+ JSObject* monthDayFromFields_ = nullptr;
+ JSObject* yearMonthFromFields_ = nullptr;
+
+#ifdef DEBUG
+ mozilla::EnumSet<CalendarMethod> lookedUp_{};
+#endif
+
+ public:
+ /**
+ * Default initialize this CalendarRecord.
+ */
+ CalendarRecord() = default;
+
+ explicit CalendarRecord(const CalendarValue& receiver)
+ : receiver_(receiver) {}
+
+ const auto& receiver() const { return receiver_; }
+ auto* dateAdd() const { return dateAdd_; }
+ auto* dateFromFields() const { return dateFromFields_; }
+ auto* dateUntil() const { return dateUntil_; }
+ auto* day() const { return day_; }
+ auto* fields() const { return fields_; }
+ auto* mergeFields() const { return mergeFields_; }
+ auto* monthDayFromFields() const { return monthDayFromFields_; }
+ auto* yearMonthFromFields() const { return yearMonthFromFields_; }
+
+#ifdef DEBUG
+ auto& lookedUp() const { return lookedUp_; }
+ auto& lookedUp() { return lookedUp_; }
+#endif
+
+ // Helper methods for (Mutable)WrappedPtrOperations.
+ auto* receiverDoNotUse() const { return &receiver_; }
+ auto* dateAddDoNotUse() const { return &dateAdd_; }
+ auto* dateAddDoNotUse() { return &dateAdd_; }
+ auto* dateFromFieldsDoNotUse() const { return &dateFromFields_; }
+ auto* dateFromFieldsDoNotUse() { return &dateFromFields_; }
+ auto* dateUntilDoNotUse() const { return &dateUntil_; }
+ auto* dateUntilDoNotUse() { return &dateUntil_; }
+ auto* dayDoNotUse() const { return &day_; }
+ auto* dayDoNotUse() { return &day_; }
+ auto* fieldsDoNotUse() const { return &fields_; }
+ auto* fieldsDoNotUse() { return &fields_; }
+ auto* mergeFieldsDoNotUse() const { return &mergeFields_; }
+ auto* mergeFieldsDoNotUse() { return &mergeFields_; }
+ auto* monthDayFromFieldsDoNotUse() const { return &monthDayFromFields_; }
+ auto* monthDayFromFieldsDoNotUse() { return &monthDayFromFields_; }
+ auto* yearMonthFromFieldsDoNotUse() const { return &yearMonthFromFields_; }
+ auto* yearMonthFromFieldsDoNotUse() { return &yearMonthFromFields_; }
+
+ // Trace implementation.
+ void trace(JSTracer* trc);
+};
+
+struct Duration;
+struct PlainDate;
+struct PlainDateTime;
+class DurationObject;
+class PlainDateObject;
+class PlainDateTimeObject;
+class PlainMonthDayObject;
+class PlainYearMonthObject;
+enum class CalendarOption;
+enum class TemporalUnit;
+
+/**
+ * ISODaysInYear ( year )
+ */
+int32_t ISODaysInYear(int32_t year);
+
+/**
+ * ISODaysInMonth ( year, month )
+ */
+int32_t ISODaysInMonth(int32_t year, int32_t month);
+
+/**
+ * ISODaysInMonth ( year, month )
+ */
+int32_t ISODaysInMonth(double year, int32_t month);
+
+/**
+ * ToISODayOfYear ( year, month, day )
+ */
+int32_t ToISODayOfYear(const PlainDate& date);
+
+/**
+ * 21.4.1.12 MakeDay ( year, month, date )
+ */
+int32_t MakeDay(const PlainDate& date);
+
+/**
+ * 21.4.1.13 MakeDate ( day, time )
+ */
+int64_t MakeDate(const PlainDateTime& dateTime);
+
+/**
+ * 21.4.1.13 MakeDate ( day, time )
+ */
+int64_t MakeDate(int32_t year, int32_t month, int32_t day);
+
+/**
+ * Return the case-normalized calendar identifier if |id| is a built-in calendar
+ * identifier. Otherwise throws a RangeError.
+ */
+bool ToBuiltinCalendar(JSContext* cx, JS::Handle<JSString*> id,
+ JS::MutableHandle<CalendarValue> result);
+
+/**
+ * ToTemporalCalendarSlotValue ( temporalCalendarLike [ , default ] )
+ */
+bool ToTemporalCalendar(JSContext* cx,
+ JS::Handle<JS::Value> temporalCalendarLike,
+ JS::MutableHandle<CalendarValue> result);
+
+/**
+ * ToTemporalCalendarSlotValue ( temporalCalendarLike [ , default ] )
+ */
+bool ToTemporalCalendarWithISODefault(
+ JSContext* cx, JS::Handle<JS::Value> temporalCalendarLike,
+ JS::MutableHandle<CalendarValue> result);
+
+/**
+ * GetTemporalCalendarWithISODefault ( item )
+ */
+bool GetTemporalCalendarWithISODefault(JSContext* cx,
+ JS::Handle<JSObject*> item,
+ JS::MutableHandle<CalendarValue> result);
+
+/**
+ * ToTemporalCalendarIdentifier ( calendarSlotValue )
+ */
+JSString* ToTemporalCalendarIdentifier(JSContext* cx,
+ JS::Handle<CalendarValue> calendar);
+
+/**
+ * ToTemporalCalendarObject ( calendarSlotValue )
+ */
+JSObject* ToTemporalCalendarObject(JSContext* cx,
+ JS::Handle<CalendarValue> calendar);
+
+enum class CalendarField {
+ Year,
+ Month,
+ MonthCode,
+ Day,
+};
+
+using CalendarFieldNames = JS::StackGCVector<JS::PropertyKey>;
+
+/**
+ * CalendarFields ( calendarRec, fieldNames )
+ */
+bool CalendarFields(JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ std::initializer_list<CalendarField> fieldNames,
+ JS::MutableHandle<CalendarFieldNames> result);
+
+/**
+ * CalendarMergeFields ( calendarRec, fields, additionalFields )
+ */
+JSObject* CalendarMergeFields(JSContext* cx,
+ JS::Handle<CalendarRecord> calendar,
+ JS::Handle<PlainObject*> fields,
+ JS::Handle<PlainObject*> additionalFields);
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+Wrapped<PlainDateObject*> CalendarDateAdd(
+ JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<Wrapped<PlainDateObject*>> date, const Duration& duration);
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+Wrapped<PlainDateObject*> CalendarDateAdd(
+ JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<Wrapped<PlainDateObject*>> date, const Duration& duration,
+ JS::Handle<JSObject*> options);
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+Wrapped<PlainDateObject*> CalendarDateAdd(
+ JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<Wrapped<PlainDateObject*>> date,
+ JS::Handle<Wrapped<DurationObject*>> duration);
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+Wrapped<PlainDateObject*> CalendarDateAdd(
+ JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<Wrapped<PlainDateObject*>> date,
+ JS::Handle<Wrapped<DurationObject*>> duration,
+ JS::Handle<JSObject*> options);
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+bool CalendarDateAdd(JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ const PlainDate& date, const Duration& duration,
+ PlainDate* result);
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+bool CalendarDateAdd(JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ const PlainDate& date, const Duration& duration,
+ JS::Handle<JSObject*> options, PlainDate* result);
+
+/**
+ * CalendarDateAdd ( calendarRec, date, duration [ , options ] )
+ */
+bool CalendarDateAdd(JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<Wrapped<PlainDateObject*>> date,
+ const Duration& duration, PlainDate* result);
+
+/**
+ * CalendarDateUntil ( calendarRec, one, two, options )
+ */
+bool CalendarDateUntil(JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<Wrapped<PlainDateObject*>> one,
+ JS::Handle<Wrapped<PlainDateObject*>> two,
+ JS::Handle<PlainObject*> options, Duration* result);
+
+/**
+ * CalendarDateUntil ( calendarRec, one, two, options )
+ */
+bool CalendarDateUntil(JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<Wrapped<PlainDateObject*>> one,
+ JS::Handle<Wrapped<PlainDateObject*>> two,
+ TemporalUnit largestUnit, Duration* result);
+
+/**
+ * CalendarYear ( calendar, dateLike )
+ */
+bool CalendarYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarYear ( calendar, dateLike )
+ */
+bool CalendarYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateTimeObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarYear ( calendar, dateLike )
+ */
+bool CalendarYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainYearMonthObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarYear ( calendar, dateLike )
+ */
+bool CalendarYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarMonth ( calendar, dateLike )
+ */
+bool CalendarMonth(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarMonth ( calendar, dateLike )
+ */
+bool CalendarMonth(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateTimeObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarMonth ( calendar, dateLike )
+ */
+bool CalendarMonth(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainYearMonthObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarMonth ( calendar, dateLike )
+ */
+bool CalendarMonth(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarMonthCode ( calendar, dateLike )
+ */
+bool CalendarMonthCode(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarMonthCode ( calendar, dateLike )
+ */
+bool CalendarMonthCode(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateTimeObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarMonthCode ( calendar, dateLike )
+ */
+bool CalendarMonthCode(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainMonthDayObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarMonthCode ( calendar, dateLike )
+ */
+bool CalendarMonthCode(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainYearMonthObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarMonthCode ( calendar, dateLike )
+ */
+bool CalendarMonthCode(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDay ( calendarRec, dateLike )
+ */
+bool CalendarDay(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDay ( calendarRec, dateLike )
+ */
+bool CalendarDay(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateTimeObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDay ( calendarRec, dateLike )
+ */
+bool CalendarDay(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainMonthDayObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDay ( calendarRec, dateLike )
+ */
+bool CalendarDay(JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ const PlainDate& date, JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDay ( calendarRec, dateLike )
+ */
+bool CalendarDay(JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ const PlainDateTime& dateTime,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDayOfWeek ( calendar, dateLike )
+ */
+bool CalendarDayOfWeek(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDayOfWeek ( calendar, dateLike )
+ */
+bool CalendarDayOfWeek(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateTimeObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDayOfWeek ( calendar, dateLike )
+ */
+bool CalendarDayOfWeek(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDayOfYear ( calendar, dateLike )
+ */
+bool CalendarDayOfYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDayOfYear ( calendar, dateLike )
+ */
+bool CalendarDayOfYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateTimeObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDayOfYear ( calendar, dateLike )
+ */
+bool CalendarDayOfYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarWeekOfYear ( calendar, dateLike )
+ */
+bool CalendarWeekOfYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarWeekOfYear ( calendar, dateLike )
+ */
+bool CalendarWeekOfYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateTimeObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarWeekOfYear ( calendar, dateLike )
+ */
+bool CalendarWeekOfYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarYearOfWeek ( calendar, dateLike )
+ */
+bool CalendarYearOfWeek(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarYearOfWeek ( calendar, dateLike )
+ */
+bool CalendarYearOfWeek(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateTimeObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarYearOfWeek ( calendar, dateLike )
+ */
+bool CalendarYearOfWeek(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDaysInWeek ( calendar, dateLike )
+ */
+bool CalendarDaysInWeek(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDaysInWeek ( calendar, dateLike )
+ */
+bool CalendarDaysInWeek(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateTimeObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDaysInWeek ( calendar, dateLike )
+ */
+bool CalendarDaysInWeek(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDaysInMonth ( calendar, dateLike )
+ */
+bool CalendarDaysInMonth(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDaysInMonth ( calendar, dateLike )
+ */
+bool CalendarDaysInMonth(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateTimeObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDaysInMonth ( calendar, dateLike )
+ */
+bool CalendarDaysInMonth(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainYearMonthObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDaysInMonth ( calendar, dateLike )
+ */
+bool CalendarDaysInMonth(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDaysInYear ( calendar, dateLike )
+ */
+bool CalendarDaysInYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDaysInYear ( calendar, dateLike )
+ */
+bool CalendarDaysInYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateTimeObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDaysInYear ( calendar, dateLike )
+ */
+bool CalendarDaysInYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainYearMonthObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDaysInYear ( calendar, dateLike )
+ */
+bool CalendarDaysInYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarMonthsInYear ( calendar, dateLike )
+ */
+bool CalendarMonthsInYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarMonthsInYear ( calendar, dateLike )
+ */
+bool CalendarMonthsInYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateTimeObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarMonthsInYear ( calendar, dateLike )
+ */
+bool CalendarMonthsInYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainYearMonthObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarMonthsInYear ( calendar, dateLike )
+ */
+bool CalendarMonthsInYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarInLeapYear ( calendar, dateLike )
+ */
+bool CalendarInLeapYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarInLeapYear ( calendar, dateLike )
+ */
+bool CalendarInLeapYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainDateTimeObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarInLeapYear ( calendar, dateLike )
+ */
+bool CalendarInLeapYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ JS::Handle<PlainYearMonthObject*> dateLike,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarInLeapYear ( calendar, dateLike )
+ */
+bool CalendarInLeapYear(JSContext* cx, JS::Handle<CalendarValue> calendar,
+ const PlainDateTime& dateTime,
+ JS::MutableHandle<JS::Value> result);
+
+/**
+ * CalendarDateFromFields ( calendarRec, fields [ , options ] )
+ */
+Wrapped<PlainDateObject*> CalendarDateFromFields(
+ JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<PlainObject*> fields);
+
+/**
+ * CalendarDateFromFields ( calendarRec, fields [ , options ] )
+ */
+Wrapped<PlainDateObject*> CalendarDateFromFields(
+ JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<PlainObject*> fields, JS::Handle<PlainObject*> options);
+
+/**
+ * CalendarYearMonthFromFields ( calendarRec, fields [ , options ] )
+ */
+Wrapped<PlainYearMonthObject*> CalendarYearMonthFromFields(
+ JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<PlainObject*> fields);
+
+/**
+ * CalendarYearMonthFromFields ( calendarRec, fields [ , options ] )
+ */
+Wrapped<PlainYearMonthObject*> CalendarYearMonthFromFields(
+ JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<PlainYearMonthObject*> fields);
+
+/**
+ * CalendarYearMonthFromFields ( calendarRec, fields [ , options ] )
+ */
+Wrapped<PlainYearMonthObject*> CalendarYearMonthFromFields(
+ JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<PlainObject*> fields, JS::Handle<PlainObject*> options);
+
+/**
+ * CalendarMonthDayFromFields ( calendarRec, fields [ , options ] )
+ */
+Wrapped<PlainMonthDayObject*> CalendarMonthDayFromFields(
+ JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<PlainObject*> fields);
+
+/**
+ * CalendarMonthDayFromFields ( calendarRec, fields [ , options ] )
+ */
+Wrapped<PlainMonthDayObject*> CalendarMonthDayFromFields(
+ JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<PlainMonthDayObject*> fields);
+
+/**
+ * CalendarMonthDayFromFields ( calendarRec, fields [ , options ] )
+ */
+Wrapped<PlainMonthDayObject*> CalendarMonthDayFromFields(
+ JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<PlainObject*> fields, JS::Handle<PlainObject*> options);
+
+/**
+ * CalendarEquals ( one, two )
+ */
+bool CalendarEquals(JSContext* cx, JS::Handle<CalendarValue> one,
+ JS::Handle<CalendarValue> two, bool* equals);
+
+/**
+ * CalendarEquals ( one, two )
+ */
+bool CalendarEqualsOrThrow(JSContext* cx, JS::Handle<CalendarValue> one,
+ JS::Handle<CalendarValue> two);
+
+/**
+ * ConsolidateCalendars ( one, two )
+ */
+bool ConsolidateCalendars(JSContext* cx, JS::Handle<CalendarValue> one,
+ JS::Handle<CalendarValue> two,
+ JS::MutableHandle<CalendarValue> result);
+
+/**
+ * CreateCalendarMethodsRecord ( calendar, methods )
+ */
+bool CreateCalendarMethodsRecord(JSContext* cx,
+ JS::Handle<CalendarValue> calendar,
+ mozilla::EnumSet<CalendarMethod> methods,
+ JS::MutableHandle<CalendarRecord> result);
+
+#ifdef DEBUG
+/**
+ * CalendarMethodsRecordHasLookedUp ( calendarRec, methodName )
+ */
+inline bool CalendarMethodsRecordHasLookedUp(const CalendarRecord& calendar,
+ CalendarMethod methodName) {
+ // Steps 1-10.
+ return calendar.lookedUp().contains(methodName);
+}
+#endif
+
+/**
+ * CalendarMethodsRecordIsBuiltin ( calendarRec )
+ */
+inline bool CalendarMethodsRecordIsBuiltin(const CalendarRecord& calendar) {
+ // Steps 1-2.
+ return calendar.receiver().isString();
+}
+
+/**
+ * Return true when accessing the calendar fields |fieldNames| can be optimized.
+ * Otherwise returns false.
+ */
+bool IsBuiltinAccess(JSContext* cx, JS::Handle<CalendarObject*> calendar,
+ std::initializer_list<CalendarField> fieldNames);
+
+// Helper for MutableWrappedPtrOperations.
+bool WrapCalendarValue(JSContext* cx, JS::MutableHandle<JS::Value> calendar);
+
+} /* namespace js::temporal */
+
+namespace js {
+
+template <typename Wrapper>
+class WrappedPtrOperations<temporal::CalendarValue, Wrapper> {
+ const auto& container() const {
+ return static_cast<const Wrapper*>(this)->get();
+ }
+
+ public:
+ explicit operator bool() const { return bool(container()); }
+
+ JS::Handle<JS::Value> toValue() const {
+ return JS::Handle<JS::Value>::fromMarkedLocation(
+ container().valueDoNotUse());
+ }
+
+ bool isString() const { return container().isString(); }
+
+ bool isObject() const { return container().isObject(); }
+
+ JSLinearString* toString() const { return container().toString(); }
+
+ JSObject* toObject() const { return container().toObject(); }
+};
+
+template <typename Wrapper>
+class MutableWrappedPtrOperations<temporal::CalendarValue, Wrapper>
+ : public WrappedPtrOperations<temporal::CalendarValue, Wrapper> {
+ auto& container() { return static_cast<Wrapper*>(this)->get(); }
+
+ JS::MutableHandle<JS::Value> toMutableValue() {
+ return JS::MutableHandle<JS::Value>::fromMarkedLocation(
+ container().valueDoNotUse());
+ }
+
+ public:
+ bool wrap(JSContext* cx) {
+ return temporal::WrapCalendarValue(cx, toMutableValue());
+ }
+};
+
+template <typename Wrapper>
+class WrappedPtrOperations<temporal::CalendarRecord, Wrapper> {
+ const auto& container() const {
+ return static_cast<const Wrapper*>(this)->get();
+ }
+
+ public:
+ JS::Handle<temporal::CalendarValue> receiver() const {
+ return JS::Handle<temporal::CalendarValue>::fromMarkedLocation(
+ container().receiverDoNotUse());
+ }
+
+ JS::Handle<JSObject*> dateAdd() const {
+ return JS::Handle<JSObject*>::fromMarkedLocation(
+ container().dateAddDoNotUse());
+ }
+ JS::Handle<JSObject*> dateFromFields() const {
+ return JS::Handle<JSObject*>::fromMarkedLocation(
+ container().dateFromFieldsDoNotUse());
+ }
+ JS::Handle<JSObject*> dateUntil() const {
+ return JS::Handle<JSObject*>::fromMarkedLocation(
+ container().dateUntilDoNotUse());
+ }
+ JS::Handle<JSObject*> day() const {
+ return JS::Handle<JSObject*>::fromMarkedLocation(container().dayDoNotUse());
+ }
+ JS::Handle<JSObject*> fields() const {
+ return JS::Handle<JSObject*>::fromMarkedLocation(
+ container().fieldsDoNotUse());
+ }
+ JS::Handle<JSObject*> mergeFields() const {
+ return JS::Handle<JSObject*>::fromMarkedLocation(
+ container().mergeFieldsDoNotUse());
+ }
+ JS::Handle<JSObject*> monthDayFromFields() const {
+ return JS::Handle<JSObject*>::fromMarkedLocation(
+ container().monthDayFromFieldsDoNotUse());
+ }
+ JS::Handle<JSObject*> yearMonthFromFields() const {
+ return JS::Handle<JSObject*>::fromMarkedLocation(
+ container().yearMonthFromFieldsDoNotUse());
+ }
+};
+
+template <typename Wrapper>
+class MutableWrappedPtrOperations<temporal::CalendarRecord, Wrapper>
+ : public WrappedPtrOperations<temporal::CalendarRecord, Wrapper> {
+ auto& container() { return static_cast<Wrapper*>(this)->get(); }
+
+ public:
+ JS::MutableHandle<JSObject*> dateAdd() {
+ return JS::MutableHandle<JSObject*>::fromMarkedLocation(
+ container().dateAddDoNotUse());
+ }
+ JS::MutableHandle<JSObject*> dateFromFields() {
+ return JS::MutableHandle<JSObject*>::fromMarkedLocation(
+ container().dateFromFieldsDoNotUse());
+ }
+ JS::MutableHandle<JSObject*> dateUntil() {
+ return JS::MutableHandle<JSObject*>::fromMarkedLocation(
+ container().dateUntilDoNotUse());
+ }
+ JS::MutableHandle<JSObject*> day() {
+ return JS::MutableHandle<JSObject*>::fromMarkedLocation(
+ container().dayDoNotUse());
+ }
+ JS::MutableHandle<JSObject*> fields() {
+ return JS::MutableHandle<JSObject*>::fromMarkedLocation(
+ container().fieldsDoNotUse());
+ }
+ JS::MutableHandle<JSObject*> mergeFields() {
+ return JS::MutableHandle<JSObject*>::fromMarkedLocation(
+ container().mergeFieldsDoNotUse());
+ }
+ JS::MutableHandle<JSObject*> monthDayFromFields() {
+ return JS::MutableHandle<JSObject*>::fromMarkedLocation(
+ container().monthDayFromFieldsDoNotUse());
+ }
+ JS::MutableHandle<JSObject*> yearMonthFromFields() {
+ return JS::MutableHandle<JSObject*>::fromMarkedLocation(
+ container().yearMonthFromFieldsDoNotUse());
+ }
+};
+
+} /* namespace js */
+
+#endif /* builtin_temporal_Calendar_h */
diff --git a/js/src/builtin/temporal/Duration.cpp b/js/src/builtin/temporal/Duration.cpp
new file mode 100644
index 0000000000..7e922aa68b
--- /dev/null
+++ b/js/src/builtin/temporal/Duration.cpp
@@ -0,0 +1,6802 @@
+/* -*- 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/Duration.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/EnumSet.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 "NamespaceImports.h"
+
+#include "builtin/temporal/Calendar.h"
+#include "builtin/temporal/Instant.h"
+#include "builtin/temporal/PlainDate.h"
+#include "builtin/temporal/PlainDateTime.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/Wrapped.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/Conversions.h"
+#include "js/ErrorReport.h"
+#include "js/friend/ErrorMessages.h"
+#include "js/GCVector.h"
+#include "js/Id.h"
+#include "js/Printer.h"
+#include "js/PropertyDescriptor.h"
+#include "js/PropertySpec.h"
+#include "js/RootingAPI.h"
+#include "js/Value.h"
+#include "util/StringBuffer.h"
+#include "vm/BigIntType.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/GlobalObject.h"
+#include "vm/JSAtomState.h"
+#include "vm/JSContext.h"
+#include "vm/JSObject.h"
+#include "vm/ObjectOperations.h"
+#include "vm/PlainObject.h"
+#include "vm/StringType.h"
+
+#include "vm/JSObject-inl.h"
+#include "vm/NativeObject-inl.h"
+#include "vm/ObjectOperations-inl.h"
+
+using namespace js;
+using namespace js::temporal;
+
+static inline bool IsDuration(Handle<Value> v) {
+ return v.isObject() && v.toObject().is<DurationObject>();
+}
+
+#ifdef DEBUG
+static bool IsIntegerOrInfinity(double d) {
+ return IsInteger(d) || std::isinf(d);
+}
+
+static bool IsIntegerOrInfinityDuration(const Duration& duration) {
+ auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
+ microseconds, nanoseconds] = duration;
+
+ // Integers exceeding the Number range are represented as infinity.
+
+ return IsIntegerOrInfinity(years) && IsIntegerOrInfinity(months) &&
+ IsIntegerOrInfinity(weeks) && IsIntegerOrInfinity(days) &&
+ IsIntegerOrInfinity(hours) && IsIntegerOrInfinity(minutes) &&
+ IsIntegerOrInfinity(seconds) && IsIntegerOrInfinity(milliseconds) &&
+ IsIntegerOrInfinity(microseconds) && IsIntegerOrInfinity(nanoseconds);
+}
+
+static bool IsIntegerDuration(const Duration& duration) {
+ auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
+ microseconds, nanoseconds] = duration;
+
+ return IsInteger(years) && IsInteger(months) && IsInteger(weeks) &&
+ IsInteger(days) && IsInteger(hours) && IsInteger(minutes) &&
+ IsInteger(seconds) && IsInteger(milliseconds) &&
+ IsInteger(microseconds) && IsInteger(nanoseconds);
+}
+#endif
+
+/**
+ * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds )
+ */
+int32_t js::temporal::DurationSign(const Duration& duration) {
+ MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
+
+ auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
+ microseconds, nanoseconds] = duration;
+
+ // Step 1.
+ for (auto v : {years, months, weeks, days, hours, minutes, seconds,
+ milliseconds, microseconds, nanoseconds}) {
+ // Step 1.a.
+ if (v < 0) {
+ return -1;
+ }
+
+ // Step 1.b.
+ if (v > 0) {
+ return 1;
+ }
+ }
+
+ // Step 2.
+ return 0;
+}
+
+/**
+ * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds )
+ */
+bool js::temporal::IsValidDuration(const Duration& duration) {
+ MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
+
+ auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
+ microseconds, nanoseconds] = duration;
+
+ // Step 1.
+ int32_t sign = DurationSign(duration);
+
+ // Step 2.
+ for (auto v : {years, months, weeks, days, hours, minutes, seconds,
+ milliseconds, microseconds, nanoseconds}) {
+ // Step 2.a.
+ if (!std::isfinite(v)) {
+ return false;
+ }
+
+ // Step 2.b.
+ if (v < 0 && sign > 0) {
+ return false;
+ }
+
+ // Step 2.c.
+ if (v > 0 && sign < 0) {
+ return false;
+ }
+ }
+
+ // Step 3.
+ return true;
+}
+
+/**
+ * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds )
+ */
+bool js::temporal::ThrowIfInvalidDuration(JSContext* cx,
+ const Duration& duration) {
+ MOZ_ASSERT(IsIntegerOrInfinityDuration(duration));
+
+ auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
+ microseconds, nanoseconds] = duration;
+
+ // Step 1.
+ int32_t sign = DurationSign(duration);
+
+ auto report = [&](double v, const char* name, unsigned errorNumber) {
+ ToCStringBuf cbuf;
+ const char* numStr = NumberToCString(&cbuf, v);
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, errorNumber, name,
+ numStr);
+ };
+
+ auto throwIfInvalid = [&](double v, const char* name) {
+ // Step 2.a.
+ if (!std::isfinite(v)) {
+ report(v, name, JSMSG_TEMPORAL_DURATION_INVALID_NON_FINITE);
+ return false;
+ }
+
+ // Steps 2.b-c.
+ if ((v < 0 && sign > 0) || (v > 0 && sign < 0)) {
+ report(v, name, JSMSG_TEMPORAL_DURATION_INVALID_SIGN);
+ return false;
+ }
+
+ return true;
+ };
+
+ // Step 2.
+ if (!throwIfInvalid(years, "years")) {
+ return false;
+ }
+ if (!throwIfInvalid(months, "months")) {
+ return false;
+ }
+ if (!throwIfInvalid(weeks, "weeks")) {
+ return false;
+ }
+ if (!throwIfInvalid(days, "days")) {
+ return false;
+ }
+ if (!throwIfInvalid(hours, "hours")) {
+ return false;
+ }
+ if (!throwIfInvalid(minutes, "minutes")) {
+ return false;
+ }
+ if (!throwIfInvalid(seconds, "seconds")) {
+ return false;
+ }
+ if (!throwIfInvalid(milliseconds, "milliseconds")) {
+ return false;
+ }
+ if (!throwIfInvalid(microseconds, "microseconds")) {
+ return false;
+ }
+ if (!throwIfInvalid(nanoseconds, "nanoseconds")) {
+ return false;
+ }
+
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ // Step 3.
+ return true;
+}
+
+/**
+ * DefaultTemporalLargestUnit ( years, months, weeks, days, hours, minutes,
+ * seconds, milliseconds, microseconds )
+ */
+static TemporalUnit DefaultTemporalLargestUnit(const Duration& duration) {
+ MOZ_ASSERT(IsIntegerDuration(duration));
+
+ // Step 1.
+ if (duration.years != 0) {
+ return TemporalUnit::Year;
+ }
+
+ // Step 2.
+ if (duration.months != 0) {
+ return TemporalUnit::Month;
+ }
+
+ // Step 3.
+ if (duration.weeks != 0) {
+ return TemporalUnit::Week;
+ }
+
+ // Step 4.
+ if (duration.days != 0) {
+ return TemporalUnit::Day;
+ }
+
+ // Step 5.
+ if (duration.hours != 0) {
+ return TemporalUnit::Hour;
+ }
+
+ // Step 6.
+ if (duration.minutes != 0) {
+ return TemporalUnit::Minute;
+ }
+
+ // Step 7.
+ if (duration.seconds != 0) {
+ return TemporalUnit::Second;
+ }
+
+ // Step 8.
+ if (duration.milliseconds != 0) {
+ return TemporalUnit::Millisecond;
+ }
+
+ // Step 9.
+ if (duration.microseconds != 0) {
+ return TemporalUnit::Microsecond;
+ }
+
+ // Step 10.
+ return TemporalUnit::Nanosecond;
+}
+
+/**
+ * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds [ , newTarget ] )
+ */
+static DurationObject* CreateTemporalDuration(JSContext* cx,
+ const CallArgs& args,
+ const Duration& duration) {
+ auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
+ microseconds, nanoseconds] = duration;
+
+ // Step 1.
+ if (!ThrowIfInvalidDuration(cx, duration)) {
+ return nullptr;
+ }
+
+ // Steps 2-3.
+ Rooted<JSObject*> proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Duration, &proto)) {
+ return nullptr;
+ }
+
+ auto* object = NewObjectWithClassProto<DurationObject>(cx, proto);
+ if (!object) {
+ return nullptr;
+ }
+
+ // Steps 4-13.
+ // Add zero to convert -0 to +0.
+ object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
+ object->setFixedSlot(DurationObject::MONTHS_SLOT,
+ NumberValue(months + (+0.0)));
+ object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
+ object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
+ object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
+ object->setFixedSlot(DurationObject::MINUTES_SLOT,
+ NumberValue(minutes + (+0.0)));
+ object->setFixedSlot(DurationObject::SECONDS_SLOT,
+ NumberValue(seconds + (+0.0)));
+ object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
+ NumberValue(milliseconds + (+0.0)));
+ object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
+ NumberValue(microseconds + (+0.0)));
+ object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
+ NumberValue(nanoseconds + (+0.0)));
+
+ // Step 14.
+ return object;
+}
+
+/**
+ * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds [ , newTarget ] )
+ */
+DurationObject* js::temporal::CreateTemporalDuration(JSContext* cx,
+ const Duration& duration) {
+ auto& [years, months, weeks, days, hours, minutes, seconds, milliseconds,
+ microseconds, nanoseconds] = duration;
+
+ MOZ_ASSERT(IsInteger(years));
+ MOZ_ASSERT(IsInteger(months));
+ MOZ_ASSERT(IsInteger(weeks));
+ MOZ_ASSERT(IsInteger(days));
+ MOZ_ASSERT(IsInteger(hours));
+ MOZ_ASSERT(IsInteger(minutes));
+ MOZ_ASSERT(IsInteger(seconds));
+ MOZ_ASSERT(IsInteger(milliseconds));
+ MOZ_ASSERT(IsInteger(microseconds));
+ MOZ_ASSERT(IsInteger(nanoseconds));
+
+ // Step 1.
+ if (!ThrowIfInvalidDuration(cx, duration)) {
+ return nullptr;
+ }
+
+ // Steps 2-3.
+ auto* object = NewBuiltinClassInstance<DurationObject>(cx);
+ if (!object) {
+ return nullptr;
+ }
+
+ // Steps 4-13.
+ // Add zero to convert -0 to +0.
+ object->setFixedSlot(DurationObject::YEARS_SLOT, NumberValue(years + (+0.0)));
+ object->setFixedSlot(DurationObject::MONTHS_SLOT,
+ NumberValue(months + (+0.0)));
+ object->setFixedSlot(DurationObject::WEEKS_SLOT, NumberValue(weeks + (+0.0)));
+ object->setFixedSlot(DurationObject::DAYS_SLOT, NumberValue(days + (+0.0)));
+ object->setFixedSlot(DurationObject::HOURS_SLOT, NumberValue(hours + (+0.0)));
+ object->setFixedSlot(DurationObject::MINUTES_SLOT,
+ NumberValue(minutes + (+0.0)));
+ object->setFixedSlot(DurationObject::SECONDS_SLOT,
+ NumberValue(seconds + (+0.0)));
+ object->setFixedSlot(DurationObject::MILLISECONDS_SLOT,
+ NumberValue(milliseconds + (+0.0)));
+ object->setFixedSlot(DurationObject::MICROSECONDS_SLOT,
+ NumberValue(microseconds + (+0.0)));
+ object->setFixedSlot(DurationObject::NANOSECONDS_SLOT,
+ NumberValue(nanoseconds + (+0.0)));
+
+ // Step 14.
+ return object;
+}
+
+/**
+ * ToIntegerIfIntegral ( argument )
+ */
+static bool ToIntegerIfIntegral(JSContext* cx, const char* name,
+ Handle<Value> argument, double* num) {
+ // Step 1.
+ double d;
+ if (!JS::ToNumber(cx, argument, &d)) {
+ return false;
+ }
+
+ // Step 2.
+ if (!js::IsInteger(d)) {
+ ToCStringBuf cbuf;
+ const char* numStr = NumberToCString(&cbuf, d);
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
+ name);
+ return false;
+ }
+
+ // Step 3.
+ *num = d;
+ return true;
+}
+
+/**
+ * ToIntegerIfIntegral ( argument )
+ */
+static bool ToIntegerIfIntegral(JSContext* cx, Handle<PropertyName*> name,
+ Handle<Value> argument, double* result) {
+ // Step 1.
+ double d;
+ if (!JS::ToNumber(cx, argument, &d)) {
+ return false;
+ }
+
+ // Step 2.
+ if (!js::IsInteger(d)) {
+ if (auto nameStr = js::QuoteString(cx, name)) {
+ ToCStringBuf cbuf;
+ const char* numStr = NumberToCString(&cbuf, d);
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_NOT_INTEGER, numStr,
+ nameStr.get());
+ }
+ return false;
+ }
+
+ // Step 3.
+ *result = d;
+ return true;
+}
+
+/**
+ * ToTemporalPartialDurationRecord ( temporalDurationLike )
+ */
+static bool ToTemporalPartialDurationRecord(
+ JSContext* cx, Handle<JSObject*> temporalDurationLike, Duration* result) {
+ // Steps 1-3. (Not applicable in our implementation.)
+
+ Rooted<Value> value(cx);
+ bool any = false;
+
+ auto getDurationProperty = [&](Handle<PropertyName*> name, double* num) {
+ if (!GetProperty(cx, temporalDurationLike, temporalDurationLike, name,
+ &value)) {
+ return false;
+ }
+
+ if (!value.isUndefined()) {
+ any = true;
+
+ if (!ToIntegerIfIntegral(cx, name, value, num)) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ // Steps 4-23.
+ if (!getDurationProperty(cx->names().days, &result->days)) {
+ return false;
+ }
+ if (!getDurationProperty(cx->names().hours, &result->hours)) {
+ return false;
+ }
+ if (!getDurationProperty(cx->names().microseconds, &result->microseconds)) {
+ return false;
+ }
+ if (!getDurationProperty(cx->names().milliseconds, &result->milliseconds)) {
+ return false;
+ }
+ if (!getDurationProperty(cx->names().minutes, &result->minutes)) {
+ return false;
+ }
+ if (!getDurationProperty(cx->names().months, &result->months)) {
+ return false;
+ }
+ if (!getDurationProperty(cx->names().nanoseconds, &result->nanoseconds)) {
+ return false;
+ }
+ if (!getDurationProperty(cx->names().seconds, &result->seconds)) {
+ return false;
+ }
+ if (!getDurationProperty(cx->names().weeks, &result->weeks)) {
+ return false;
+ }
+ if (!getDurationProperty(cx->names().years, &result->years)) {
+ return false;
+ }
+
+ // Step 24.
+ if (!any) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_MISSING_UNIT);
+ return false;
+ }
+
+ // Step 25.
+ return true;
+}
+
+/**
+ * ToTemporalDurationRecord ( temporalDurationLike )
+ */
+bool js::temporal::ToTemporalDurationRecord(JSContext* cx,
+ Handle<Value> temporalDurationLike,
+ Duration* result) {
+ // Step 1.
+ if (!temporalDurationLike.isObject()) {
+ // Step 1.a.
+ if (!temporalDurationLike.isString()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
+ temporalDurationLike, nullptr, "not a string");
+ return false;
+ }
+ Rooted<JSString*> string(cx, temporalDurationLike.toString());
+
+ // Step 1.b.
+ return ParseTemporalDurationString(cx, string, result);
+ }
+
+ Rooted<JSObject*> durationLike(cx, &temporalDurationLike.toObject());
+
+ // Step 2.
+ if (auto* duration = durationLike->maybeUnwrapIf<DurationObject>()) {
+ *result = ToDuration(duration);
+ return true;
+ }
+
+ // Step 3.
+ Duration duration = {};
+
+ // Steps 4-14.
+ if (!ToTemporalPartialDurationRecord(cx, durationLike, &duration)) {
+ return false;
+ }
+
+ // Step 15.
+ if (!ThrowIfInvalidDuration(cx, duration)) {
+ return false;
+ }
+
+ // Step 16.
+ *result = duration;
+ return true;
+}
+
+/**
+ * ToTemporalDuration ( item )
+ */
+Wrapped<DurationObject*> js::temporal::ToTemporalDuration(JSContext* cx,
+ Handle<Value> item) {
+ // Step 1.
+ if (item.isObject()) {
+ JSObject* itemObj = &item.toObject();
+ if (itemObj->canUnwrapAs<DurationObject>()) {
+ return itemObj;
+ }
+ }
+
+ // Step 2.
+ Duration result;
+ if (!ToTemporalDurationRecord(cx, item, &result)) {
+ return nullptr;
+ }
+
+ // Step 3.
+ return CreateTemporalDuration(cx, result);
+}
+
+/**
+ * ToTemporalDuration ( item )
+ */
+bool js::temporal::ToTemporalDuration(JSContext* cx, Handle<Value> item,
+ Duration* result) {
+ auto obj = ToTemporalDuration(cx, item);
+ if (!obj) {
+ return false;
+ }
+
+ *result = ToDuration(&obj.unwrap());
+ return true;
+}
+
+/**
+ * DaysUntil ( earlier, later )
+ */
+int32_t js::temporal::DaysUntil(const PlainDate& earlier,
+ const PlainDate& later) {
+ MOZ_ASSERT(ISODateTimeWithinLimits(earlier));
+ MOZ_ASSERT(ISODateTimeWithinLimits(later));
+
+ // Steps 1-2.
+ int32_t epochDaysEarlier = MakeDay(earlier);
+ MOZ_ASSERT(std::abs(epochDaysEarlier) <= 100'000'000);
+
+ // Steps 3-4.
+ int32_t epochDaysLater = MakeDay(later);
+ MOZ_ASSERT(std::abs(epochDaysLater) <= 100'000'000);
+
+ // Step 5.
+ return epochDaysLater - epochDaysEarlier;
+}
+
+/**
+ * MoveRelativeDate ( calendarRec, relativeTo, duration )
+ */
+static bool MoveRelativeDate(
+ JSContext* cx, Handle<CalendarRecord> calendar,
+ Handle<Wrapped<PlainDateObject*>> relativeTo, const Duration& duration,
+ MutableHandle<Wrapped<PlainDateObject*>> relativeToResult,
+ int32_t* daysResult) {
+ auto* unwrappedRelativeTo = relativeTo.unwrap(cx);
+ if (!unwrappedRelativeTo) {
+ return false;
+ }
+ auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
+
+ // Step 1.
+ auto newDate = AddDate(cx, calendar, relativeTo, duration);
+ if (!newDate) {
+ return false;
+ }
+ auto later = ToPlainDate(&newDate.unwrap());
+ relativeToResult.set(newDate);
+
+ // Step 2.
+ *daysResult = DaysUntil(relativeToDate, later);
+ MOZ_ASSERT(std::abs(*daysResult) <= 200'000'000);
+
+ // Step 3.
+ return true;
+}
+
+/**
+ * MoveRelativeZonedDateTime ( zonedDateTime, calendarRec, timeZoneRec, years,
+ * months, weeks, days, precalculatedPlainDateTime )
+ */
+static bool MoveRelativeZonedDateTime(
+ JSContext* cx, Handle<ZonedDateTime> zonedDateTime,
+ Handle<CalendarRecord> calendar, Handle<TimeZoneRecord> timeZone,
+ const Duration& duration,
+ mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
+ MutableHandle<ZonedDateTime> result) {
+ // Step 1.
+ MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
+ timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
+
+ // Step 2.
+ MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
+ timeZone, TimeZoneMethod::GetPossibleInstantsFor));
+
+ // Step 3.
+ Instant intermediateNs;
+ if (precalculatedPlainDateTime) {
+ if (!AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
+ duration.date(), *precalculatedPlainDateTime,
+ &intermediateNs)) {
+ return false;
+ }
+ } else {
+ if (!AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
+ duration.date(), &intermediateNs)) {
+ return false;
+ }
+ }
+ MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
+
+ // Step 4.
+ result.set(ZonedDateTime{intermediateNs, zonedDateTime.timeZone(),
+ zonedDateTime.calendar()});
+ return true;
+}
+
+/**
+ * TotalDurationNanoseconds ( hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds )
+ */
+static mozilla::Maybe<int64_t> TotalDurationNanoseconds(
+ const Duration& duration) {
+ // Our implementation supports |duration.days| to avoid computing |days * 24|
+ // in the caller, which may not be representable as a double value.
+ int64_t days;
+ if (!mozilla::NumberEqualsInt64(duration.days, &days)) {
+ return mozilla::Nothing();
+ }
+ int64_t hours;
+ if (!mozilla::NumberEqualsInt64(duration.hours, &hours)) {
+ return mozilla::Nothing();
+ }
+ mozilla::CheckedInt64 result = days;
+ result *= 24;
+ result += hours;
+
+ // Step 1.
+ int64_t minutes;
+ if (!mozilla::NumberEqualsInt64(duration.minutes, &minutes)) {
+ return mozilla::Nothing();
+ }
+ result *= 60;
+ result += minutes;
+
+ // Step 2.
+ int64_t seconds;
+ if (!mozilla::NumberEqualsInt64(duration.seconds, &seconds)) {
+ return mozilla::Nothing();
+ }
+ result *= 60;
+ result += seconds;
+
+ // Step 3.
+ int64_t milliseconds;
+ if (!mozilla::NumberEqualsInt64(duration.milliseconds, &milliseconds)) {
+ return mozilla::Nothing();
+ }
+ result *= 1000;
+ result += milliseconds;
+
+ // Step 4.
+ int64_t microseconds;
+ if (!mozilla::NumberEqualsInt64(duration.microseconds, &microseconds)) {
+ return mozilla::Nothing();
+ }
+ result *= 1000;
+ result += microseconds;
+
+ // Step 5.
+ int64_t nanoseconds;
+ if (!mozilla::NumberEqualsInt64(duration.nanoseconds, &nanoseconds)) {
+ return mozilla::Nothing();
+ }
+ result *= 1000;
+ result += nanoseconds;
+
+ // Step 5 (Return).
+ if (!result.isValid()) {
+ return mozilla::Nothing();
+ }
+ return mozilla::Some(result.value());
+}
+
+/**
+ * TotalDurationNanoseconds ( hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds )
+ */
+static BigInt* TotalDurationNanosecondsSlow(JSContext* cx,
+ const Duration& duration) {
+ // Our implementation supports |duration.days| to avoid computing |days * 24|
+ // in the caller, which may not be representable as a double value.
+ Rooted<BigInt*> result(cx, BigInt::createFromDouble(cx, duration.days));
+ if (!result) {
+ return nullptr;
+ }
+
+ Rooted<BigInt*> temp(cx);
+ auto multiplyAdd = [&](int32_t factor, double number) {
+ temp = BigInt::createFromInt64(cx, factor);
+ if (!temp) {
+ return false;
+ }
+
+ result = BigInt::mul(cx, result, temp);
+ if (!result) {
+ return false;
+ }
+
+ temp = BigInt::createFromDouble(cx, number);
+ if (!temp) {
+ return false;
+ }
+
+ result = BigInt::add(cx, result, temp);
+ return !!result;
+ };
+
+ if (!multiplyAdd(24, duration.hours)) {
+ return nullptr;
+ }
+
+ // Step 1.
+ if (!multiplyAdd(60, duration.minutes)) {
+ return nullptr;
+ }
+
+ // Step 2.
+ if (!multiplyAdd(60, duration.seconds)) {
+ return nullptr;
+ }
+
+ // Step 3.
+ if (!multiplyAdd(1000, duration.milliseconds)) {
+ return nullptr;
+ }
+
+ // Step 4.
+ if (!multiplyAdd(1000, duration.microseconds)) {
+ return nullptr;
+ }
+
+ // Step 5.
+ if (!multiplyAdd(1000, duration.nanoseconds)) {
+ return nullptr;
+ }
+
+ // Step 5 (Return).
+ return result;
+}
+
+struct NanosecondsAndDays final {
+ int32_t days = 0;
+ int64_t nanoseconds = 0;
+};
+
+/**
+ * Split duration into full days and remainding nanoseconds.
+ */
+static ::NanosecondsAndDays NanosecondsToDays(int64_t nanoseconds) {
+ constexpr int64_t dayLengthNs = ToNanoseconds(TemporalUnit::Day);
+
+ static_assert(INT64_MAX / dayLengthNs <= INT32_MAX,
+ "days doesn't exceed INT32_MAX");
+
+ return {int32_t(nanoseconds / dayLengthNs), nanoseconds % dayLengthNs};
+}
+
+/**
+ * Split duration into full days and remainding nanoseconds.
+ */
+static bool NanosecondsToDaysSlow(
+ JSContext* cx, Handle<BigInt*> nanoseconds,
+ MutableHandle<temporal::NanosecondsAndDays> result) {
+ constexpr int64_t dayLengthNs = ToNanoseconds(TemporalUnit::Day);
+
+ Rooted<BigInt*> dayLength(cx, BigInt::createFromInt64(cx, dayLengthNs));
+ if (!dayLength) {
+ return false;
+ }
+
+ Rooted<BigInt*> days(cx);
+ Rooted<BigInt*> nanos(cx);
+ if (!BigInt::divmod(cx, nanoseconds, dayLength, &days, &nanos)) {
+ return false;
+ }
+
+ result.set(temporal::NanosecondsAndDays::from(
+ days, ToInstantSpan(nanos), InstantSpan::fromNanoseconds(dayLengthNs)));
+ return true;
+}
+
+/**
+ * Split duration into full days and remainding nanoseconds.
+ */
+static bool NanosecondsToDays(
+ JSContext* cx, const Duration& duration,
+ MutableHandle<temporal::NanosecondsAndDays> result) {
+ if (auto total = TotalDurationNanoseconds(duration.time())) {
+ auto nanosAndDays = ::NanosecondsToDays(*total);
+
+ result.set(temporal::NanosecondsAndDays::from(
+ nanosAndDays.days,
+ InstantSpan::fromNanoseconds(nanosAndDays.nanoseconds),
+ InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day))));
+ return true;
+ }
+
+ Rooted<BigInt*> nanoseconds(
+ cx, TotalDurationNanosecondsSlow(cx, duration.time()));
+ if (!nanoseconds) {
+ return false;
+ }
+
+ return ::NanosecondsToDaysSlow(cx, nanoseconds, result);
+}
+
+/**
+ * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ ,
+ * precalculatedPlainDateTime ] )
+ */
+static bool NanosecondsToDays(
+ JSContext* cx, const Duration& duration,
+ Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
+ MutableHandle<temporal::NanosecondsAndDays> result) {
+ if (auto total = TotalDurationNanoseconds(duration.time())) {
+ auto nanoseconds = InstantSpan::fromNanoseconds(*total);
+ MOZ_ASSERT(IsValidInstantSpan(nanoseconds));
+
+ return NanosecondsToDays(cx, nanoseconds, zonedRelativeTo, timeZone,
+ result);
+ }
+
+ auto* nanoseconds = TotalDurationNanosecondsSlow(cx, duration.time());
+ if (!nanoseconds) {
+ return false;
+ }
+
+ // NanosecondsToDays, step 6.
+ if (!IsValidInstantSpan(nanoseconds)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_INVALID);
+ return false;
+ }
+
+ return NanosecondsToDays(cx, ToInstantSpan(nanoseconds), zonedRelativeTo,
+ timeZone, result);
+}
+
+/**
+ * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds )
+ */
+static TimeDuration CreateTimeDurationRecord(int64_t days, int64_t hours,
+ int64_t minutes, int64_t seconds,
+ int64_t milliseconds,
+ int64_t microseconds,
+ int64_t nanoseconds) {
+ // Step 1.
+ MOZ_ASSERT(IsValidDuration({0, 0, 0, double(days), double(hours),
+ double(minutes), double(seconds),
+ double(microseconds), double(nanoseconds)}));
+
+ // Step 2.
+ return {
+ double(days), double(hours), double(minutes),
+ double(seconds), double(milliseconds), double(microseconds),
+ double(nanoseconds),
+ };
+}
+
+/**
+ * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds )
+ */
+static TimeDuration CreateTimeDurationRecord(double days, double hours,
+ double minutes, double seconds,
+ double milliseconds,
+ double microseconds,
+ double nanoseconds) {
+ // Step 1.
+ MOZ_ASSERT(IsValidDuration({0, 0, 0, days, hours, minutes, seconds,
+ milliseconds, microseconds, nanoseconds}));
+
+ // Step 2.
+ // NB: Adds +0.0 to correctly handle negative zero.
+ return {
+ days + (+0.0), hours + (+0.0), minutes + (+0.0),
+ seconds + (+0.0), milliseconds + (+0.0), microseconds + (+0.0),
+ nanoseconds + (+0.0),
+ };
+}
+
+/**
+ * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds, largestUnit )
+ *
+ * BalancePossiblyInfiniteTimeDuration ( days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds, largestUnit )
+ */
+static TimeDuration BalanceTimeDuration(int64_t nanoseconds,
+ TemporalUnit largestUnit) {
+ // Step 1. (Handled in caller.)
+
+ // Step 2.
+ int64_t days = 0;
+ int64_t hours = 0;
+ int64_t minutes = 0;
+ int64_t seconds = 0;
+ int64_t milliseconds = 0;
+ int64_t microseconds = 0;
+
+ // Steps 3-4. (Not applicable in our implementation.)
+ //
+ // We don't need to convert to positive numbers, because integer division
+ // truncates and the %-operator has modulo semantics.
+
+ // Steps 5-11.
+ switch (largestUnit) {
+ // Step 5.
+ case TemporalUnit::Year:
+ case TemporalUnit::Month:
+ case TemporalUnit::Week:
+ case TemporalUnit::Day: {
+ // Step 5.a.
+ microseconds = nanoseconds / 1000;
+
+ // Step 5.b.
+ nanoseconds = nanoseconds % 1000;
+
+ // Step 5.c.
+ milliseconds = microseconds / 1000;
+
+ // Step 5.d.
+ microseconds = microseconds % 1000;
+
+ // Step 5.e.
+ seconds = milliseconds / 1000;
+
+ // Step 5.f.
+ milliseconds = milliseconds % 1000;
+
+ // Step 5.g.
+ minutes = seconds / 60;
+
+ // Step 5.h.
+ seconds = seconds % 60;
+
+ // Step 5.i.
+ hours = minutes / 60;
+
+ // Step 5.j.
+ minutes = minutes % 60;
+
+ // Step 5.k.
+ days = hours / 24;
+
+ // Step 5.l.
+ hours = hours % 24;
+
+ break;
+ }
+
+ case TemporalUnit::Hour: {
+ // Step 6.a.
+ microseconds = nanoseconds / 1000;
+
+ // Step 6.b.
+ nanoseconds = nanoseconds % 1000;
+
+ // Step 6.c.
+ milliseconds = microseconds / 1000;
+
+ // Step 6.d.
+ microseconds = microseconds % 1000;
+
+ // Step 6.e.
+ seconds = milliseconds / 1000;
+
+ // Step 6.f.
+ milliseconds = milliseconds % 1000;
+
+ // Step 6.g.
+ minutes = seconds / 60;
+
+ // Step 6.h.
+ seconds = seconds % 60;
+
+ // Step 6.i.
+ hours = minutes / 60;
+
+ // Step 6.j.
+ minutes = minutes % 60;
+
+ break;
+ }
+
+ // Step 7.
+ case TemporalUnit::Minute: {
+ // Step 7.a.
+ microseconds = nanoseconds / 1000;
+
+ // Step 7.b.
+ nanoseconds = nanoseconds % 1000;
+
+ // Step 7.c.
+ milliseconds = microseconds / 1000;
+
+ // Step 7.d.
+ microseconds = microseconds % 1000;
+
+ // Step 7.e.
+ seconds = milliseconds / 1000;
+
+ // Step 7.f.
+ milliseconds = milliseconds % 1000;
+
+ // Step 7.g.
+ minutes = seconds / 60;
+
+ // Step 7.h.
+ seconds = seconds % 60;
+
+ break;
+ }
+
+ // Step 8.
+ case TemporalUnit::Second: {
+ // Step 8.a.
+ microseconds = nanoseconds / 1000;
+
+ // Step 8.b.
+ nanoseconds = nanoseconds % 1000;
+
+ // Step 8.c.
+ milliseconds = microseconds / 1000;
+
+ // Step 8.d.
+ microseconds = microseconds % 1000;
+
+ // Step 8.e.
+ seconds = milliseconds / 1000;
+
+ // Step 8.f.
+ milliseconds = milliseconds % 1000;
+
+ break;
+ }
+
+ // Step 9.
+ case TemporalUnit::Millisecond: {
+ // Step 9.a.
+ microseconds = nanoseconds / 1000;
+
+ // Step 9.b.
+ nanoseconds = nanoseconds % 1000;
+
+ // Step 9.c.
+ milliseconds = microseconds / 1000;
+
+ // Step 9.d.
+ microseconds = microseconds % 1000;
+
+ break;
+ }
+
+ // Step 10.
+ case TemporalUnit::Microsecond: {
+ // Step 10.a.
+ microseconds = nanoseconds / 1000;
+
+ // Step 10.b.
+ nanoseconds = nanoseconds % 1000;
+
+ break;
+ }
+
+ // Step 11.
+ case TemporalUnit::Nanosecond: {
+ // Nothing to do.
+ break;
+ }
+
+ case TemporalUnit::Auto:
+ MOZ_CRASH("Unexpected temporal unit");
+ }
+
+ // Step 12. (Not applicable, all values are finite)
+
+ // Step 13.
+ return CreateTimeDurationRecord(days, hours, minutes, seconds, milliseconds,
+ microseconds, nanoseconds);
+}
+
+/**
+ * BalancePossiblyInfiniteTimeDuration ( days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds, largestUnit )
+ */
+static bool BalancePossiblyInfiniteTimeDurationSlow(JSContext* cx,
+ Handle<BigInt*> nanos,
+ TemporalUnit largestUnit,
+ TimeDuration* result) {
+ // Steps 1-2. (Handled in caller.)
+
+ BigInt* zero = BigInt::zero(cx);
+ if (!zero) {
+ return false;
+ }
+
+ // Step 3.
+ Rooted<BigInt*> days(cx, zero);
+ Rooted<BigInt*> hours(cx, zero);
+ Rooted<BigInt*> minutes(cx, zero);
+ Rooted<BigInt*> seconds(cx, zero);
+ Rooted<BigInt*> milliseconds(cx, zero);
+ Rooted<BigInt*> microseconds(cx, zero);
+ Rooted<BigInt*> nanoseconds(cx, nanos);
+
+ // Steps 4-5.
+ //
+ // We don't need to convert to positive numbers, because BigInt division
+ // truncates and BigInt modulo has modulo semantics.
+
+ // Steps 6-12.
+ Rooted<BigInt*> thousand(cx, BigInt::createFromInt64(cx, 1000));
+ if (!thousand) {
+ return false;
+ }
+
+ Rooted<BigInt*> sixty(cx, BigInt::createFromInt64(cx, 60));
+ if (!sixty) {
+ return false;
+ }
+
+ Rooted<BigInt*> twentyfour(cx, BigInt::createFromInt64(cx, 24));
+ if (!twentyfour) {
+ return false;
+ }
+
+ switch (largestUnit) {
+ // Step 6.
+ case TemporalUnit::Year:
+ case TemporalUnit::Month:
+ case TemporalUnit::Week:
+ case TemporalUnit::Day: {
+ // Steps 6.a-b.
+ if (!BigInt::divmod(cx, nanoseconds, thousand, &microseconds,
+ &nanoseconds)) {
+ return false;
+ }
+
+ // Steps 6.c-d.
+ if (!BigInt::divmod(cx, microseconds, thousand, &milliseconds,
+ &microseconds)) {
+ return false;
+ }
+
+ // Steps 6.e-f.
+ if (!BigInt::divmod(cx, milliseconds, thousand, &seconds,
+ &milliseconds)) {
+ return false;
+ }
+
+ // Steps 6.g-h.
+ if (!BigInt::divmod(cx, seconds, sixty, &minutes, &seconds)) {
+ return false;
+ }
+
+ // Steps 6.i-j.
+ if (!BigInt::divmod(cx, minutes, sixty, &hours, &minutes)) {
+ return false;
+ }
+
+ // Steps 6.k-l.
+ if (!BigInt::divmod(cx, hours, twentyfour, &days, &hours)) {
+ return false;
+ }
+
+ break;
+ }
+
+ // Step 7.
+ case TemporalUnit::Hour: {
+ // Steps 7.a-b.
+ if (!BigInt::divmod(cx, nanoseconds, thousand, &microseconds,
+ &nanoseconds)) {
+ return false;
+ }
+
+ // Steps 7.c-d.
+ if (!BigInt::divmod(cx, microseconds, thousand, &milliseconds,
+ &microseconds)) {
+ return false;
+ }
+
+ // Steps 7.e-f.
+ if (!BigInt::divmod(cx, milliseconds, thousand, &seconds,
+ &milliseconds)) {
+ return false;
+ }
+
+ // Steps 7.g-h.
+ if (!BigInt::divmod(cx, seconds, sixty, &minutes, &seconds)) {
+ return false;
+ }
+
+ // Steps 7.i-j.
+ if (!BigInt::divmod(cx, minutes, sixty, &hours, &minutes)) {
+ return false;
+ }
+
+ break;
+ }
+
+ // Step 8.
+ case TemporalUnit::Minute: {
+ // Steps 8.a-b.
+ if (!BigInt::divmod(cx, nanoseconds, thousand, &microseconds,
+ &nanoseconds)) {
+ return false;
+ }
+
+ // Steps 8.c-d.
+ if (!BigInt::divmod(cx, microseconds, thousand, &milliseconds,
+ &microseconds)) {
+ return false;
+ }
+
+ // Steps 8.e-f.
+ if (!BigInt::divmod(cx, milliseconds, thousand, &seconds,
+ &milliseconds)) {
+ return false;
+ }
+
+ // Steps 8.g-h.
+ if (!BigInt::divmod(cx, seconds, sixty, &minutes, &seconds)) {
+ return false;
+ }
+
+ break;
+ }
+
+ // Step 9.
+ case TemporalUnit::Second: {
+ // Steps 9.a-b.
+ if (!BigInt::divmod(cx, nanoseconds, thousand, &microseconds,
+ &nanoseconds)) {
+ return false;
+ }
+
+ // Steps 9.c-d.
+ if (!BigInt::divmod(cx, microseconds, thousand, &milliseconds,
+ &microseconds)) {
+ return false;
+ }
+
+ // Steps 9.e-f.
+ if (!BigInt::divmod(cx, milliseconds, thousand, &seconds,
+ &milliseconds)) {
+ return false;
+ }
+
+ break;
+ }
+
+ // Step 10.
+ case TemporalUnit::Millisecond: {
+ // Steps 10.a-b.
+ if (!BigInt::divmod(cx, nanoseconds, thousand, &microseconds,
+ &nanoseconds)) {
+ return false;
+ }
+
+ // Steps 10.c-d.
+ if (!BigInt::divmod(cx, microseconds, thousand, &milliseconds,
+ &microseconds)) {
+ return false;
+ }
+
+ break;
+ }
+
+ // Step 11.
+ case TemporalUnit::Microsecond: {
+ // Steps 11.a-b.
+ if (!BigInt::divmod(cx, nanoseconds, thousand, &microseconds,
+ &nanoseconds)) {
+ return false;
+ }
+
+ break;
+ }
+
+ // Step 12.
+ case TemporalUnit::Nanosecond: {
+ // Nothing to do.
+ break;
+ }
+
+ case TemporalUnit::Auto:
+ MOZ_CRASH("Unexpected temporal unit");
+ }
+
+ double daysNumber = BigInt::numberValue(days);
+ double hoursNumber = BigInt::numberValue(hours);
+ double minutesNumber = BigInt::numberValue(minutes);
+ double secondsNumber = BigInt::numberValue(seconds);
+ double millisecondsNumber = BigInt::numberValue(milliseconds);
+ double microsecondsNumber = BigInt::numberValue(microseconds);
+ double nanosecondsNumber = BigInt::numberValue(nanoseconds);
+
+ // Step 13.
+ for (double v : {daysNumber, hoursNumber, minutesNumber, secondsNumber,
+ millisecondsNumber, microsecondsNumber, nanosecondsNumber}) {
+ if (std::isinf(v)) {
+ *result = {
+ daysNumber, hoursNumber, minutesNumber,
+ secondsNumber, millisecondsNumber, microsecondsNumber,
+ nanosecondsNumber,
+ };
+ return true;
+ }
+ }
+
+ // Step 14.
+ *result = CreateTimeDurationRecord(daysNumber, hoursNumber, minutesNumber,
+ secondsNumber, millisecondsNumber,
+ microsecondsNumber, nanosecondsNumber);
+ return true;
+}
+
+/**
+ * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds, largestUnit )
+ */
+static bool BalanceTimeDurationSlow(JSContext* cx, Handle<BigInt*> nanoseconds,
+ TemporalUnit largestUnit,
+ TimeDuration* result) {
+ // Step 1.
+ if (!BalancePossiblyInfiniteTimeDurationSlow(cx, nanoseconds, largestUnit,
+ result)) {
+ return false;
+ }
+
+ // Steps 2-3.
+ return ThrowIfInvalidDuration(cx, result->toDuration());
+}
+
+/**
+ * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds, largestUnit )
+ */
+static bool BalanceTimeDuration(JSContext* cx, const Duration& one,
+ const Duration& two, TemporalUnit largestUnit,
+ TimeDuration* result) {
+ MOZ_ASSERT(IsValidDuration(one));
+ MOZ_ASSERT(IsValidDuration(two));
+ MOZ_ASSERT(largestUnit >= TemporalUnit::Day);
+
+ // Fast-path when we can perform the whole computation with int64 values.
+ if (auto oneNanoseconds = TotalDurationNanoseconds(one)) {
+ if (auto twoNanoseconds = TotalDurationNanoseconds(two)) {
+ mozilla::CheckedInt64 nanoseconds = *oneNanoseconds;
+ nanoseconds += *twoNanoseconds;
+ if (nanoseconds.isValid()) {
+ *result = ::BalanceTimeDuration(nanoseconds.value(), largestUnit);
+ return true;
+ }
+ }
+ }
+
+ Rooted<BigInt*> oneNanoseconds(cx, TotalDurationNanosecondsSlow(cx, one));
+ if (!oneNanoseconds) {
+ return false;
+ }
+
+ Rooted<BigInt*> twoNanoseconds(cx, TotalDurationNanosecondsSlow(cx, two));
+ if (!twoNanoseconds) {
+ return false;
+ }
+
+ Rooted<BigInt*> nanoseconds(cx,
+ BigInt::add(cx, oneNanoseconds, twoNanoseconds));
+ if (!nanoseconds) {
+ return false;
+ }
+
+ return BalanceTimeDurationSlow(cx, nanoseconds, largestUnit, result);
+}
+
+/**
+ * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds, largestUnit )
+ */
+static bool BalanceTimeDuration(JSContext* cx, double days, const Duration& one,
+ const Duration& two, TemporalUnit largestUnit,
+ TimeDuration* result) {
+ MOZ_ASSERT(IsInteger(days));
+ MOZ_ASSERT(IsValidDuration(one));
+ MOZ_ASSERT(IsValidDuration(two));
+
+ // Fast-path when we can perform the whole computation with int64 values.
+ if (auto oneNanoseconds = TotalDurationNanoseconds(one)) {
+ if (auto twoNanoseconds = TotalDurationNanoseconds(two)) {
+ int64_t intDays;
+ if (mozilla::NumberEqualsInt64(days, &intDays)) {
+ mozilla::CheckedInt64 daysNanoseconds = intDays;
+ daysNanoseconds *= ToNanoseconds(TemporalUnit::Day);
+
+ mozilla::CheckedInt64 nanoseconds = *oneNanoseconds;
+ nanoseconds += *twoNanoseconds;
+ nanoseconds += daysNanoseconds;
+
+ if (nanoseconds.isValid()) {
+ *result = ::BalanceTimeDuration(nanoseconds.value(), largestUnit);
+ return true;
+ }
+ }
+ }
+ }
+
+ Rooted<BigInt*> oneNanoseconds(cx, TotalDurationNanosecondsSlow(cx, one));
+ if (!oneNanoseconds) {
+ return false;
+ }
+
+ Rooted<BigInt*> twoNanoseconds(cx, TotalDurationNanosecondsSlow(cx, two));
+ if (!twoNanoseconds) {
+ return false;
+ }
+
+ Rooted<BigInt*> nanoseconds(cx,
+ BigInt::add(cx, oneNanoseconds, twoNanoseconds));
+ if (!nanoseconds) {
+ return false;
+ }
+
+ if (days) {
+ Rooted<BigInt*> daysNanoseconds(
+ cx, TotalDurationNanosecondsSlow(cx, {0, 0, 0, days}));
+ if (!daysNanoseconds) {
+ return false;
+ }
+
+ nanoseconds = BigInt::add(cx, nanoseconds, daysNanoseconds);
+ if (!nanoseconds) {
+ return false;
+ }
+ }
+
+ return BalanceTimeDurationSlow(cx, nanoseconds, largestUnit, result);
+}
+
+/**
+ * BalancePossiblyInfiniteTimeDuration ( days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds, largestUnit )
+ */
+static bool BalancePossiblyInfiniteTimeDuration(JSContext* cx,
+ const Duration& duration,
+ TemporalUnit largestUnit,
+ TimeDuration* result) {
+ // NB: |duration.days| can have a different sign than the time components.
+ MOZ_ASSERT(IsValidDuration(duration.time()));
+
+ // Fast-path when we can perform the whole computation with int64 values.
+ if (auto nanoseconds = TotalDurationNanoseconds(duration)) {
+ *result = ::BalanceTimeDuration(*nanoseconds, largestUnit);
+ return true;
+ }
+
+ // Steps 1-2.
+ Rooted<BigInt*> nanoseconds(cx, TotalDurationNanosecondsSlow(cx, duration));
+ if (!nanoseconds) {
+ return false;
+ }
+
+ // Steps 3-14.
+ return ::BalancePossiblyInfiniteTimeDurationSlow(cx, nanoseconds, largestUnit,
+ result);
+}
+
+/**
+ * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds, largestUnit )
+ */
+bool js::temporal::BalanceTimeDuration(JSContext* cx, const Duration& duration,
+ TemporalUnit largestUnit,
+ TimeDuration* result) {
+ if (!::BalancePossiblyInfiniteTimeDuration(cx, duration, largestUnit,
+ result)) {
+ return false;
+ }
+ return ThrowIfInvalidDuration(cx, result->toDuration());
+}
+
+/**
+ * BalancePossiblyInfiniteTimeDurationRelative ( days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds, largestUnit, zonedRelativeTo,
+ * timeZoneRec [ , precalculatedPlainDateTime ] )
+ */
+static bool BalancePossiblyInfiniteTimeDurationRelative(
+ JSContext* cx, const Duration& duration, TemporalUnit largestUnit,
+ Handle<ZonedDateTime> relativeTo, Handle<TimeZoneRecord> timeZone,
+ mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
+ TimeDuration* result) {
+ // Step 1. (Not applicable)
+
+ // Step 2.
+ auto intermediateNs = relativeTo.instant();
+
+ // Step 3.
+ const auto& startInstant = relativeTo.instant();
+
+ // Step 4.
+ PlainDateTime startDateTime;
+ if (duration.days != 0) {
+ // Step 4.a.
+ if (!precalculatedPlainDateTime) {
+ if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &startDateTime)) {
+ return false;
+ }
+ precalculatedPlainDateTime =
+ mozilla::SomeRef<const PlainDateTime>(startDateTime);
+ }
+
+ // Steps 4.b-c.
+ Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
+ if (!AddDaysToZonedDateTime(cx, startInstant, *precalculatedPlainDateTime,
+ timeZone, isoCalendar, duration.days,
+ &intermediateNs)) {
+ return false;
+ }
+ }
+
+ // Step 5.
+ Instant endNs;
+ if (!AddInstant(cx, intermediateNs, duration.time(), &endNs)) {
+ return false;
+ }
+ MOZ_ASSERT(IsValidEpochInstant(endNs));
+
+ // Step 6.
+ auto nanoseconds = endNs - relativeTo.instant();
+ MOZ_ASSERT(IsValidInstantSpan(nanoseconds));
+
+ // Step 7.
+ if (nanoseconds == InstantSpan{}) {
+ *result = {};
+ return true;
+ }
+
+ // Steps 8-9.
+ double days = 0;
+ if (TemporalUnit::Year <= largestUnit && largestUnit <= TemporalUnit::Day) {
+ // Step 8.a.
+ if (!precalculatedPlainDateTime) {
+ if (!GetPlainDateTimeFor(cx, timeZone, startInstant, &startDateTime)) {
+ return false;
+ }
+ precalculatedPlainDateTime =
+ mozilla::SomeRef<const PlainDateTime>(startDateTime);
+ }
+
+ // Step 8.b.
+ Rooted<temporal::NanosecondsAndDays> nanosAndDays(cx);
+ if (!NanosecondsToDays(cx, nanoseconds, relativeTo, timeZone,
+ *precalculatedPlainDateTime, &nanosAndDays)) {
+ return false;
+ }
+
+ // NB: |days| is passed to CreateTimeDurationRecord, which performs
+ // |โ„(๐”ฝ(days))|, so it's safe to convert from BigInt to double here.
+
+ // Step 8.c.
+ days = nanosAndDays.daysNumber();
+ MOZ_ASSERT(IsInteger(days));
+
+ // FIXME: spec issue - `result.[[Nanoseconds]]` not created in all branches
+ // https://github.com/tc39/proposal-temporal/issues/2616
+
+ // Step 8.d.
+ nanoseconds = nanosAndDays.nanoseconds();
+ MOZ_ASSERT_IF(days > 0, nanoseconds >= InstantSpan{});
+ MOZ_ASSERT_IF(days < 0, nanoseconds <= InstantSpan{});
+
+ // Step 8.e.
+ largestUnit = TemporalUnit::Hour;
+ }
+
+ // Step 10. (Not applicable in our implementation.)
+
+ // Steps 11-12.
+ TimeDuration balanceResult;
+ if (auto nanos = nanoseconds.toNanoseconds(); nanos.isValid()) {
+ // Step 11.
+ balanceResult = ::BalanceTimeDuration(nanos.value(), largestUnit);
+
+ // Step 12.
+ MOZ_ASSERT(IsValidDuration(balanceResult.toDuration()));
+ } else {
+ Rooted<BigInt*> ns(cx, ToEpochNanoseconds(cx, nanoseconds));
+ if (!ns) {
+ return false;
+ }
+
+ // Step 11.
+ if (!::BalancePossiblyInfiniteTimeDurationSlow(cx, ns, largestUnit,
+ &balanceResult)) {
+ return false;
+ }
+
+ // Step 12.
+ if (!IsValidDuration(balanceResult.toDuration())) {
+ *result = balanceResult;
+ return true;
+ }
+ }
+
+ // Step 13.
+ *result = {
+ days,
+ balanceResult.hours,
+ balanceResult.minutes,
+ balanceResult.seconds,
+ balanceResult.milliseconds,
+ balanceResult.microseconds,
+ balanceResult.nanoseconds,
+ };
+ return true;
+}
+
+/**
+ * BalancePossiblyInfiniteTimeDurationRelative ( days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds, largestUnit, zonedRelativeTo,
+ * timeZoneRec [ , precalculatedPlainDateTime ] )
+ */
+static bool BalancePossiblyInfiniteTimeDurationRelative(
+ JSContext* cx, const Duration& duration, TemporalUnit largestUnit,
+ Handle<ZonedDateTime> relativeTo, Handle<TimeZoneRecord> timeZone,
+ TimeDuration* result) {
+ return BalancePossiblyInfiniteTimeDurationRelative(
+ cx, duration, largestUnit, relativeTo, timeZone, mozilla::Nothing(),
+ result);
+}
+
+/**
+ * BalanceTimeDurationRelative ( days, hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds, largestUnit, zonedRelativeTo, timeZoneRec,
+ * precalculatedPlainDateTime )
+ */
+static bool BalanceTimeDurationRelative(
+ JSContext* cx, const Duration& duration, TemporalUnit largestUnit,
+ Handle<ZonedDateTime> relativeTo, Handle<TimeZoneRecord> timeZone,
+ mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
+ TimeDuration* result) {
+ // Step 1.
+ if (!BalancePossiblyInfiniteTimeDurationRelative(
+ cx, duration, largestUnit, relativeTo, timeZone,
+ precalculatedPlainDateTime, result)) {
+ return false;
+ }
+
+ // Steps 2-3.
+ return ThrowIfInvalidDuration(cx, result->toDuration());
+}
+
+/**
+ * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds, largestUnit )
+ */
+bool js::temporal::BalanceTimeDuration(JSContext* cx,
+ const InstantSpan& nanoseconds,
+ TemporalUnit largestUnit,
+ TimeDuration* result) {
+ MOZ_ASSERT(IsValidInstantSpan(nanoseconds));
+
+ // Steps 1-3. (Not applicable)
+
+ // Fast-path when we can perform the whole computation with int64 values.
+ if (auto nanos = nanoseconds.toNanoseconds(); nanos.isValid()) {
+ *result = ::BalanceTimeDuration(nanos.value(), largestUnit);
+ return true;
+ }
+
+ Rooted<BigInt*> nanos(cx, ToEpochNanoseconds(cx, nanoseconds));
+ if (!nanos) {
+ return false;
+ }
+
+ // Steps 4-16.
+ return ::BalanceTimeDurationSlow(cx, nanos, largestUnit, result);
+}
+
+/**
+ * CreateDateDurationRecord ( years, months, weeks, days )
+ */
+static DateDuration CreateDateDurationRecord(double years, double months,
+ double weeks, double days) {
+ MOZ_ASSERT(IsValidDuration({years, months, weeks, days}));
+ return {years, months, weeks, days};
+}
+
+/**
+ * CreateDateDurationRecord ( years, months, weeks, days )
+ */
+static bool CreateDateDurationRecord(JSContext* cx, double years, double months,
+ double weeks, double days,
+ DateDuration* result) {
+ if (!ThrowIfInvalidDuration(cx, {years, months, weeks, days})) {
+ return false;
+ }
+
+ *result = {years, months, weeks, days};
+ return true;
+}
+
+static bool UnbalanceDateDurationRelativeHasEffect(const Duration& duration,
+ TemporalUnit largestUnit) {
+ MOZ_ASSERT(largestUnit != TemporalUnit::Auto);
+
+ // Steps 2, 3.a-b, 4.a-b, 6-7.
+ return (largestUnit > TemporalUnit::Year && duration.years != 0) ||
+ (largestUnit > TemporalUnit::Month && duration.months != 0) ||
+ (largestUnit > TemporalUnit::Week && duration.weeks != 0);
+}
+
+/**
+ * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
+ * plainRelativeTo, calendarRec )
+ */
+static bool UnbalanceDateDurationRelative(
+ JSContext* cx, const Duration& duration, TemporalUnit largestUnit,
+ Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
+ Handle<CalendarRecord> calendar, DateDuration* result) {
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ double years = duration.years;
+ double months = duration.months;
+ double weeks = duration.weeks;
+ double days = duration.days;
+
+ // Step 1. (Not applicable in our implementation.)
+
+ // Steps 2, 3.a, 4.a, and 6.
+ if (!UnbalanceDateDurationRelativeHasEffect(duration, largestUnit)) {
+ // Steps 2.a, 3.a, 4.a, and 6.
+ *result = CreateDateDurationRecord(years, months, weeks, days);
+ return true;
+ }
+
+ // Step 3.
+ if (largestUnit == TemporalUnit::Month) {
+ // Step 3.a. (Handled above)
+ MOZ_ASSERT(years != 0);
+
+ // Step 3.b. (Not applicable in our implementation.)
+
+ // Step 3.c.
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+
+ // Step 3.d.
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
+
+ // Step 3.e.
+ auto yearsDuration = Duration{years};
+
+ // Step 3.f.
+ Rooted<Wrapped<PlainDateObject*>> later(
+ cx, CalendarDateAdd(cx, calendar, plainRelativeTo, yearsDuration));
+ if (!later) {
+ return false;
+ }
+
+ // Steps 3.g-i.
+ Duration untilResult;
+ if (!CalendarDateUntil(cx, calendar, plainRelativeTo, later,
+ TemporalUnit::Month, &untilResult)) {
+ return false;
+ }
+
+ // Step 3.j.
+ double yearsInMonths = untilResult.months;
+
+ // Step 3.k.
+ //
+ // The addition |months + yearsInMonths| can be imprecise, but this is
+ // safe to ignore, because all values are passed to
+ // CreateDateDurationRecord, which converts the values to Numbers.
+ return CreateDateDurationRecord(cx, 0, months + yearsInMonths, weeks, days,
+ result);
+ }
+
+ // Step 4.
+ if (largestUnit == TemporalUnit::Week) {
+ // Step 4.a. (Handled above)
+ MOZ_ASSERT(years != 0 || months != 0);
+
+ // Step 4.b. (Not applicable in our implementation.)
+
+ // Step 4.c.
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+
+ // Step 4.d.
+ auto yearsMonthsDuration = Duration{years, months};
+
+ // Step 4.e.
+ auto later =
+ CalendarDateAdd(cx, calendar, plainRelativeTo, yearsMonthsDuration);
+ if (!later) {
+ return false;
+ }
+ auto laterDate = ToPlainDate(&later.unwrap());
+
+ auto* unwrappedRelativeTo = plainRelativeTo.unwrap(cx);
+ if (!unwrappedRelativeTo) {
+ return false;
+ }
+ auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
+
+ // Step 4.f.
+ int32_t yearsMonthsInDays = DaysUntil(relativeToDate, laterDate);
+
+ // Step 4.g.
+ //
+ // The addition |days + yearsMonthsInDays| can be imprecise, but this is
+ // safe to ignore, because all values are passed to
+ // CreateDateDurationRecord, which converts the values to Numbers.
+ return CreateDateDurationRecord(cx, 0, 0, weeks, days + yearsMonthsInDays,
+ result);
+ }
+
+ // Step 5. (Not applicable in our implementation.)
+
+ // Step 6. (Handled above)
+ MOZ_ASSERT(years != 0 || months != 0 || weeks != 0);
+
+ // FIXME: why don't we unconditionally throw an error for missing calendars?
+
+ // Step 7. (Not applicable in our implementation.)
+
+ // Step 8.
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+
+ // Step 9.
+ auto yearsMonthsWeeksDuration = Duration{years, months, weeks};
+
+ // Step 10.
+ auto later =
+ CalendarDateAdd(cx, calendar, plainRelativeTo, yearsMonthsWeeksDuration);
+ if (!later) {
+ return false;
+ }
+ auto laterDate = ToPlainDate(&later.unwrap());
+
+ auto* unwrappedRelativeTo = plainRelativeTo.unwrap(cx);
+ if (!unwrappedRelativeTo) {
+ return false;
+ }
+ auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
+
+ // Step 11.
+ int32_t yearsMonthsWeeksInDay = DaysUntil(relativeToDate, laterDate);
+
+ // Step 12.
+ //
+ // The addition |days + yearsMonthsWeeksInDay| can be imprecise, but this is
+ // safe to ignore, because all values are passed to CreateDateDurationRecord,
+ // which converts the values to Numbers.
+ return CreateDateDurationRecord(cx, 0, 0, 0, days + yearsMonthsWeeksInDay,
+ result);
+}
+
+/**
+ * UnbalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
+ * plainRelativeTo, calendarRec )
+ */
+static bool UnbalanceDateDurationRelative(JSContext* cx,
+ const Duration& duration,
+ TemporalUnit largestUnit,
+ DateDuration* result) {
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ double years = duration.years;
+ double months = duration.months;
+ double weeks = duration.weeks;
+ double days = duration.days;
+
+ // Step 1. (Not applicable.)
+
+ // Steps 2, 3.a, 4.a, and 6.
+ if (!UnbalanceDateDurationRelativeHasEffect(duration, largestUnit)) {
+ // Steps 2.a, 3.a, 4.a, and 6.
+ *result = CreateDateDurationRecord(years, months, weeks, days);
+ return true;
+ }
+
+ // Step 5. (Not applicable.)
+
+ // Steps 3.b, 4.b, and 7.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_UNCOMPARABLE, "calendar");
+ return false;
+}
+
+/**
+ * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
+ * smallestUnit, plainRelativeTo, calendarRec )
+ */
+static bool BalanceDateDurationRelative(
+ JSContext* cx, const Duration& duration, TemporalUnit largestUnit,
+ TemporalUnit smallestUnit,
+ Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
+ Handle<CalendarRecord> calendar, DateDuration* result) {
+ MOZ_ASSERT(IsValidDuration(duration));
+ MOZ_ASSERT(largestUnit <= smallestUnit);
+
+ double years = duration.years;
+ double months = duration.months;
+ double weeks = duration.weeks;
+ double days = duration.days;
+
+ // FIXME: spec issue - effectful code paths should be more fine-grained
+ // similar to UnbalanceDateDurationRelative. For example:
+ // 1. If largestUnit = "year" and days = 0 and months = 0, then no-op.
+ // 2. Else if largestUnit = "month" and days = 0, then no-op.
+ // 3. Else if days = 0, then no-op.
+ //
+ // Also note that |weeks| is never balanced, even when non-zero.
+
+ // Step 1. (Not applicable in our implementation.)
+
+ // Steps 2-4.
+ if (largestUnit > TemporalUnit::Week ||
+ (years == 0 && months == 0 && weeks == 0 && days == 0)) {
+ // Step 4.a.
+ *result = CreateDateDurationRecord(years, months, weeks, days);
+ return true;
+ }
+
+ // Step 5.
+ if (!plainRelativeTo) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
+ "relativeTo");
+ return false;
+ }
+
+ // Step 6.
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+
+ // Step 7.
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
+
+ // Steps 8-9. (Not applicable in our implementation.)
+
+ auto untilAddedDate = [&](const Duration& duration, Duration* untilResult) {
+ Rooted<Wrapped<PlainDateObject*>> later(
+ cx, AddDate(cx, calendar, plainRelativeTo, duration));
+ if (!later) {
+ return false;
+ }
+
+ return CalendarDateUntil(cx, calendar, plainRelativeTo, later, largestUnit,
+ untilResult);
+ };
+
+ // Step 10.
+ if (largestUnit == TemporalUnit::Year) {
+ // Step 10.a.
+ if (smallestUnit == TemporalUnit::Week) {
+ // Step 10.a.i.
+ MOZ_ASSERT(days == 0);
+
+ // Step 10.a.ii.
+ auto yearsMonthsDuration = Duration{years, months};
+
+ // Steps 10.a.iii-iv.
+ Duration untilResult;
+ if (!untilAddedDate(yearsMonthsDuration, &untilResult)) {
+ return false;
+ }
+
+ // FIXME: spec bug - CreateDateDurationRecord is infallible
+
+ // Step 10.a.v.
+ *result = CreateDateDurationRecord(untilResult.years, untilResult.months,
+ weeks, 0);
+ return true;
+ }
+
+ // Step 10.b.
+ auto yearsMonthsWeeksDaysDuration = Duration{years, months, weeks, days};
+
+ // Steps 10.c-d.
+ Duration untilResult;
+ if (!untilAddedDate(yearsMonthsWeeksDaysDuration, &untilResult)) {
+ return false;
+ }
+
+ // FIXME: spec bug - CreateDateDurationRecord is infallible
+ // https://github.com/tc39/proposal-temporal/issues/2750
+
+ // Step 10.e.
+ *result = CreateDateDurationRecord(untilResult.years, untilResult.months,
+ untilResult.weeks, untilResult.days);
+ return true;
+ }
+
+ // Step 11.
+ if (largestUnit == TemporalUnit::Month) {
+ // Step 11.a.
+ MOZ_ASSERT(years == 0);
+
+ // Step 11.b.
+ if (smallestUnit == TemporalUnit::Week) {
+ // Step 10.b.i.
+ MOZ_ASSERT(days == 0);
+
+ // Step 10.b.ii.
+ *result = CreateDateDurationRecord(0, months, weeks, 0);
+ return true;
+ }
+
+ // Step 11.c.
+ auto monthsWeeksDaysDuration = Duration{0, months, weeks, days};
+
+ // Steps 11.d-e.
+ Duration untilResult;
+ if (!untilAddedDate(monthsWeeksDaysDuration, &untilResult)) {
+ return false;
+ }
+
+ // FIXME: spec bug - CreateDateDurationRecord is infallible
+ // https://github.com/tc39/proposal-temporal/issues/2750
+
+ // Step 11.f.
+ *result = CreateDateDurationRecord(0, untilResult.months, untilResult.weeks,
+ untilResult.days);
+ return true;
+ }
+
+ // Step 12.
+ MOZ_ASSERT(largestUnit == TemporalUnit::Week);
+
+ // Step 13.
+ MOZ_ASSERT(years == 0);
+
+ // Step 14.
+ MOZ_ASSERT(months == 0);
+
+ // Step 15.
+ auto weeksDaysDuration = Duration{0, 0, weeks, days};
+
+ // Steps 16-17.
+ Duration untilResult;
+ if (!untilAddedDate(weeksDaysDuration, &untilResult)) {
+ return false;
+ }
+
+ // FIXME: spec bug - CreateDateDurationRecord is infallible
+ // https://github.com/tc39/proposal-temporal/issues/2750
+
+ // Step 18.
+ *result = CreateDateDurationRecord(0, 0, untilResult.weeks, untilResult.days);
+ return true;
+}
+
+/**
+ * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
+ * smallestUnit, plainRelativeTo, calendarRec )
+ */
+bool js::temporal::BalanceDateDurationRelative(
+ JSContext* cx, const Duration& duration, TemporalUnit largestUnit,
+ TemporalUnit smallestUnit,
+ Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
+ Handle<CalendarRecord> calendar, DateDuration* result) {
+ MOZ_ASSERT(plainRelativeTo);
+ MOZ_ASSERT(calendar.receiver());
+
+ return ::BalanceDateDurationRelative(cx, duration, largestUnit, smallestUnit,
+ plainRelativeTo, calendar, result);
+}
+
+/**
+ * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
+ * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
+ * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
+ */
+static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
+ Duration* duration) {
+ MOZ_ASSERT(IsValidDuration(one));
+ MOZ_ASSERT(IsValidDuration(two));
+
+ // Steps 1-2. (Not applicable)
+
+ // Step 3.
+ auto largestUnit1 = DefaultTemporalLargestUnit(one);
+
+ // Step 4.
+ auto largestUnit2 = DefaultTemporalLargestUnit(two);
+
+ // Step 5.
+ auto largestUnit = std::min(largestUnit1, largestUnit2);
+
+ // Step 6.a.
+ if (largestUnit <= TemporalUnit::Week) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
+ "relativeTo");
+ return false;
+ }
+
+ // Step 6.b.
+ TimeDuration result;
+ if (!BalanceTimeDuration(cx, one, two, largestUnit, &result)) {
+ return false;
+ }
+
+ // Steps 6.c.
+ *duration = result.toDuration();
+ return true;
+}
+
+/**
+ * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
+ * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
+ * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
+ */
+static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
+ Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
+ Handle<CalendarRecord> calendar, Duration* duration) {
+ MOZ_ASSERT(IsValidDuration(one));
+ MOZ_ASSERT(IsValidDuration(two));
+
+ // Steps 1-2. (Not applicable)
+
+ // FIXME: spec issue - calendarRec is not undefined when plainRelativeTo is
+ // not undefined.
+
+ // Step 3.
+ auto largestUnit1 = DefaultTemporalLargestUnit(one);
+
+ // Step 4.
+ auto largestUnit2 = DefaultTemporalLargestUnit(two);
+
+ // Step 5.
+ auto largestUnit = std::min(largestUnit1, largestUnit2);
+
+ // Step 6. (Not applicable)
+
+ // Step 7.a. (Not applicable in our implementation.)
+
+ // Step 7.b.
+ auto dateDuration1 = one.date();
+
+ // Step 7.c.
+ auto dateDuration2 = two.date();
+
+ // FIXME: spec issue - calendarUnitsPresent is unused.
+
+ // Step 7.d.
+ [[maybe_unused]] bool calendarUnitsPresent = true;
+
+ // Step 7.e.
+ if (dateDuration1.years == 0 && dateDuration1.months == 0 &&
+ dateDuration1.weeks == 0 && dateDuration2.years == 0 &&
+ dateDuration2.months == 0 && dateDuration2.weeks == 0) {
+ calendarUnitsPresent = false;
+ }
+
+ // Step 7.f.
+ Rooted<Wrapped<PlainDateObject*>> intermediate(
+ cx, AddDate(cx, calendar, plainRelativeTo, dateDuration1));
+ if (!intermediate) {
+ return false;
+ }
+
+ // Step 7.g.
+ Rooted<Wrapped<PlainDateObject*>> end(
+ cx, AddDate(cx, calendar, intermediate, dateDuration2));
+ if (!end) {
+ return false;
+ }
+
+ // Step 7.h.
+ auto dateLargestUnit = std::min(TemporalUnit::Day, largestUnit);
+
+ // Steps 7.i-k.
+ Duration dateDifference;
+ if (!DifferenceDate(cx, calendar, plainRelativeTo, end, dateLargestUnit,
+ &dateDifference)) {
+ return false;
+ }
+
+ // Step 7.l.
+ TimeDuration result;
+ if (!BalanceTimeDuration(cx, dateDifference.days, one.time(), two.time(),
+ largestUnit, &result)) {
+ return false;
+ }
+
+ // Steps 7.m.
+ *duration = {
+ dateDifference.years, dateDifference.months, dateDifference.weeks,
+ result.days, result.hours, result.minutes,
+ result.seconds, result.milliseconds, result.microseconds,
+ result.nanoseconds,
+ };
+ return true;
+}
+
+/**
+ * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
+ * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
+ * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
+ */
+static bool AddDuration(
+ JSContext* cx, const Duration& one, const Duration& two,
+ Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
+ Handle<TimeZoneRecord> timeZone,
+ mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
+ Duration* result) {
+ // Steps 1-2. (Not applicable)
+
+ // Step 3.
+ auto largestUnit1 = DefaultTemporalLargestUnit(one);
+
+ // Step 4.
+ auto largestUnit2 = DefaultTemporalLargestUnit(two);
+
+ // Step 5.
+ auto largestUnit = std::min(largestUnit1, largestUnit2);
+
+ // Steps 6-7. (Not applicable)
+
+ // Steps 8-9. (Not applicable in our implementation.)
+
+ // FIXME: spec issue - GetPlainDateTimeFor called unnecessarily
+ //
+ // clang-format off
+ //
+ // 10. If largestUnit is one of "year", "month", "week", or "day", then
+ // a. If precalculatedPlainDateTime is undefined, then
+ // i. Let startDateTime be ? GetPlainDateTimeFor(timeZone, zonedRelativeTo.[[Nanoseconds]], calendar).
+ // b. Else,
+ // i. Let startDateTime be precalculatedPlainDateTime.
+ // c. Let intermediateNs be ? AddZonedDateTime(zonedRelativeTo.[[Nanoseconds]], timeZone, calendar, y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, startDateTime).
+ // d. Let endNs be ? AddZonedDateTime(intermediateNs, timeZone, calendar, y2, mon2, w2, d2, h2, min2, s2, ms2, mus2, ns2).
+ // e. Return ? DifferenceZonedDateTime(zonedRelativeTo.[[Nanoseconds]], endNs, timeZone, calendar, largestUnit, OrdinaryObjectCreate(null), startDateTime).
+ // 11. Let intermediateNs be ? AddInstant(zonedRelativeTo.[[Nanoseconds]], h1, min1, s1, ms1, mus1, ns1).
+ // 12. Let endNs be ? AddInstant(intermediateNs, h2, min2, s2, ms2, mus2, ns2).
+ // 13. Let result be DifferenceInstant(zonedRelativeTo.[[Nanoseconds]], endNs, 1, "nanosecond", largestUnit, "halfExpand").
+ // 14. Return ! CreateDurationRecord(0, 0, 0, 0, result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]).
+ //
+ // clang-format on
+
+ // Step 10.
+ bool startDateTimeNeeded = largestUnit <= TemporalUnit::Day;
+
+ // Steps 11-14 and 16.
+ if (startDateTimeNeeded) {
+ // Steps 11-12.
+ PlainDateTime startDateTime;
+ if (!precalculatedPlainDateTime) {
+ if (!GetPlainDateTimeFor(cx, timeZone, zonedRelativeTo.instant(),
+ &startDateTime)) {
+ return false;
+ }
+ } else {
+ startDateTime = *precalculatedPlainDateTime;
+ }
+
+ // Step 13.
+ Instant intermediateNs;
+ if (!AddZonedDateTime(cx, zonedRelativeTo.instant(), timeZone, calendar,
+ one, startDateTime, &intermediateNs)) {
+ return false;
+ }
+ MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
+
+ // Step 14.
+ Instant endNs;
+ if (!AddZonedDateTime(cx, intermediateNs, timeZone, calendar, two,
+ &endNs)) {
+ return false;
+ }
+ MOZ_ASSERT(IsValidEpochInstant(endNs));
+
+ // Step 15. (Not applicable)
+
+ // Step 16.
+ return DifferenceZonedDateTime(cx, zonedRelativeTo.instant(), endNs,
+ timeZone, calendar, largestUnit,
+ startDateTime, result);
+ }
+
+ // Steps 11-12. (Not applicable)
+
+ // Step 13. (Inlined AddZonedDateTime, step 6.)
+ Instant intermediateNs;
+ if (!AddInstant(cx, zonedRelativeTo.instant(), one, &intermediateNs)) {
+ return false;
+ }
+ MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
+
+ // Step 14. (Inlined AddZonedDateTime, step 6.)
+ Instant endNs;
+ if (!AddInstant(cx, intermediateNs, two, &endNs)) {
+ return false;
+ }
+ MOZ_ASSERT(IsValidEpochInstant(endNs));
+
+ // Steps 15.a-b.
+ return DifferenceInstant(cx, zonedRelativeTo.instant(), endNs, Increment{1},
+ TemporalUnit::Nanosecond, largestUnit,
+ TemporalRoundingMode::HalfExpand, result);
+}
+
+/**
+ * AddDuration ( y1, mon1, w1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2, w2,
+ * d2, h2, min2, s2, ms2, mus2, ns2, plainRelativeTo, calendarRec,
+ * zonedRelativeTo, timeZoneRec [ , precalculatedPlainDateTime ] )
+ */
+static bool AddDuration(JSContext* cx, const Duration& one, const Duration& two,
+ Handle<ZonedDateTime> zonedRelativeTo,
+ Handle<CalendarRecord> calendar,
+ Handle<TimeZoneRecord> timeZone, Duration* result) {
+ return AddDuration(cx, one, two, zonedRelativeTo, calendar, timeZone,
+ mozilla::Nothing(), result);
+}
+
+static bool RoundDuration(JSContext* cx, int64_t totalNanoseconds,
+ TemporalUnit unit, Increment increment,
+ TemporalRoundingMode roundingMode, Duration* result) {
+ MOZ_ASSERT(unit >= TemporalUnit::Hour);
+
+ double rounded;
+ if (!RoundNumberToIncrement(cx, totalNanoseconds, unit, increment,
+ roundingMode, &rounded)) {
+ return false;
+ }
+
+ double hours = 0;
+ double minutes = 0;
+ double seconds = 0;
+ double milliseconds = 0;
+ double microseconds = 0;
+ double nanoseconds = 0;
+
+ switch (unit) {
+ case TemporalUnit::Auto:
+ case TemporalUnit::Year:
+ case TemporalUnit::Week:
+ case TemporalUnit::Month:
+ case TemporalUnit::Day:
+ MOZ_CRASH("Unexpected temporal unit");
+
+ case TemporalUnit::Hour:
+ hours = rounded;
+ break;
+ case TemporalUnit::Minute:
+ minutes = rounded;
+ break;
+ case TemporalUnit::Second:
+ seconds = rounded;
+ break;
+ case TemporalUnit::Millisecond:
+ milliseconds = rounded;
+ break;
+ case TemporalUnit::Microsecond:
+ microseconds = rounded;
+ break;
+ case TemporalUnit::Nanosecond:
+ nanoseconds = rounded;
+ break;
+ }
+
+ *result = {
+ 0, 0, 0, 0, hours, minutes, seconds, milliseconds, microseconds,
+ nanoseconds,
+ };
+ return ThrowIfInvalidDuration(cx, *result);
+}
+
+static bool RoundDuration(JSContext* cx, Handle<BigInt*> totalNanoseconds,
+ TemporalUnit unit, Increment increment,
+ TemporalRoundingMode roundingMode, Duration* result) {
+ MOZ_ASSERT(unit >= TemporalUnit::Hour);
+
+ double rounded;
+ if (!RoundNumberToIncrement(cx, totalNanoseconds, unit, increment,
+ roundingMode, &rounded)) {
+ return false;
+ }
+
+ double hours = 0;
+ double minutes = 0;
+ double seconds = 0;
+ double milliseconds = 0;
+ double microseconds = 0;
+ double nanoseconds = 0;
+
+ switch (unit) {
+ case TemporalUnit::Auto:
+ case TemporalUnit::Year:
+ case TemporalUnit::Week:
+ case TemporalUnit::Month:
+ case TemporalUnit::Day:
+ MOZ_CRASH("Unexpected temporal unit");
+
+ case TemporalUnit::Hour:
+ hours = rounded;
+ break;
+ case TemporalUnit::Minute:
+ minutes = rounded;
+ break;
+ case TemporalUnit::Second:
+ seconds = rounded;
+ break;
+ case TemporalUnit::Millisecond:
+ milliseconds = rounded;
+ break;
+ case TemporalUnit::Microsecond:
+ microseconds = rounded;
+ break;
+ case TemporalUnit::Nanosecond:
+ nanoseconds = rounded;
+ break;
+ }
+
+ *result = {
+ 0, 0, 0, 0, hours, minutes, seconds, milliseconds, microseconds,
+ nanoseconds,
+ };
+ return ThrowIfInvalidDuration(cx, *result);
+}
+
+/**
+ * AdjustRoundedDurationDays ( years, months, weeks, days, hours, minutes,
+ * seconds, milliseconds, microseconds, nanoseconds, increment, unit,
+ * roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
+ * precalculatedPlainDateTime )
+ */
+static bool AdjustRoundedDurationDaysSlow(
+ JSContext* cx, const Duration& duration, Increment increment,
+ TemporalUnit unit, TemporalRoundingMode roundingMode,
+ Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
+ Handle<TimeZoneRecord> timeZone,
+ mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
+ InstantSpan dayLength, Duration* result) {
+ MOZ_ASSERT(IsValidDuration(duration));
+ MOZ_ASSERT(IsValidInstantSpan(dayLength));
+
+ // Step 3.
+ Rooted<BigInt*> timeRemainderNs(
+ cx, TotalDurationNanosecondsSlow(cx, duration.time()));
+ if (!timeRemainderNs) {
+ return false;
+ }
+
+ // Steps 4-6.
+ int32_t direction = timeRemainderNs->sign();
+
+ // Steps 7-10. (Computed in caller)
+
+ // Step 11.
+ Rooted<BigInt*> dayLengthNs(cx, ToEpochNanoseconds(cx, dayLength));
+ if (!dayLengthNs) {
+ return false;
+ }
+ MOZ_ASSERT(IsValidInstantSpan(dayLengthNs));
+
+ // Step 12.
+ Rooted<BigInt*> oneDayLess(cx, BigInt::sub(cx, timeRemainderNs, dayLengthNs));
+ if (!oneDayLess) {
+ return false;
+ }
+
+ // Step 13.
+ if ((direction > 0 && oneDayLess->sign() < 0) ||
+ (direction < 0 && oneDayLess->sign() > 0)) {
+ *result = duration;
+ return true;
+ }
+
+ // Step 14.
+ Duration adjustedDateDuration;
+ if (!AddDuration(cx,
+ {
+ duration.years,
+ duration.months,
+ duration.weeks,
+ duration.days,
+ },
+ {0, 0, 0, double(direction)}, zonedRelativeTo, calendar,
+ timeZone, precalculatedPlainDateTime,
+ &adjustedDateDuration)) {
+ return false;
+ }
+
+ // Step 15.
+ Duration roundedTimeDuration;
+ if (!RoundDuration(cx, oneDayLess, unit, increment, roundingMode,
+ &roundedTimeDuration)) {
+ return false;
+ }
+
+ // Step 16.
+ TimeDuration adjustedTimeDuration;
+ if (!BalanceTimeDuration(cx, roundedTimeDuration, TemporalUnit::Hour,
+ &adjustedTimeDuration)) {
+ return false;
+ }
+
+ // Step 17.
+ *result = {
+ adjustedDateDuration.years, adjustedDateDuration.months,
+ adjustedDateDuration.weeks, adjustedDateDuration.days,
+ adjustedTimeDuration.hours, adjustedTimeDuration.minutes,
+ adjustedTimeDuration.seconds, adjustedTimeDuration.milliseconds,
+ adjustedTimeDuration.microseconds, adjustedTimeDuration.nanoseconds,
+ };
+ MOZ_ASSERT(IsValidDuration(*result));
+ return true;
+}
+
+/**
+ * AdjustRoundedDurationDays ( years, months, weeks, days, hours, minutes,
+ * seconds, milliseconds, microseconds, nanoseconds, increment, unit,
+ * roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
+ * precalculatedPlainDateTime )
+ */
+static bool AdjustRoundedDurationDays(
+ JSContext* cx, const Duration& duration, Increment increment,
+ TemporalUnit unit, TemporalRoundingMode roundingMode,
+ Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
+ Handle<TimeZoneRecord> timeZone,
+ mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
+ Duration* result) {
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ // Step 1.
+ if ((TemporalUnit::Year <= unit && unit <= TemporalUnit::Day) ||
+ (unit == TemporalUnit::Nanosecond && increment == Increment{1})) {
+ *result = duration;
+ return true;
+ }
+
+ // The increment is limited for all smaller temporal units.
+ MOZ_ASSERT(increment < MaximumTemporalDurationRoundingIncrement(unit));
+
+ // Step 2.
+ MOZ_ASSERT(precalculatedPlainDateTime);
+
+ // Steps 4-6.
+ //
+ // Step 3 is moved below, so compute |direction| through DurationSign.
+ int32_t direction = DurationSign(duration.time());
+
+ // Steps 7-8.
+ Instant dayStart;
+ if (!AddZonedDateTime(cx, zonedRelativeTo.instant(), timeZone, calendar,
+ duration.date(), *precalculatedPlainDateTime,
+ &dayStart)) {
+ return false;
+ }
+ MOZ_ASSERT(IsValidEpochInstant(dayStart));
+
+ // Step 9.
+ PlainDateTime dayStartDateTime;
+ if (!GetPlainDateTimeFor(cx, timeZone, dayStart, &dayStartDateTime)) {
+ return false;
+ }
+
+ // Step 10.
+ Instant dayEnd;
+ if (!AddDaysToZonedDateTime(cx, dayStart, dayStartDateTime, timeZone,
+ zonedRelativeTo.calendar(), direction, &dayEnd)) {
+ return false;
+ }
+ MOZ_ASSERT(IsValidEpochInstant(dayEnd));
+
+ // Step 11.
+ auto dayLength = dayEnd - dayStart;
+ MOZ_ASSERT(IsValidInstantSpan(dayLength));
+
+ // Step 3. (Reordered)
+ auto timeRemainderNs = TotalDurationNanoseconds(duration.time());
+ if (!timeRemainderNs) {
+ return AdjustRoundedDurationDaysSlow(
+ cx, duration, increment, unit, roundingMode, zonedRelativeTo, calendar,
+ timeZone, precalculatedPlainDateTime, dayLength, result);
+ }
+
+ // Step 12.
+ auto checkedOneDayLess = *timeRemainderNs - dayLength.toNanoseconds();
+ if (!checkedOneDayLess.isValid()) {
+ return AdjustRoundedDurationDaysSlow(
+ cx, duration, increment, unit, roundingMode, zonedRelativeTo, calendar,
+ timeZone, precalculatedPlainDateTime, dayLength, result);
+ }
+ auto oneDayLess = checkedOneDayLess.value();
+
+ // Step 13.
+ if ((direction > 0 && oneDayLess < 0) || (direction < 0 && oneDayLess > 0)) {
+ *result = duration;
+ return true;
+ }
+
+ // Step 14.
+ Duration adjustedDateDuration;
+ if (!AddDuration(cx,
+ {
+ duration.years,
+ duration.months,
+ duration.weeks,
+ duration.days,
+ },
+ {0, 0, 0, double(direction)}, zonedRelativeTo, calendar,
+ timeZone, precalculatedPlainDateTime,
+ &adjustedDateDuration)) {
+ return false;
+ }
+
+ // Step 15.
+ Duration roundedTimeDuration;
+ if (!RoundDuration(cx, oneDayLess, unit, increment, roundingMode,
+ &roundedTimeDuration)) {
+ return false;
+ }
+
+ // Step 16.
+ TimeDuration adjustedTimeDuration;
+ if (!BalanceTimeDuration(cx, roundedTimeDuration, TemporalUnit::Hour,
+ &adjustedTimeDuration)) {
+ return false;
+ }
+
+ // FIXME: spec bug - CreateDurationRecord is fallible because the adjusted
+ // date and time durations can be have different signs.
+ // https://github.com/tc39/proposal-temporal/issues/2536
+ //
+ // clang-format off
+ //
+ // {
+ // let calendar = new class extends Temporal.Calendar {
+ // dateAdd(date, duration, options) {
+ // console.log(`dateAdd(${date}, ${duration})`);
+ // if (duration.days === 10) {
+ // return super.dateAdd(date, duration.negated(), options);
+ // }
+ // return super.dateAdd(date, duration, options);
+ // }
+ // }("iso8601");
+ //
+ // let zdt = new Temporal.ZonedDateTime(0n, "UTC", calendar);
+ //
+ // let d = Temporal.Duration.from({
+ // days: 10,
+ // hours: 25,
+ // });
+ //
+ // let r = d.round({
+ // smallestUnit: "nanoseconds",
+ // roundingIncrement: 5,
+ // relativeTo: zdt,
+ // });
+ // console.log(r.toString());
+ // }
+ //
+ // clang-format on
+
+ // Step 17.
+ *result = {
+ adjustedDateDuration.years, adjustedDateDuration.months,
+ adjustedDateDuration.weeks, adjustedDateDuration.days,
+ adjustedTimeDuration.hours, adjustedTimeDuration.minutes,
+ adjustedTimeDuration.seconds, adjustedTimeDuration.milliseconds,
+ adjustedTimeDuration.microseconds, adjustedTimeDuration.nanoseconds,
+ };
+ return ThrowIfInvalidDuration(cx, *result);
+}
+
+/**
+ * AdjustRoundedDurationDays ( years, months, weeks, days, hours, minutes,
+ * seconds, milliseconds, microseconds, nanoseconds, increment, unit,
+ * roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
+ * precalculatedPlainDateTime )
+ */
+bool js::temporal::AdjustRoundedDurationDays(
+ JSContext* cx, const Duration& duration, Increment increment,
+ TemporalUnit unit, TemporalRoundingMode roundingMode,
+ Handle<ZonedDateTime> zonedRelativeTo, Handle<CalendarRecord> calendar,
+ Handle<TimeZoneRecord> timeZone,
+ const PlainDateTime& precalculatedPlainDateTime, Duration* result) {
+ return ::AdjustRoundedDurationDays(
+ cx, duration, increment, unit, roundingMode, zonedRelativeTo, calendar,
+ timeZone, mozilla::SomeRef(precalculatedPlainDateTime), result);
+}
+
+static bool BigIntToStringBuilder(JSContext* cx, Handle<BigInt*> num,
+ JSStringBuilder& sb) {
+ MOZ_ASSERT(!num->isNegative());
+
+ JSLinearString* str = BigInt::toString<CanGC>(cx, num, 10);
+ if (!str) {
+ return false;
+ }
+ return sb.append(str);
+}
+
+static bool NumberToStringBuilder(JSContext* cx, double num,
+ JSStringBuilder& sb) {
+ MOZ_ASSERT(IsInteger(num));
+ MOZ_ASSERT(num >= 0);
+
+ if (num < DOUBLE_INTEGRAL_PRECISION_LIMIT) {
+ ToCStringBuf cbuf;
+ size_t length;
+ const char* numStr = NumberToCString(&cbuf, num, &length);
+
+ return sb.append(numStr, length);
+ }
+
+ Rooted<BigInt*> bi(cx, BigInt::createFromDouble(cx, num));
+ if (!bi) {
+ return false;
+ }
+ return BigIntToStringBuilder(cx, bi, sb);
+}
+
+static Duration AbsoluteDuration(const Duration& duration) {
+ return {
+ std::abs(duration.years), std::abs(duration.months),
+ std::abs(duration.weeks), std::abs(duration.days),
+ std::abs(duration.hours), std::abs(duration.minutes),
+ std::abs(duration.seconds), std::abs(duration.milliseconds),
+ std::abs(duration.microseconds), std::abs(duration.nanoseconds),
+ };
+}
+
+/**
+ * FormatFractionalSeconds ( subSecondNanoseconds, precision )
+ */
+[[nodiscard]] static bool FormatFractionalSeconds(JSStringBuilder& result,
+ int32_t subSecondNanoseconds,
+ Precision precision) {
+ MOZ_ASSERT(0 <= subSecondNanoseconds && subSecondNanoseconds < 1'000'000'000);
+ MOZ_ASSERT(precision != Precision::Minute());
+
+ // Steps 1-2.
+ if (precision == Precision::Auto()) {
+ // Step 1.a.
+ if (subSecondNanoseconds == 0) {
+ return true;
+ }
+
+ // Step 3. (Reordered)
+ if (!result.append('.')) {
+ return false;
+ }
+
+ // Steps 1.b-c.
+ uint32_t k = 100'000'000;
+ do {
+ if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
+ return false;
+ }
+ subSecondNanoseconds %= k;
+ k /= 10;
+ } while (subSecondNanoseconds);
+ } else {
+ // Step 2.a.
+ uint8_t p = precision.value();
+ if (p == 0) {
+ return true;
+ }
+
+ // Step 3. (Reordered)
+ if (!result.append('.')) {
+ return false;
+ }
+
+ // Steps 2.b-c.
+ uint32_t k = 100'000'000;
+ for (uint8_t i = 0; i < precision.value(); i++) {
+ if (!result.append(char('0' + (subSecondNanoseconds / k)))) {
+ return false;
+ }
+ subSecondNanoseconds %= k;
+ k /= 10;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * TemporalDurationToString ( years, months, weeks, days, hours, minutes,
+ * seconds, milliseconds, microseconds, nanoseconds, precision )
+ */
+static JSString* TemporalDurationToString(JSContext* cx,
+ const Duration& duration,
+ Precision precision) {
+ MOZ_ASSERT(IsValidDuration(duration));
+ MOZ_ASSERT(precision != Precision::Minute());
+
+ // Convert to absolute values up front. This is okay to do, because when the
+ // duration is valid, all components have the same sign.
+ const auto& [years, months, weeks, days, hours, minutes, seconds,
+ milliseconds, microseconds, nanoseconds] =
+ AbsoluteDuration(duration);
+
+ // Fast path for zero durations.
+ if (years == 0 && months == 0 && weeks == 0 && days == 0 && hours == 0 &&
+ minutes == 0 && seconds == 0 && milliseconds == 0 && microseconds == 0 &&
+ nanoseconds == 0 &&
+ (precision == Precision::Auto() || precision.value() == 0)) {
+ return NewStringCopyZ<CanGC>(cx, "PT0S");
+ }
+
+ Rooted<BigInt*> totalSecondsBigInt(cx);
+ double totalSeconds = seconds;
+ int32_t fraction = 0;
+ if (milliseconds != 0 || microseconds != 0 || nanoseconds != 0) {
+ bool imprecise = false;
+ do {
+ int64_t sec;
+ int64_t milli;
+ int64_t micro;
+ int64_t nano;
+ if (!mozilla::NumberEqualsInt64(seconds, &sec) ||
+ !mozilla::NumberEqualsInt64(milliseconds, &milli) ||
+ !mozilla::NumberEqualsInt64(microseconds, &micro) ||
+ !mozilla::NumberEqualsInt64(nanoseconds, &nano)) {
+ imprecise = true;
+ break;
+ }
+
+ mozilla::CheckedInt64 intermediate;
+
+ // Step 2.
+ intermediate = micro;
+ intermediate += (nano / 1000);
+ if (!intermediate.isValid()) {
+ imprecise = true;
+ break;
+ }
+ micro = intermediate.value();
+
+ // Step 3.
+ nano %= 1000;
+
+ // Step 4.
+ intermediate = milli;
+ intermediate += (micro / 1000);
+ if (!intermediate.isValid()) {
+ imprecise = true;
+ break;
+ }
+ milli = intermediate.value();
+
+ // Step 5.
+ micro %= 1000;
+
+ // Step 6.
+ intermediate = sec;
+ intermediate += (milli / 1000);
+ if (!intermediate.isValid()) {
+ imprecise = true;
+ break;
+ }
+ sec = intermediate.value();
+
+ // Step 7.
+ milli %= 1000;
+
+ if (sec < int64_t(DOUBLE_INTEGRAL_PRECISION_LIMIT)) {
+ totalSeconds = double(sec);
+ } else {
+ totalSecondsBigInt = BigInt::createFromInt64(cx, sec);
+ if (!totalSecondsBigInt) {
+ return nullptr;
+ }
+ }
+
+ // These are now all in the range [0, 999].
+ MOZ_ASSERT(0 <= milli && milli <= 999);
+ MOZ_ASSERT(0 <= micro && micro <= 999);
+ MOZ_ASSERT(0 <= nano && nano <= 999);
+
+ // Step 20.b. (Reordered)
+ fraction = milli * 1'000'000 + micro * 1'000 + nano;
+ MOZ_ASSERT(0 <= fraction && fraction < 1'000'000'000);
+ } while (false);
+
+ // If a result was imprecise, recompute with BigInt to get full precision.
+ if (imprecise) {
+ Rooted<BigInt*> secs(cx, BigInt::createFromDouble(cx, seconds));
+ if (!secs) {
+ return nullptr;
+ }
+
+ Rooted<BigInt*> millis(cx, BigInt::createFromDouble(cx, milliseconds));
+ if (!millis) {
+ return nullptr;
+ }
+
+ Rooted<BigInt*> micros(cx, BigInt::createFromDouble(cx, microseconds));
+ if (!micros) {
+ return nullptr;
+ }
+
+ Rooted<BigInt*> nanos(cx, BigInt::createFromDouble(cx, nanoseconds));
+ if (!nanos) {
+ return nullptr;
+ }
+
+ Rooted<BigInt*> thousand(cx, BigInt::createFromInt64(cx, 1000));
+ if (!thousand) {
+ return nullptr;
+ }
+
+ // Steps 2-3.
+ Rooted<BigInt*> quotient(cx);
+ if (!BigInt::divmod(cx, nanos, thousand, &quotient, &nanos)) {
+ return nullptr;
+ }
+
+ micros = BigInt::add(cx, micros, quotient);
+ if (!micros) {
+ return nullptr;
+ }
+
+ // Steps 4-5.
+ if (!BigInt::divmod(cx, micros, thousand, &quotient, &micros)) {
+ return nullptr;
+ }
+
+ millis = BigInt::add(cx, millis, quotient);
+ if (!millis) {
+ return nullptr;
+ }
+
+ // Steps 6-7.
+ if (!BigInt::divmod(cx, millis, thousand, &quotient, &millis)) {
+ return nullptr;
+ }
+
+ totalSecondsBigInt = BigInt::add(cx, secs, quotient);
+ if (!totalSecondsBigInt) {
+ return nullptr;
+ }
+
+ // These are now all in the range [0, 999].
+ int64_t milli = BigInt::toInt64(millis);
+ int64_t micro = BigInt::toInt64(micros);
+ int64_t nano = BigInt::toInt64(nanos);
+
+ MOZ_ASSERT(0 <= milli && milli <= 999);
+ MOZ_ASSERT(0 <= micro && micro <= 999);
+ MOZ_ASSERT(0 <= nano && nano <= 999);
+
+ // Step 20.b. (Reordered)
+ fraction = milli * 1'000'000 + micro * 1'000 + nano;
+ MOZ_ASSERT(0 <= fraction && fraction < 1'000'000'000);
+ }
+ }
+
+ // Steps 8 and 13.
+ JSStringBuilder result(cx);
+
+ // Step 1. (Reordered)
+ int32_t sign = DurationSign(duration);
+
+ // Step 21. (Reordered)
+ if (sign < 0) {
+ if (!result.append('-')) {
+ return nullptr;
+ }
+ }
+
+ // Step 22. (Reordered)
+ if (!result.append('P')) {
+ return nullptr;
+ }
+
+ // Step 9.
+ if (years != 0) {
+ if (!NumberToStringBuilder(cx, years, result)) {
+ return nullptr;
+ }
+ if (!result.append('Y')) {
+ return nullptr;
+ }
+ }
+
+ // Step 10.
+ if (months != 0) {
+ if (!NumberToStringBuilder(cx, months, result)) {
+ return nullptr;
+ }
+ if (!result.append('M')) {
+ return nullptr;
+ }
+ }
+
+ // Step 11.
+ if (weeks != 0) {
+ if (!NumberToStringBuilder(cx, weeks, result)) {
+ return nullptr;
+ }
+ if (!result.append('W')) {
+ return nullptr;
+ }
+ }
+
+ // Step 12.
+ if (days != 0) {
+ if (!NumberToStringBuilder(cx, days, result)) {
+ return nullptr;
+ }
+ if (!result.append('D')) {
+ return nullptr;
+ }
+ }
+
+ // Steps 16-17.
+ bool nonzeroSecondsAndLower = seconds != 0 || milliseconds != 0 ||
+ microseconds != 0 || nanoseconds != 0;
+ MOZ_ASSERT(nonzeroSecondsAndLower ==
+ (totalSeconds != 0 ||
+ (totalSecondsBigInt && !totalSecondsBigInt->isZero()) ||
+ fraction != 0));
+
+ // Steps 18-19.
+ bool zeroMinutesAndHigher = years == 0 && months == 0 && weeks == 0 &&
+ days == 0 && hours == 0 && minutes == 0;
+
+ // Step 20. (if-condition)
+ bool hasSecondsPart = nonzeroSecondsAndLower || zeroMinutesAndHigher ||
+ precision != Precision::Auto();
+
+ if (hours != 0 || minutes != 0 || hasSecondsPart) {
+ // Step 23. (Reordered)
+ if (!result.append('T')) {
+ return nullptr;
+ }
+
+ // Step 14.
+ if (hours != 0) {
+ if (!NumberToStringBuilder(cx, hours, result)) {
+ return nullptr;
+ }
+ if (!result.append('H')) {
+ return nullptr;
+ }
+ }
+
+ // Step 15.
+ if (minutes != 0) {
+ if (!NumberToStringBuilder(cx, minutes, result)) {
+ return nullptr;
+ }
+ if (!result.append('M')) {
+ return nullptr;
+ }
+ }
+
+ // Step 20.
+ if (hasSecondsPart) {
+ // Step 20.a.
+ if (totalSecondsBigInt) {
+ if (!BigIntToStringBuilder(cx, totalSecondsBigInt, result)) {
+ return nullptr;
+ }
+ } else {
+ if (!NumberToStringBuilder(cx, totalSeconds, result)) {
+ return nullptr;
+ }
+ }
+
+ // Step 20.b. (Moved above)
+
+ // Step 20.c.
+ if (!FormatFractionalSeconds(result, fraction, precision)) {
+ return nullptr;
+ }
+
+ // Step 20.d.
+ if (!result.append('S')) {
+ return nullptr;
+ }
+ }
+ }
+
+ // Step 24.
+ return result.finishString();
+}
+
+/**
+ * ToRelativeTemporalObject ( options )
+ */
+static bool ToRelativeTemporalObject(
+ JSContext* cx, Handle<JSObject*> options,
+ MutableHandle<Wrapped<PlainDateObject*>> plainRelativeTo,
+ MutableHandle<ZonedDateTime> zonedRelativeTo,
+ MutableHandle<TimeZoneRecord> timeZoneRecord) {
+ // Step 1.
+ Rooted<Value> value(cx);
+ if (!GetProperty(cx, options, options, cx->names().relativeTo, &value)) {
+ return false;
+ }
+
+ // Step 2.
+ if (value.isUndefined()) {
+ // FIXME: spec issue - switch return record fields for consistency.
+ // FIXME: spec bug - [[TimeZoneRec]] field not created
+
+ plainRelativeTo.set(nullptr);
+ zonedRelativeTo.set(ZonedDateTime{});
+ timeZoneRecord.set(TimeZoneRecord{});
+ return true;
+ }
+
+ // Step 3.
+ auto offsetBehaviour = OffsetBehaviour::Option;
+
+ // Step 4.
+ auto matchBehaviour = MatchBehaviour::MatchExactly;
+
+ // Steps 5-6.
+ PlainDateTime dateTime;
+ Rooted<CalendarValue> calendar(cx);
+ Rooted<TimeZoneValue> timeZone(cx);
+ int64_t offsetNs;
+ if (value.isObject()) {
+ Rooted<JSObject*> obj(cx, &value.toObject());
+
+ // Step 5.a.
+ if (auto* zonedDateTime = obj->maybeUnwrapIf<ZonedDateTimeObject>()) {
+ auto instant = ToInstant(zonedDateTime);
+ Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
+ Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
+
+ if (!timeZone.wrap(cx)) {
+ return false;
+ }
+ if (!calendar.wrap(cx)) {
+ return false;
+ }
+
+ // Step 5.a.i.
+ Rooted<TimeZoneRecord> timeZoneRec(cx);
+ if (!CreateTimeZoneMethodsRecord(
+ cx, timeZone,
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ TimeZoneMethod::GetPossibleInstantsFor,
+ },
+ &timeZoneRec)) {
+ return false;
+ }
+
+ // Step 5.a.ii.
+ plainRelativeTo.set(nullptr);
+ zonedRelativeTo.set(ZonedDateTime{instant, timeZone, calendar});
+ timeZoneRecord.set(timeZoneRec);
+ return true;
+ }
+
+ // Step 5.b.
+ if (obj->canUnwrapAs<PlainDateObject>()) {
+ plainRelativeTo.set(obj);
+ zonedRelativeTo.set(ZonedDateTime{});
+ timeZoneRecord.set(TimeZoneRecord{});
+ return true;
+ }
+
+ // Step 5.c.
+ if (auto* dateTime = obj->maybeUnwrapIf<PlainDateTimeObject>()) {
+ auto plainDateTime = ToPlainDate(dateTime);
+
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+ if (!calendar.wrap(cx)) {
+ return false;
+ }
+
+ // Step 5.c.i.
+ auto* plainDate = CreateTemporalDate(cx, plainDateTime, calendar);
+ if (!plainDate) {
+ return false;
+ }
+
+ // Step 5.c.ii.
+ plainRelativeTo.set(plainDate);
+ zonedRelativeTo.set(ZonedDateTime{});
+ timeZoneRecord.set(TimeZoneRecord{});
+ return true;
+ }
+
+ // Step 5.d.
+ if (!GetTemporalCalendarWithISODefault(cx, obj, &calendar)) {
+ return false;
+ }
+
+ // Step 5.e.
+ Rooted<CalendarRecord> calendarRec(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendar,
+ {
+ CalendarMethod::DateFromFields,
+ CalendarMethod::Fields,
+ },
+ &calendarRec)) {
+ return false;
+ }
+
+ // Step 5.f.
+ JS::RootedVector<PropertyKey> fieldNames(cx);
+ if (!CalendarFields(cx, calendarRec,
+ {CalendarField::Day, CalendarField::Month,
+ CalendarField::MonthCode, CalendarField::Year},
+ &fieldNames)) {
+ return false;
+ }
+
+ // Step 5.g.
+ if (!AppendSorted(cx, fieldNames.get(),
+ {
+ TemporalField::Hour,
+ TemporalField::Microsecond,
+ TemporalField::Millisecond,
+ TemporalField::Minute,
+ TemporalField::Nanosecond,
+ TemporalField::Offset,
+ TemporalField::Second,
+ TemporalField::TimeZone,
+ })) {
+ return false;
+ }
+
+ // Step 5.h.
+ Rooted<PlainObject*> fields(cx, PrepareTemporalFields(cx, obj, fieldNames));
+ if (!fields) {
+ return false;
+ }
+
+ // Step 5.i.
+ Rooted<PlainObject*> dateOptions(cx, NewPlainObjectWithProto(cx, nullptr));
+ if (!dateOptions) {
+ return false;
+ }
+
+ // Step 5.j.
+ Rooted<Value> overflow(cx, StringValue(cx->names().constrain));
+ if (!DefineDataProperty(cx, dateOptions, cx->names().overflow, overflow)) {
+ return false;
+ }
+
+ // Step 5.k.
+ if (!InterpretTemporalDateTimeFields(cx, calendarRec, fields, dateOptions,
+ &dateTime)) {
+ return false;
+ }
+
+ // Step 5.l.
+ Rooted<Value> offset(cx);
+ if (!GetProperty(cx, fields, fields, cx->names().offset, &offset)) {
+ return false;
+ }
+
+ // Step 5.m.
+ Rooted<Value> timeZoneValue(cx);
+ if (!GetProperty(cx, fields, fields, cx->names().timeZone,
+ &timeZoneValue)) {
+ return false;
+ }
+
+ // Step 5.n.
+ if (!timeZoneValue.isUndefined()) {
+ if (!ToTemporalTimeZone(cx, timeZoneValue, &timeZone)) {
+ return false;
+ }
+ }
+
+ // Step 5.o.
+ if (offset.isUndefined()) {
+ offsetBehaviour = OffsetBehaviour::Wall;
+ }
+
+ // Steps 8-9.
+ if (timeZone) {
+ if (offsetBehaviour == OffsetBehaviour::Option) {
+ MOZ_ASSERT(!offset.isUndefined());
+ MOZ_ASSERT(offset.isString());
+
+ // Step 8.a.
+ Rooted<JSString*> offsetString(cx, offset.toString());
+ if (!offsetString) {
+ return false;
+ }
+
+ // Step 8.b.
+ if (!ParseDateTimeUTCOffset(cx, offsetString, &offsetNs)) {
+ return false;
+ }
+ } else {
+ // Step 9.
+ offsetNs = 0;
+ }
+ }
+ } else {
+ // Step 6.a.
+ if (!value.isString()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, value,
+ nullptr, "not a string");
+ return false;
+ }
+ Rooted<JSString*> string(cx, value.toString());
+
+ // Step 6.b.
+ bool isUTC;
+ bool hasOffset;
+ int64_t timeZoneOffset;
+ Rooted<ParsedTimeZone> timeZoneName(cx);
+ Rooted<JSString*> calendarString(cx);
+ if (!ParseTemporalRelativeToString(cx, string, &dateTime, &isUTC,
+ &hasOffset, &timeZoneOffset,
+ &timeZoneName, &calendarString)) {
+ return false;
+ }
+
+ // Step 6.c. (Not applicable in our implementation.)
+
+ // Steps 6.e-f.
+ if (timeZoneName) {
+ // Step 6.f.i.
+ if (!ToTemporalTimeZone(cx, timeZoneName, &timeZone)) {
+ return false;
+ }
+
+ // Steps 6.f.ii-iii.
+ if (isUTC) {
+ offsetBehaviour = OffsetBehaviour::Exact;
+ } else if (!hasOffset) {
+ offsetBehaviour = OffsetBehaviour::Wall;
+ }
+
+ // Step 6.f.iv.
+ matchBehaviour = MatchBehaviour::MatchMinutes;
+ } else {
+ MOZ_ASSERT(!timeZone);
+ }
+
+ // Steps 6.g-j.
+ if (calendarString) {
+ if (!ToBuiltinCalendar(cx, calendarString, &calendar)) {
+ return false;
+ }
+ } else {
+ calendar.set(CalendarValue(cx->names().iso8601));
+ }
+
+ // Steps 8-9.
+ if (timeZone) {
+ if (offsetBehaviour == OffsetBehaviour::Option) {
+ MOZ_ASSERT(hasOffset);
+
+ // Step 8.a.
+ offsetNs = timeZoneOffset;
+ } else {
+ // Step 9.
+ offsetNs = 0;
+ }
+ }
+ }
+
+ // Step 7.
+ if (!timeZone) {
+ // Step 7.a.
+ auto* plainDate = CreateTemporalDate(cx, dateTime.date, calendar);
+ if (!plainDate) {
+ return false;
+ }
+
+ plainRelativeTo.set(plainDate);
+ zonedRelativeTo.set(ZonedDateTime{});
+ timeZoneRecord.set(TimeZoneRecord{});
+ return true;
+ }
+
+ // Steps 8-9. (Moved above)
+
+ // Step 10.
+ Rooted<TimeZoneRecord> timeZoneRec(cx);
+ if (!CreateTimeZoneMethodsRecord(cx, timeZone,
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ TimeZoneMethod::GetPossibleInstantsFor,
+ },
+ &timeZoneRec)) {
+ return false;
+ }
+
+ // Step 11.
+ Instant epochNanoseconds;
+ if (!InterpretISODateTimeOffset(
+ cx, dateTime, offsetBehaviour, offsetNs, timeZoneRec,
+ TemporalDisambiguation::Compatible, TemporalOffset::Reject,
+ matchBehaviour, &epochNanoseconds)) {
+ return false;
+ }
+ MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds));
+
+ // Step 12.
+ plainRelativeTo.set(nullptr);
+ zonedRelativeTo.set(ZonedDateTime{epochNanoseconds, timeZone, calendar});
+ timeZoneRecord.set(timeZoneRec);
+ return true;
+}
+
+/**
+ * CreateCalendarMethodsRecordFromRelativeTo ( plainRelativeTo, zonedRelativeTo,
+ * methods )
+ */
+static bool CreateCalendarMethodsRecordFromRelativeTo(
+ JSContext* cx, Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
+ Handle<ZonedDateTime> zonedRelativeTo,
+ mozilla::EnumSet<CalendarMethod> methods,
+ MutableHandle<CalendarRecord> result) {
+ // Step 1.
+ if (zonedRelativeTo) {
+ return CreateCalendarMethodsRecord(cx, zonedRelativeTo.calendar(), methods,
+ result);
+ }
+
+ // Step 2.
+ if (plainRelativeTo) {
+ auto* unwrapped = plainRelativeTo.unwrap(cx);
+ if (!unwrapped) {
+ return false;
+ }
+
+ Rooted<CalendarValue> calendar(cx, unwrapped->calendar());
+ if (!calendar.wrap(cx)) {
+ return false;
+ }
+
+ return CreateCalendarMethodsRecord(cx, calendar, methods, result);
+ }
+
+ // Step 3.
+ return true;
+}
+
+static constexpr bool IsSafeInteger(int64_t x) {
+ constexpr int64_t MaxSafeInteger = int64_t(1) << 53;
+ constexpr int64_t MinSafeInteger = -MaxSafeInteger;
+ return MinSafeInteger < x && x < MaxSafeInteger;
+}
+
+/**
+ * RoundNumberToIncrement ( x, increment, roundingMode )
+ */
+static void TruncateNumber(int64_t numerator, int64_t denominator,
+ double* quotient, double* total) {
+ // Computes the quotient and real number value of the rational number
+ // |numerator / denominator|.
+
+ // Int64 division truncates.
+ int64_t q = numerator / denominator;
+ int64_t r = numerator % denominator;
+
+ // The total value is stored as a mathematical number in the draft proposal,
+ // so we can't convert it to a double without loss of precision. We use two
+ // different approaches to compute the total value based on the input range.
+ //
+ // For example:
+ //
+ // When |numerator = 1000001| and |denominator = 60 * 1000|, the exact result
+ // is |16.66668333...| and the best possible approximation is
+ // |16.666683333333335070...๐”ฝ|. We can this approximation when casting both
+ // numerator and denominator to doubles and then performing a double division.
+ //
+ // When |numerator = 14400000000000001| and |denominator = 3600000000000|, we
+ // can't use double division, because |14400000000000001| can't be represented
+ // as an exact double value. The exact result is |4000.0000000000002777...|.
+ //
+ // The best possible approximation is |4000.0000000000004547...๐”ฝ|, which can
+ // be computed through |q + r / denominator|.
+ if (::IsSafeInteger(numerator) && ::IsSafeInteger(denominator)) {
+ *quotient = double(q);
+ *total = double(numerator) / double(denominator);
+ } else {
+ *quotient = double(q);
+ *total = double(q) + double(r) / double(denominator);
+ }
+}
+
+/**
+ * RoundNumberToIncrement ( x, increment, roundingMode )
+ */
+static bool TruncateNumber(JSContext* cx, Handle<BigInt*> numerator,
+ Handle<BigInt*> denominator, double* quotient,
+ double* total) {
+ MOZ_ASSERT(!denominator->isNegative());
+ MOZ_ASSERT(!denominator->isZero());
+
+ // Dividing zero is always zero.
+ if (numerator->isZero()) {
+ *quotient = 0;
+ *total = 0;
+ return true;
+ }
+
+ int64_t num, denom;
+ if (BigInt::isInt64(numerator, &num) &&
+ BigInt::isInt64(denominator, &denom)) {
+ TruncateNumber(num, denom, quotient, total);
+ return true;
+ }
+
+ // BigInt division truncates.
+ Rooted<BigInt*> quot(cx);
+ Rooted<BigInt*> rem(cx);
+ if (!BigInt::divmod(cx, numerator, denominator, &quot, &rem)) {
+ return false;
+ }
+
+ double q = BigInt::numberValue(quot);
+ *quotient = q;
+ *total = q + BigInt::numberValue(rem) / BigInt::numberValue(denominator);
+ return true;
+}
+
+/**
+ * RoundNumberToIncrement ( x, increment, roundingMode )
+ */
+static bool TruncateNumber(JSContext* cx, const Duration& toRound,
+ TemporalUnit unit, double* quotient, double* total) {
+ MOZ_ASSERT(unit >= TemporalUnit::Day);
+
+ int64_t denominator = ToNanoseconds(unit);
+ MOZ_ASSERT(denominator > 0);
+ MOZ_ASSERT(denominator <= 86'400'000'000'000);
+
+ // Fast-path when we can perform the whole computation with int64 values.
+ if (auto numerator = TotalDurationNanoseconds(toRound)) {
+ TruncateNumber(*numerator, denominator, quotient, total);
+ return true;
+ }
+
+ Rooted<BigInt*> numerator(cx, TotalDurationNanosecondsSlow(cx, toRound));
+ if (!numerator) {
+ return false;
+ }
+
+ // Division by one has no remainder.
+ if (denominator == 1) {
+ double q = BigInt::numberValue(numerator);
+ *quotient = q;
+ *total = q;
+ return true;
+ }
+
+ Rooted<BigInt*> denom(cx, BigInt::createFromInt64(cx, denominator));
+ if (!denom) {
+ return false;
+ }
+
+ // BigInt division truncates.
+ Rooted<BigInt*> quot(cx);
+ Rooted<BigInt*> rem(cx);
+ if (!BigInt::divmod(cx, numerator, denom, &quot, &rem)) {
+ return false;
+ }
+
+ double q = BigInt::numberValue(quot);
+ *quotient = q;
+ *total = q + BigInt::numberValue(rem) / double(denominator);
+ return true;
+}
+
+/**
+ * RoundNumberToIncrement ( x, increment, roundingMode )
+ */
+static bool RoundNumberToIncrement(JSContext* cx, const Duration& toRound,
+ TemporalUnit unit, Increment increment,
+ TemporalRoundingMode roundingMode,
+ double* result) {
+ MOZ_ASSERT(unit >= TemporalUnit::Day);
+
+ // Fast-path when we can perform the whole computation with int64 values.
+ if (auto total = TotalDurationNanoseconds(toRound)) {
+ return RoundNumberToIncrement(cx, *total, unit, increment, roundingMode,
+ result);
+ }
+
+ Rooted<BigInt*> totalNs(cx, TotalDurationNanosecondsSlow(cx, toRound));
+ if (!totalNs) {
+ return false;
+ }
+
+ return RoundNumberToIncrement(cx, totalNs, unit, increment, roundingMode,
+ result);
+}
+
+struct RoundedDuration final {
+ Duration duration;
+ double total = 0;
+};
+
+enum class ComputeRemainder : bool { No, Yes };
+
+/**
+ * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
+ * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
+ * precalculatedPlainDateTime ] ] ] ] ] )
+ */
+static bool RoundDuration(JSContext* cx, const Duration& duration,
+ Increment increment, TemporalUnit unit,
+ TemporalRoundingMode roundingMode,
+ ComputeRemainder computeRemainder,
+ RoundedDuration* result) {
+ // The remainder is only needed when called from |Duration_total|. And `total`
+ // always passes |increment=1| and |roundingMode=trunc|.
+ MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
+ increment == Increment{1});
+ MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
+ roundingMode == TemporalRoundingMode::Trunc);
+
+ auto [years, months, weeks, days, hours, minutes, seconds, milliseconds,
+ microseconds, nanoseconds] = duration;
+
+ // Steps 1-5. (Not applicable.)
+
+ // Step 6.
+ if (unit <= TemporalUnit::Week) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_UNCOMPARABLE,
+ "relativeTo");
+ return false;
+ }
+
+ // TODO: We could directly return here if unit=nanoseconds and increment=1,
+ // because in that case this operation is a no-op. This case happens for
+ // example when calling Temporal.PlainTime.prototype.{since,until} without an
+ // options object.
+ //
+ // But maybe this can be even more efficiently handled in the callers. For
+ // example when Temporal.PlainTime.prototype.{since,until} is called without
+ // an options object, we can not only skip the RoundDuration call, but also
+ // the following BalanceTimeDuration call.
+
+ // Step 7. (Not applicable.)
+
+ // Step 8. (Moved below.)
+
+ // Step 9. (Not applicable.)
+
+ // Steps 10-19.
+ Duration toRound;
+ double* roundedTime;
+ switch (unit) {
+ case TemporalUnit::Auto:
+ case TemporalUnit::Year:
+ case TemporalUnit::Week:
+ case TemporalUnit::Month:
+ // Steps 10-12. (Not applicable.)
+ MOZ_CRASH("Unexpected temporal unit");
+
+ case TemporalUnit::Day: {
+ // clang-format off
+ //
+ // Relevant steps from the spec algorithm:
+ //
+ // 6.a Let nanoseconds be ! TotalDurationNanoseconds(0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, 0).
+ // 6.d Let result be ? NanosecondsToDays(nanoseconds, intermediate).
+ // 6.e Set days to days + result.[[Days]] + result.[[Nanoseconds]] / abs(result.[[DayLength]]).
+ // ...
+ // 12.a Let fractionalDays be days.
+ // 12.b Set days to ? RoundNumberToIncrement(days, increment, roundingMode).
+ // 12.c Set remainder to fractionalDays - days.
+ //
+ // Where `result.[[Days]]` is `the integral part of nanoseconds / dayLengthNs`
+ // and `result.[[Nanoseconds]]` is `nanoseconds modulo dayLengthNs`.
+ // With `dayLengthNs = 8.64 ร— 10^13`.
+ //
+ // So we have:
+ // d + r.days + (r.nanoseconds / len)
+ // = d + [ns / len] + ((ns % len) / len)
+ // = d + [ns / len] + ((ns - ([ns / len] ร— len)) / len)
+ // = d + [ns / len] + (ns / len) - (([ns / len] ร— len) / len)
+ // = d + [ns / len] + (ns / len) - [ns / len]
+ // = d + (ns / len)
+ // = ((d ร— len) / len) + (ns / len)
+ // = ((d ร— len) + ns) / len
+ //
+ // `((d ร— len) + ns)` is the result of calling TotalDurationNanoseconds(),
+ // which means we can use the same code for all time computations in this
+ // function.
+ //
+ // clang-format on
+
+ MOZ_ASSERT(increment <= Increment{1'000'000'000},
+ "limited by ToTemporalRoundingIncrement");
+
+ // Steps 7.a, 7.c, and 13.a-b.
+ toRound = duration;
+ roundedTime = &days;
+
+ // Step 7.b. (Not applicable)
+
+ // Steps 7.d-e.
+ hours = 0;
+ minutes = 0;
+ seconds = 0;
+ milliseconds = 0;
+ microseconds = 0;
+ nanoseconds = 0;
+ break;
+ }
+
+ case TemporalUnit::Hour: {
+ MOZ_ASSERT(increment <= Increment{24},
+ "limited by MaximumTemporalDurationRoundingIncrement");
+
+ // Steps 8 and 14.a-c.
+ toRound = {
+ 0,
+ 0,
+ 0,
+ 0,
+ hours,
+ minutes,
+ seconds,
+ milliseconds,
+ microseconds,
+ nanoseconds,
+ };
+ roundedTime = &hours;
+
+ // Step 14.d.
+ minutes = 0;
+ seconds = 0;
+ milliseconds = 0;
+ microseconds = 0;
+ nanoseconds = 0;
+ break;
+ }
+
+ case TemporalUnit::Minute: {
+ MOZ_ASSERT(increment <= Increment{60},
+ "limited by MaximumTemporalDurationRoundingIncrement");
+
+ // Steps 8 and 15.a-c.
+ toRound = {
+ 0, 0, 0, 0, 0, minutes, seconds, milliseconds, microseconds,
+ nanoseconds,
+ };
+ roundedTime = &minutes;
+
+ // Step 15.d.
+ seconds = 0;
+ milliseconds = 0;
+ microseconds = 0;
+ nanoseconds = 0;
+ break;
+ }
+
+ case TemporalUnit::Second: {
+ MOZ_ASSERT(increment <= Increment{60},
+ "limited by MaximumTemporalDurationRoundingIncrement");
+
+ // Steps 8 and 16.a-b.
+ toRound = {
+ 0, 0, 0, 0, 0, 0, seconds, milliseconds, microseconds, nanoseconds,
+ };
+ roundedTime = &seconds;
+
+ // Step 16.c.
+ milliseconds = 0;
+ microseconds = 0;
+ nanoseconds = 0;
+ break;
+ }
+
+ case TemporalUnit::Millisecond: {
+ MOZ_ASSERT(increment <= Increment{1000},
+ "limited by MaximumTemporalDurationRoundingIncrement");
+
+ // Steps 17.a-c.
+ toRound = {0, 0, 0, 0, 0, 0, 0, milliseconds, microseconds, nanoseconds};
+ roundedTime = &milliseconds;
+
+ // Step 17.d.
+ microseconds = 0;
+ nanoseconds = 0;
+ break;
+ }
+
+ case TemporalUnit::Microsecond: {
+ MOZ_ASSERT(increment <= Increment{1000},
+ "limited by MaximumTemporalDurationRoundingIncrement");
+
+ // Steps 18.a-c.
+ toRound = {0, 0, 0, 0, 0, 0, 0, 0, microseconds, nanoseconds};
+ roundedTime = &microseconds;
+
+ // Step 18.d.
+ nanoseconds = 0;
+ break;
+ }
+
+ case TemporalUnit::Nanosecond: {
+ MOZ_ASSERT(increment <= Increment{1000},
+ "limited by MaximumTemporalDurationRoundingIncrement");
+
+ // Step 19.a. (Implicit)
+
+ // Steps 19.b-c.
+ toRound = {0, 0, 0, 0, 0, 0, 0, 0, 0, nanoseconds};
+ roundedTime = &nanoseconds;
+ break;
+ }
+ }
+
+ // clang-format off
+ //
+ // The specification uses mathematical values in its computations, which
+ // requires to be able to represent decimals with arbitrary precision. To
+ // avoid having to struggle with decimals, we can transform the steps to work
+ // on integer values, which we can conveniently represent with BigInts.
+ //
+ // As an example here are the transformation steps for "hours", but all other
+ // units can be handled similarly.
+ //
+ // Relevant spec steps:
+ //
+ // 8.a Let fractionalSeconds be nanoseconds ร— 10^9 + microseconds ร— 10^6 + milliseconds ร— 10^3 + seconds.
+ // ...
+ // 14.a Let fractionalHours be (fractionalSeconds / 60 + minutes) / 60 + hours.
+ // 14.b Set hours to ? RoundNumberToIncrement(fractionalHours, increment, roundingMode).
+ //
+ // And from RoundNumberToIncrement:
+ //
+ // 1. Let quotient be x / increment.
+ // 2-7. Let rounded be op(quotient).
+ // 8. Return rounded ร— increment.
+ //
+ // With `fractionalHours = (totalNs / nsPerHour)`, the rounding operation
+ // computes:
+ //
+ // op(fractionalHours / increment) ร— increment
+ // = op((totalNs / nsPerHour) / increment) ร— increment
+ // = op(totalNs / (nsPerHour ร— increment)) ร— increment
+ //
+ // So when we pass `totalNs` and `nsPerHour` as separate arguments to
+ // RoundNumberToIncrement, we can avoid any precision losses and instead
+ // compute with exact values.
+ //
+ // clang-format on
+
+ double total = 0;
+ if (computeRemainder == ComputeRemainder::No) {
+ if (!RoundNumberToIncrement(cx, toRound, unit, increment, roundingMode,
+ roundedTime)) {
+ return false;
+ }
+ } else {
+ // clang-format off
+ //
+ // The remainder is only used for Duration.prototype.total(), which calls
+ // this operation with increment=1 and roundingMode=trunc.
+ //
+ // That means the remainder computation is actually just
+ // `(totalNs % toNanos) / toNanos`, where `totalNs % toNanos` is already
+ // computed in RoundNumberToIncrement():
+ //
+ // rounded = trunc(totalNs / toNanos)
+ // = [totalNs / toNanos]
+ //
+ // roundedTime = โ„(๐”ฝ(rounded))
+ //
+ // remainder = (totalNs - (rounded * toNanos)) / toNanos
+ // = (totalNs - ([totalNs / toNanos] * toNanos)) / toNanos
+ // = (totalNs % toNanos) / toNanos
+ //
+ // When used in Duration.prototype.total(), the overall computed value is
+ // `[totalNs / toNanos] + (totalNs % toNanos) / toNanos`.
+ //
+ // Applying normal math rules would allow to simplify this to:
+ //
+ // [totalNs / toNanos] + (totalNs % toNanos) / toNanos
+ // = [totalNs / toNanos] + (totalNs - [totalNs / toNanos] * toNanos) / toNanos
+ // = total / toNanos
+ //
+ // We can't apply this simplification because it'd introduce double
+ // precision issues. Instead of that, we use a specialized version of
+ // RoundNumberToIncrement which directly returns the remainder. The
+ // remainder `(totalNs % toNanos) / toNanos` is a value near zero, so this
+ // approach is as exact as possible. (Double numbers near zero can be
+ // computed more precisely than large numbers with fractional parts.)
+ //
+ // clang-format on
+
+ MOZ_ASSERT(increment == Increment{1});
+ MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
+
+ if (!TruncateNumber(cx, toRound, unit, roundedTime, &total)) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(years == duration.years);
+ MOZ_ASSERT(months == duration.months);
+ MOZ_ASSERT(weeks == duration.weeks);
+ MOZ_ASSERT(IsIntegerOrInfinity(days));
+
+ // Step 20.
+ Duration resultDuration = {years, months, weeks, days,
+ hours, minutes, seconds, milliseconds,
+ microseconds, nanoseconds};
+ if (!ThrowIfInvalidDuration(cx, resultDuration)) {
+ return false;
+ }
+
+ // Step 21.
+ *result = {resultDuration, total};
+ return true;
+}
+
+/**
+ * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
+ * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
+ * precalculatedPlainDateTime ] ] ] ] ] )
+ */
+static bool RoundDuration(JSContext* cx, const Duration& duration,
+ Increment increment, TemporalUnit unit,
+ TemporalRoundingMode roundingMode, double* result) {
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ // Only called from |Duration_total|, which always passes |increment=1| and
+ // |roundingMode=trunc|.
+ MOZ_ASSERT(increment == Increment{1});
+ MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
+
+ RoundedDuration rounded;
+ if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
+ ComputeRemainder::Yes, &rounded)) {
+ return false;
+ }
+
+ *result = rounded.total;
+ return true;
+}
+
+/**
+ * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
+ * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
+ * precalculatedPlainDateTime ] ] ] ] ] )
+ */
+static bool RoundDuration(JSContext* cx, const Duration& duration,
+ Increment increment, TemporalUnit unit,
+ TemporalRoundingMode roundingMode, Duration* result) {
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ RoundedDuration rounded;
+ if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
+ ComputeRemainder::No, &rounded)) {
+ return false;
+ }
+
+ *result = rounded.duration;
+ return true;
+}
+
+/**
+ * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
+ * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
+ * precalculatedPlainDateTime ] ] ] ] ] )
+ */
+bool js::temporal::RoundDuration(JSContext* cx, const Duration& duration,
+ Increment increment, TemporalUnit unit,
+ TemporalRoundingMode roundingMode,
+ Duration* result) {
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ return ::RoundDuration(cx, duration, increment, unit, roundingMode, result);
+}
+
+static mozilla::Maybe<int64_t> DaysFrom(
+ const temporal::NanosecondsAndDays& nanosAndDays) {
+ if (auto* days = nanosAndDays.days) {
+ int64_t daysInt;
+ if (BigInt::isInt64(days, &daysInt)) {
+ return mozilla::Some(daysInt);
+ }
+ return mozilla::Nothing();
+ }
+ return mozilla::Some(nanosAndDays.daysInt);
+}
+
+static BigInt* DaysFrom(JSContext* cx,
+ Handle<temporal::NanosecondsAndDays> nanosAndDays) {
+ if (auto days = nanosAndDays.days()) {
+ return days;
+ }
+ return BigInt::createFromInt64(cx, nanosAndDays.daysInt());
+}
+
+static bool TruncateDays(JSContext* cx,
+ Handle<temporal::NanosecondsAndDays> nanosAndDays,
+ double days, int32_t daysToAdd, double* result) {
+ do {
+ int64_t intDays;
+ if (!mozilla::NumberEqualsInt64(days, &intDays)) {
+ break;
+ }
+
+ auto nanoDays = DaysFrom(nanosAndDays);
+ if (!nanoDays) {
+ break;
+ }
+
+ auto totalDays = mozilla::CheckedInt64(intDays);
+ totalDays += *nanoDays;
+ totalDays += daysToAdd;
+ if (!totalDays.isValid()) {
+ break;
+ }
+
+ int64_t truncatedDays = totalDays.value();
+ if (nanosAndDays.nanoseconds() > InstantSpan{}) {
+ // Round toward positive infinity when the integer days are negative and
+ // the fractional part is positive.
+ if (truncatedDays < 0) {
+ truncatedDays += 1;
+ }
+ } else if (nanosAndDays.nanoseconds() < InstantSpan{}) {
+ // Round toward negative infinity when the integer days are positive and
+ // the fractional part is negative.
+ if (truncatedDays > 0) {
+ truncatedDays -= 1;
+ }
+ }
+
+ *result = double(truncatedDays);
+ return true;
+ } while (false);
+
+ Rooted<BigInt*> biDays(cx, BigInt::createFromDouble(cx, days));
+ if (!biDays) {
+ return false;
+ }
+
+ Rooted<BigInt*> biNanoDays(cx, DaysFrom(cx, nanosAndDays));
+ if (!biNanoDays) {
+ return false;
+ }
+
+ Rooted<BigInt*> biDaysToAdd(cx, BigInt::createFromInt64(cx, daysToAdd));
+ if (!biDaysToAdd) {
+ return false;
+ }
+
+ Rooted<BigInt*> truncatedDays(cx, BigInt::add(cx, biDays, biNanoDays));
+ if (!truncatedDays) {
+ return false;
+ }
+
+ truncatedDays = BigInt::add(cx, truncatedDays, biDaysToAdd);
+ if (!truncatedDays) {
+ return false;
+ }
+
+ if (nanosAndDays.nanoseconds() > InstantSpan{}) {
+ // Round toward positive infinity when the integer days are negative and
+ // the fractional part is positive.
+ if (truncatedDays->isNegative()) {
+ truncatedDays = BigInt::inc(cx, truncatedDays);
+ if (!truncatedDays) {
+ return false;
+ }
+ }
+ } else if (nanosAndDays.nanoseconds() < InstantSpan{}) {
+ // Round toward negative infinity when the integer days are positive and
+ // the fractional part is negative.
+ if (!truncatedDays->isNegative() && !truncatedDays->isZero()) {
+ truncatedDays = BigInt::dec(cx, truncatedDays);
+ if (!truncatedDays) {
+ return false;
+ }
+ }
+ }
+
+ *result = BigInt::numberValue(truncatedDays);
+ return true;
+}
+
+static bool DaysIsNegative(double days,
+ Handle<temporal::NanosecondsAndDays> nanosAndDays,
+ int32_t daysToAdd) {
+ // Numbers of days between nsMinInstant and nsMaxInstant.
+ static constexpr int32_t epochDays = 200'000'000;
+
+ MOZ_ASSERT(std::abs(daysToAdd) <= epochDays * 2);
+
+ // We don't need the exact value, so it's safe to convert from BigInt.
+ double nanoDays = nanosAndDays.daysNumber();
+
+ // When non-zero |days| and |nanoDays| have oppositive signs, the absolute
+ // value of |days| is less-or-equal to |epochDays|. That means when adding
+ // |days + nanoDays| we don't have to worry about a case like:
+ //
+ // days = 9007199254740991 and
+ // nanoDays = ๐”ฝ(-9007199254740993) = -9007199254740992
+ //
+ // โ„(๐”ฝ(days) + ๐”ฝ(nanoDays)) is -1, whereas the correct result is -2.
+ MOZ_ASSERT((days <= 0 && nanoDays <= 0) || (days >= 0 && nanoDays >= 0) ||
+ std::abs(days) <= epochDays);
+
+ // This addition can be imprecise, so |daysApproximation| is only an
+ // approximation of the actual value.
+ double daysApproximation = days + nanoDays;
+
+ if (std::abs(daysApproximation) <= epochDays * 2) {
+ int32_t intDays = int32_t(daysApproximation) + daysToAdd;
+ return intDays < 0 ||
+ (intDays == 0 && nanosAndDays.nanoseconds() < InstantSpan{});
+ }
+
+ // |daysApproximation| is too large, adding |daysToAdd| and |daysToSubtract|
+ // doesn't change the sign.
+ return daysApproximation < 0;
+}
+
+struct RoundedNumber {
+ double rounded;
+ double total;
+};
+
+static bool RoundNumberToIncrementSlow(
+ JSContext* cx, double durationAmount, double amountPassed,
+ double durationDays, int32_t daysToAdd,
+ Handle<temporal::NanosecondsAndDays> nanosAndDays, int32_t oneUnitDays,
+ Increment increment, TemporalRoundingMode roundingMode,
+ ComputeRemainder computeRemainder, RoundedNumber* result) {
+ MOZ_ASSERT(nanosAndDays.dayLength() > InstantSpan{});
+ MOZ_ASSERT(nanosAndDays.nanoseconds().abs() < nanosAndDays.dayLength().abs());
+ MOZ_ASSERT(oneUnitDays != 0);
+
+ Rooted<BigInt*> biAmount(cx, BigInt::createFromDouble(cx, durationAmount));
+ if (!biAmount) {
+ return false;
+ }
+
+ Rooted<BigInt*> biAmountPassed(cx,
+ BigInt::createFromDouble(cx, amountPassed));
+ if (!biAmountPassed) {
+ return false;
+ }
+
+ biAmount = BigInt::add(cx, biAmount, biAmountPassed);
+ if (!biAmount) {
+ return false;
+ }
+
+ Rooted<BigInt*> days(cx, BigInt::createFromDouble(cx, durationDays));
+ if (!days) {
+ return false;
+ }
+
+ Rooted<BigInt*> nanoDays(cx, DaysFrom(cx, nanosAndDays));
+ if (!nanoDays) {
+ return false;
+ }
+
+ Rooted<BigInt*> biDaysToAdd(cx, BigInt::createFromInt64(cx, daysToAdd));
+ if (!biDaysToAdd) {
+ return false;
+ }
+
+ days = BigInt::add(cx, days, nanoDays);
+ if (!days) {
+ return false;
+ }
+
+ days = BigInt::add(cx, days, biDaysToAdd);
+ if (!days) {
+ return false;
+ }
+
+ Rooted<BigInt*> nanoseconds(
+ cx, ToEpochNanoseconds(cx, nanosAndDays.nanoseconds()));
+ if (!nanoseconds) {
+ return false;
+ }
+
+ Rooted<BigInt*> dayLength(cx,
+ ToEpochNanoseconds(cx, nanosAndDays.dayLength()));
+ if (!dayLength) {
+ return false;
+ }
+
+ Rooted<BigInt*> denominator(
+ cx, BigInt::createFromInt64(cx, std::abs(oneUnitDays)));
+ if (!denominator) {
+ return false;
+ }
+
+ denominator = BigInt::mul(cx, denominator, dayLength);
+ if (!denominator) {
+ return false;
+ }
+
+ Rooted<BigInt*> totalNanoseconds(cx, BigInt::mul(cx, days, dayLength));
+ if (!totalNanoseconds) {
+ return false;
+ }
+
+ totalNanoseconds = BigInt::add(cx, totalNanoseconds, nanoseconds);
+ if (!totalNanoseconds) {
+ return false;
+ }
+
+ Rooted<BigInt*> amountNanos(cx, BigInt::mul(cx, biAmount, denominator));
+ if (!amountNanos) {
+ return false;
+ }
+
+ totalNanoseconds = BigInt::add(cx, totalNanoseconds, amountNanos);
+ if (!totalNanoseconds) {
+ return false;
+ }
+
+ double rounded;
+ double total = 0;
+ if (computeRemainder == ComputeRemainder::No) {
+ if (!temporal::RoundNumberToIncrement(cx, totalNanoseconds, denominator,
+ increment, roundingMode, &rounded)) {
+ return false;
+ }
+ } else {
+ if (!::TruncateNumber(cx, totalNanoseconds, denominator, &rounded,
+ &total)) {
+ return false;
+ }
+ }
+
+ *result = {rounded, total};
+ return true;
+}
+
+static bool RoundNumberToIncrement(
+ JSContext* cx, double durationAmount, double amountPassed,
+ double durationDays, int32_t daysToAdd,
+ Handle<temporal::NanosecondsAndDays> nanosAndDays, int32_t oneUnitDays,
+ Increment increment, TemporalRoundingMode roundingMode,
+ ComputeRemainder computeRemainder, RoundedNumber* result) {
+ MOZ_ASSERT(nanosAndDays.dayLength() > InstantSpan{});
+ MOZ_ASSERT(nanosAndDays.nanoseconds().abs() < nanosAndDays.dayLength().abs());
+ MOZ_ASSERT(oneUnitDays != 0);
+
+ // TODO(anba): Rename variables.
+
+ // clang-format off
+ //
+ // Change the representation of |fractionalWeeks| from a real number to a
+ // rational number, because we don't support arbitrary precision real
+ // numbers.
+ //
+ // |fractionalWeeks| is defined as:
+ //
+ // fractionalWeeks
+ // = weeks + days' / abs(oneWeekDays)
+ //
+ // where days' = days + nanoseconds / dayLength.
+ //
+ // The fractional part |nanoseconds / dayLength| is from step 4.
+ //
+ // The denominator for |fractionalWeeks| is |dayLength * abs(oneWeekDays)|.
+ //
+ // fractionalWeeks
+ // = weeks + (days + nanoseconds / dayLength) / abs(oneWeekDays)
+ // = weeks + days / abs(oneWeekDays) + nanoseconds / (dayLength * abs(oneWeekDays))
+ // = (weeks * dayLength * abs(oneWeekDays) + days * dayLength + nanoseconds) / (dayLength * abs(oneWeekDays))
+ //
+ // clang-format on
+
+ do {
+ auto nanoseconds = nanosAndDays.nanoseconds().toNanoseconds();
+ if (!nanoseconds.isValid()) {
+ break;
+ }
+
+ auto dayLength = nanosAndDays.dayLength().toNanoseconds();
+ if (!dayLength.isValid()) {
+ break;
+ }
+
+ auto denominator = dayLength * std::abs(oneUnitDays);
+ if (!denominator.isValid()) {
+ break;
+ }
+
+ int64_t intDays;
+ if (!mozilla::NumberEqualsInt64(durationDays, &intDays)) {
+ break;
+ }
+
+ auto nanoDays = DaysFrom(nanosAndDays);
+ if (!nanoDays) {
+ break;
+ }
+
+ auto totalDays = mozilla::CheckedInt64(intDays);
+ totalDays += *nanoDays;
+ totalDays += daysToAdd;
+ if (!totalDays.isValid()) {
+ break;
+ }
+
+ auto totalNanoseconds = dayLength * totalDays;
+ if (!totalNanoseconds.isValid()) {
+ break;
+ }
+
+ totalNanoseconds += nanoseconds;
+ if (!totalNanoseconds.isValid()) {
+ break;
+ }
+
+ int64_t intAmount;
+ if (!mozilla::NumberEqualsInt64(durationAmount, &intAmount)) {
+ break;
+ }
+
+ int64_t intAmountPassed;
+ if (!mozilla::NumberEqualsInt64(amountPassed, &intAmountPassed)) {
+ break;
+ }
+
+ auto totalAmount = mozilla::CheckedInt64(intAmount) + intAmountPassed;
+ if (!totalAmount.isValid()) {
+ break;
+ }
+
+ auto amountNanos = denominator * totalAmount;
+ if (!amountNanos.isValid()) {
+ break;
+ }
+
+ totalNanoseconds += amountNanos;
+ if (!totalNanoseconds.isValid()) {
+ break;
+ }
+
+ double rounded;
+ double total = 0;
+ if (computeRemainder == ComputeRemainder::No) {
+ if (!temporal::RoundNumberToIncrement(cx, totalNanoseconds.value(),
+ denominator.value(), increment,
+ roundingMode, &rounded)) {
+ return false;
+ }
+ } else {
+ TruncateNumber(totalNanoseconds.value(), denominator.value(), &rounded,
+ &total);
+ }
+
+ *result = {rounded, total};
+ return true;
+ } while (false);
+
+ return RoundNumberToIncrementSlow(
+ cx, durationAmount, amountPassed, durationDays, daysToAdd, nanosAndDays,
+ oneUnitDays, increment, roundingMode, computeRemainder, result);
+}
+
+static bool RoundNumberToIncrement(
+ JSContext* cx, double durationDays,
+ Handle<temporal::NanosecondsAndDays> nanosAndDays, Increment increment,
+ TemporalRoundingMode roundingMode, ComputeRemainder computeRemainder,
+ RoundedNumber* result) {
+ constexpr double daysAmount = 0;
+ constexpr double daysPassed = 0;
+ constexpr int32_t oneDayDays = 1;
+ constexpr int32_t daysToAdd = 0;
+
+ return RoundNumberToIncrement(cx, daysAmount, daysPassed, durationDays,
+ daysToAdd, nanosAndDays, oneDayDays, increment,
+ roundingMode, computeRemainder, result);
+}
+
+static bool RoundDurationYear(JSContext* cx, const Duration& duration,
+ Handle<temporal::NanosecondsAndDays> nanosAndDays,
+ Increment increment,
+ TemporalRoundingMode roundingMode,
+ Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
+ Handle<CalendarRecord> calendar,
+ ComputeRemainder computeRemainder,
+ RoundedDuration* result) {
+ // Numbers of days between nsMinInstant and nsMaxInstant.
+ static constexpr int32_t epochDays = 200'000'000;
+
+ double years = duration.years;
+ double months = duration.months;
+ double weeks = duration.weeks;
+ double days = duration.days;
+
+ // Step 10.a.
+ Duration yearsDuration = {years};
+
+ // Step 10.b.
+ auto yearsLater = AddDate(cx, calendar, dateRelativeTo, yearsDuration);
+ if (!yearsLater) {
+ return false;
+ }
+ auto yearsLaterDate = ToPlainDate(&yearsLater.unwrap());
+
+ // Step 10.f. (Reordered)
+ Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx, yearsLater);
+
+ // Step 10.c.
+ Duration yearsMonthsWeeks = {years, months, weeks};
+
+ // Step 10.d.
+ PlainDate yearsMonthsWeeksLater;
+ if (!AddDate(cx, calendar, dateRelativeTo, yearsMonthsWeeks,
+ &yearsMonthsWeeksLater)) {
+ return false;
+ }
+
+ // Step 10.e.
+ int32_t monthsWeeksInDays = DaysUntil(yearsLaterDate, yearsMonthsWeeksLater);
+ MOZ_ASSERT(std::abs(monthsWeeksInDays) <= epochDays);
+
+ // Step 10.f. (Moved up)
+
+ // Step 10.g.
+ // Our implementation keeps |days| and |monthsWeeksInDays| separate.
+
+ // FIXME: spec issue - truncation doesn't match the spec polyfill.
+ // https://github.com/tc39/proposal-temporal/issues/2540
+
+ // Step 10.h.
+ double truncatedDays;
+ if (!TruncateDays(cx, nanosAndDays, days, monthsWeeksInDays,
+ &truncatedDays)) {
+ return false;
+ }
+
+ // FIXME: spec bug - truncated days can be infinity:
+ //
+ // Temporal.Duration.from({
+ // days: Number.MAX_VALUE,
+ // hours: Number.MAX_VALUE,
+ // }).round({
+ // smallestUnit: "years",
+ // relativeTo: "1970-01-01",
+ // });
+ if (!IsInteger(truncatedDays)) {
+ MOZ_ASSERT(std::isinf(truncatedDays));
+ JS_ReportErrorASCII(cx, "truncated days is infinity");
+ return false;
+ }
+
+ PlainDate isoResult;
+ if (!AddISODate(cx, yearsLaterDate, {0, 0, 0, truncatedDays},
+ TemporalOverflow::Constrain, &isoResult)) {
+ return false;
+ }
+
+ // Step 10.i.
+ Rooted<PlainDateObject*> wholeDaysLater(
+ cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
+ if (!wholeDaysLater) {
+ return false;
+ }
+
+ // Steps 10.j-l.
+ Duration timePassed;
+ if (!DifferenceDate(cx, calendar, newRelativeTo, wholeDaysLater,
+ TemporalUnit::Year, &timePassed)) {
+ return false;
+ }
+
+ // Step 10.m.
+ double yearsPassed = timePassed.years;
+
+ // Step 10.n.
+ // Our implementation keeps |years| and |yearsPassed| separate.
+
+ // Step 10.o.
+ Duration yearsPassedDuration = {yearsPassed};
+
+ // Steps 10.p-r.
+ int32_t daysPassed;
+ if (!MoveRelativeDate(cx, calendar, newRelativeTo, yearsPassedDuration,
+ &newRelativeTo, &daysPassed)) {
+ return false;
+ }
+ MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
+
+ // Step 10.s.
+ //
+ // Our implementation keeps |days| and |daysPassed| separate.
+ int32_t daysToAdd = monthsWeeksInDays - daysPassed;
+ MOZ_ASSERT(std::abs(daysToAdd) <= epochDays * 2);
+
+ // Steps 10.t.
+ double sign = DaysIsNegative(days, nanosAndDays, daysToAdd) ? -1 : 1;
+
+ // Step 10.u.
+ Duration oneYear = {sign};
+
+ // Steps 10.v-w.
+ Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
+ int32_t oneYearDays;
+ if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneYear,
+ &moveResultIgnored, &oneYearDays)) {
+ return false;
+ }
+
+ // Step 10.x.
+ if (oneYearDays == 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_NUMBER, "days");
+ return false;
+ }
+
+ // Steps 10.y-aa.
+ RoundedNumber rounded;
+ if (!RoundNumberToIncrement(cx, years, yearsPassed, days, daysToAdd,
+ nanosAndDays, oneYearDays, increment,
+ roundingMode, computeRemainder, &rounded)) {
+ return false;
+ }
+ auto [numYears, total] = rounded;
+
+ // Step 10.ab.
+ double numMonths = 0;
+ double numWeeks = 0;
+
+ // Step 20.
+ Duration resultDuration = {numYears, numMonths, numWeeks};
+ if (!ThrowIfInvalidDuration(cx, resultDuration)) {
+ return false;
+ }
+
+ // Step 21.
+ *result = {resultDuration, total};
+ return true;
+}
+
+static bool RoundDurationMonth(
+ JSContext* cx, const Duration& duration,
+ Handle<temporal::NanosecondsAndDays> nanosAndDays, Increment increment,
+ TemporalRoundingMode roundingMode,
+ Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
+ Handle<CalendarRecord> calendar, ComputeRemainder computeRemainder,
+ RoundedDuration* result) {
+ // Numbers of days between nsMinInstant and nsMaxInstant.
+ static constexpr int32_t epochDays = 200'000'000;
+
+ double years = duration.years;
+ double months = duration.months;
+ double weeks = duration.weeks;
+ double days = duration.days;
+
+ // Step 11.a.
+ Duration yearsMonths = {years, months};
+
+ // Step 11.b.
+ auto yearsMonthsLater = AddDate(cx, calendar, dateRelativeTo, yearsMonths);
+ if (!yearsMonthsLater) {
+ return false;
+ }
+ auto yearsMonthsLaterDate = ToPlainDate(&yearsMonthsLater.unwrap());
+
+ // Step 11.f. (Reordered)
+ Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx, yearsMonthsLater);
+
+ // Step 11.c.
+ Duration yearsMonthsWeeks = {years, months, weeks};
+
+ // Step 11.d.
+ PlainDate yearsMonthsWeeksLater;
+ if (!AddDate(cx, calendar, dateRelativeTo, yearsMonthsWeeks,
+ &yearsMonthsWeeksLater)) {
+ return false;
+ }
+
+ // Step 11.e.
+ int32_t weeksInDays = DaysUntil(yearsMonthsLaterDate, yearsMonthsWeeksLater);
+ MOZ_ASSERT(std::abs(weeksInDays) <= epochDays);
+
+ // Step 11.f. (Moved up)
+
+ // Step 11.g.
+ // Our implementation keeps |days| and |weeksInDays| separate.
+
+ // FIXME: spec issue - truncation doesn't match the spec polyfill.
+ // https://github.com/tc39/proposal-temporal/issues/2540
+
+ // Step 11.h.
+ double truncatedDays;
+ if (!TruncateDays(cx, nanosAndDays, days, weeksInDays, &truncatedDays)) {
+ return false;
+ }
+
+ // FIXME: spec bug - truncated days can be infinity:
+ //
+ // Temporal.Duration.from({
+ // days: Number.MAX_VALUE,
+ // hours: Number.MAX_VALUE,
+ // }).round({
+ // smallestUnit: "months",
+ // relativeTo: "1970-01-01",
+ // });
+ if (!IsInteger(truncatedDays)) {
+ MOZ_ASSERT(std::isinf(truncatedDays));
+ JS_ReportErrorASCII(cx, "truncated days is infinity");
+ return false;
+ }
+
+ PlainDate isoResult;
+ if (!AddISODate(cx, yearsMonthsLaterDate, {0, 0, 0, truncatedDays},
+ TemporalOverflow::Constrain, &isoResult)) {
+ return false;
+ }
+
+ // Step 11.i.
+ Rooted<PlainDateObject*> wholeDaysLater(
+ cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
+ if (!wholeDaysLater) {
+ return false;
+ }
+
+ // Steps 11.j-l.
+ Duration timePassed;
+ if (!DifferenceDate(cx, calendar, newRelativeTo, wholeDaysLater,
+ TemporalUnit::Month, &timePassed)) {
+ return false;
+ }
+
+ // Step 11.m.
+ double monthsPassed = timePassed.months;
+
+ // Step 11.n.
+ // Our implementation keeps |months| and |monthsPassed| separate.
+
+ // Step 11.o.
+ Duration monthsPassedDuration = {0, monthsPassed};
+
+ // Steps 11.p-r.
+ int32_t daysPassed;
+ if (!MoveRelativeDate(cx, calendar, newRelativeTo, monthsPassedDuration,
+ &newRelativeTo, &daysPassed)) {
+ return false;
+ }
+ MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
+
+ // Step 11.s.
+ //
+ // Our implementation keeps |days| and |daysPassed| separate.
+ int32_t daysToAdd = weeksInDays - daysPassed;
+ MOZ_ASSERT(std::abs(daysToAdd) <= epochDays * 2);
+
+ // Steps 11.t.
+ double sign = DaysIsNegative(days, nanosAndDays, daysToAdd) ? -1 : 1;
+
+ // Step 11.u.
+ Duration oneMonth = {0, sign};
+
+ // Steps 11.v-w.
+ Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
+ int32_t oneMonthDays;
+ if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneMonth,
+ &moveResultIgnored, &oneMonthDays)) {
+ return false;
+ }
+
+ // Step 11.x.
+ if (oneMonthDays == 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_NUMBER, "days");
+ return false;
+ }
+
+ // Steps 11.y-aa.
+ RoundedNumber rounded;
+ if (!RoundNumberToIncrement(cx, months, monthsPassed, days, daysToAdd,
+ nanosAndDays, oneMonthDays, increment,
+ roundingMode, computeRemainder, &rounded)) {
+ return false;
+ }
+ auto [numMonths, total] = rounded;
+
+ // Step 11.ab.
+ double numWeeks = 0;
+
+ // Step 20.
+ Duration resultDuration = {years, numMonths, numWeeks};
+ if (!ThrowIfInvalidDuration(cx, resultDuration)) {
+ return false;
+ }
+
+ // Step 21.
+ *result = {resultDuration, total};
+ return true;
+}
+
+static bool RoundDurationWeek(JSContext* cx, const Duration& duration,
+ Handle<temporal::NanosecondsAndDays> nanosAndDays,
+ Increment increment,
+ TemporalRoundingMode roundingMode,
+ Handle<Wrapped<PlainDateObject*>> dateRelativeTo,
+ Handle<CalendarRecord> calendar,
+ ComputeRemainder computeRemainder,
+ RoundedDuration* result) {
+ // Numbers of days between nsMinInstant and nsMaxInstant.
+ static constexpr int32_t epochDays = 200'000'000;
+
+ double years = duration.years;
+ double months = duration.months;
+ double weeks = duration.weeks;
+ double days = duration.days;
+
+ auto* unwrappedRelativeTo = dateRelativeTo.unwrap(cx);
+ if (!unwrappedRelativeTo) {
+ return false;
+ }
+ auto relativeToDate = ToPlainDate(unwrappedRelativeTo);
+
+ // Step 12.a
+ double truncatedDays;
+ if (!TruncateDays(cx, nanosAndDays, days, 0, &truncatedDays)) {
+ return false;
+ }
+
+ // FIXME: spec bug - truncated days can be infinity:
+ //
+ // Temporal.Duration.from({
+ // days: Number.MAX_VALUE,
+ // hours: Number.MAX_VALUE,
+ // }).round({
+ // smallestUnit: "weeks",
+ // relativeTo: "1970-01-01",
+ // });
+ if (!IsInteger(truncatedDays)) {
+ MOZ_ASSERT(std::isinf(truncatedDays));
+ JS_ReportErrorASCII(cx, "truncated days is infinity");
+ return false;
+ }
+
+ PlainDate isoResult;
+ if (!AddISODate(cx, relativeToDate, {0, 0, 0, truncatedDays},
+ TemporalOverflow::Constrain, &isoResult)) {
+ return false;
+ }
+
+ // Step 12.b.
+ Rooted<PlainDateObject*> wholeDaysLater(
+ cx, CreateTemporalDate(cx, isoResult, calendar.receiver()));
+ if (!wholeDaysLater) {
+ return false;
+ }
+
+ // Steps 12.c-e.
+ Duration timePassed;
+ if (!DifferenceDate(cx, calendar, dateRelativeTo, wholeDaysLater,
+ TemporalUnit::Week, &timePassed)) {
+ return false;
+ }
+
+ // Step 12.f.
+ double weeksPassed = timePassed.weeks;
+
+ // Step 12.g.
+ // Our implementation keeps |weeks| and |weeksPassed| separate.
+
+ // Step 12.h.
+ Duration weeksPassedDuration = {0, 0, weeksPassed};
+
+ // Steps 12.i-k.
+ Rooted<Wrapped<PlainDateObject*>> newRelativeTo(cx);
+ int32_t daysPassed;
+ if (!MoveRelativeDate(cx, calendar, dateRelativeTo, weeksPassedDuration,
+ &newRelativeTo, &daysPassed)) {
+ return false;
+ }
+ MOZ_ASSERT(std::abs(daysPassed) <= epochDays);
+
+ // Step 12.l.
+ //
+ // Our implementation keeps |days| and |daysPassed| separate.
+ int32_t daysToAdd = -daysPassed;
+ MOZ_ASSERT(std::abs(daysToAdd) <= epochDays);
+
+ // Steps 12.m.
+ double sign = DaysIsNegative(days, nanosAndDays, daysToAdd) ? -1 : 1;
+
+ // Step 12.n.
+ Duration oneWeek = {0, 0, sign};
+
+ // Steps 12.o-p.
+ Rooted<Wrapped<PlainDateObject*>> moveResultIgnored(cx);
+ int32_t oneWeekDays;
+ if (!MoveRelativeDate(cx, calendar, newRelativeTo, oneWeek,
+ &moveResultIgnored, &oneWeekDays)) {
+ return false;
+ }
+
+ // Step 12.q.
+ if (oneWeekDays == 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_NUMBER, "days");
+ return false;
+ }
+
+ // Steps 12.r-t.
+ RoundedNumber rounded;
+ if (!RoundNumberToIncrement(cx, weeks, weeksPassed, days, daysToAdd,
+ nanosAndDays, oneWeekDays, increment,
+ roundingMode, computeRemainder, &rounded)) {
+ return false;
+ }
+ auto [numWeeks, total] = rounded;
+
+ // Step 20.
+ Duration resultDuration = {years, months, numWeeks};
+ if (!ThrowIfInvalidDuration(cx, resultDuration)) {
+ return false;
+ }
+
+ // Step 21.
+ *result = {resultDuration, total};
+ return true;
+}
+
+static bool RoundDurationDay(JSContext* cx, const Duration& duration,
+ Handle<temporal::NanosecondsAndDays> nanosAndDays,
+ Increment increment,
+ TemporalRoundingMode roundingMode,
+ ComputeRemainder computeRemainder,
+ RoundedDuration* result) {
+ double years = duration.years;
+ double months = duration.months;
+ double weeks = duration.weeks;
+ double days = duration.days;
+
+ // Steps 13.a-b.
+ RoundedNumber rounded;
+ if (!RoundNumberToIncrement(cx, days, nanosAndDays, increment, roundingMode,
+ computeRemainder, &rounded)) {
+ return false;
+ }
+ auto [numDays, total] = rounded;
+
+ // Step 20.
+ Duration resultDuration = {years, months, weeks, numDays};
+ if (!ThrowIfInvalidDuration(cx, resultDuration)) {
+ return false;
+ }
+
+ // Step 21.
+ *result = {resultDuration, total};
+ return true;
+}
+
+/**
+ * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
+ * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
+ * precalculatedPlainDateTime ] ] ] ] ] )
+ */
+static bool RoundDuration(
+ JSContext* cx, const Duration& duration, Increment increment,
+ TemporalUnit unit, TemporalRoundingMode roundingMode,
+ Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
+ Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
+ Handle<TimeZoneRecord> timeZone,
+ mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
+ ComputeRemainder computeRemainder, RoundedDuration* result) {
+ // Note: |duration.days| can have a different sign than the other date
+ // components. The date and time components can have different signs, too.
+ MOZ_ASSERT(
+ IsValidDuration({duration.years, duration.months, duration.weeks}));
+ MOZ_ASSERT(IsValidDuration(duration.time()));
+
+ MOZ_ASSERT(plainRelativeTo || zonedRelativeTo,
+ "Use RoundDuration without relativeTo when plainRelativeTo and "
+ "zonedRelativeTo are both undefined");
+
+ // The remainder is only needed when called from |Duration_total|. And `total`
+ // always passes |increment=1| and |roundingMode=trunc|.
+ MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
+ increment == Increment{1});
+ MOZ_ASSERT_IF(computeRemainder == ComputeRemainder::Yes,
+ roundingMode == TemporalRoundingMode::Trunc);
+
+ // Steps 1-5. (Not applicable in our implementation.)
+
+ // Step 6.a. (Not applicable in our implementation.)
+ MOZ_ASSERT_IF(unit <= TemporalUnit::Week, plainRelativeTo);
+
+ // Step 6.b.
+ MOZ_ASSERT_IF(
+ unit <= TemporalUnit::Week,
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+
+ // Step 6.c.
+ MOZ_ASSERT_IF(
+ unit <= TemporalUnit::Week,
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
+
+ switch (unit) {
+ case TemporalUnit::Year:
+ case TemporalUnit::Month:
+ case TemporalUnit::Week:
+ break;
+ case TemporalUnit::Day:
+ // We can't take the faster code path when |zonedRelativeTo| is present.
+ if (zonedRelativeTo) {
+ break;
+ }
+ [[fallthrough]];
+ case TemporalUnit::Hour:
+ case TemporalUnit::Minute:
+ case TemporalUnit::Second:
+ case TemporalUnit::Millisecond:
+ case TemporalUnit::Microsecond:
+ case TemporalUnit::Nanosecond:
+ // Steps 7-9 and 13-21.
+ return ::RoundDuration(cx, duration, increment, unit, roundingMode,
+ computeRemainder, result);
+ case TemporalUnit::Auto:
+ MOZ_CRASH("Unexpected temporal unit");
+ }
+
+ // Step 7.
+ MOZ_ASSERT(TemporalUnit::Year <= unit && unit <= TemporalUnit::Day);
+
+ // Steps 7.a-c.
+ Rooted<temporal::NanosecondsAndDays> nanosAndDays(cx);
+ if (zonedRelativeTo) {
+ // Step 7.b.i. (Reordered)
+ Rooted<ZonedDateTime> intermediate(cx);
+ if (!MoveRelativeZonedDateTime(cx, zonedRelativeTo, calendar, timeZone,
+ duration.date(), precalculatedPlainDateTime,
+ &intermediate)) {
+ return false;
+ }
+
+ // Steps 7.a and 7.b.ii.
+ if (!NanosecondsToDays(cx, duration, intermediate, timeZone,
+ &nanosAndDays)) {
+ return false;
+ }
+
+ // Step 7.b.iii. (Not applicable in our implementation.)
+ } else {
+ // Steps 7.a and 7.c.
+ if (!::NanosecondsToDays(cx, duration, &nanosAndDays)) {
+ return false;
+ }
+ }
+
+ // NanosecondsToDays guarantees that |abs(nanosAndDays.nanoseconds)| is less
+ // than |abs(nanosAndDays.dayLength)|.
+ MOZ_ASSERT(nanosAndDays.nanoseconds().abs() < nanosAndDays.dayLength());
+
+ // Step 7.d. (Moved below)
+
+ // Step 7.e. (Implicit)
+
+ // Step 8. (Not applicable)
+
+ // Step 9.
+ // FIXME: spec issue - `total` doesn't need be initialised.
+
+ // Steps 10-21.
+ switch (unit) {
+ // Steps 10 and 20-21.
+ case TemporalUnit::Year:
+ return RoundDurationYear(cx, duration, nanosAndDays, increment,
+ roundingMode, plainRelativeTo, calendar,
+ computeRemainder, result);
+
+ // Steps 11 and 20-21.
+ case TemporalUnit::Month:
+ return RoundDurationMonth(cx, duration, nanosAndDays, increment,
+ roundingMode, plainRelativeTo, calendar,
+ computeRemainder, result);
+
+ // Steps 12 and 20-21.
+ case TemporalUnit::Week:
+ return RoundDurationWeek(cx, duration, nanosAndDays, increment,
+ roundingMode, plainRelativeTo, calendar,
+ computeRemainder, result);
+
+ // Steps 13 and 20-21.
+ case TemporalUnit::Day:
+ return RoundDurationDay(cx, duration, nanosAndDays, increment,
+ roundingMode, computeRemainder, result);
+
+ // Steps 14-19. (Handled elsewhere)
+ case TemporalUnit::Auto:
+ case TemporalUnit::Hour:
+ case TemporalUnit::Minute:
+ case TemporalUnit::Second:
+ case TemporalUnit::Millisecond:
+ case TemporalUnit::Microsecond:
+ case TemporalUnit::Nanosecond:
+ break;
+ }
+
+ MOZ_CRASH("Unexpected temporal unit");
+}
+
+/**
+ * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
+ * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
+ * precalculatedPlainDateTime ] ] ] ] ] )
+ */
+static bool RoundDuration(
+ JSContext* cx, const Duration& duration, Increment increment,
+ TemporalUnit unit, TemporalRoundingMode roundingMode,
+ Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
+ Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
+ Handle<TimeZoneRecord> timeZone,
+ mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
+ double* result) {
+ // Only called from |Duration_total|, which always passes |increment=1| and
+ // |roundingMode=trunc|.
+ MOZ_ASSERT(increment == Increment{1});
+ MOZ_ASSERT(roundingMode == TemporalRoundingMode::Trunc);
+
+ RoundedDuration rounded;
+ if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
+ plainRelativeTo, calendar, zonedRelativeTo, timeZone,
+ precalculatedPlainDateTime, ComputeRemainder::Yes,
+ &rounded)) {
+ return false;
+ }
+
+ *result = rounded.total;
+ return true;
+}
+
+/**
+ * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
+ * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
+ * precalculatedPlainDateTime ] ] ] ] ] )
+ */
+static bool RoundDuration(
+ JSContext* cx, const Duration& duration, Increment increment,
+ TemporalUnit unit, TemporalRoundingMode roundingMode,
+ Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
+ Handle<CalendarRecord> calendar, Handle<ZonedDateTime> zonedRelativeTo,
+ Handle<TimeZoneRecord> timeZone,
+ mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
+ Duration* result) {
+ RoundedDuration rounded;
+ if (!::RoundDuration(cx, duration, increment, unit, roundingMode,
+ plainRelativeTo, calendar, zonedRelativeTo, timeZone,
+ precalculatedPlainDateTime, ComputeRemainder::No,
+ &rounded)) {
+ return false;
+ }
+
+ *result = rounded.duration;
+ return true;
+}
+
+/**
+ * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
+ * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
+ * precalculatedPlainDateTime ] ] ] ] ] )
+ */
+bool js::temporal::RoundDuration(
+ JSContext* cx, const Duration& duration, Increment increment,
+ TemporalUnit unit, TemporalRoundingMode roundingMode,
+ Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
+ Handle<CalendarRecord> calendar, Duration* result) {
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ Rooted<ZonedDateTime> zonedRelativeTo(cx, ZonedDateTime{});
+ Rooted<TimeZoneRecord> timeZone(cx, TimeZoneRecord{});
+ mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
+ return ::RoundDuration(cx, duration, increment, unit, roundingMode,
+ plainRelativeTo, calendar, zonedRelativeTo, timeZone,
+ precalculatedPlainDateTime, result);
+}
+
+/**
+ * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
+ * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
+ * precalculatedPlainDateTime ] ] ] ] ] )
+ */
+bool js::temporal::RoundDuration(
+ JSContext* cx, const Duration& duration, Increment increment,
+ TemporalUnit unit, TemporalRoundingMode roundingMode,
+ Handle<PlainDateObject*> plainRelativeTo, Handle<CalendarRecord> calendar,
+ Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
+ const PlainDateTime& precalculatedPlainDateTime, Duration* result) {
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ return ::RoundDuration(cx, duration, increment, unit, roundingMode,
+ plainRelativeTo, calendar, zonedRelativeTo, timeZone,
+ mozilla::SomeRef(precalculatedPlainDateTime), result);
+}
+
+enum class DurationOperation { Add, Subtract };
+
+/**
+ * AddDurationToOrSubtractDurationFromDuration ( operation, duration, other,
+ * options )
+ */
+static bool AddDurationToOrSubtractDurationFromDuration(
+ JSContext* cx, DurationOperation operation, const CallArgs& args) {
+ auto* durationObj = &args.thisv().toObject().as<DurationObject>();
+ auto duration = ToDuration(durationObj);
+
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ Duration other;
+ if (!ToTemporalDurationRecord(cx, args.get(0), &other)) {
+ return false;
+ }
+
+ Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
+ Rooted<ZonedDateTime> zonedRelativeTo(cx);
+ Rooted<TimeZoneRecord> timeZone(cx);
+ if (args.hasDefined(1)) {
+ const char* name = operation == DurationOperation::Add ? "add" : "subtract";
+
+ // Step 3.
+ Rooted<JSObject*> options(cx,
+ RequireObjectArg(cx, "options", name, args[1]));
+ if (!options) {
+ return false;
+ }
+
+ // Steps 4-7.
+ if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
+ &zonedRelativeTo, &timeZone)) {
+ return false;
+ }
+ MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
+ MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
+ }
+
+ // Step 8.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
+ zonedRelativeTo,
+ {
+ CalendarMethod::DateAdd,
+ CalendarMethod::DateUntil,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Step 9.
+ if (operation == DurationOperation::Subtract) {
+ other = other.negate();
+ }
+
+ Duration result;
+ if (plainRelativeTo) {
+ if (!AddDuration(cx, duration, other, plainRelativeTo, calendar, &result)) {
+ return false;
+ }
+ } else if (zonedRelativeTo) {
+ if (!AddDuration(cx, duration, other, zonedRelativeTo, calendar, timeZone,
+ &result)) {
+ return false;
+ }
+ } else {
+ if (!AddDuration(cx, duration, other, &result)) {
+ return false;
+ }
+ }
+
+ // Step 10.
+ auto* obj = CreateTemporalDuration(cx, result);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.Duration ( [ years [ , months [ , weeks [ , days [ , hours [ ,
+ * minutes [ , seconds [ , milliseconds [ , microseconds [ , nanoseconds ] ] ] ]
+ * ] ] ] ] ] ] )
+ */
+static bool DurationConstructor(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ if (!ThrowIfNotConstructing(cx, args, "Temporal.Duration")) {
+ return false;
+ }
+
+ // Step 2.
+ double years = 0;
+ if (args.hasDefined(0) &&
+ !ToIntegerIfIntegral(cx, "years", args[0], &years)) {
+ return false;
+ }
+
+ // Step 3.
+ double months = 0;
+ if (args.hasDefined(1) &&
+ !ToIntegerIfIntegral(cx, "months", args[1], &months)) {
+ return false;
+ }
+
+ // Step 4.
+ double weeks = 0;
+ if (args.hasDefined(2) &&
+ !ToIntegerIfIntegral(cx, "weeks", args[2], &weeks)) {
+ return false;
+ }
+
+ // Step 5.
+ double days = 0;
+ if (args.hasDefined(3) && !ToIntegerIfIntegral(cx, "days", args[3], &days)) {
+ return false;
+ }
+
+ // Step 6.
+ double hours = 0;
+ if (args.hasDefined(4) &&
+ !ToIntegerIfIntegral(cx, "hours", args[4], &hours)) {
+ return false;
+ }
+
+ // Step 7.
+ double minutes = 0;
+ if (args.hasDefined(5) &&
+ !ToIntegerIfIntegral(cx, "minutes", args[5], &minutes)) {
+ return false;
+ }
+
+ // Step 8.
+ double seconds = 0;
+ if (args.hasDefined(6) &&
+ !ToIntegerIfIntegral(cx, "seconds", args[6], &seconds)) {
+ return false;
+ }
+
+ // Step 9.
+ double milliseconds = 0;
+ if (args.hasDefined(7) &&
+ !ToIntegerIfIntegral(cx, "milliseconds", args[7], &milliseconds)) {
+ return false;
+ }
+
+ // Step 10.
+ double microseconds = 0;
+ if (args.hasDefined(8) &&
+ !ToIntegerIfIntegral(cx, "microseconds", args[8], &microseconds)) {
+ return false;
+ }
+
+ // Step 11.
+ double nanoseconds = 0;
+ if (args.hasDefined(9) &&
+ !ToIntegerIfIntegral(cx, "nanoseconds", args[9], &nanoseconds)) {
+ return false;
+ }
+
+ // Step 12.
+ auto* duration = CreateTemporalDuration(
+ cx, args,
+ {years, months, weeks, days, hours, minutes, seconds, milliseconds,
+ microseconds, nanoseconds});
+ if (!duration) {
+ return false;
+ }
+
+ args.rval().setObject(*duration);
+ return true;
+}
+
+/**
+ * Temporal.Duration.from ( item )
+ */
+static bool Duration_from(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Handle<Value> item = args.get(0);
+
+ // Step 1.
+ if (item.isObject()) {
+ if (auto* duration = item.toObject().maybeUnwrapIf<DurationObject>()) {
+ auto* result = CreateTemporalDuration(cx, ToDuration(duration));
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+ }
+ }
+
+ // Step 2.
+ auto result = ToTemporalDuration(cx, item);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Duration.compare ( one, two [ , options ] )
+ */
+static bool Duration_compare(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ Duration one;
+ if (!ToTemporalDuration(cx, args.get(0), &one)) {
+ return false;
+ }
+
+ // Step 2.
+ Duration two;
+ if (!ToTemporalDuration(cx, args.get(1), &two)) {
+ return false;
+ }
+
+ Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
+ Rooted<ZonedDateTime> zonedRelativeTo(cx);
+ Rooted<TimeZoneRecord> timeZone(cx);
+ if (args.hasDefined(2)) {
+ // Step 3.
+ Rooted<JSObject*> options(
+ cx, RequireObjectArg(cx, "options", "compare", args[2]));
+ if (!options) {
+ return false;
+ }
+
+ // Step 4.
+ if (one == two) {
+ args.rval().setInt32(0);
+ return true;
+ }
+
+ // Steps 5-8.
+ if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
+ &zonedRelativeTo, &timeZone)) {
+ return false;
+ }
+ MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
+ MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
+ } else {
+ // Step 3. (Not applicable in our implementation.)
+
+ // Step 4.
+ if (one == two) {
+ args.rval().setInt32(0);
+ return true;
+ }
+
+ // Steps 5-8. (Not applicable in our implementation.)
+ }
+
+ // Steps 9-10.
+ auto hasCalendarUnit = [](const auto& d) {
+ return d.years != 0 || d.months != 0 || d.weeks != 0;
+ };
+ bool calendarUnitsPresent = hasCalendarUnit(one) || hasCalendarUnit(two);
+
+ // Step 11.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
+ zonedRelativeTo,
+ {
+ CalendarMethod::DateAdd,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Step 12.
+ if (zonedRelativeTo &&
+ (calendarUnitsPresent || one.days != 0 || two.days != 0)) {
+ // Step 12.a.
+ auto instant = zonedRelativeTo.instant();
+
+ // Step 12.b.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, timeZone, instant, &dateTime)) {
+ return false;
+ }
+
+ // Step 12.c.
+ Instant after1;
+ if (!AddZonedDateTime(cx, instant, timeZone, calendar, one, dateTime,
+ &after1)) {
+ return false;
+ }
+
+ // Step 12.d.
+ Instant after2;
+ if (!AddZonedDateTime(cx, instant, timeZone, calendar, two, dateTime,
+ &after2)) {
+ return false;
+ }
+
+ // Steps 12.e-g.
+ args.rval().setInt32(after1 < after2 ? -1 : after1 > after2 ? 1 : 0);
+ return true;
+ }
+
+ // Steps 13-14.
+ double days1, days2;
+ if (calendarUnitsPresent) {
+ // FIXME: spec issue - directly throw an error if plainRelativeTo is undef.
+
+ // Step 13.a.
+ DateDuration unbalanceResult1;
+ if (plainRelativeTo) {
+ if (!UnbalanceDateDurationRelative(cx, one, TemporalUnit::Day,
+ plainRelativeTo, calendar,
+ &unbalanceResult1)) {
+ return false;
+ }
+ } else {
+ if (!UnbalanceDateDurationRelative(cx, one, TemporalUnit::Day,
+ &unbalanceResult1)) {
+ return false;
+ }
+ MOZ_ASSERT(one.date() == unbalanceResult1.toDuration());
+ }
+
+ // Step 13.b.
+ DateDuration unbalanceResult2;
+ if (plainRelativeTo) {
+ if (!UnbalanceDateDurationRelative(cx, two, TemporalUnit::Day,
+ plainRelativeTo, calendar,
+ &unbalanceResult2)) {
+ return false;
+ }
+ } else {
+ if (!UnbalanceDateDurationRelative(cx, two, TemporalUnit::Day,
+ &unbalanceResult2)) {
+ return false;
+ }
+ MOZ_ASSERT(two.date() == unbalanceResult2.toDuration());
+ }
+
+ // Step 13.c.
+ days1 = unbalanceResult1.days;
+
+ // Step 13.d.
+ days2 = unbalanceResult2.days;
+ } else {
+ // Step 14.a.
+ days1 = one.days;
+
+ // Step 14.b.
+ days2 = two.days;
+ }
+
+ // Note: duration units can be arbitrary doubles, so we need to use BigInts
+ // Test case:
+ //
+ // Temporal.Duration.compare({
+ // milliseconds: 10000000000000, microseconds: 4, nanoseconds: 95
+ // }, {
+ // nanoseconds:10000000000000004000
+ // })
+ //
+ // This must return -1, but would return 0 when |double| is used.
+ //
+ // Note: BigInt(10000000000000004000) is 10000000000000004096n
+
+ Duration oneTotal = {
+ 0,
+ 0,
+ 0,
+ days1,
+ one.hours,
+ one.minutes,
+ one.seconds,
+ one.milliseconds,
+ one.microseconds,
+ one.nanoseconds,
+ };
+ Duration twoTotal = {
+ 0,
+ 0,
+ 0,
+ days2,
+ two.hours,
+ two.minutes,
+ two.seconds,
+ two.milliseconds,
+ two.microseconds,
+ two.nanoseconds,
+ };
+
+ // Steps 15-21.
+ //
+ // Fast path when the total duration amount fits into an int64.
+ if (auto ns1 = TotalDurationNanoseconds(oneTotal)) {
+ if (auto ns2 = TotalDurationNanoseconds(twoTotal)) {
+ args.rval().setInt32(*ns1 < *ns2 ? -1 : *ns1 > *ns2 ? 1 : 0);
+ return true;
+ }
+ }
+
+ // Steps 15 and 17.
+ Rooted<BigInt*> ns1(cx, TotalDurationNanosecondsSlow(cx, oneTotal));
+ if (!ns1) {
+ return false;
+ }
+
+ // Steps 16 and 18.
+ auto* ns2 = TotalDurationNanosecondsSlow(cx, twoTotal);
+ if (!ns2) {
+ return false;
+ }
+
+ // Step 19-21.
+ args.rval().setInt32(BigInt::compare(ns1, ns2));
+ return true;
+}
+
+/**
+ * get Temporal.Duration.prototype.years
+ */
+static bool Duration_years(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* duration = &args.thisv().toObject().as<DurationObject>();
+ args.rval().setNumber(duration->years());
+ return true;
+}
+
+/**
+ * get Temporal.Duration.prototype.years
+ */
+static bool Duration_years(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_years>(cx, args);
+}
+
+/**
+ * get Temporal.Duration.prototype.months
+ */
+static bool Duration_months(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* duration = &args.thisv().toObject().as<DurationObject>();
+ args.rval().setNumber(duration->months());
+ return true;
+}
+
+/**
+ * get Temporal.Duration.prototype.months
+ */
+static bool Duration_months(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_months>(cx, args);
+}
+
+/**
+ * get Temporal.Duration.prototype.weeks
+ */
+static bool Duration_weeks(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* duration = &args.thisv().toObject().as<DurationObject>();
+ args.rval().setNumber(duration->weeks());
+ return true;
+}
+
+/**
+ * get Temporal.Duration.prototype.weeks
+ */
+static bool Duration_weeks(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_weeks>(cx, args);
+}
+
+/**
+ * get Temporal.Duration.prototype.days
+ */
+static bool Duration_days(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* duration = &args.thisv().toObject().as<DurationObject>();
+ args.rval().setNumber(duration->days());
+ return true;
+}
+
+/**
+ * get Temporal.Duration.prototype.days
+ */
+static bool Duration_days(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_days>(cx, args);
+}
+
+/**
+ * get Temporal.Duration.prototype.hours
+ */
+static bool Duration_hours(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* duration = &args.thisv().toObject().as<DurationObject>();
+ args.rval().setNumber(duration->hours());
+ return true;
+}
+
+/**
+ * get Temporal.Duration.prototype.hours
+ */
+static bool Duration_hours(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_hours>(cx, args);
+}
+
+/**
+ * get Temporal.Duration.prototype.minutes
+ */
+static bool Duration_minutes(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* duration = &args.thisv().toObject().as<DurationObject>();
+ args.rval().setNumber(duration->minutes());
+ return true;
+}
+
+/**
+ * get Temporal.Duration.prototype.minutes
+ */
+static bool Duration_minutes(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_minutes>(cx, args);
+}
+
+/**
+ * get Temporal.Duration.prototype.seconds
+ */
+static bool Duration_seconds(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* duration = &args.thisv().toObject().as<DurationObject>();
+ args.rval().setNumber(duration->seconds());
+ return true;
+}
+
+/**
+ * get Temporal.Duration.prototype.seconds
+ */
+static bool Duration_seconds(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_seconds>(cx, args);
+}
+
+/**
+ * get Temporal.Duration.prototype.milliseconds
+ */
+static bool Duration_milliseconds(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* duration = &args.thisv().toObject().as<DurationObject>();
+ args.rval().setNumber(duration->milliseconds());
+ return true;
+}
+
+/**
+ * get Temporal.Duration.prototype.milliseconds
+ */
+static bool Duration_milliseconds(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_milliseconds>(cx, args);
+}
+
+/**
+ * get Temporal.Duration.prototype.microseconds
+ */
+static bool Duration_microseconds(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* duration = &args.thisv().toObject().as<DurationObject>();
+ args.rval().setNumber(duration->microseconds());
+ return true;
+}
+
+/**
+ * get Temporal.Duration.prototype.microseconds
+ */
+static bool Duration_microseconds(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_microseconds>(cx, args);
+}
+
+/**
+ * get Temporal.Duration.prototype.nanoseconds
+ */
+static bool Duration_nanoseconds(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* duration = &args.thisv().toObject().as<DurationObject>();
+ args.rval().setNumber(duration->nanoseconds());
+ return true;
+}
+
+/**
+ * get Temporal.Duration.prototype.nanoseconds
+ */
+static bool Duration_nanoseconds(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_nanoseconds>(cx, args);
+}
+
+/**
+ * get Temporal.Duration.prototype.sign
+ */
+static bool Duration_sign(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* duration = &args.thisv().toObject().as<DurationObject>();
+ int32_t sign = DurationSign(ToDuration(duration));
+ args.rval().setInt32(sign);
+ return true;
+}
+
+/**
+ * get Temporal.Duration.prototype.sign
+ */
+static bool Duration_sign(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_sign>(cx, args);
+}
+
+/**
+ * get Temporal.Duration.prototype.blank
+ */
+static bool Duration_blank(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* duration = &args.thisv().toObject().as<DurationObject>();
+ int32_t sign = DurationSign(ToDuration(duration));
+
+ // Steps 4-5.
+ args.rval().setBoolean(sign == 0);
+ return true;
+}
+
+/**
+ * get Temporal.Duration.prototype.blank
+ */
+static bool Duration_blank(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_blank>(cx, args);
+}
+
+/**
+ * Temporal.Duration.prototype.with ( temporalDurationLike )
+ *
+ * ToPartialDuration ( temporalDurationLike )
+ */
+static bool Duration_with(JSContext* cx, const CallArgs& args) {
+ auto* durationObj = &args.thisv().toObject().as<DurationObject>();
+
+ // Absent values default to the corresponding values of |this| object.
+ auto duration = ToDuration(durationObj);
+
+ // Steps 3-23.
+ Rooted<JSObject*> temporalDurationLike(
+ cx, RequireObjectArg(cx, "temporalDurationLike", "with", args.get(0)));
+ if (!temporalDurationLike) {
+ return false;
+ }
+ if (!ToTemporalPartialDurationRecord(cx, temporalDurationLike, &duration)) {
+ return false;
+ }
+
+ // Step 24.
+ auto* result = CreateTemporalDuration(cx, duration);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Duration.prototype.with ( temporalDurationLike )
+ */
+static bool Duration_with(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_with>(cx, args);
+}
+
+/**
+ * Temporal.Duration.prototype.negated ( )
+ */
+static bool Duration_negated(JSContext* cx, const CallArgs& args) {
+ auto* durationObj = &args.thisv().toObject().as<DurationObject>();
+ auto duration = ToDuration(durationObj);
+
+ // Step 3.
+ auto* result = CreateTemporalDuration(cx, duration.negate());
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Duration.prototype.negated ( )
+ */
+static bool Duration_negated(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_negated>(cx, args);
+}
+
+/**
+ * Temporal.Duration.prototype.abs ( )
+ */
+static bool Duration_abs(JSContext* cx, const CallArgs& args) {
+ auto* durationObj = &args.thisv().toObject().as<DurationObject>();
+ auto duration = ToDuration(durationObj);
+
+ // Step 3.
+ auto* result = CreateTemporalDuration(cx, AbsoluteDuration(duration));
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Duration.prototype.abs ( )
+ */
+static bool Duration_abs(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_abs>(cx, args);
+}
+
+/**
+ * Temporal.Duration.prototype.add ( other [ , options ] )
+ */
+static bool Duration_add(JSContext* cx, const CallArgs& args) {
+ return AddDurationToOrSubtractDurationFromDuration(cx, DurationOperation::Add,
+ args);
+}
+
+/**
+ * Temporal.Duration.prototype.add ( other [ , options ] )
+ */
+static bool Duration_add(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_add>(cx, args);
+}
+
+/**
+ * Temporal.Duration.prototype.subtract ( other [ , options ] )
+ */
+static bool Duration_subtract(JSContext* cx, const CallArgs& args) {
+ return AddDurationToOrSubtractDurationFromDuration(
+ cx, DurationOperation::Subtract, args);
+}
+
+/**
+ * Temporal.Duration.prototype.subtract ( other [ , options ] )
+ */
+static bool Duration_subtract(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_subtract>(cx, args);
+}
+
+/**
+ * Temporal.Duration.prototype.round ( roundTo )
+ */
+static bool Duration_round(JSContext* cx, const CallArgs& args) {
+ auto* durationObj = &args.thisv().toObject().as<DurationObject>();
+ auto duration = ToDuration(durationObj);
+
+ // Step 18. (Reordered)
+ auto existingLargestUnit = DefaultTemporalLargestUnit(duration);
+
+ // Steps 3-25.
+ auto smallestUnit = TemporalUnit::Auto;
+ TemporalUnit largestUnit;
+ auto roundingMode = TemporalRoundingMode::HalfExpand;
+ auto roundingIncrement = Increment{1};
+ Rooted<JSObject*> relativeTo(cx);
+ Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
+ Rooted<ZonedDateTime> zonedRelativeTo(cx);
+ Rooted<TimeZoneRecord> timeZone(cx);
+ if (args.get(0).isString()) {
+ // Step 4. (Not applicable in our implementation.)
+
+ // Steps 6-15. (Not applicable)
+
+ // Step 16.
+ Rooted<JSString*> paramString(cx, args[0].toString());
+ if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::SmallestUnit,
+ TemporalUnitGroup::DateTime, &smallestUnit)) {
+ return false;
+ }
+
+ // Step 17. (Not applicable)
+
+ // Step 18. (Moved above)
+
+ // Step 19.
+ auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
+
+ // Step 20. (Not applicable)
+
+ // Step 20.a. (Not applicable)
+
+ // Step 20.b.
+ largestUnit = defaultLargestUnit;
+
+ // Steps 21-25. (Not applicable)
+ } else {
+ // Steps 3 and 5.
+ Rooted<JSObject*> options(
+ cx, RequireObjectArg(cx, "roundTo", "round", args.get(0)));
+ if (!options) {
+ return false;
+ }
+
+ // Step 6.
+ bool smallestUnitPresent = true;
+
+ // Step 7.
+ bool largestUnitPresent = true;
+
+ // Steps 8-9.
+ //
+ // Inlined GetTemporalUnit and GetOption so we can more easily detect an
+ // absent "largestUnit" value.
+ Rooted<Value> largestUnitValue(cx);
+ if (!GetProperty(cx, options, options, cx->names().largestUnit,
+ &largestUnitValue)) {
+ return false;
+ }
+
+ if (!largestUnitValue.isUndefined()) {
+ Rooted<JSString*> largestUnitStr(cx, JS::ToString(cx, largestUnitValue));
+ if (!largestUnitStr) {
+ return false;
+ }
+
+ largestUnit = TemporalUnit::Auto;
+ if (!GetTemporalUnit(cx, largestUnitStr, TemporalUnitKey::LargestUnit,
+ TemporalUnitGroup::DateTime, &largestUnit)) {
+ return false;
+ }
+ }
+
+ // Steps 10-13.
+ if (!ToRelativeTemporalObject(cx, options, &plainRelativeTo,
+ &zonedRelativeTo, &timeZone)) {
+ return false;
+ }
+ MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
+ MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
+
+ // Step 14.
+ if (!ToTemporalRoundingIncrement(cx, options, &roundingIncrement)) {
+ return false;
+ }
+
+ // Step 15.
+ if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
+ return false;
+ }
+
+ // Step 16.
+ if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
+ TemporalUnitGroup::DateTime, &smallestUnit)) {
+ return false;
+ }
+
+ // Step 17.
+ if (smallestUnit == TemporalUnit::Auto) {
+ // Step 17.a.
+ smallestUnitPresent = false;
+
+ // Step 17.b.
+ smallestUnit = TemporalUnit::Nanosecond;
+ }
+
+ // Step 18. (Moved above)
+
+ // Step 19.
+ auto defaultLargestUnit = std::min(existingLargestUnit, smallestUnit);
+
+ // Steps 20-21.
+ if (largestUnitValue.isUndefined()) {
+ // Step 20.a.
+ largestUnitPresent = false;
+
+ // Step 20.b.
+ largestUnit = defaultLargestUnit;
+ } else if (largestUnit == TemporalUnit::Auto) {
+ // Step 21.a
+ largestUnit = defaultLargestUnit;
+ }
+
+ // Step 22.
+ if (!smallestUnitPresent && !largestUnitPresent) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DURATION_MISSING_UNIT_SPECIFIER);
+ return false;
+ }
+
+ // Step 23.
+ if (largestUnit > smallestUnit) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_UNIT_RANGE);
+ return false;
+ }
+
+ // Steps 24-25.
+ if (smallestUnit > TemporalUnit::Day) {
+ // Step 24.
+ auto maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
+
+ // Step 25.
+ if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
+ false)) {
+ return false;
+ }
+ }
+ }
+
+ // Step 26.
+ bool hoursToDaysConversionMayOccur = false;
+
+ // Step 27.
+ if (duration.days != 0 && zonedRelativeTo) {
+ hoursToDaysConversionMayOccur = true;
+ }
+
+ // Step 28.
+ else if (std::abs(duration.hours) >= 24) {
+ hoursToDaysConversionMayOccur = true;
+ }
+
+ // Step 29.
+ bool roundingGranularityIsNoop = smallestUnit == TemporalUnit::Nanosecond &&
+ roundingIncrement == Increment{1};
+
+ // Step 30.
+ bool calendarUnitsPresent =
+ duration.years != 0 || duration.months != 0 || duration.weeks != 0;
+
+ // Step 31.
+ if (roundingGranularityIsNoop && largestUnit == existingLargestUnit &&
+ !calendarUnitsPresent && !hoursToDaysConversionMayOccur &&
+ std::abs(duration.minutes) < 60 && std::abs(duration.seconds) < 60 &&
+ std::abs(duration.milliseconds) < 1000 &&
+ std::abs(duration.microseconds) < 1000 &&
+ std::abs(duration.nanoseconds) < 1000) {
+ // Steps 31.a-b.
+ auto* obj = CreateTemporalDuration(cx, duration);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+ }
+
+ // Step 32.
+ mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
+
+ // Step 33.
+ bool plainDateTimeOrRelativeToWillBeUsed =
+ !roundingGranularityIsNoop || largestUnit <= TemporalUnit::Day ||
+ calendarUnitsPresent || duration.days != 0;
+
+ // Step 34.
+ PlainDateTime relativeToDateTime;
+ if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
+ // Steps 34.a-b.
+ auto instant = zonedRelativeTo.instant();
+
+ // Step 34.c.
+ if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
+ return false;
+ }
+ precalculatedPlainDateTime =
+ mozilla::SomeRef<const PlainDateTime>(relativeToDateTime);
+
+ // Step 34.d.
+ plainRelativeTo = CreateTemporalDate(cx, relativeToDateTime.date,
+ zonedRelativeTo.calendar());
+ if (!plainRelativeTo) {
+ return false;
+ }
+ }
+
+ // Step 35.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
+ zonedRelativeTo,
+ {
+ CalendarMethod::DateAdd,
+ CalendarMethod::DateUntil,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Step 36.
+ DateDuration unbalanceResult;
+ if (plainRelativeTo) {
+ if (!UnbalanceDateDurationRelative(cx, duration, largestUnit,
+ plainRelativeTo, calendar,
+ &unbalanceResult)) {
+ return false;
+ }
+ } else {
+ if (!UnbalanceDateDurationRelative(cx, duration, largestUnit,
+ &unbalanceResult)) {
+ return false;
+ }
+ MOZ_ASSERT(duration.date() == unbalanceResult.toDuration());
+ }
+
+ // Steps 37-38.
+ Duration roundInput = {
+ unbalanceResult.years, unbalanceResult.months, unbalanceResult.weeks,
+ unbalanceResult.days, duration.hours, duration.minutes,
+ duration.seconds, duration.milliseconds, duration.microseconds,
+ duration.nanoseconds,
+ };
+ Duration roundResult;
+ if (plainRelativeTo || zonedRelativeTo) {
+ if (!::RoundDuration(cx, roundInput, roundingIncrement, smallestUnit,
+ roundingMode, plainRelativeTo, calendar,
+ zonedRelativeTo, timeZone, precalculatedPlainDateTime,
+ &roundResult)) {
+ return false;
+ }
+ } else {
+ if (!::RoundDuration(cx, roundInput, roundingIncrement, smallestUnit,
+ roundingMode, &roundResult)) {
+ return false;
+ }
+ }
+
+ // Steps 39-40.
+ TimeDuration balanceResult;
+ if (zonedRelativeTo) {
+ // Step 39.a.
+ Duration adjustResult;
+ if (!AdjustRoundedDurationDays(cx, roundResult, roundingIncrement,
+ smallestUnit, roundingMode, zonedRelativeTo,
+ calendar, timeZone,
+ precalculatedPlainDateTime, &adjustResult)) {
+ return false;
+ }
+ roundResult = adjustResult;
+
+ // Step 39.b.
+ if (!BalanceTimeDurationRelative(
+ cx, roundResult, largestUnit, zonedRelativeTo, timeZone,
+ precalculatedPlainDateTime, &balanceResult)) {
+ return false;
+ }
+ } else {
+ // Step 40.a.
+ if (!BalanceTimeDuration(cx, roundResult, largestUnit, &balanceResult)) {
+ return false;
+ }
+ }
+
+ // Step 41.
+ Duration balanceInput = {
+ roundResult.years,
+ roundResult.months,
+ roundResult.weeks,
+ balanceResult.days,
+ };
+ DateDuration result;
+ if (!::BalanceDateDurationRelative(cx, balanceInput, largestUnit,
+ smallestUnit, plainRelativeTo, calendar,
+ &result)) {
+ return false;
+ }
+
+ // Step 42.
+ auto* obj = CreateTemporalDuration(cx, {
+ result.years,
+ result.months,
+ result.weeks,
+ result.days,
+ balanceResult.hours,
+ balanceResult.minutes,
+ balanceResult.seconds,
+ balanceResult.milliseconds,
+ balanceResult.microseconds,
+ balanceResult.nanoseconds,
+ });
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.Duration.prototype.round ( options )
+ */
+static bool Duration_round(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_round>(cx, args);
+}
+
+/**
+ * Temporal.Duration.prototype.total ( totalOf )
+ */
+static bool Duration_total(JSContext* cx, const CallArgs& args) {
+ auto* durationObj = &args.thisv().toObject().as<DurationObject>();
+ auto duration = ToDuration(durationObj);
+
+ // Steps 3-11.
+ Rooted<JSObject*> relativeTo(cx);
+ Rooted<Wrapped<PlainDateObject*>> plainRelativeTo(cx);
+ Rooted<ZonedDateTime> zonedRelativeTo(cx);
+ Rooted<TimeZoneRecord> timeZone(cx);
+ auto unit = TemporalUnit::Auto;
+ if (args.get(0).isString()) {
+ // Step 4. (Not applicable in our implementation.)
+
+ // Steps 6-10. (Implicit)
+ MOZ_ASSERT(!plainRelativeTo && !zonedRelativeTo);
+
+ // Step 11.
+ Rooted<JSString*> paramString(cx, args[0].toString());
+ if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::Unit,
+ TemporalUnitGroup::DateTime, &unit)) {
+ return false;
+ }
+ } else {
+ // Steps 3 and 5.
+ Rooted<JSObject*> totalOf(
+ cx, RequireObjectArg(cx, "totalOf", "total", args.get(0)));
+ if (!totalOf) {
+ return false;
+ }
+
+ // Steps 6-10.
+ if (!ToRelativeTemporalObject(cx, totalOf, &plainRelativeTo,
+ &zonedRelativeTo, &timeZone)) {
+ return false;
+ }
+ MOZ_ASSERT(!plainRelativeTo || !zonedRelativeTo);
+ MOZ_ASSERT_IF(zonedRelativeTo, timeZone.receiver());
+
+ // Step 11.
+ if (!GetTemporalUnit(cx, totalOf, TemporalUnitKey::Unit,
+ TemporalUnitGroup::DateTime, &unit)) {
+ return false;
+ }
+
+ if (unit == TemporalUnit::Auto) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_MISSING_OPTION, "unit");
+ return false;
+ }
+ }
+
+ // Step 12.
+ mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime{};
+
+ // Step 13.
+ bool plainDateTimeOrRelativeToWillBeUsed =
+ unit <= TemporalUnit::Day || duration.years != 0 ||
+ duration.months != 0 || duration.weeks != 0 || duration.days != 0;
+
+ // Step 14.
+ PlainDateTime relativeToDateTime;
+ if (zonedRelativeTo && plainDateTimeOrRelativeToWillBeUsed) {
+ // Steps 14.a-b.
+ auto instant = zonedRelativeTo.instant();
+
+ // Step 14.c.
+ if (!GetPlainDateTimeFor(cx, timeZone, instant, &relativeToDateTime)) {
+ return false;
+ }
+ precalculatedPlainDateTime =
+ mozilla::SomeRef<const PlainDateTime>(relativeToDateTime);
+
+ // Step 14.d
+ plainRelativeTo = CreateTemporalDate(cx, relativeToDateTime.date,
+ zonedRelativeTo.calendar());
+ if (!plainRelativeTo) {
+ return false;
+ }
+ }
+
+ // Step 15.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecordFromRelativeTo(cx, plainRelativeTo,
+ zonedRelativeTo,
+ {
+ CalendarMethod::DateAdd,
+ CalendarMethod::DateUntil,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Step 16.
+ DateDuration unbalanceResult;
+ if (plainRelativeTo) {
+ if (!UnbalanceDateDurationRelative(cx, duration, unit, plainRelativeTo,
+ calendar, &unbalanceResult)) {
+ return false;
+ }
+ } else {
+ if (!UnbalanceDateDurationRelative(cx, duration, unit, &unbalanceResult)) {
+ return false;
+ }
+ MOZ_ASSERT(duration.date() == unbalanceResult.toDuration());
+ }
+
+ Duration balanceInput = {
+ 0,
+ 0,
+ 0,
+ unbalanceResult.days,
+ duration.hours,
+ duration.minutes,
+ duration.seconds,
+ duration.milliseconds,
+ duration.microseconds,
+ duration.nanoseconds,
+ };
+
+ // Steps 17-18.
+ TimeDuration balanceResult;
+ if (zonedRelativeTo) {
+ // Step 17.a
+ Rooted<ZonedDateTime> intermediate(cx);
+ if (!MoveRelativeZonedDateTime(
+ cx, zonedRelativeTo, calendar, timeZone,
+ {unbalanceResult.years, unbalanceResult.months,
+ unbalanceResult.weeks, 0},
+ precalculatedPlainDateTime, &intermediate)) {
+ return false;
+ }
+
+ // Step 17.b.
+ if (!BalancePossiblyInfiniteTimeDurationRelative(
+ cx, balanceInput, unit, intermediate, timeZone, &balanceResult)) {
+ return false;
+ }
+ } else {
+ // Step 18.
+ if (!BalancePossiblyInfiniteTimeDuration(cx, balanceInput, unit,
+ &balanceResult)) {
+ return false;
+ }
+ }
+
+ // Steps 19-20.
+ for (double v : {
+ balanceResult.days,
+ balanceResult.hours,
+ balanceResult.minutes,
+ balanceResult.seconds,
+ balanceResult.milliseconds,
+ balanceResult.microseconds,
+ balanceResult.nanoseconds,
+ }) {
+ if (std::isinf(v)) {
+ args.rval().setDouble(v);
+ return true;
+ }
+ }
+ MOZ_ASSERT(IsValidDuration(balanceResult.toDuration()));
+
+ // Step 21. (Not applicable in our implementation.)
+
+ // Step 22.
+ Duration roundInput = {
+ unbalanceResult.years, unbalanceResult.months,
+ unbalanceResult.weeks, balanceResult.days,
+ balanceResult.hours, balanceResult.minutes,
+ balanceResult.seconds, balanceResult.milliseconds,
+ balanceResult.microseconds, balanceResult.nanoseconds,
+ };
+ double total;
+ if (plainRelativeTo || zonedRelativeTo) {
+ if (!::RoundDuration(cx, roundInput, Increment{1}, unit,
+ TemporalRoundingMode::Trunc, plainRelativeTo, calendar,
+ zonedRelativeTo, timeZone, precalculatedPlainDateTime,
+ &total)) {
+ return false;
+ }
+ } else {
+ if (!::RoundDuration(cx, roundInput, Increment{1}, unit,
+ TemporalRoundingMode::Trunc, &total)) {
+ return false;
+ }
+ }
+
+ // Step 23.
+ args.rval().setNumber(total);
+ return true;
+}
+
+/**
+ * Temporal.Duration.prototype.total ( totalOf )
+ */
+static bool Duration_total(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_total>(cx, args);
+}
+
+/**
+ * Temporal.Duration.prototype.toString ( [ options ] )
+ */
+static bool Duration_toString(JSContext* cx, const CallArgs& args) {
+ auto duration = ToDuration(&args.thisv().toObject().as<DurationObject>());
+
+ // Steps 3-9.
+ SecondsStringPrecision precision = {Precision::Auto(),
+ TemporalUnit::Nanosecond, Increment{1}};
+ auto roundingMode = TemporalRoundingMode::Trunc;
+ if (args.hasDefined(0)) {
+ // Step 3.
+ Rooted<JSObject*> options(
+ cx, RequireObjectArg(cx, "options", "toString", args[0]));
+ if (!options) {
+ return false;
+ }
+
+ // Steps 4-5.
+ auto digits = Precision::Auto();
+ if (!ToFractionalSecondDigits(cx, options, &digits)) {
+ return false;
+ }
+
+ // Step 6.
+ if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
+ return false;
+ }
+
+ // Step 7.
+ auto smallestUnit = TemporalUnit::Auto;
+ if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
+ TemporalUnitGroup::Time, &smallestUnit)) {
+ return false;
+ }
+
+ // Step 8.
+ if (smallestUnit == TemporalUnit::Hour ||
+ smallestUnit == TemporalUnit::Minute) {
+ const char* smallestUnitStr =
+ smallestUnit == TemporalUnit::Hour ? "hour" : "minute";
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_UNIT_OPTION,
+ smallestUnitStr, "smallestUnit");
+ return false;
+ }
+
+ // Step 9.
+ precision = ToSecondsStringPrecision(smallestUnit, digits);
+ }
+
+ // Steps 10-11.
+ Duration result;
+ if (precision.unit != TemporalUnit::Nanosecond ||
+ precision.increment != Increment{1}) {
+ // Step 10.a.
+ auto largestUnit = DefaultTemporalLargestUnit(duration);
+
+ // Steps 10.b-c.
+ auto toRound = Duration{
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ duration.seconds,
+ duration.milliseconds,
+ duration.microseconds,
+ duration.nanoseconds,
+ };
+ Duration roundResult;
+ if (!temporal::RoundDuration(cx, toRound, precision.increment,
+ precision.unit, roundingMode, &roundResult)) {
+ return false;
+ }
+
+ // Step 10.d.
+ auto toBalance = Duration{
+ 0,
+ 0,
+ 0,
+ duration.days,
+ duration.hours,
+ duration.minutes,
+ roundResult.seconds,
+ roundResult.milliseconds,
+ roundResult.microseconds,
+ roundResult.nanoseconds,
+ };
+ TimeDuration balanceResult;
+ if (!BalanceTimeDuration(cx, toBalance, largestUnit, &balanceResult)) {
+ return false;
+ }
+
+ // Step 10.e.
+ result = {
+ duration.years,
+ duration.months,
+ duration.weeks,
+ balanceResult.days,
+ balanceResult.hours,
+ balanceResult.minutes,
+ balanceResult.seconds,
+ balanceResult.milliseconds,
+ balanceResult.microseconds,
+ balanceResult.nanoseconds,
+ };
+ } else {
+ // Step 11.
+ result = duration;
+ }
+
+ // Step 12.
+ JSString* str = TemporalDurationToString(cx, result, precision.precision);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.Duration.prototype.toString ( [ options ] )
+ */
+static bool Duration_toString(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_toString>(cx, args);
+}
+
+/**
+ * Temporal.Duration.prototype.toJSON ( )
+ */
+static bool Duration_toJSON(JSContext* cx, const CallArgs& args) {
+ auto* duration = &args.thisv().toObject().as<DurationObject>();
+
+ // Step 3.
+ JSString* str =
+ TemporalDurationToString(cx, ToDuration(duration), Precision::Auto());
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.Duration.prototype.toJSON ( )
+ */
+static bool Duration_toJSON(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_toJSON>(cx, args);
+}
+
+/**
+ * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
+ */
+static bool Duration_toLocaleString(JSContext* cx, const CallArgs& args) {
+ auto* duration = &args.thisv().toObject().as<DurationObject>();
+
+ // Step 3.
+ JSString* str =
+ TemporalDurationToString(cx, ToDuration(duration), Precision::Auto());
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.Duration.prototype.toLocaleString ( [ locales [ , options ] ] )
+ */
+static bool Duration_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsDuration, Duration_toLocaleString>(cx, args);
+}
+
+/**
+ * Temporal.Duration.prototype.valueOf ( )
+ */
+static bool Duration_valueOf(JSContext* cx, unsigned argc, Value* vp) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
+ "Duration", "primitive type");
+ return false;
+}
+
+const JSClass DurationObject::class_ = {
+ "Temporal.Duration",
+ JSCLASS_HAS_RESERVED_SLOTS(DurationObject::SLOT_COUNT) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_Duration),
+ JS_NULL_CLASS_OPS,
+ &DurationObject::classSpec_,
+};
+
+const JSClass& DurationObject::protoClass_ = PlainObject::class_;
+
+static const JSFunctionSpec Duration_methods[] = {
+ JS_FN("from", Duration_from, 1, 0),
+ JS_FN("compare", Duration_compare, 2, 0),
+ JS_FS_END,
+};
+
+static const JSFunctionSpec Duration_prototype_methods[] = {
+ JS_FN("with", Duration_with, 1, 0),
+ JS_FN("negated", Duration_negated, 0, 0),
+ JS_FN("abs", Duration_abs, 0, 0),
+ JS_FN("add", Duration_add, 1, 0),
+ JS_FN("subtract", Duration_subtract, 1, 0),
+ JS_FN("round", Duration_round, 1, 0),
+ JS_FN("total", Duration_total, 1, 0),
+ JS_FN("toString", Duration_toString, 0, 0),
+ JS_FN("toJSON", Duration_toJSON, 0, 0),
+ JS_FN("toLocaleString", Duration_toLocaleString, 0, 0),
+ JS_FN("valueOf", Duration_valueOf, 0, 0),
+ JS_FS_END,
+};
+
+static const JSPropertySpec Duration_prototype_properties[] = {
+ JS_PSG("years", Duration_years, 0),
+ JS_PSG("months", Duration_months, 0),
+ JS_PSG("weeks", Duration_weeks, 0),
+ JS_PSG("days", Duration_days, 0),
+ JS_PSG("hours", Duration_hours, 0),
+ JS_PSG("minutes", Duration_minutes, 0),
+ JS_PSG("seconds", Duration_seconds, 0),
+ JS_PSG("milliseconds", Duration_milliseconds, 0),
+ JS_PSG("microseconds", Duration_microseconds, 0),
+ JS_PSG("nanoseconds", Duration_nanoseconds, 0),
+ JS_PSG("sign", Duration_sign, 0),
+ JS_PSG("blank", Duration_blank, 0),
+ JS_STRING_SYM_PS(toStringTag, "Temporal.Duration", JSPROP_READONLY),
+ JS_PS_END,
+};
+
+const ClassSpec DurationObject::classSpec_ = {
+ GenericCreateConstructor<DurationConstructor, 0, gc::AllocKind::FUNCTION>,
+ GenericCreatePrototype<DurationObject>,
+ Duration_methods,
+ nullptr,
+ Duration_prototype_methods,
+ Duration_prototype_properties,
+ nullptr,
+ ClassSpec::DontDefineConstructor,
+};
diff --git a/js/src/builtin/temporal/Duration.h b/js/src/builtin/temporal/Duration.h
new file mode 100644
index 0000000000..47708458f4
--- /dev/null
+++ b/js/src/builtin/temporal/Duration.h
@@ -0,0 +1,210 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_Duration_h
+#define builtin_temporal_Duration_h
+
+#include <stdint.h>
+
+#include "builtin/temporal/TemporalTypes.h"
+#include "builtin/temporal/Wrapped.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "vm/NativeObject.h"
+
+namespace js {
+struct ClassSpec;
+}
+
+namespace js::temporal {
+
+class DurationObject : public NativeObject {
+ public:
+ static const JSClass class_;
+ static const JSClass& protoClass_;
+
+ static constexpr uint32_t YEARS_SLOT = 0;
+ static constexpr uint32_t MONTHS_SLOT = 1;
+ static constexpr uint32_t WEEKS_SLOT = 2;
+ static constexpr uint32_t DAYS_SLOT = 3;
+ static constexpr uint32_t HOURS_SLOT = 4;
+ static constexpr uint32_t MINUTES_SLOT = 5;
+ static constexpr uint32_t SECONDS_SLOT = 6;
+ static constexpr uint32_t MILLISECONDS_SLOT = 7;
+ static constexpr uint32_t MICROSECONDS_SLOT = 8;
+ static constexpr uint32_t NANOSECONDS_SLOT = 9;
+ static constexpr uint32_t SLOT_COUNT = 10;
+
+ double years() const { return getFixedSlot(YEARS_SLOT).toNumber(); }
+ double months() const { return getFixedSlot(MONTHS_SLOT).toNumber(); }
+ double weeks() const { return getFixedSlot(WEEKS_SLOT).toNumber(); }
+ double days() const { return getFixedSlot(DAYS_SLOT).toNumber(); }
+ double hours() const { return getFixedSlot(HOURS_SLOT).toNumber(); }
+ double minutes() const { return getFixedSlot(MINUTES_SLOT).toNumber(); }
+ double seconds() const { return getFixedSlot(SECONDS_SLOT).toNumber(); }
+ double milliseconds() const {
+ return getFixedSlot(MILLISECONDS_SLOT).toNumber();
+ }
+ double microseconds() const {
+ return getFixedSlot(MICROSECONDS_SLOT).toNumber();
+ }
+ double nanoseconds() const {
+ return getFixedSlot(NANOSECONDS_SLOT).toNumber();
+ }
+
+ private:
+ static const ClassSpec classSpec_;
+};
+
+/**
+ * Extract the duration fields from the Duration object.
+ */
+inline Duration ToDuration(const DurationObject* duration) {
+ return {
+ duration->years(), duration->months(),
+ duration->weeks(), duration->days(),
+ duration->hours(), duration->minutes(),
+ duration->seconds(), duration->milliseconds(),
+ duration->microseconds(), duration->nanoseconds(),
+ };
+}
+
+class Increment;
+class CalendarRecord;
+class PlainDateObject;
+class TimeZoneRecord;
+class ZonedDateTime;
+class ZonedDateTimeObject;
+enum class TemporalRoundingMode;
+enum class TemporalUnit;
+
+/**
+ * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds )
+ */
+int32_t DurationSign(const Duration& duration);
+
+/**
+ * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds )
+ */
+bool IsValidDuration(const Duration& duration);
+
+/**
+ * IsValidDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds )
+ */
+bool ThrowIfInvalidDuration(JSContext* cx, const Duration& duration);
+
+/**
+ * CreateTemporalDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds [ , newTarget ] )
+ */
+DurationObject* CreateTemporalDuration(JSContext* cx, const Duration& duration);
+
+/**
+ * ToTemporalDuration ( item )
+ */
+Wrapped<DurationObject*> ToTemporalDuration(JSContext* cx,
+ JS::Handle<JS::Value> item);
+
+/**
+ * ToTemporalDuration ( item )
+ */
+bool ToTemporalDuration(JSContext* cx, JS::Handle<JS::Value> item,
+ Duration* result);
+
+/**
+ * ToTemporalDurationRecord ( temporalDurationLike )
+ */
+bool ToTemporalDurationRecord(JSContext* cx,
+ JS::Handle<JS::Value> temporalDurationLike,
+ Duration* result);
+
+/**
+ * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds, largestUnit )
+ */
+bool BalanceTimeDuration(JSContext* cx, const Duration& duration,
+ TemporalUnit largestUnit, TimeDuration* result);
+
+/**
+ * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds, largestUnit )
+ */
+bool BalanceTimeDuration(JSContext* cx, const InstantSpan& nanoseconds,
+ TemporalUnit largestUnit, TimeDuration* result);
+
+/**
+ * BalanceDateDurationRelative ( years, months, weeks, days, largestUnit,
+ * smallestUnit, plainRelativeTo, calendarRec )
+ */
+bool BalanceDateDurationRelative(
+ JSContext* cx, const Duration& duration, TemporalUnit largestUnit,
+ TemporalUnit smallestUnit,
+ JS::Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
+ JS::Handle<CalendarRecord> calendar, DateDuration* result);
+
+/**
+ * AdjustRoundedDurationDays ( years, months, weeks, days, hours, minutes,
+ * seconds, milliseconds, microseconds, nanoseconds, increment, unit,
+ * roundingMode, zonedRelativeTo, calendarRec, timeZoneRec,
+ * precalculatedPlainDateTime )
+ */
+bool AdjustRoundedDurationDays(JSContext* cx, const Duration& duration,
+ Increment increment, TemporalUnit unit,
+ TemporalRoundingMode roundingMode,
+ JS::Handle<ZonedDateTime> relativeTo,
+ JS::Handle<CalendarRecord> calendar,
+ JS::Handle<TimeZoneRecord> timeZone,
+ const PlainDateTime& precalculatedPlainDateTime,
+ Duration* result);
+
+/**
+ * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
+ * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
+ * precalculatedPlainDateTime ] ] ] ] ] )
+ */
+bool RoundDuration(JSContext* cx, const Duration& duration, Increment increment,
+ TemporalUnit unit, TemporalRoundingMode roundingMode,
+ Duration* result);
+
+/**
+ * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
+ * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
+ * precalculatedPlainDateTime ] ] ] ] ] )
+ */
+bool RoundDuration(JSContext* cx, const Duration& duration, Increment increment,
+ TemporalUnit unit, TemporalRoundingMode roundingMode,
+ JS::Handle<Wrapped<PlainDateObject*>> plainRelativeTo,
+ JS::Handle<CalendarRecord> calendar, Duration* result);
+
+/**
+ * RoundDuration ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds, increment, unit, roundingMode [ ,
+ * plainRelativeTo [ , calendarRec [ , zonedRelativeTo [ , timeZoneRec [ ,
+ * precalculatedPlainDateTime ] ] ] ] ] )
+ */
+bool RoundDuration(JSContext* cx, const Duration& duration, Increment increment,
+ TemporalUnit unit, TemporalRoundingMode roundingMode,
+ JS::Handle<PlainDateObject*> plainRelativeTo,
+ JS::Handle<CalendarRecord> calendar,
+ JS::Handle<ZonedDateTime> zonedRelativeTo,
+ JS::Handle<TimeZoneRecord> timeZone,
+ const PlainDateTime& precalculatedPlainDateTime,
+ Duration* result);
+
+/**
+ * DaysUntil ( earlier, later )
+ */
+int32_t DaysUntil(const PlainDate& earlier, const PlainDate& later);
+
+} /* namespace js::temporal */
+
+#endif /* builtin_temporal_Duration_h */
diff --git a/js/src/builtin/temporal/Instant.cpp b/js/src/builtin/temporal/Instant.cpp
new file mode 100644
index 0000000000..78fc15f313
--- /dev/null
+++ b/js/src/builtin/temporal/Instant.cpp
@@ -0,0 +1,1792 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "builtin/temporal/Instant.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Span.h"
+
+#include <algorithm>
+#include <array>
+#include <cstdlib>
+#include <iterator>
+#include <stddef.h>
+#include <stdint.h>
+#include <utility>
+
+#include "jsnum.h"
+#include "jspubtd.h"
+#include "NamespaceImports.h"
+
+#include "builtin/temporal/Calendar.h"
+#include "builtin/temporal/Duration.h"
+#include "builtin/temporal/Int96.h"
+#include "builtin/temporal/PlainDateTime.h"
+#include "builtin/temporal/Temporal.h"
+#include "builtin/temporal/TemporalParser.h"
+#include "builtin/temporal/TemporalRoundingMode.h"
+#include "builtin/temporal/TemporalTypes.h"
+#include "builtin/temporal/TemporalUnit.h"
+#include "builtin/temporal/TimeZone.h"
+#include "builtin/temporal/ToString.h"
+#include "builtin/temporal/Wrapped.h"
+#include "builtin/temporal/ZonedDateTime.h"
+#include "gc/AllocKind.h"
+#include "gc/Barrier.h"
+#include "js/CallArgs.h"
+#include "js/CallNonGenericMethod.h"
+#include "js/Class.h"
+#include "js/Conversions.h"
+#include "js/ErrorReport.h"
+#include "js/friend/ErrorMessages.h"
+#include "js/PropertyDescriptor.h"
+#include "js/PropertySpec.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "vm/BigIntType.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/GlobalObject.h"
+#include "vm/JSAtomState.h"
+#include "vm/JSContext.h"
+#include "vm/JSObject.h"
+#include "vm/PlainObject.h"
+#include "vm/StringType.h"
+
+#include "vm/JSObject-inl.h"
+#include "vm/NativeObject-inl.h"
+#include "vm/ObjectOperations-inl.h"
+
+using namespace js;
+using namespace js::temporal;
+
+static inline bool IsInstant(Handle<Value> v) {
+ return v.isObject() && v.toObject().is<InstantObject>();
+}
+
+/**
+ * Check if the absolute value is less-or-equal to the given limit.
+ */
+template <const auto& digits>
+static bool AbsoluteValueIsLessOrEqual(const BigInt* bigInt) {
+ size_t length = bigInt->digitLength();
+
+ // Fewer digits than the limit, so definitely in range.
+ if (length < std::size(digits)) {
+ return true;
+ }
+
+ // More digits than the limit, so definitely out of range.
+ if (length > std::size(digits)) {
+ return false;
+ }
+
+ // Compare each digit when the input has the same number of digits.
+ size_t index = std::size(digits);
+ for (auto digit : digits) {
+ auto d = bigInt->digit(--index);
+ if (d < digit) {
+ return true;
+ }
+ if (d > digit) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static constexpr auto NanosecondsMaxInstant() {
+ static_assert(BigInt::DigitBits == 64 || BigInt::DigitBits == 32);
+
+ // ยฑ8.64 ร— 10^21 is the nanoseconds from epoch limit.
+ // 8.64 ร— 10^21 is 86_40000_00000_00000_00000 or 0x1d4_60162f51_6f000000.
+ // Return the BigInt digits of that number for fast BigInt comparisons.
+ if constexpr (BigInt::DigitBits == 64) {
+ return std::array{
+ BigInt::Digit(0x1d4),
+ BigInt::Digit(0x6016'2f51'6f00'0000),
+ };
+ } else {
+ return std::array{
+ BigInt::Digit(0x1d4),
+ BigInt::Digit(0x6016'2f51),
+ BigInt::Digit(0x6f00'0000),
+ };
+ }
+}
+
+/**
+ * IsValidEpochNanoseconds ( epochNanoseconds )
+ */
+bool js::temporal::IsValidEpochNanoseconds(const BigInt* epochNanoseconds) {
+ // Steps 1-3.
+ static constexpr auto epochLimit = NanosecondsMaxInstant();
+ return AbsoluteValueIsLessOrEqual<epochLimit>(epochNanoseconds);
+}
+
+static bool IsValidEpochMicroseconds(const BigInt* epochMicroseconds) {
+ int64_t i;
+ if (!BigInt::isInt64(epochMicroseconds, &i)) {
+ return false;
+ }
+
+ constexpr int64_t MicrosecondsMaxInstant = Instant::max().toMicroseconds();
+ return -MicrosecondsMaxInstant <= i && i <= MicrosecondsMaxInstant;
+}
+
+static bool IsValidEpochMilliseconds(double epochMilliseconds) {
+ MOZ_ASSERT(IsInteger(epochMilliseconds));
+
+ constexpr int64_t MillisecondsMaxInstant = Instant::max().toMilliseconds();
+ return std::abs(epochMilliseconds) <= double(MillisecondsMaxInstant);
+}
+
+static bool IsValidEpochSeconds(double epochSeconds) {
+ MOZ_ASSERT(IsInteger(epochSeconds));
+
+ constexpr int64_t SecondsMaxInstant = Instant::max().toSeconds();
+ return std::abs(epochSeconds) <= double(SecondsMaxInstant);
+}
+
+/**
+ * IsValidEpochNanoseconds ( epochNanoseconds )
+ */
+bool js::temporal::IsValidEpochInstant(const Instant& instant) {
+ MOZ_ASSERT(0 <= instant.nanoseconds && instant.nanoseconds <= 999'999'999);
+
+ // Steps 1-3.
+ return Instant::min() <= instant && instant <= Instant::max();
+}
+
+static constexpr auto NanosecondsMaxInstantSpan() {
+ static_assert(BigInt::DigitBits == 64 || BigInt::DigitBits == 32);
+
+ // ยฑ8.64 ร— 10^21 is the nanoseconds from epoch limit.
+ // 2 ร— 8.64 ร— 10^21 is 172_80000_00000_00000_00000 or 0x3a8_c02c5ea2_de000000.
+ // Return the BigInt digits of that number for fast BigInt comparisons.
+ if constexpr (BigInt::DigitBits == 64) {
+ return std::array{
+ BigInt::Digit(0x3a8),
+ BigInt::Digit(0xc02c'5ea2'de00'0000),
+ };
+ } else {
+ return std::array{
+ BigInt::Digit(0x3a8),
+ BigInt::Digit(0xc02c'5ea2),
+ BigInt::Digit(0xde00'0000),
+ };
+ }
+}
+
+/**
+ * Validates a nanoseconds amount is at most as large as the difference
+ * between two valid nanoseconds from the epoch instants.
+ *
+ * Useful when we want to ensure a BigInt doesn't exceed a certain limit.
+ */
+bool js::temporal::IsValidInstantSpan(const BigInt* nanoseconds) {
+ static constexpr auto spanLimit = NanosecondsMaxInstantSpan();
+ return AbsoluteValueIsLessOrEqual<spanLimit>(nanoseconds);
+}
+
+bool js::temporal::IsValidInstantSpan(const InstantSpan& span) {
+ MOZ_ASSERT(0 <= span.nanoseconds && span.nanoseconds <= 999'999'999);
+
+ // Steps 1-3.
+ return InstantSpan::min() <= span && span <= InstantSpan::max();
+}
+
+/**
+ * Return the BigInt as a 96-bit integer. The BigInt digits must not consist of
+ * more than 96-bits.
+ */
+static Int96 ToInt96(const BigInt* ns) {
+ static_assert(BigInt::DigitBits == 64 || BigInt::DigitBits == 32);
+
+ auto digits = ns->digits();
+ if constexpr (BigInt::DigitBits == 64) {
+ BigInt::Digit x = 0, y = 0;
+ switch (digits.size()) {
+ case 2:
+ y = digits[1];
+ [[fallthrough]];
+ case 1:
+ x = digits[0];
+ [[fallthrough]];
+ case 0:
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected digit length");
+ }
+ return Int96{
+ Int96::Digits{Int96::Digit(x), Int96::Digit(x >> 32), Int96::Digit(y)},
+ ns->isNegative()};
+ } else {
+ BigInt::Digit x = 0, y = 0, z = 0;
+ switch (digits.size()) {
+ case 3:
+ z = digits[2];
+ [[fallthrough]];
+ case 2:
+ y = digits[1];
+ [[fallthrough]];
+ case 1:
+ x = digits[0];
+ [[fallthrough]];
+ case 0:
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected digit length");
+ }
+ return Int96{
+ Int96::Digits{Int96::Digit(x), Int96::Digit(y), Int96::Digit(z)},
+ ns->isNegative()};
+ }
+}
+
+Instant js::temporal::ToInstant(const BigInt* epochNanoseconds) {
+ MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds));
+
+ auto [seconds, nanos] =
+ ToInt96(epochNanoseconds) / ToNanoseconds(TemporalUnit::Second);
+ return {seconds, nanos};
+}
+
+InstantSpan js::temporal::ToInstantSpan(const BigInt* nanoseconds) {
+ MOZ_ASSERT(IsValidInstantSpan(nanoseconds));
+
+ auto [seconds, nanos] =
+ ToInt96(nanoseconds) / ToNanoseconds(TemporalUnit::Second);
+ return {seconds, nanos};
+}
+
+static BigInt* CreateBigInt(JSContext* cx,
+ const std::array<uint32_t, 3>& digits,
+ bool negative) {
+ static_assert(BigInt::DigitBits == 64 || BigInt::DigitBits == 32);
+
+ if constexpr (BigInt::DigitBits == 64) {
+ uint64_t x = (uint64_t(digits[1]) << 32) | digits[0];
+ uint64_t y = digits[2];
+
+ size_t length = y ? 2 : x ? 1 : 0;
+ auto* result = BigInt::createUninitialized(cx, length, negative);
+ if (!result) {
+ return nullptr;
+ }
+ if (y) {
+ result->setDigit(1, y);
+ }
+ if (x) {
+ result->setDigit(0, x);
+ }
+ return result;
+ } else {
+ size_t length = digits[2] ? 3 : digits[1] ? 2 : digits[0] ? 1 : 0;
+ auto* result = BigInt::createUninitialized(cx, length, negative);
+ if (!result) {
+ return nullptr;
+ }
+ while (length--) {
+ result->setDigit(length, digits[length]);
+ }
+ return result;
+ }
+}
+
+static BigInt* ToEpochBigInt(JSContext* cx, const InstantSpan& instant) {
+ MOZ_ASSERT(IsValidInstantSpan(instant));
+
+ // Multiplies two uint32_t values and returns the lower 32-bits. The higher
+ // 32-bits are stored in |high|.
+ auto digitMul = [](uint32_t a, uint32_t b, uint32_t* high) {
+ uint64_t result = static_cast<uint64_t>(a) * static_cast<uint64_t>(b);
+ *high = result >> 32;
+ return static_cast<uint32_t>(result);
+ };
+
+ // Adds two uint32_t values and returns the result. Overflow is added to the
+ // out-param |carry|.
+ auto digitAdd = [](uint32_t a, uint32_t b, uint32_t* carry) {
+ uint32_t result = a + b;
+ *carry += static_cast<uint32_t>(result < a);
+ return result;
+ };
+
+ constexpr uint32_t secToNanos = ToNanoseconds(TemporalUnit::Second);
+
+ uint64_t seconds = std::abs(instant.seconds);
+ uint32_t nanoseconds = instant.nanoseconds;
+
+ // Negative nanoseconds are represented as the difference to 1'000'000'000.
+ // Convert these back to their absolute value and adjust the seconds part
+ // accordingly.
+ //
+ // For example the nanoseconds from the epoch value |-1n| is represented as
+ // the instant {seconds: -1, nanoseconds: 999'999'999}.
+ if (instant.seconds < 0 && nanoseconds != 0) {
+ nanoseconds = secToNanos - nanoseconds;
+ seconds -= 1;
+ }
+
+ // uint32_t digits stored in the same order as BigInt digits, i.e. the least
+ // significant digit is stored at index zero.
+ std::array<uint32_t, 2> multiplicand = {uint32_t(seconds),
+ uint32_t(seconds >> 32)};
+ std::array<uint32_t, 3> accumulator = {nanoseconds, 0, 0};
+
+ // This code follows the implementation of |BigInt::multiplyAccumulate()|.
+
+ uint32_t carry = 0;
+ {
+ uint32_t high = 0;
+ uint32_t low = digitMul(secToNanos, multiplicand[0], &high);
+
+ uint32_t newCarry = 0;
+ accumulator[0] = digitAdd(accumulator[0], low, &newCarry);
+ accumulator[1] = digitAdd(high, newCarry, &carry);
+ }
+ {
+ uint32_t high = 0;
+ uint32_t low = digitMul(secToNanos, multiplicand[1], &high);
+
+ uint32_t newCarry = 0;
+ accumulator[1] = digitAdd(accumulator[1], low, &carry);
+ accumulator[2] = digitAdd(high, carry, &newCarry);
+ MOZ_ASSERT(newCarry == 0);
+ }
+
+ return CreateBigInt(cx, accumulator, instant.seconds < 0);
+}
+
+BigInt* js::temporal::ToEpochNanoseconds(JSContext* cx,
+ const Instant& instant) {
+ MOZ_ASSERT(IsValidEpochInstant(instant));
+ return ::ToEpochBigInt(cx, InstantSpan{instant.seconds, instant.nanoseconds});
+}
+
+BigInt* js::temporal::ToEpochNanoseconds(JSContext* cx,
+ const InstantSpan& instant) {
+ MOZ_ASSERT(IsValidInstantSpan(instant));
+ return ::ToEpochBigInt(cx, instant);
+}
+
+/**
+ * Return an Instant for the input nanoseconds if the input is less-or-equal to
+ * the maximum instant span. Otherwise returns nothing.
+ */
+static mozilla::Maybe<InstantSpan> NanosecondsToInstantSpan(
+ double nanoseconds) {
+ MOZ_ASSERT(IsInteger(nanoseconds));
+
+ if (auto int96 = Int96::fromInteger(nanoseconds)) {
+ constexpr auto maximum = Int96{InstantSpan::max().toSeconds()} *
+ ToNanoseconds(TemporalUnit::Second);
+
+ // Accept if the value is less-or-equal to the maximum instant span.
+ if (int96->abs() <= maximum) {
+ // Split into seconds and nanoseconds.
+ auto [seconds, nanos] = *int96 / ToNanoseconds(TemporalUnit::Second);
+
+ auto result = InstantSpan{seconds, nanos};
+ MOZ_ASSERT(IsValidInstantSpan(result));
+ return mozilla::Some(result);
+ }
+ }
+ return mozilla::Nothing();
+}
+
+/**
+ * Return an Instant for the input microseconds if the input is less-or-equal to
+ * the maximum instant span. Otherwise returns nothing.
+ */
+static mozilla::Maybe<InstantSpan> MicrosecondsToInstantSpan(
+ double microseconds) {
+ MOZ_ASSERT(IsInteger(microseconds));
+
+ constexpr int64_t spanLimit = InstantSpan::max().toSeconds();
+ constexpr int64_t secToMicros = ToNanoseconds(TemporalUnit::Second) /
+ ToNanoseconds(TemporalUnit::Microsecond);
+ constexpr int32_t microToNanos = ToNanoseconds(TemporalUnit::Microsecond);
+
+ // Fast path for the common case.
+ if (microseconds == 0) {
+ return mozilla::Some(InstantSpan{});
+ }
+
+ // Reject if the value is larger than the maximum instant span.
+ if (std::abs(microseconds) > double(spanLimit) * double(secToMicros)) {
+ return mozilla::Nothing();
+ }
+
+ // |spanLimit| in microseconds is below UINT64_MAX, so we can use uint64 in
+ // the following computations.
+ static_assert(double(spanLimit) * double(secToMicros) <= double(UINT64_MAX));
+
+ // Use the absolute value and convert it then into uint64_t.
+ uint64_t absMicros = uint64_t(std::abs(microseconds));
+
+ // Seconds and remainder are small enough to fit into int64_t resp. int32_t.
+ int64_t seconds = absMicros / uint64_t(secToMicros);
+ int32_t remainder = absMicros % uint64_t(secToMicros);
+
+ // Correct the sign of |seconds| and |remainder|, and then constrain
+ // |remainder| to the range [0, 999'999].
+ if (microseconds < 0) {
+ seconds *= -1;
+ if (remainder != 0) {
+ seconds -= 1;
+ remainder = secToMicros - remainder;
+ }
+ }
+
+ InstantSpan result = {seconds, remainder * microToNanos};
+ MOZ_ASSERT(IsValidInstantSpan(result));
+ return mozilla::Some(result);
+}
+
+/**
+ * GetUTCEpochNanoseconds ( year, month, day, hour, minute, second, millisecond,
+ * microsecond, nanosecond [ , offsetNanoseconds ] )
+ */
+Instant js::temporal::GetUTCEpochNanoseconds(const PlainDateTime& dateTime) {
+ auto& [date, time] = dateTime;
+
+ // Step 1.
+ MOZ_ASSERT(IsValidISODateTime(dateTime));
+
+ // Additionally ensure the date-time value can be represented as an Instant.
+ MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
+
+ // Steps 2-5.
+ int64_t ms = MakeDate(dateTime);
+
+ // Propagate the input range to the compiler.
+ int32_t nanos =
+ std::clamp(time.microsecond * 1'000 + time.nanosecond, 0, 999'999);
+
+ // Steps 6-8.
+ return Instant::fromMilliseconds(ms) + InstantSpan{0, nanos};
+}
+
+/**
+ * GetUTCEpochNanoseconds ( year, month, day, hour, minute, second, millisecond,
+ * microsecond, nanosecond [ , offsetNanoseconds ] )
+ */
+Instant js::temporal::GetUTCEpochNanoseconds(
+ const PlainDateTime& dateTime, const InstantSpan& offsetNanoseconds) {
+ MOZ_ASSERT(offsetNanoseconds.abs() <
+ InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day)));
+
+ // Steps 1-6.
+ auto epochNanoseconds = GetUTCEpochNanoseconds(dateTime);
+
+ // Steps 7-9.
+ return epochNanoseconds - offsetNanoseconds;
+}
+
+/**
+ * CompareEpochNanoseconds ( epochNanosecondsOne, epochNanosecondsTwo )
+ */
+static int32_t CompareEpochNanoseconds(const Instant& epochNanosecondsOne,
+ const Instant& epochNanosecondsTwo) {
+ // Step 1.
+ if (epochNanosecondsOne > epochNanosecondsTwo) {
+ return 1;
+ }
+
+ // Step 2.
+ if (epochNanosecondsOne < epochNanosecondsTwo) {
+ return -1;
+ }
+
+ // Step 3.
+ return 0;
+}
+
+/**
+ * CreateTemporalInstant ( epochNanoseconds [ , newTarget ] )
+ */
+InstantObject* js::temporal::CreateTemporalInstant(JSContext* cx,
+ const Instant& instant) {
+ // Step 1.
+ MOZ_ASSERT(IsValidEpochInstant(instant));
+
+ // Steps 2-3.
+ auto* object = NewBuiltinClassInstance<InstantObject>(cx);
+ if (!object) {
+ return nullptr;
+ }
+
+ // Step 4.
+ object->setFixedSlot(InstantObject::SECONDS_SLOT,
+ NumberValue(instant.seconds));
+ object->setFixedSlot(InstantObject::NANOSECONDS_SLOT,
+ Int32Value(instant.nanoseconds));
+
+ // Step 5.
+ return object;
+}
+
+/**
+ * CreateTemporalInstant ( epochNanoseconds [ , newTarget ] )
+ */
+static InstantObject* CreateTemporalInstant(JSContext* cx, const CallArgs& args,
+ Handle<BigInt*> epochNanoseconds) {
+ // Step 1.
+ MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds));
+
+ // Steps 2-3.
+ Rooted<JSObject*> proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_Instant, &proto)) {
+ return nullptr;
+ }
+
+ auto* object = NewObjectWithClassProto<InstantObject>(cx, proto);
+ if (!object) {
+ return nullptr;
+ }
+
+ // Step 4.
+ auto instant = ToInstant(epochNanoseconds);
+ object->setFixedSlot(InstantObject::SECONDS_SLOT,
+ NumberValue(instant.seconds));
+ object->setFixedSlot(InstantObject::NANOSECONDS_SLOT,
+ Int32Value(instant.nanoseconds));
+
+ // Step 5.
+ return object;
+}
+
+/**
+ * ToTemporalInstant ( item )
+ */
+Wrapped<InstantObject*> js::temporal::ToTemporalInstant(JSContext* cx,
+ Handle<Value> item) {
+ // Step 1.
+ if (item.isObject()) {
+ JSObject* itemObj = &item.toObject();
+
+ // Step 1.a.
+ if (itemObj->canUnwrapAs<InstantObject>()) {
+ return itemObj;
+ }
+ }
+
+ // Steps 1.b-d and 3-6
+ Instant epochNanoseconds;
+ if (!ToTemporalInstant(cx, item, &epochNanoseconds)) {
+ return nullptr;
+ }
+
+ // Step 7.
+ return CreateTemporalInstant(cx, epochNanoseconds);
+}
+
+/**
+ * ToTemporalInstant ( item )
+ */
+bool js::temporal::ToTemporalInstant(JSContext* cx, Handle<Value> item,
+ Instant* result) {
+ // Step 1.
+ Rooted<Value> primitiveValue(cx, item);
+ if (item.isObject()) {
+ JSObject* itemObj = &item.toObject();
+
+ // Step 1.a.
+ if (auto* instant = itemObj->maybeUnwrapIf<InstantObject>()) {
+ *result = ToInstant(instant);
+ return true;
+ }
+
+ // Step 1.b.
+ if (auto* zonedDateTime = itemObj->maybeUnwrapIf<ZonedDateTimeObject>()) {
+ *result = ToInstant(zonedDateTime);
+ return true;
+ }
+
+ // Steps 1.c-d.
+ if (!ToPrimitive(cx, JSTYPE_STRING, &primitiveValue)) {
+ return false;
+ }
+ }
+
+ // Step 2.
+ if (!primitiveValue.isString()) {
+ // The value is always on the stack, so JSDVG_SEARCH_STACK can be used for
+ // better error reporting.
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK,
+ primitiveValue, nullptr, "not a string");
+ return false;
+ }
+ Rooted<JSString*> string(cx, primitiveValue.toString());
+
+ // Steps 3-4.
+ PlainDateTime dateTime;
+ int64_t offset;
+ if (!ParseTemporalInstantString(cx, string, &dateTime, &offset)) {
+ return false;
+ }
+ MOZ_ASSERT(std::abs(offset) < ToNanoseconds(TemporalUnit::Day));
+
+ // Step 6. (Reordered)
+ if (!ISODateTimeWithinLimits(dateTime)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_INVALID);
+ return false;
+ }
+
+ // Step 5.
+ auto epochNanoseconds =
+ GetUTCEpochNanoseconds(dateTime, InstantSpan::fromNanoseconds(offset));
+
+ // Step 6.
+ if (!IsValidEpochInstant(epochNanoseconds)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_INVALID);
+ return false;
+ }
+
+ // Step 7.
+ *result = epochNanoseconds;
+ return true;
+}
+
+/**
+ * AddInstant ( epochNanoseconds, hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds )
+ */
+bool js::temporal::AddInstant(JSContext* cx, const Instant& instant,
+ const Duration& duration, Instant* result) {
+ MOZ_ASSERT(IsValidEpochInstant(instant));
+ MOZ_ASSERT(IsValidDuration(duration));
+ MOZ_ASSERT(duration.years == 0);
+ MOZ_ASSERT(duration.months == 0);
+ MOZ_ASSERT(duration.weeks == 0);
+ MOZ_ASSERT(duration.days == 0);
+
+ do {
+ auto nanoseconds = NanosecondsToInstantSpan(duration.nanoseconds);
+ if (!nanoseconds) {
+ break;
+ }
+ MOZ_ASSERT(IsValidInstantSpan(*nanoseconds));
+
+ auto microseconds = MicrosecondsToInstantSpan(duration.microseconds);
+ if (!microseconds) {
+ break;
+ }
+ MOZ_ASSERT(IsValidInstantSpan(*microseconds));
+
+ // Overflows for millis/seconds/minutes/hours always result in an invalid
+ // instant.
+
+ int64_t milliseconds;
+ if (!mozilla::NumberEqualsInt64(duration.milliseconds, &milliseconds)) {
+ break;
+ }
+
+ int64_t seconds;
+ if (!mozilla::NumberEqualsInt64(duration.seconds, &seconds)) {
+ break;
+ }
+
+ int64_t minutes;
+ if (!mozilla::NumberEqualsInt64(duration.minutes, &minutes)) {
+ break;
+ }
+
+ int64_t hours;
+ if (!mozilla::NumberEqualsInt64(duration.hours, &hours)) {
+ break;
+ }
+
+ // Compute the overall amount of milliseconds to add.
+ mozilla::CheckedInt64 millis = hours;
+ millis *= 60;
+ millis += minutes;
+ millis *= 60;
+ millis += seconds;
+ millis *= 1000;
+ millis += milliseconds;
+ if (!millis.isValid()) {
+ break;
+ }
+
+ auto milli = InstantSpan::fromMilliseconds(millis.value());
+ if (!IsValidInstantSpan(milli)) {
+ break;
+ }
+
+ // Compute the overall instant span.
+ auto span = milli + *microseconds + *nanoseconds;
+ if (!IsValidInstantSpan(span)) {
+ break;
+ }
+
+ *result = instant + span;
+ if (IsValidEpochInstant(*result)) {
+ return true;
+ }
+ } while (false);
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_INVALID);
+ return false;
+}
+
+/**
+ * DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, largestUnit,
+ * roundingMode )
+ */
+bool js::temporal::DifferenceInstant(JSContext* cx, const Instant& ns1,
+ const Instant& ns2,
+ Increment roundingIncrement,
+ TemporalUnit smallestUnit,
+ TemporalUnit largestUnit,
+ TemporalRoundingMode roundingMode,
+ Duration* result) {
+ MOZ_ASSERT(IsValidEpochInstant(ns1));
+ MOZ_ASSERT(IsValidEpochInstant(ns2));
+ MOZ_ASSERT(largestUnit > TemporalUnit::Day);
+ MOZ_ASSERT(largestUnit <= smallestUnit);
+ MOZ_ASSERT(roundingIncrement <=
+ MaximumTemporalDurationRoundingIncrement(smallestUnit));
+
+ // Step 1.
+ auto diff = ns2 - ns1;
+ MOZ_ASSERT(IsValidInstantSpan(diff));
+
+ // Negative nanoseconds are represented as the difference to 1'000'000'000.
+ auto [seconds, nanoseconds] = diff;
+ if (seconds < 0 && nanoseconds != 0) {
+ seconds += 1;
+ nanoseconds -= ToNanoseconds(TemporalUnit::Second);
+ }
+
+ // Steps 2-5.
+ Duration duration = {
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ double(seconds),
+ double((nanoseconds / 1000'000) % 1000),
+ double((nanoseconds / 1000) % 1000),
+ double(nanoseconds % 1000),
+ };
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ // Step 6.
+ if (smallestUnit == TemporalUnit::Nanosecond &&
+ roundingIncrement == Increment{1}) {
+ TimeDuration balanced;
+ if (!BalanceTimeDuration(cx, duration, largestUnit, &balanced)) {
+ return false;
+ }
+ MOZ_ASSERT(balanced.days == 0);
+
+ *result = balanced.toDuration().time();
+ return true;
+ }
+
+ // Steps 7-8.
+ Duration roundResult;
+ if (!temporal::RoundDuration(cx, duration, roundingIncrement, smallestUnit,
+ roundingMode, &roundResult)) {
+ return false;
+ }
+
+ // Step 9.
+ MOZ_ASSERT(roundResult.days == 0);
+
+ // Step 10.
+ TimeDuration balanced;
+ if (!BalanceTimeDuration(cx, roundResult, largestUnit, &balanced)) {
+ return false;
+ }
+ MOZ_ASSERT(balanced.days == 0);
+
+ *result = balanced.toDuration().time();
+ return true;
+}
+
+/**
+ * RoundNumberToIncrementAsIfPositive ( x, increment, roundingMode )
+ */
+static bool RoundNumberToIncrementAsIfPositive(
+ JSContext* cx, const Instant& x, int64_t increment,
+ TemporalRoundingMode roundingMode, Instant* result) {
+ // This operation is equivalent to adjusting the rounding mode through
+ // |ToPositiveRoundingMode| and then calling |RoundNumberToIncrement|.
+ return RoundNumberToIncrement(cx, x, increment,
+ ToPositiveRoundingMode(roundingMode), result);
+}
+
+/**
+ * RoundTemporalInstant ( ns, increment, unit, roundingMode )
+ */
+bool js::temporal::RoundTemporalInstant(JSContext* cx, const Instant& ns,
+ Increment increment, TemporalUnit unit,
+ TemporalRoundingMode roundingMode,
+ Instant* result) {
+ MOZ_ASSERT(IsValidEpochInstant(ns));
+ MOZ_ASSERT(increment >= Increment::min());
+ MOZ_ASSERT(uint64_t(increment.value()) <= ToNanoseconds(TemporalUnit::Day));
+ MOZ_ASSERT(unit > TemporalUnit::Day);
+
+ // Steps 1-6.
+ int64_t toNanoseconds = ToNanoseconds(unit);
+ MOZ_ASSERT(
+ (increment.value() * toNanoseconds) <= ToNanoseconds(TemporalUnit::Day),
+ "increment * toNanoseconds shouldn't overflow instant resolution");
+
+ // Step 7.
+ return RoundNumberToIncrementAsIfPositive(
+ cx, ns, increment.value() * toNanoseconds, roundingMode, result);
+}
+
+/**
+ * DifferenceTemporalInstant ( operation, instant, other, options )
+ */
+static bool DifferenceTemporalInstant(JSContext* cx,
+ TemporalDifference operation,
+ const CallArgs& args) {
+ auto instant = ToInstant(&args.thisv().toObject().as<InstantObject>());
+
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ Instant other;
+ if (!ToTemporalInstant(cx, args.get(0), &other)) {
+ return false;
+ }
+
+ // Steps 3-4.
+ DifferenceSettings settings;
+ if (args.hasDefined(1)) {
+ Rooted<JSObject*> options(
+ cx, RequireObjectArg(cx, "options", ToName(operation), args[1]));
+ if (!options) {
+ return false;
+ }
+
+ // Step 3.
+ Rooted<PlainObject*> resolvedOptions(cx,
+ SnapshotOwnProperties(cx, options));
+ if (!resolvedOptions) {
+ return false;
+ }
+
+ // Step 4.
+ if (!GetDifferenceSettings(
+ cx, operation, resolvedOptions, TemporalUnitGroup::Time,
+ TemporalUnit::Nanosecond, TemporalUnit::Second, &settings)) {
+ return false;
+ }
+ } else {
+ // Steps 3-4.
+ settings = {
+ TemporalUnit::Nanosecond,
+ TemporalUnit::Second,
+ TemporalRoundingMode::Trunc,
+ Increment{1},
+ };
+ }
+
+ // Step 5.
+ Duration difference;
+ if (!DifferenceInstant(cx, instant, other, settings.roundingIncrement,
+ settings.smallestUnit, settings.largestUnit,
+ settings.roundingMode, &difference)) {
+ return false;
+ }
+
+ // Step 6.
+ if (operation == TemporalDifference::Since) {
+ difference = difference.negate();
+ }
+
+ auto* obj = CreateTemporalDuration(cx, difference);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+enum class InstantDuration { Add, Subtract };
+
+/**
+ * AddDurationToOrSubtractDurationFromInstant ( operation, instant,
+ * temporalDurationLike )
+ */
+static bool AddDurationToOrSubtractDurationFromInstant(
+ JSContext* cx, InstantDuration operation, const CallArgs& args) {
+ auto* instant = &args.thisv().toObject().as<InstantObject>();
+ auto epochNanoseconds = ToInstant(instant);
+
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ Duration duration;
+ if (!ToTemporalDurationRecord(cx, args.get(0), &duration)) {
+ return false;
+ }
+
+ // Steps 3-6.
+ if (duration.years != 0 || duration.months != 0 || duration.weeks != 0 ||
+ duration.days != 0) {
+ const char* part = duration.years != 0 ? "years"
+ : duration.months != 0 ? "months"
+ : duration.weeks != 0 ? "weeks"
+ : "days";
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_BAD_DURATION, part);
+ return false;
+ }
+
+ // Step 7.
+ if (operation == InstantDuration::Subtract) {
+ duration = duration.negate();
+ }
+
+ Instant ns;
+ if (!AddInstant(cx, epochNanoseconds, duration, &ns)) {
+ return false;
+ }
+
+ // Step 8.
+ auto* result = CreateTemporalInstant(cx, ns);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Instant ( epochNanoseconds )
+ */
+static bool InstantConstructor(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ if (!ThrowIfNotConstructing(cx, args, "Temporal.Instant")) {
+ return false;
+ }
+
+ // Step 2.
+ Rooted<BigInt*> epochNanoseconds(cx, js::ToBigInt(cx, args.get(0)));
+ if (!epochNanoseconds) {
+ return false;
+ }
+
+ // Step 3.
+ if (!IsValidEpochNanoseconds(epochNanoseconds)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_INVALID);
+ return false;
+ }
+
+ // Step 4.
+ auto* result = CreateTemporalInstant(cx, args, epochNanoseconds);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Instant.from ( item )
+ */
+static bool Instant_from(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Steps 1-2.
+ Instant epochInstant;
+ if (!ToTemporalInstant(cx, args.get(0), &epochInstant)) {
+ return false;
+ }
+
+ auto* result = CreateTemporalInstant(cx, epochInstant);
+ if (!result) {
+ return false;
+ }
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Instant.fromEpochSeconds ( epochSeconds )
+ */
+static bool Instant_fromEpochSeconds(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ double epochSeconds;
+ if (!JS::ToNumber(cx, args.get(0), &epochSeconds)) {
+ return false;
+ }
+
+ // Step 2.
+ //
+ // NumberToBigInt throws a RangeError for non-integral numbers.
+ if (!IsInteger(epochSeconds)) {
+ ToCStringBuf cbuf;
+ const char* str = NumberToCString(&cbuf, epochSeconds);
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_NONINTEGER, str);
+ return false;
+ }
+
+ // Step 3. (Not applicable)
+
+ // Step 4.
+ if (!IsValidEpochSeconds(epochSeconds)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_INVALID);
+ return false;
+ }
+
+ // Step 5.
+ auto* result = CreateTemporalInstant(cx, Instant::fromSeconds(epochSeconds));
+ if (!result) {
+ return false;
+ }
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Instant.fromEpochMilliseconds ( epochMilliseconds )
+ */
+static bool Instant_fromEpochMilliseconds(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ double epochMilliseconds;
+ if (!JS::ToNumber(cx, args.get(0), &epochMilliseconds)) {
+ return false;
+ }
+
+ // Step 2.
+ //
+ // NumberToBigInt throws a RangeError for non-integral numbers.
+ if (!IsInteger(epochMilliseconds)) {
+ ToCStringBuf cbuf;
+ const char* str = NumberToCString(&cbuf, epochMilliseconds);
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_NONINTEGER, str);
+ return false;
+ }
+
+ // Step 3. (Not applicable)
+
+ // Step 4.
+ if (!IsValidEpochMilliseconds(epochMilliseconds)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_INVALID);
+ return false;
+ }
+
+ // Step 5.
+ auto* result =
+ CreateTemporalInstant(cx, Instant::fromMilliseconds(epochMilliseconds));
+ if (!result) {
+ return false;
+ }
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Instant.fromEpochMicroseconds ( epochMicroseconds )
+ */
+static bool Instant_fromEpochMicroseconds(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ Rooted<BigInt*> epochMicroseconds(cx, js::ToBigInt(cx, args.get(0)));
+ if (!epochMicroseconds) {
+ return false;
+ }
+
+ // Step 2. (Not applicable)
+
+ // Step 3.
+ if (!IsValidEpochMicroseconds(epochMicroseconds)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_INVALID);
+ return false;
+ }
+
+ int64_t i;
+ MOZ_ALWAYS_TRUE(BigInt::isInt64(epochMicroseconds, &i));
+
+ // Step 4.
+ auto* result = CreateTemporalInstant(cx, Instant::fromMicroseconds(i));
+ if (!result) {
+ return false;
+ }
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Instant.fromEpochNanoseconds ( epochNanoseconds )
+ */
+static bool Instant_fromEpochNanoseconds(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ Rooted<BigInt*> epochNanoseconds(cx, js::ToBigInt(cx, args.get(0)));
+ if (!epochNanoseconds) {
+ return false;
+ }
+
+ // Step 2.
+ if (!IsValidEpochNanoseconds(epochNanoseconds)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_INVALID);
+ return false;
+ }
+
+ // Step 3.
+ auto* result = CreateTemporalInstant(cx, ToInstant(epochNanoseconds));
+ if (!result) {
+ return false;
+ }
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Instant.compare ( one, two )
+ */
+static bool Instant_compare(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ Instant one;
+ if (!ToTemporalInstant(cx, args.get(0), &one)) {
+ return false;
+ }
+
+ // Step 2.
+ Instant two;
+ if (!ToTemporalInstant(cx, args.get(1), &two)) {
+ return false;
+ }
+
+ // Step 3.
+ args.rval().setInt32(CompareEpochNanoseconds(one, two));
+ return true;
+}
+
+/**
+ * get Temporal.Instant.prototype.epochSeconds
+ */
+static bool Instant_epochSeconds(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto instant = ToInstant(&args.thisv().toObject().as<InstantObject>());
+
+ // Steps 4-5.
+ args.rval().setNumber(instant.seconds);
+ return true;
+}
+
+/**
+ * get Temporal.Instant.prototype.epochSeconds
+ */
+static bool Instant_epochSeconds(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsInstant, Instant_epochSeconds>(cx, args);
+}
+
+/**
+ * get Temporal.Instant.prototype.epochMilliseconds
+ */
+static bool Instant_epochMilliseconds(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto instant = ToInstant(&args.thisv().toObject().as<InstantObject>());
+
+ // Step 4-5.
+ args.rval().setNumber(instant.floorToMilliseconds());
+ return true;
+}
+
+/**
+ * get Temporal.Instant.prototype.epochMilliseconds
+ */
+static bool Instant_epochMilliseconds(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsInstant, Instant_epochMilliseconds>(cx, args);
+}
+
+/**
+ * get Temporal.Instant.prototype.epochMicroseconds
+ */
+static bool Instant_epochMicroseconds(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto instant = ToInstant(&args.thisv().toObject().as<InstantObject>());
+
+ // Step 4.
+ auto* microseconds =
+ BigInt::createFromInt64(cx, instant.floorToMicroseconds());
+ if (!microseconds) {
+ return false;
+ }
+
+ // Step 5.
+ args.rval().setBigInt(microseconds);
+ return true;
+}
+
+/**
+ * get Temporal.Instant.prototype.epochMicroseconds
+ */
+static bool Instant_epochMicroseconds(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsInstant, Instant_epochMicroseconds>(cx, args);
+}
+
+/**
+ * get Temporal.Instant.prototype.epochNanoseconds
+ */
+static bool Instant_epochNanoseconds(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto instant = ToInstant(&args.thisv().toObject().as<InstantObject>());
+ auto* nanoseconds = ToEpochNanoseconds(cx, instant);
+ if (!nanoseconds) {
+ return false;
+ }
+
+ // Step 4.
+ args.rval().setBigInt(nanoseconds);
+ return true;
+}
+
+/**
+ * get Temporal.Instant.prototype.epochNanoseconds
+ */
+static bool Instant_epochNanoseconds(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsInstant, Instant_epochNanoseconds>(cx, args);
+}
+
+/**
+ * Temporal.Instant.prototype.add ( temporalDurationLike )
+ */
+static bool Instant_add(JSContext* cx, const CallArgs& args) {
+ return AddDurationToOrSubtractDurationFromInstant(cx, InstantDuration::Add,
+ args);
+}
+
+/**
+ * Temporal.Instant.prototype.add ( temporalDurationLike )
+ */
+static bool Instant_add(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsInstant, Instant_add>(cx, args);
+}
+
+/**
+ * Temporal.Instant.prototype.subtract ( temporalDurationLike )
+ */
+static bool Instant_subtract(JSContext* cx, const CallArgs& args) {
+ return AddDurationToOrSubtractDurationFromInstant(
+ cx, InstantDuration::Subtract, args);
+}
+
+/**
+ * Temporal.Instant.prototype.subtract ( temporalDurationLike )
+ */
+static bool Instant_subtract(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsInstant, Instant_subtract>(cx, args);
+}
+
+/**
+ * Temporal.Instant.prototype.until ( other [ , options ] )
+ */
+static bool Instant_until(JSContext* cx, const CallArgs& args) {
+ return DifferenceTemporalInstant(cx, TemporalDifference::Until, args);
+}
+
+/**
+ * Temporal.Instant.prototype.until ( other [ , options ] )
+ */
+static bool Instant_until(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsInstant, Instant_until>(cx, args);
+}
+
+/**
+ * Temporal.Instant.prototype.since ( other [ , options ] )
+ */
+static bool Instant_since(JSContext* cx, const CallArgs& args) {
+ return DifferenceTemporalInstant(cx, TemporalDifference::Since, args);
+}
+
+/**
+ * Temporal.Instant.prototype.since ( other [ , options ] )
+ */
+static bool Instant_since(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsInstant, Instant_since>(cx, args);
+}
+
+/**
+ * Temporal.Instant.prototype.round ( roundTo )
+ */
+static bool Instant_round(JSContext* cx, const CallArgs& args) {
+ auto instant = ToInstant(&args.thisv().toObject().as<InstantObject>());
+
+ // Steps 3-16.
+ auto smallestUnit = TemporalUnit::Auto;
+ auto roundingMode = TemporalRoundingMode::HalfExpand;
+ auto roundingIncrement = Increment{1};
+ if (args.get(0).isString()) {
+ // Steps 4 and 6-8. (Not applicable in our implementation.)
+
+ // Step 9.
+ Rooted<JSString*> paramString(cx, args[0].toString());
+ if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::SmallestUnit,
+ TemporalUnitGroup::Time, &smallestUnit)) {
+ return false;
+ }
+
+ // Steps 10-16. (Not applicable in our implementation.)
+ } else {
+ // Steps 3 and 5.
+ Rooted<JSObject*> options(
+ cx, RequireObjectArg(cx, "roundTo", "round", args.get(0)));
+ if (!options) {
+ return false;
+ }
+
+ // Steps 6-7.
+ if (!ToTemporalRoundingIncrement(cx, options, &roundingIncrement)) {
+ return false;
+ }
+
+ // Step 8.
+ if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
+ return false;
+ }
+
+ // Step 9.
+ if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
+ TemporalUnitGroup::Time, &smallestUnit)) {
+ return false;
+ }
+ if (smallestUnit == TemporalUnit::Auto) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_MISSING_OPTION, "smallestUnit");
+ return false;
+ }
+
+ // Steps 10-15.
+ uint64_t maximum = UnitsPerDay(smallestUnit);
+
+ // Step 16.
+ if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
+ true)) {
+ return false;
+ }
+ }
+
+ // Step 17.
+ Instant roundedNs;
+ if (!RoundTemporalInstant(cx, instant, roundingIncrement, smallestUnit,
+ roundingMode, &roundedNs)) {
+ return false;
+ }
+
+ // Step 18.
+ auto* result = CreateTemporalInstant(cx, roundedNs);
+ if (!result) {
+ return false;
+ }
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Instant.prototype.round ( options )
+ */
+static bool Instant_round(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsInstant, Instant_round>(cx, args);
+}
+
+/**
+ * Temporal.Instant.prototype.equals ( other )
+ */
+static bool Instant_equals(JSContext* cx, const CallArgs& args) {
+ auto instant = ToInstant(&args.thisv().toObject().as<InstantObject>());
+
+ // Step 3.
+ Instant other;
+ if (!ToTemporalInstant(cx, args.get(0), &other)) {
+ return false;
+ }
+
+ // Steps 4-5.
+ args.rval().setBoolean(instant == other);
+ return true;
+}
+
+/**
+ * Temporal.Instant.prototype.equals ( other )
+ */
+static bool Instant_equals(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsInstant, Instant_equals>(cx, args);
+}
+
+/**
+ * Temporal.Instant.prototype.toString ( [ options ] )
+ */
+static bool Instant_toString(JSContext* cx, const CallArgs& args) {
+ auto instant = ToInstant(&args.thisv().toObject().as<InstantObject>());
+
+ Rooted<TimeZoneValue> timeZone(cx);
+ auto roundingMode = TemporalRoundingMode::Trunc;
+ SecondsStringPrecision precision = {Precision::Auto(),
+ TemporalUnit::Nanosecond, Increment{1}};
+ if (args.hasDefined(0)) {
+ // Step 3.
+ Rooted<JSObject*> options(
+ cx, RequireObjectArg(cx, "options", "toString", args[0]));
+ if (!options) {
+ return false;
+ }
+
+ // Steps 4-5.
+ auto digits = Precision::Auto();
+ if (!ToFractionalSecondDigits(cx, options, &digits)) {
+ return false;
+ }
+
+ // Step 6.
+ if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
+ return false;
+ }
+
+ // Step 7.
+ auto smallestUnit = TemporalUnit::Auto;
+ if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
+ TemporalUnitGroup::Time, &smallestUnit)) {
+ return false;
+ }
+
+ // Step 8.
+ if (smallestUnit == TemporalUnit::Hour) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_UNIT_OPTION, "hour",
+ "smallestUnit");
+ return false;
+ }
+
+ // Step 9.
+ Rooted<Value> value(cx);
+ if (!GetProperty(cx, options, options, cx->names().timeZone, &value)) {
+ return false;
+ }
+
+ // Step 10.
+ if (!value.isUndefined()) {
+ if (!ToTemporalTimeZone(cx, value, &timeZone)) {
+ return false;
+ }
+ }
+
+ // Step 11.
+ precision = ToSecondsStringPrecision(smallestUnit, digits);
+ }
+
+ // Step 12.
+ Instant ns;
+ if (!RoundTemporalInstant(cx, instant, precision.increment, precision.unit,
+ roundingMode, &ns)) {
+ return false;
+ }
+
+ // Step 13.
+ Rooted<InstantObject*> roundedInstant(cx, CreateTemporalInstant(cx, ns));
+ if (!roundedInstant) {
+ return false;
+ }
+
+ // Step 14.
+ JSString* str = TemporalInstantToString(cx, roundedInstant, timeZone,
+ precision.precision);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.Instant.prototype.toString ( [ options ] )
+ */
+static bool Instant_toString(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsInstant, Instant_toString>(cx, args);
+}
+
+/**
+ * Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] )
+ */
+static bool Instant_toLocaleString(JSContext* cx, const CallArgs& args) {
+ Rooted<InstantObject*> instant(cx,
+ &args.thisv().toObject().as<InstantObject>());
+
+ // Step 3.
+ Rooted<TimeZoneValue> timeZone(cx);
+ JSString* str =
+ TemporalInstantToString(cx, instant, timeZone, Precision::Auto());
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.Instant.prototype.toLocaleString ( [ locales [ , options ] ] )
+ */
+static bool Instant_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsInstant, Instant_toLocaleString>(cx, args);
+}
+
+/**
+ * Temporal.Instant.prototype.toJSON ( )
+ */
+static bool Instant_toJSON(JSContext* cx, const CallArgs& args) {
+ Rooted<InstantObject*> instant(cx,
+ &args.thisv().toObject().as<InstantObject>());
+
+ // Step 3.
+ Rooted<TimeZoneValue> timeZone(cx);
+ JSString* str =
+ TemporalInstantToString(cx, instant, timeZone, Precision::Auto());
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.Instant.prototype.toJSON ( )
+ */
+static bool Instant_toJSON(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsInstant, Instant_toJSON>(cx, args);
+}
+
+/**
+ * Temporal.Instant.prototype.valueOf ( )
+ */
+static bool Instant_valueOf(JSContext* cx, unsigned argc, Value* vp) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
+ "Instant", "primitive type");
+ return false;
+}
+
+/**
+ * Temporal.Instant.prototype.toZonedDateTime ( item )
+ */
+static bool Instant_toZonedDateTime(JSContext* cx, const CallArgs& args) {
+ auto instant = ToInstant(&args.thisv().toObject().as<InstantObject>());
+
+ // Step 3.
+ Rooted<JSObject*> item(
+ cx, RequireObjectArg(cx, "item", "toZonedDateTime", args.get(0)));
+ if (!item) {
+ return false;
+ }
+
+ // Step 4.
+ Rooted<Value> calendarLike(cx);
+ if (!GetProperty(cx, item, item, cx->names().calendar, &calendarLike)) {
+ return false;
+ }
+
+ // Step 5.
+ if (calendarLike.isUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_MISSING_PROPERTY, "calendar");
+ return false;
+ }
+
+ // Step 6.
+ Rooted<CalendarValue> calendar(cx);
+ if (!ToTemporalCalendar(cx, calendarLike, &calendar)) {
+ return false;
+ }
+
+ // Step 7.
+ Rooted<Value> timeZoneLike(cx);
+ if (!GetProperty(cx, item, item, cx->names().timeZone, &timeZoneLike)) {
+ return false;
+ }
+
+ // Step 8.
+ if (timeZoneLike.isUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_MISSING_PROPERTY, "timeZone");
+ return false;
+ }
+
+ // Step 9.
+ Rooted<TimeZoneValue> timeZone(cx);
+ if (!ToTemporalTimeZone(cx, timeZoneLike, &timeZone)) {
+ return false;
+ }
+
+ // Step 10.
+ auto* result = CreateTemporalZonedDateTime(cx, instant, timeZone, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Instant.prototype.toZonedDateTime ( item )
+ */
+static bool Instant_toZonedDateTime(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsInstant, Instant_toZonedDateTime>(cx, args);
+}
+
+/**
+ * Temporal.Instant.prototype.toZonedDateTimeISO ( item )
+ */
+static bool Instant_toZonedDateTimeISO(JSContext* cx, const CallArgs& args) {
+ auto instant = ToInstant(&args.thisv().toObject().as<InstantObject>());
+
+ // Step 3.
+ Rooted<TimeZoneValue> timeZone(cx);
+ if (!ToTemporalTimeZone(cx, args.get(0), &timeZone)) {
+ return false;
+ }
+
+ // Step 4.
+ Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
+ auto* result = CreateTemporalZonedDateTime(cx, instant, timeZone, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Instant.prototype.toZonedDateTimeISO ( item )
+ */
+static bool Instant_toZonedDateTimeISO(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsInstant, Instant_toZonedDateTimeISO>(cx, args);
+}
+
+const JSClass InstantObject::class_ = {
+ "Temporal.Instant",
+ JSCLASS_HAS_RESERVED_SLOTS(InstantObject::SLOT_COUNT) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_Instant),
+ JS_NULL_CLASS_OPS,
+ &InstantObject::classSpec_,
+};
+
+const JSClass& InstantObject::protoClass_ = PlainObject::class_;
+
+static const JSFunctionSpec Instant_methods[] = {
+ JS_FN("from", Instant_from, 1, 0),
+ JS_FN("fromEpochSeconds", Instant_fromEpochSeconds, 1, 0),
+ JS_FN("fromEpochMilliseconds", Instant_fromEpochMilliseconds, 1, 0),
+ JS_FN("fromEpochMicroseconds", Instant_fromEpochMicroseconds, 1, 0),
+ JS_FN("fromEpochNanoseconds", Instant_fromEpochNanoseconds, 1, 0),
+ JS_FN("compare", Instant_compare, 2, 0),
+ JS_FS_END,
+};
+
+static const JSFunctionSpec Instant_prototype_methods[] = {
+ JS_FN("add", Instant_add, 1, 0),
+ JS_FN("subtract", Instant_subtract, 1, 0),
+ JS_FN("until", Instant_until, 1, 0),
+ JS_FN("since", Instant_since, 1, 0),
+ JS_FN("round", Instant_round, 1, 0),
+ JS_FN("equals", Instant_equals, 1, 0),
+ JS_FN("toString", Instant_toString, 0, 0),
+ JS_FN("toLocaleString", Instant_toLocaleString, 0, 0),
+ JS_FN("toJSON", Instant_toJSON, 0, 0),
+ JS_FN("valueOf", Instant_valueOf, 0, 0),
+ JS_FN("toZonedDateTime", Instant_toZonedDateTime, 1, 0),
+ JS_FN("toZonedDateTimeISO", Instant_toZonedDateTimeISO, 1, 0),
+ JS_FS_END,
+};
+
+static const JSPropertySpec Instant_prototype_properties[] = {
+ JS_PSG("epochSeconds", Instant_epochSeconds, 0),
+ JS_PSG("epochMilliseconds", Instant_epochMilliseconds, 0),
+ JS_PSG("epochMicroseconds", Instant_epochMicroseconds, 0),
+ JS_PSG("epochNanoseconds", Instant_epochNanoseconds, 0),
+ JS_STRING_SYM_PS(toStringTag, "Temporal.Instant", JSPROP_READONLY),
+ JS_PS_END,
+};
+
+const ClassSpec InstantObject::classSpec_ = {
+ GenericCreateConstructor<InstantConstructor, 1, gc::AllocKind::FUNCTION>,
+ GenericCreatePrototype<InstantObject>,
+ Instant_methods,
+ nullptr,
+ Instant_prototype_methods,
+ Instant_prototype_properties,
+ nullptr,
+ ClassSpec::DontDefineConstructor,
+};
diff --git a/js/src/builtin/temporal/Instant.h b/js/src/builtin/temporal/Instant.h
new file mode 100644
index 0000000000..edce677d10
--- /dev/null
+++ b/js/src/builtin/temporal/Instant.h
@@ -0,0 +1,159 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_Instant_h
+#define builtin_temporal_Instant_h
+
+#include "mozilla/Assertions.h"
+
+#include <stdint.h>
+
+#include "builtin/temporal/TemporalTypes.h"
+#include "builtin/temporal/Wrapped.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "vm/NativeObject.h"
+
+namespace js {
+struct ClassSpec;
+}
+
+namespace js::temporal {
+
+class InstantObject : public NativeObject {
+ public:
+ static const JSClass class_;
+ static const JSClass& protoClass_;
+
+ static constexpr uint32_t SECONDS_SLOT = 0;
+ static constexpr uint32_t NANOSECONDS_SLOT = 1;
+ static constexpr uint32_t SLOT_COUNT = 2;
+
+ int64_t seconds() const {
+ double seconds = getFixedSlot(SECONDS_SLOT).toNumber();
+ MOZ_ASSERT(-8'640'000'000'000 <= seconds && seconds <= 8'640'000'000'000);
+ return int64_t(seconds);
+ }
+
+ int32_t nanoseconds() const {
+ int32_t nanoseconds = getFixedSlot(NANOSECONDS_SLOT).toInt32();
+ MOZ_ASSERT(0 <= nanoseconds && nanoseconds <= 999'999'999);
+ return nanoseconds;
+ }
+
+ private:
+ static const ClassSpec classSpec_;
+};
+
+/**
+ * Extract the instant fields from the Instant object.
+ */
+inline Instant ToInstant(const InstantObject* instant) {
+ return {instant->seconds(), instant->nanoseconds()};
+}
+
+class Increment;
+enum class TemporalUnit;
+enum class TemporalRoundingMode;
+
+/**
+ * IsValidEpochNanoseconds ( epochNanoseconds )
+ */
+bool IsValidEpochNanoseconds(const JS::BigInt* epochNanoseconds);
+
+/**
+ * IsValidEpochNanoseconds ( epochNanoseconds )
+ */
+bool IsValidEpochInstant(const Instant& instant);
+
+/**
+ * Return true if the input is within the valid instant span limits.
+ */
+bool IsValidInstantSpan(const InstantSpan& span);
+
+/**
+ * Return true if the input is within the valid instant span limits.
+ */
+bool IsValidInstantSpan(const JS::BigInt* nanoseconds);
+
+/**
+ * Convert a BigInt to an instant. The input must be a valid epoch nanoseconds
+ * value.
+ */
+Instant ToInstant(const JS::BigInt* epochNanoseconds);
+
+/**
+ * Convert a BigInt to an instant span. The input must be a valid epoch
+ * nanoseconds span value.
+ */
+InstantSpan ToInstantSpan(const JS::BigInt* nanoseconds);
+
+/**
+ * Convert an instant to a BigInt. The input must be a valid epoch instant.
+ */
+JS::BigInt* ToEpochNanoseconds(JSContext* cx, const Instant& instant);
+
+/**
+ * Convert an instant span to a BigInt. The input must be a valid instant span.
+ */
+JS::BigInt* ToEpochNanoseconds(JSContext* cx, const InstantSpan& instant);
+
+/**
+ * ToTemporalInstant ( item )
+ */
+Wrapped<InstantObject*> ToTemporalInstant(JSContext* cx,
+ JS::Handle<JS::Value> item);
+
+/**
+ * ToTemporalInstant ( item )
+ */
+bool ToTemporalInstant(JSContext* cx, JS::Handle<JS::Value> item,
+ Instant* result);
+
+/**
+ * CreateTemporalInstant ( epochNanoseconds [ , newTarget ] )
+ */
+InstantObject* CreateTemporalInstant(JSContext* cx, const Instant& instant);
+
+/**
+ * GetUTCEpochNanoseconds ( year, month, day, hour, minute, second, millisecond,
+ * microsecond, nanosecond [ , offsetNanoseconds ] )
+ */
+Instant GetUTCEpochNanoseconds(const PlainDateTime& dateTime);
+
+/**
+ * GetUTCEpochNanoseconds ( year, month, day, hour, minute, second, millisecond,
+ * microsecond, nanosecond [ , offsetNanoseconds ] )
+ */
+Instant GetUTCEpochNanoseconds(const PlainDateTime& dateTime,
+ const InstantSpan& offsetNanoseconds);
+
+/**
+ * RoundTemporalInstant ( ns, increment, unit, roundingMode )
+ */
+bool RoundTemporalInstant(JSContext* cx, const Instant& ns, Increment increment,
+ TemporalUnit unit, TemporalRoundingMode roundingMode,
+ Instant* result);
+
+/**
+ * AddInstant ( epochNanoseconds, hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds )
+ */
+bool AddInstant(JSContext* cx, const Instant& instant, const Duration& duration,
+ Instant* result);
+
+/**
+ * DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, largestUnit,
+ * roundingMode )
+ */
+bool DifferenceInstant(JSContext* cx, const Instant& ns1, const Instant& ns2,
+ Increment roundingIncrement, TemporalUnit smallestUnit,
+ TemporalUnit largestUnit,
+ TemporalRoundingMode roundingMode, Duration* result);
+
+} /* namespace js::temporal */
+
+#endif /* builtin_temporal_Instant_h */
diff --git a/js/src/builtin/temporal/Int96.cpp b/js/src/builtin/temporal/Int96.cpp
new file mode 100644
index 0000000000..73ea5ce90e
--- /dev/null
+++ b/js/src/builtin/temporal/Int96.cpp
@@ -0,0 +1,81 @@
+/* -*- 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/Int96.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Casting.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Maybe.h"
+
+#include <cmath>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "jsnum.h"
+
+using namespace js;
+using namespace js::temporal;
+
+mozilla::Maybe<Int96> Int96::fromInteger(double value) {
+ MOZ_ASSERT(IsInteger(value));
+
+ // Fast path for the common case.
+ int64_t intValue;
+ if (mozilla::NumberEqualsInt64(value, &intValue)) {
+ return mozilla::Some(Int96{intValue});
+ }
+
+ // First double integer which requires more than three digits.
+ constexpr double maximum = 0x1p+96;
+
+ // Reject if the value needs more than 96 bits.
+ if (std::abs(value) >= maximum) {
+ return mozilla::Nothing();
+ }
+
+ // Inlined version of |BigInt::createFromDouble()| for DigitBits=32. See the
+ // comments in |BigInt::createFromDouble()| for how this code works.
+ constexpr size_t DigitBits = 32;
+
+ // The number can't have more than three digits when it's below |maximum|.
+ Int96::Digits digits = {};
+
+ int exponent = mozilla::ExponentComponent(value);
+ MOZ_ASSERT(0 <= exponent && exponent <= 95,
+ "exponent is lower than exponent of 0x1p+96");
+
+ int length = exponent / DigitBits + 1;
+ MOZ_ASSERT(1 <= length && length <= 3);
+
+ using Double = mozilla::FloatingPoint<double>;
+ uint64_t mantissa =
+ mozilla::BitwiseCast<uint64_t>(value) & Double::kSignificandBits;
+
+ // Add implicit high bit.
+ mantissa |= 1ull << Double::kSignificandWidth;
+
+ // 0-indexed position of the double's most significant bit within the `msd`.
+ int msdTopBit = exponent % DigitBits;
+
+ // First, build the MSD by shifting the mantissa appropriately.
+ int remainingMantissaBits = Double::kSignificandWidth - msdTopBit;
+ digits[--length] = mantissa >> remainingMantissaBits;
+
+ // Fill in digits containing mantissa contributions.
+ mantissa = mantissa << (64 - remainingMantissaBits);
+ if (mantissa) {
+ MOZ_ASSERT(length > 0);
+ digits[--length] = uint32_t(mantissa >> 32);
+
+ if (uint32_t(mantissa)) {
+ MOZ_ASSERT(length > 0);
+ digits[--length] = uint32_t(mantissa);
+ }
+ }
+
+ return mozilla::Some(Int96{digits, value < 0});
+}
diff --git a/js/src/builtin/temporal/Int96.h b/js/src/builtin/temporal/Int96.h
new file mode 100644
index 0000000000..dfb0a5c231
--- /dev/null
+++ b/js/src/builtin/temporal/Int96.h
@@ -0,0 +1,161 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_Int96_h
+#define builtin_temporal_Int96_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Maybe.h"
+
+#include <array>
+#include <climits>
+#include <stddef.h>
+#include <stdint.h>
+#include <utility>
+
+namespace js::temporal {
+
+/**
+ * 96-bit integer with explicit sign. Supports integers in the range
+ * [-(2**96 - 1), 2**96 - 1].
+ */
+class Int96 final {
+ public:
+ using Digit = uint32_t;
+ using TwoDigit = uint64_t;
+
+ // The 96-bit integer is stored as three separate 32-bit integers.
+ using Digits = std::array<Digit, 3>;
+
+ private:
+ // Unsigned number in the range [0, 0xffff'ffff'ffff'ffff'ffff'ffff].
+ //
+ // The least significant digit is stored at index 0. The most significant
+ // digit is stored at index 2.
+ Digits digits = {};
+
+ // Explicit negative sign.
+ bool negative = false;
+
+ public:
+ // Default constructor initializes to zero.
+ constexpr Int96() = default;
+
+ // Create from an 64-bit integer.
+ constexpr explicit Int96(int64_t value) : negative(value < 0) {
+ // NB: Not std::abs, because std::abs(INT64_MIN) is undefined behavior.
+ uint64_t abs = mozilla::Abs(value);
+ digits[1] = uint32_t(abs >> 32);
+ digits[0] = uint32_t(abs);
+ }
+
+ constexpr Int96(Digits digits, bool negative)
+ : digits(digits), negative(negative) {
+ // Assert zero is non-negative.
+ MOZ_ASSERT_IF((digits[0] | digits[1] | digits[2]) == 0, !negative);
+ }
+
+ constexpr bool operator==(const Int96& other) const {
+ return digits[0] == other.digits[0] && digits[1] == other.digits[1] &&
+ digits[2] == other.digits[2] && negative == other.negative;
+ }
+
+ constexpr bool operator<(const Int96& other) const {
+ if (negative != other.negative) {
+ return negative;
+ }
+ for (size_t i = digits.size(); i != 0; --i) {
+ Digit x = digits[i - 1];
+ Digit y = other.digits[i - 1];
+ if (x != y) {
+ return negative ? x > y : x < y;
+ }
+ }
+ return false;
+ }
+
+ // Other operators are implemented in terms of operator== and operator<.
+ constexpr bool operator!=(const Int96& other) const {
+ return !(*this == other);
+ }
+ constexpr bool operator>(const Int96& other) const { return other < *this; }
+ constexpr bool operator<=(const Int96& other) const {
+ return !(other < *this);
+ }
+ constexpr bool operator>=(const Int96& other) const {
+ return !(*this < other);
+ }
+
+ /**
+ * Multiply this integer with an multiplier. Overflow is not supported.
+ */
+ constexpr Int96& operator*=(Digit multiplier) {
+ Digit carry = 0;
+ for (auto& digit : digits) {
+ TwoDigit d = digit;
+ d *= multiplier;
+ d += carry;
+
+ digit = Digit(d);
+ carry = Digit(d >> 32);
+ }
+ MOZ_ASSERT(carry == 0, "unsupported overflow");
+
+ return *this;
+ }
+
+ /**
+ * Multiply this integer with an multiplier. Overflow is not supported.
+ */
+ constexpr Int96 operator*(Digit multiplier) const {
+ auto result = *this;
+ result *= multiplier;
+ return result;
+ }
+
+ /**
+ * Divide this integer by the divisor using Euclidean division. The divisor
+ * must be smaller than the most significant digit of the integer. Returns the
+ * quotient and the remainder.
+ */
+ constexpr std::pair<int64_t, int32_t> operator/(Digit divisor) const {
+ MOZ_ASSERT(digits[2] < divisor, "unsupported divisor");
+
+ Digit quotient[2] = {};
+ Digit remainder = digits[2];
+ for (int32_t i = 1; i >= 0; i--) {
+ TwoDigit n = (TwoDigit(remainder) << 32) | digits[i];
+ quotient[i] = n / divisor;
+ remainder = n % divisor;
+ }
+
+ int64_t result = (TwoDigit(quotient[1]) << 32) | quotient[0];
+ if (negative) {
+ result *= -1;
+ if (remainder != 0) {
+ result -= 1;
+ remainder = divisor - remainder;
+ }
+ }
+ return {result, int32_t(remainder)};
+ }
+
+ /**
+ * Return the absolute value of this integer.
+ */
+ constexpr Int96 abs() const { return {digits, false}; }
+
+ /**
+ * Return Some(Int96) if the integer value fits into a 96-bit integer.
+ * Otherwise returns Nothing().
+ */
+ static mozilla::Maybe<Int96> fromInteger(double value);
+};
+
+} /* namespace js::temporal */
+
+#endif /* builtin_temporal_Int96_h */
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;
+}
diff --git a/js/src/builtin/temporal/PlainDate.h b/js/src/builtin/temporal/PlainDate.h
new file mode 100644
index 0000000000..75a3a3f2a1
--- /dev/null
+++ b/js/src/builtin/temporal/PlainDate.h
@@ -0,0 +1,292 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_PlainDate_h
+#define builtin_temporal_PlainDate_h
+
+#include "mozilla/Assertions.h"
+
+#include <initializer_list>
+#include <stdint.h>
+
+#include "builtin/temporal/Calendar.h"
+#include "builtin/temporal/PlainDateTime.h"
+#include "builtin/temporal/TemporalTypes.h"
+#include "builtin/temporal/Wrapped.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "vm/NativeObject.h"
+
+class JS_PUBLIC_API JSTracer;
+
+namespace js {
+struct ClassSpec;
+class PlainObject;
+} // namespace js
+
+namespace js::temporal {
+
+class PlainDateObject : public NativeObject {
+ public:
+ static const JSClass class_;
+ static const JSClass& protoClass_;
+
+ // TODO: Consider compacting fields to reduce object size.
+ //
+ // ceil(log2(271821)) + ceil(log2(12)) + ceil(log2(31)) = 28 bits are
+ // needed to store a date value in a single int32.
+
+ static constexpr uint32_t ISO_YEAR_SLOT = 0;
+ static constexpr uint32_t ISO_MONTH_SLOT = 1;
+ static constexpr uint32_t ISO_DAY_SLOT = 2;
+ static constexpr uint32_t CALENDAR_SLOT = 3;
+ static constexpr uint32_t SLOT_COUNT = 4;
+
+ int32_t isoYear() const { return getFixedSlot(ISO_YEAR_SLOT).toInt32(); }
+
+ int32_t isoMonth() const { return getFixedSlot(ISO_MONTH_SLOT).toInt32(); }
+
+ int32_t isoDay() const { return getFixedSlot(ISO_DAY_SLOT).toInt32(); }
+
+ CalendarValue calendar() const {
+ return CalendarValue(getFixedSlot(CALENDAR_SLOT));
+ }
+
+ private:
+ static const ClassSpec classSpec_;
+};
+
+class PlainDateWithCalendar {
+ PlainDate date_;
+ CalendarValue calendar_;
+
+ public:
+ PlainDateWithCalendar() = default;
+
+ PlainDateWithCalendar(const PlainDate& date, const CalendarValue& calendar)
+ : date_(date), calendar_(calendar) {
+ MOZ_ASSERT(ISODateTimeWithinLimits(date));
+ }
+
+ const auto& date() const { return date_; }
+ const auto& calendar() const { return calendar_; }
+
+ // Allow implicit conversion to a calendar-less PlainDate.
+ operator const PlainDate&() const { return date(); }
+
+ void trace(JSTracer* trc) { calendar_.trace(trc); }
+
+ const auto* calendarDoNotUse() const { return &calendar_; }
+};
+
+/**
+ * Extract the date fields from the PlainDate object.
+ */
+inline PlainDate ToPlainDate(const PlainDateObject* date) {
+ return {date->isoYear(), date->isoMonth(), date->isoDay()};
+}
+
+enum class TemporalOverflow;
+enum class TemporalUnit;
+class DurationObject;
+class ZonedDateTimeObject;
+
+#ifdef DEBUG
+/**
+ * IsValidISODate ( year, month, day )
+ */
+bool IsValidISODate(const PlainDate& date);
+
+/**
+ * IsValidISODate ( year, month, day )
+ */
+bool IsValidISODate(double year, double month, double day);
+#endif
+
+/**
+ * IsValidISODate ( year, month, day )
+ */
+bool ThrowIfInvalidISODate(JSContext* cx, const PlainDate& date);
+
+/**
+ * IsValidISODate ( year, month, day )
+ */
+bool ThrowIfInvalidISODate(JSContext* cx, double year, double month,
+ double day);
+
+/**
+ * ToTemporalDate ( item [ , options ] )
+ */
+bool ToTemporalDate(JSContext* cx, JS::Handle<JS::Value> item,
+ PlainDate* result);
+
+/**
+ * ToTemporalDate ( item [ , options ] )
+ */
+bool ToTemporalDate(JSContext* cx, JS::Handle<JS::Value> item,
+ JS::MutableHandle<PlainDateWithCalendar> result);
+
+/**
+ * CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )
+ */
+PlainDateObject* CreateTemporalDate(JSContext* cx, const PlainDate& date,
+ JS::Handle<CalendarValue> calendar);
+
+/**
+ * CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] )
+ */
+bool CreateTemporalDate(JSContext* cx, const PlainDate& date,
+ JS::Handle<CalendarValue> calendar,
+ JS::MutableHandle<PlainDateWithCalendar> result);
+
+/**
+ * RegulateISODate ( year, month, day, overflow )
+ */
+bool RegulateISODate(JSContext* cx, const PlainDate& date,
+ TemporalOverflow overflow, PlainDate* result);
+
+struct RegulatedISODate final {
+ double year;
+ int32_t month;
+ int32_t day;
+};
+
+/**
+ * RegulateISODate ( year, month, day, overflow )
+ */
+bool RegulateISODate(JSContext* cx, double year, double month, double day,
+ TemporalOverflow overflow, RegulatedISODate* result);
+
+/**
+ * AddISODate ( year, month, day, years, months, weeks, days, overflow )
+ */
+bool AddISODate(JSContext* cx, const PlainDate& date, const Duration& duration,
+ TemporalOverflow overflow, PlainDate* result);
+
+/**
+ * AddDate ( calendarRec, plainDate, duration [ , options ] )
+ */
+Wrapped<PlainDateObject*> AddDate(JSContext* cx,
+ JS::Handle<CalendarRecord> calendar,
+ JS::Handle<Wrapped<PlainDateObject*>> date,
+ const Duration& duration,
+ JS::Handle<JSObject*> options);
+
+/**
+ * AddDate ( calendarRec, plainDate, duration [ , options ] )
+ */
+Wrapped<PlainDateObject*> AddDate(JSContext* cx,
+ JS::Handle<CalendarRecord> calendar,
+ JS::Handle<Wrapped<PlainDateObject*>> date,
+ const Duration& duration);
+
+/**
+ * AddDate ( calendarRec, plainDate, duration [ , options ] )
+ */
+Wrapped<PlainDateObject*> AddDate(
+ JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<Wrapped<PlainDateObject*>> date,
+ JS::Handle<Wrapped<DurationObject*>> durationObj,
+ JS::Handle<JSObject*> options);
+
+/**
+ * AddDate ( calendarRec, plainDate, duration [ , options ] )
+ */
+Wrapped<PlainDateObject*> AddDate(
+ JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<Wrapped<PlainDateObject*>> date,
+ JS::Handle<Wrapped<DurationObject*>> durationObj);
+
+/**
+ * AddDate ( calendarRec, plainDate, duration [ , options ] )
+ */
+bool AddDate(JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ const PlainDate& date, const Duration& duration,
+ JS::Handle<JSObject*> options, PlainDate* result);
+
+/**
+ * AddDate ( calendarRec, plainDate, duration [ , options ] )
+ */
+bool AddDate(JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<Wrapped<PlainDateObject*>> date,
+ const Duration& duration, PlainDate* result);
+
+/**
+ * DifferenceISODate ( y1, m1, d1, y2, m2, d2, largestUnit )
+ */
+DateDuration DifferenceISODate(const PlainDate& start, const PlainDate& end,
+ TemporalUnit largestUnit);
+
+/**
+ * DifferenceDate ( calendarRec, one, two, options )
+ */
+bool DifferenceDate(JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<Wrapped<PlainDateObject*>> one,
+ JS::Handle<Wrapped<PlainDateObject*>> two,
+ JS::Handle<PlainObject*> options, Duration* result);
+
+/**
+ * DifferenceDate ( calendarRec, one, two, options )
+ */
+bool DifferenceDate(JSContext* cx, JS::Handle<CalendarRecord> calendar,
+ JS::Handle<Wrapped<PlainDateObject*>> one,
+ JS::Handle<Wrapped<PlainDateObject*>> two,
+ TemporalUnit largestUnit, Duration* result);
+
+/**
+ * CompareISODate ( y1, m1, d1, y2, m2, d2 )
+ */
+int32_t CompareISODate(const PlainDate& one, const PlainDate& two);
+
+/**
+ * BalanceISODate ( year, month, day )
+ */
+bool BalanceISODate(JSContext* cx, int32_t year, int32_t month, int64_t day,
+ PlainDate* result);
+
+/**
+ * BalanceISODate ( year, month, day )
+ */
+PlainDate BalanceISODate(int32_t year, int32_t month, int32_t day);
+
+/**
+ * BalanceISODate ( year, month, day )
+ */
+PlainDate BalanceISODateNew(int32_t year, int32_t month, int32_t day);
+
+/**
+ * Return true when accessing the calendar fields |fieldNames| can be optimized.
+ * Otherwise returns false.
+ */
+bool IsBuiltinAccess(JSContext* cx, JS::Handle<PlainDateObject*> date,
+ std::initializer_list<CalendarField> fieldNames);
+
+} /* namespace js::temporal */
+
+namespace js {
+
+template <typename Wrapper>
+class WrappedPtrOperations<temporal::PlainDateWithCalendar, Wrapper> {
+ const auto& container() const {
+ return static_cast<const Wrapper*>(this)->get();
+ }
+
+ public:
+ const auto& date() const { return container().date(); }
+
+ JS::Handle<temporal::CalendarValue> calendar() const {
+ return JS::Handle<temporal::CalendarValue>::fromMarkedLocation(
+ container().calendarDoNotUse());
+ }
+
+ // Allow implicit conversion to a calendar-less PlainDate.
+ operator const temporal::PlainDate&() const { return date(); }
+};
+
+} // namespace js
+
+#endif /* builtin_temporal_PlainDate_h */
diff --git a/js/src/builtin/temporal/PlainDateTime.cpp b/js/src/builtin/temporal/PlainDateTime.cpp
new file mode 100644
index 0000000000..8861b484bd
--- /dev/null
+++ b/js/src/builtin/temporal/PlainDateTime.cpp
@@ -0,0 +1,2856 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "builtin/temporal/PlainDateTime.h"
+
+#include "mozilla/Assertions.h"
+
+#include <algorithm>
+#include <type_traits>
+#include <utility>
+
+#include "jsnum.h"
+#include "jspubtd.h"
+#include "NamespaceImports.h"
+
+#include "builtin/temporal/Calendar.h"
+#include "builtin/temporal/Duration.h"
+#include "builtin/temporal/PlainDate.h"
+#include "builtin/temporal/PlainMonthDay.h"
+#include "builtin/temporal/PlainTime.h"
+#include "builtin/temporal/PlainYearMonth.h"
+#include "builtin/temporal/Temporal.h"
+#include "builtin/temporal/TemporalFields.h"
+#include "builtin/temporal/TemporalParser.h"
+#include "builtin/temporal/TemporalRoundingMode.h"
+#include "builtin/temporal/TemporalTypes.h"
+#include "builtin/temporal/TemporalUnit.h"
+#include "builtin/temporal/TimeZone.h"
+#include "builtin/temporal/ToString.h"
+#include "builtin/temporal/Wrapped.h"
+#include "builtin/temporal/ZonedDateTime.h"
+#include "ds/IdValuePair.h"
+#include "gc/AllocKind.h"
+#include "gc/Barrier.h"
+#include "js/AllocPolicy.h"
+#include "js/CallArgs.h"
+#include "js/CallNonGenericMethod.h"
+#include "js/Class.h"
+#include "js/ErrorReport.h"
+#include "js/friend/ErrorMessages.h"
+#include "js/GCVector.h"
+#include "js/Id.h"
+#include "js/PropertyDescriptor.h"
+#include "js/PropertySpec.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/GlobalObject.h"
+#include "vm/JSAtomState.h"
+#include "vm/JSContext.h"
+#include "vm/JSObject.h"
+#include "vm/ObjectOperations.h"
+#include "vm/PlainObject.h"
+#include "vm/StringType.h"
+
+#include "vm/JSObject-inl.h"
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+using namespace js::temporal;
+
+static inline bool IsPlainDateTime(Handle<Value> v) {
+ return v.isObject() && v.toObject().is<PlainDateTimeObject>();
+}
+
+#ifdef DEBUG
+/**
+ * IsValidISODateTime ( year, month, day, hour, minute, second, millisecond,
+ * microsecond, nanosecond )
+ */
+bool js::temporal::IsValidISODateTime(const PlainDateTime& dateTime) {
+ return IsValidISODate(dateTime.date) && IsValidTime(dateTime.time);
+}
+#endif
+
+/**
+ * IsValidISODateTime ( year, month, day, hour, minute, second, millisecond,
+ * microsecond, nanosecond )
+ */
+static bool ThrowIfInvalidISODateTime(JSContext* cx,
+ const PlainDateTime& dateTime) {
+ return ThrowIfInvalidISODate(cx, dateTime.date) &&
+ ThrowIfInvalidTime(cx, dateTime.time);
+}
+
+/**
+ * ISODateTimeWithinLimits ( year, month, day, hour, minute, second,
+ * millisecond, microsecond, nanosecond )
+ */
+template <typename T>
+static bool ISODateTimeWithinLimits(T year, T month, T day, T hour, T minute,
+ T second, T millisecond, T microsecond,
+ T nanosecond) {
+ static_assert(std::is_same_v<T, int32_t> || std::is_same_v<T, double>);
+
+ // Step 1.
+ MOZ_ASSERT(IsInteger(year));
+ MOZ_ASSERT(IsInteger(month));
+ MOZ_ASSERT(IsInteger(day));
+ MOZ_ASSERT(IsInteger(hour));
+ MOZ_ASSERT(IsInteger(minute));
+ MOZ_ASSERT(IsInteger(second));
+ MOZ_ASSERT(IsInteger(millisecond));
+ MOZ_ASSERT(IsInteger(microsecond));
+ MOZ_ASSERT(IsInteger(nanosecond));
+
+ MOZ_ASSERT(IsValidISODate(year, month, day));
+ MOZ_ASSERT(
+ IsValidTime(hour, minute, second, millisecond, microsecond, nanosecond));
+
+ // js> new Date(-8_64000_00000_00000).toISOString()
+ // "-271821-04-20T00:00:00.000Z"
+ //
+ // js> new Date(+8_64000_00000_00000).toISOString()
+ // "+275760-09-13T00:00:00.000Z"
+
+ constexpr int32_t minYear = -271821;
+ constexpr int32_t maxYear = 275760;
+
+ // Definitely in range.
+ if (minYear < year && year < maxYear) {
+ return true;
+ }
+
+ // -271821 April, 20
+ if (year < 0) {
+ if (year != minYear) {
+ return false;
+ }
+ if (month != 4) {
+ return month > 4;
+ }
+ if (day != (20 - 1)) {
+ return day > (20 - 1);
+ }
+ // Needs to be past midnight on April, 19.
+ return !(hour == 0 && minute == 0 && second == 0 && millisecond == 0 &&
+ microsecond == 0 && nanosecond == 0);
+ }
+
+ // 275760 September, 13
+ if (year != maxYear) {
+ return false;
+ }
+ if (month != 9) {
+ return month < 9;
+ }
+ if (day > 13) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * ISODateTimeWithinLimits ( year, month, day, hour, minute, second,
+ * millisecond, microsecond, nanosecond )
+ */
+template <typename T>
+static bool ISODateTimeWithinLimits(T year, T month, T day) {
+ static_assert(std::is_same_v<T, int32_t> || std::is_same_v<T, double>);
+
+ MOZ_ASSERT(IsValidISODate(year, month, day));
+
+ // js> new Date(-8_64000_00000_00000).toISOString()
+ // "-271821-04-20T00:00:00.000Z"
+ //
+ // js> new Date(+8_64000_00000_00000).toISOString()
+ // "+275760-09-13T00:00:00.000Z"
+
+ constexpr int32_t minYear = -271821;
+ constexpr int32_t maxYear = 275760;
+
+ // ISODateTimeWithinLimits is called with hour=12 and the remaining time
+ // components set to zero. That means the maximum value is exclusive, whereas
+ // the minimum value is inclusive.
+
+ // FIXME: spec bug - GetUTCEpochNanoseconds when called with large |year| may
+ // cause MakeDay to return NaN, which makes MakeDate return NaN, which is
+ // unexpected in GetUTCEpochNanoseconds, step 4.
+ // https://github.com/tc39/proposal-temporal/issues/2315
+
+ // Definitely in range.
+ if (minYear < year && year < maxYear) {
+ return true;
+ }
+
+ // -271821 April, 20
+ if (year < 0) {
+ if (year != minYear) {
+ return false;
+ }
+ if (month != 4) {
+ return month > 4;
+ }
+ if (day < (20 - 1)) {
+ return false;
+ }
+ return true;
+ }
+
+ // 275760 September, 13
+ if (year != maxYear) {
+ return false;
+ }
+ if (month != 9) {
+ return month < 9;
+ }
+ if (day > 13) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * ISODateTimeWithinLimits ( year, month, day, hour, minute, second,
+ * millisecond, microsecond, nanosecond )
+ */
+bool js::temporal::ISODateTimeWithinLimits(double year, double month,
+ double day) {
+ return ::ISODateTimeWithinLimits(year, month, day);
+}
+
+/**
+ * ISODateTimeWithinLimits ( year, month, day, hour, minute, second,
+ * millisecond, microsecond, nanosecond )
+ */
+bool js::temporal::ISODateTimeWithinLimits(const PlainDateTime& dateTime) {
+ auto& [date, time] = dateTime;
+ return ::ISODateTimeWithinLimits(date.year, date.month, date.day, time.hour,
+ time.minute, time.second, time.millisecond,
+ time.microsecond, time.nanosecond);
+}
+
+/**
+ * ISODateTimeWithinLimits ( year, month, day, hour, minute, second,
+ * millisecond, microsecond, nanosecond )
+ */
+bool js::temporal::ISODateTimeWithinLimits(const PlainDate& date) {
+ return ::ISODateTimeWithinLimits(date.year, date.month, date.day);
+}
+
+/**
+ * CreateTemporalDateTime ( isoYear, isoMonth, isoDay, hour, minute, second,
+ * millisecond, microsecond, nanosecond, calendar [ , newTarget ] )
+ */
+static PlainDateTimeObject* CreateTemporalDateTime(
+ JSContext* cx, const CallArgs& args, double isoYear, double isoMonth,
+ double isoDay, double hour, double minute, double second,
+ double millisecond, double microsecond, double nanosecond,
+ Handle<CalendarValue> calendar) {
+ MOZ_ASSERT(IsInteger(isoYear));
+ MOZ_ASSERT(IsInteger(isoMonth));
+ MOZ_ASSERT(IsInteger(isoDay));
+ MOZ_ASSERT(IsInteger(hour));
+ MOZ_ASSERT(IsInteger(minute));
+ MOZ_ASSERT(IsInteger(second));
+ MOZ_ASSERT(IsInteger(millisecond));
+ MOZ_ASSERT(IsInteger(microsecond));
+ MOZ_ASSERT(IsInteger(nanosecond));
+
+ // Step 1.
+ if (!ThrowIfInvalidISODate(cx, isoYear, isoMonth, isoDay)) {
+ return nullptr;
+ }
+
+ // Step 2.
+ if (!ThrowIfInvalidTime(cx, hour, minute, second, millisecond, microsecond,
+ nanosecond)) {
+ return nullptr;
+ }
+
+ // Step 3.
+ if (!ISODateTimeWithinLimits(isoYear, isoMonth, isoDay, hour, minute, second,
+ millisecond, microsecond, nanosecond)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID);
+ return nullptr;
+ }
+
+ // Steps 4-5.
+ Rooted<JSObject*> proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_PlainDateTime,
+ &proto)) {
+ return nullptr;
+ }
+
+ auto* dateTime = NewObjectWithClassProto<PlainDateTimeObject>(cx, proto);
+ if (!dateTime) {
+ return nullptr;
+ }
+
+ // Step 6.
+ dateTime->setFixedSlot(PlainDateTimeObject::ISO_YEAR_SLOT,
+ Int32Value(isoYear));
+
+ // Step 7.
+ dateTime->setFixedSlot(PlainDateTimeObject::ISO_MONTH_SLOT,
+ Int32Value(isoMonth));
+
+ // Step 8.
+ dateTime->setFixedSlot(PlainDateTimeObject::ISO_DAY_SLOT, Int32Value(isoDay));
+
+ // Step 9.
+ dateTime->setFixedSlot(PlainDateTimeObject::ISO_HOUR_SLOT, Int32Value(hour));
+
+ // Step 10.
+ dateTime->setFixedSlot(PlainDateTimeObject::ISO_MINUTE_SLOT,
+ Int32Value(minute));
+
+ // Step 11.
+ dateTime->setFixedSlot(PlainDateTimeObject::ISO_SECOND_SLOT,
+ Int32Value(second));
+
+ // Step 12.
+ dateTime->setFixedSlot(PlainDateTimeObject::ISO_MILLISECOND_SLOT,
+ Int32Value(millisecond));
+
+ // Step 13.
+ dateTime->setFixedSlot(PlainDateTimeObject::ISO_MICROSECOND_SLOT,
+ Int32Value(microsecond));
+
+ // Step 14.
+ dateTime->setFixedSlot(PlainDateTimeObject::ISO_NANOSECOND_SLOT,
+ Int32Value(nanosecond));
+
+ // Step 15.
+ dateTime->setFixedSlot(PlainDateTimeObject::CALENDAR_SLOT,
+ calendar.toValue());
+
+ // Step 16.
+ return dateTime;
+}
+
+/**
+ * CreateTemporalDateTime ( isoYear, isoMonth, isoDay, hour, minute, second,
+ * millisecond, microsecond, nanosecond, calendar [ , newTarget ] )
+ */
+PlainDateTimeObject* js::temporal::CreateTemporalDateTime(
+ JSContext* cx, const PlainDateTime& dateTime,
+ Handle<CalendarValue> calendar) {
+ auto& [date, time] = dateTime;
+ auto& [isoYear, isoMonth, isoDay] = date;
+ auto& [hour, minute, second, millisecond, microsecond, nanosecond] = time;
+
+ // Steps 1-2.
+ if (!ThrowIfInvalidISODateTime(cx, dateTime)) {
+ return nullptr;
+ }
+
+ // Step 3.
+ if (!ISODateTimeWithinLimits(dateTime)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID);
+ return nullptr;
+ }
+
+ // Steps 4-5.
+ auto* object = NewBuiltinClassInstance<PlainDateTimeObject>(cx);
+ if (!object) {
+ return nullptr;
+ }
+
+ // Step 6.
+ object->setFixedSlot(PlainDateTimeObject::ISO_YEAR_SLOT, Int32Value(isoYear));
+
+ // Step 7.
+ object->setFixedSlot(PlainDateTimeObject::ISO_MONTH_SLOT,
+ Int32Value(isoMonth));
+
+ // Step 8.
+ object->setFixedSlot(PlainDateTimeObject::ISO_DAY_SLOT, Int32Value(isoDay));
+
+ // Step 9.
+ object->setFixedSlot(PlainDateTimeObject::ISO_HOUR_SLOT, Int32Value(hour));
+
+ // Step 10.
+ object->setFixedSlot(PlainDateTimeObject::ISO_MINUTE_SLOT,
+ Int32Value(minute));
+
+ // Step 11.
+ object->setFixedSlot(PlainDateTimeObject::ISO_SECOND_SLOT,
+ Int32Value(second));
+
+ // Step 12.
+ object->setFixedSlot(PlainDateTimeObject::ISO_MILLISECOND_SLOT,
+ Int32Value(millisecond));
+
+ // Step 13.
+ object->setFixedSlot(PlainDateTimeObject::ISO_MICROSECOND_SLOT,
+ Int32Value(microsecond));
+
+ // Step 14.
+ object->setFixedSlot(PlainDateTimeObject::ISO_NANOSECOND_SLOT,
+ Int32Value(nanosecond));
+
+ // Step 15.
+ object->setFixedSlot(PlainDateTimeObject::CALENDAR_SLOT, calendar.toValue());
+
+ // Step 16.
+ return object;
+}
+
+/**
+ * CreateTemporalDateTime ( isoYear, isoMonth, isoDay, hour, minute, second,
+ * millisecond, microsecond, nanosecond, calendar [ , newTarget ] )
+ */
+bool js::temporal::CreateTemporalDateTime(
+ JSContext* cx, const PlainDateTime& dateTime,
+ Handle<CalendarValue> calendar,
+ MutableHandle<PlainDateTimeWithCalendar> result) {
+ // Steps 1-2.
+ if (!ThrowIfInvalidISODateTime(cx, dateTime)) {
+ return false;
+ }
+
+ // Step 3.
+ if (!ISODateTimeWithinLimits(dateTime)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PLAIN_DATE_TIME_INVALID);
+ return false;
+ }
+
+ result.set(PlainDateTimeWithCalendar{dateTime, calendar});
+ return true;
+}
+
+/**
+ * InterpretTemporalDateTimeFields ( calendarRec, fields, options )
+ */
+bool js::temporal::InterpretTemporalDateTimeFields(
+ JSContext* cx, Handle<CalendarRecord> calendar, Handle<PlainObject*> fields,
+ Handle<PlainObject*> options, PlainDateTime* result) {
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ MOZ_ASSERT(CalendarMethodsRecordHasLookedUp(calendar,
+ CalendarMethod::DateFromFields));
+
+ // Step 3.
+ TimeRecord timeResult;
+ if (!ToTemporalTimeRecord(cx, fields, &timeResult)) {
+ return false;
+ }
+
+ // Step 4.
+ auto overflow = TemporalOverflow::Constrain;
+ if (!ToTemporalOverflow(cx, options, &overflow)) {
+ return false;
+ }
+
+ // Steps 5-6.
+ Rooted<Value> overflowValue(cx);
+ if (overflow == TemporalOverflow::Constrain) {
+ overflowValue.setString(cx->names().constrain);
+ } else {
+ MOZ_ASSERT(overflow == TemporalOverflow::Reject);
+ overflowValue.setString(cx->names().reject);
+ }
+ if (!DefineDataProperty(cx, options, cx->names().overflow, overflowValue)) {
+ return false;
+ }
+
+ // Step 7.
+ auto temporalDate =
+ js::temporal::CalendarDateFromFields(cx, calendar, fields, options);
+ if (!temporalDate) {
+ return false;
+ }
+ auto date = ToPlainDate(&temporalDate.unwrap());
+
+ // Step 8.
+ PlainTime time;
+ if (!RegulateTime(cx, timeResult, overflow, &time)) {
+ return false;
+ }
+
+ // Step 9.
+ *result = {date, time};
+ return true;
+}
+
+/**
+ * InterpretTemporalDateTimeFields ( calendarRec, fields, options )
+ */
+bool js::temporal::InterpretTemporalDateTimeFields(
+ JSContext* cx, Handle<CalendarRecord> calendar, Handle<PlainObject*> fields,
+ PlainDateTime* result) {
+ // TODO: Avoid creating the options object when CalendarDateFromFields calls
+ // the built-in Calendar.prototype.dateFromFields method.
+ Rooted<PlainObject*> options(cx, NewPlainObjectWithProto(cx, nullptr));
+ if (!options) {
+ return false;
+ }
+
+ return InterpretTemporalDateTimeFields(cx, calendar, fields, options, result);
+}
+
+/**
+ * ToTemporalDateTime ( item [ , options ] )
+ */
+static Wrapped<PlainDateTimeObject*> ToTemporalDateTime(
+ JSContext* cx, Handle<Value> item, Handle<JSObject*> maybeOptions) {
+ // Step 1. (Not applicable)
+
+ // Step 2.
+ Rooted<PlainObject*> maybeResolvedOptions(cx);
+ if (maybeOptions) {
+ maybeResolvedOptions = SnapshotOwnProperties(cx, maybeOptions);
+ if (!maybeResolvedOptions) {
+ return nullptr;
+ }
+ }
+
+ // Steps 3-4.
+ Rooted<CalendarValue> calendar(cx);
+ PlainDateTime result;
+ if (item.isObject()) {
+ Rooted<JSObject*> itemObj(cx, &item.toObject());
+
+ // Step 3.a.
+ if (itemObj->canUnwrapAs<PlainDateTimeObject>()) {
+ return itemObj;
+ }
+
+ // Step 3.b.
+ if (auto* zonedDateTime = itemObj->maybeUnwrapIf<ZonedDateTimeObject>()) {
+ auto epochInstant = ToInstant(zonedDateTime);
+ Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
+ Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
+
+ if (!timeZone.wrap(cx)) {
+ return nullptr;
+ }
+ if (!calendar.wrap(cx)) {
+ return nullptr;
+ }
+
+ // Step 3.b.i.
+ if (maybeResolvedOptions) {
+ TemporalOverflow ignored;
+ if (!ToTemporalOverflow(cx, maybeResolvedOptions, &ignored)) {
+ return nullptr;
+ }
+ }
+
+ // Steps 3.b.ii-iv.
+ return GetPlainDateTimeFor(cx, timeZone, epochInstant, calendar);
+ }
+
+ // Step 3.c.
+ if (auto* date = itemObj->maybeUnwrapIf<PlainDateObject>()) {
+ PlainDateTime dateTime = {ToPlainDate(date), {}};
+ Rooted<CalendarValue> calendar(cx, date->calendar());
+ if (!calendar.wrap(cx)) {
+ return nullptr;
+ }
+
+ // Step 3.c.i.
+ if (maybeResolvedOptions) {
+ TemporalOverflow ignored;
+ if (!ToTemporalOverflow(cx, maybeResolvedOptions, &ignored)) {
+ return nullptr;
+ }
+ }
+
+ // Step 3.c.ii.
+ return CreateTemporalDateTime(cx, dateTime, calendar);
+ }
+
+ // Step 3.d.
+ if (!GetTemporalCalendarWithISODefault(cx, itemObj, &calendar)) {
+ return nullptr;
+ }
+
+ // Step 3.e.
+ Rooted<CalendarRecord> calendarRec(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendar,
+ {
+ CalendarMethod::DateFromFields,
+ CalendarMethod::Fields,
+ },
+ &calendarRec)) {
+ return nullptr;
+ }
+
+ // Step 3.f.
+ JS::RootedVector<PropertyKey> fieldNames(cx);
+ if (!CalendarFields(cx, calendarRec,
+ {CalendarField::Day, CalendarField::Month,
+ CalendarField::MonthCode, CalendarField::Year},
+ &fieldNames)) {
+ return nullptr;
+ }
+
+ // Step 3.g.
+ if (!AppendSorted(cx, fieldNames.get(),
+ {
+ TemporalField::Hour,
+ TemporalField::Microsecond,
+ TemporalField::Millisecond,
+ TemporalField::Minute,
+ TemporalField::Nanosecond,
+ TemporalField::Second,
+ })) {
+ return nullptr;
+ }
+
+ // Step 3.h.
+ Rooted<PlainObject*> fields(cx,
+ PrepareTemporalFields(cx, itemObj, fieldNames));
+ if (!fields) {
+ return nullptr;
+ }
+
+ // Step 3.i.
+ if (maybeResolvedOptions) {
+ if (!InterpretTemporalDateTimeFields(cx, calendarRec, fields,
+ maybeResolvedOptions, &result)) {
+ return nullptr;
+ }
+ } else {
+ if (!InterpretTemporalDateTimeFields(cx, calendarRec, fields, &result)) {
+ return nullptr;
+ }
+ }
+ } else {
+ // Step 4.a.
+ if (!item.isString()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, item,
+ nullptr, "not a string");
+ return nullptr;
+ }
+ Rooted<JSString*> string(cx, item.toString());
+
+ // Step 4.b.
+ Rooted<JSString*> calendarString(cx);
+ if (!ParseTemporalDateTimeString(cx, string, &result, &calendarString)) {
+ return nullptr;
+ }
+
+ // Step 4.c.
+ MOZ_ASSERT(IsValidISODate(result.date));
+
+ // Step 4.d.
+ MOZ_ASSERT(IsValidTime(result.time));
+
+ // Steps 4.e-h.
+ if (calendarString) {
+ if (!ToBuiltinCalendar(cx, calendarString, &calendar)) {
+ return nullptr;
+ }
+ } else {
+ calendar.set(CalendarValue(cx->names().iso8601));
+ }
+
+ // Step 4.i.
+ if (maybeResolvedOptions) {
+ TemporalOverflow ignored;
+ if (!ToTemporalOverflow(cx, maybeResolvedOptions, &ignored)) {
+ return nullptr;
+ }
+ }
+ }
+
+ // Step 5.
+ return CreateTemporalDateTime(cx, result, calendar);
+}
+
+/**
+ * ToTemporalDateTime ( item [ , options ] )
+ */
+Wrapped<PlainDateTimeObject*> js::temporal::ToTemporalDateTime(
+ JSContext* cx, Handle<Value> item) {
+ return ::ToTemporalDateTime(cx, item, nullptr);
+}
+
+/**
+ * ToTemporalDateTime ( item [ , options ] )
+ */
+bool js::temporal::ToTemporalDateTime(JSContext* cx, Handle<Value> item,
+ PlainDateTime* result) {
+ auto obj = ::ToTemporalDateTime(cx, item, nullptr);
+ if (!obj) {
+ return false;
+ }
+
+ *result = ToPlainDateTime(&obj.unwrap());
+ return true;
+}
+
+/**
+ * ToTemporalDateTime ( item [ , options ] )
+ */
+static bool ToTemporalDateTime(
+ JSContext* cx, Handle<Value> item,
+ MutableHandle<PlainDateTimeWithCalendar> result) {
+ HandleObject options = nullptr;
+
+ auto* obj = ::ToTemporalDateTime(cx, item, options).unwrapOrNull();
+ if (!obj) {
+ return false;
+ }
+
+ auto dateTime = ToPlainDateTime(obj);
+ Rooted<CalendarValue> calendar(cx, obj->calendar());
+ if (!calendar.wrap(cx)) {
+ return false;
+ }
+
+ result.set(PlainDateTimeWithCalendar{dateTime, calendar});
+ return true;
+}
+
+/**
+ * CompareISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2,
+ * d2, h2, min2, s2, ms2, mus2, ns2 )
+ */
+static int32_t CompareISODateTime(const PlainDateTime& one,
+ const PlainDateTime& two) {
+ // Step 1. (Not applicable in our implementation.)
+
+ // Steps 2-3.
+ if (int32_t dateResult = CompareISODate(one.date, two.date)) {
+ return dateResult;
+ }
+
+ // Steps 4.
+ return CompareTemporalTime(one.time, two.time);
+}
+
+/**
+ * AddDateTime ( year, month, day, hour, minute, second, millisecond,
+ * microsecond, nanosecond, calendarRec, years, months, weeks, days, hours,
+ * minutes, seconds, milliseconds, microseconds, nanoseconds, options )
+ */
+static bool AddDateTime(JSContext* cx, const PlainDateTime& dateTime,
+ Handle<CalendarRecord> calendar,
+ const Duration& duration, Handle<JSObject*> options,
+ PlainDateTime* result) {
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ // Step 1.
+ MOZ_ASSERT(IsValidISODateTime(dateTime));
+ MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
+
+ // Step 2.
+ PlainTime timeResult;
+ double daysResult;
+ if (!AddTime(cx, dateTime.time, duration, &timeResult, &daysResult)) {
+ return false;
+ }
+
+ // Step 3.
+ const auto& datePart = dateTime.date;
+
+ // Step 4.
+ Duration dateDuration = {duration.years, duration.months, duration.weeks,
+ daysResult};
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ // Step 5.
+ PlainDate addedDate;
+ if (!AddDate(cx, calendar, datePart, dateDuration, options, &addedDate)) {
+ return false;
+ }
+
+ // Step 6.
+ *result = {addedDate, timeResult};
+ return true;
+}
+
+/**
+ * DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2,
+ * d2, h2, min2, s2, ms2, mus2, ns2, calendarRec, largestUnit, options )
+ */
+static bool DifferenceISODateTime(JSContext* cx, const PlainDateTime& one,
+ const PlainDateTime& two,
+ Handle<CalendarRecord> calendar,
+ TemporalUnit largestUnit,
+ Handle<PlainObject*> maybeOptions,
+ Duration* result) {
+ // Steps 1-2.
+ MOZ_ASSERT(IsValidISODateTime(one));
+ MOZ_ASSERT(IsValidISODateTime(two));
+ MOZ_ASSERT(ISODateTimeWithinLimits(one));
+ MOZ_ASSERT(ISODateTimeWithinLimits(two));
+
+ // Step 3.
+ MOZ_ASSERT_IF(
+ one.date != two.date && largestUnit < TemporalUnit::Day,
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateUntil));
+
+ // Step 4.
+ auto timeDifference = DifferenceTime(one.time, two.time);
+
+ // Step 5.
+ int32_t timeSign = DurationSign(timeDifference.toDuration());
+
+ // Step 6.
+ int32_t dateSign = CompareISODate(two.date, one.date);
+
+ // Step 7.
+ auto adjustedDate = one.date;
+
+ // Step 8.
+ if (timeSign == -dateSign) {
+ // Step 8.a.
+ adjustedDate = BalanceISODate(adjustedDate.year, adjustedDate.month,
+ adjustedDate.day - timeSign);
+
+ // Step 8.b.
+ if (!BalanceTimeDuration(cx,
+ {
+ 0,
+ 0,
+ 0,
+ double(-timeSign),
+ timeDifference.hours,
+ timeDifference.minutes,
+ timeDifference.seconds,
+ timeDifference.milliseconds,
+ timeDifference.microseconds,
+ timeDifference.nanoseconds,
+ },
+ largestUnit, &timeDifference)) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(IsValidISODate(adjustedDate));
+ MOZ_ASSERT(ISODateTimeWithinLimits(adjustedDate));
+
+ // TODO: Avoid allocating CreateTemporalDate.
+
+ // Step 9.
+ Rooted<PlainDateObject*> date1(
+ cx, CreateTemporalDate(cx, adjustedDate, calendar.receiver()));
+ if (!date1) {
+ return false;
+ }
+
+ // Step 10.
+ Rooted<PlainDateObject*> date2(
+ cx, CreateTemporalDate(cx, two.date, calendar.receiver()));
+ if (!date2) {
+ return false;
+ }
+
+ // Step 11.
+ auto dateLargestUnit = std::min(TemporalUnit::Day, largestUnit);
+
+ Duration dateDifference;
+ if (maybeOptions) {
+ // FIXME: spec issue - this copy is no longer needed, all callers have
+ // already copied the user input object.
+ // https://github.com/tc39/proposal-temporal/issues/2525
+
+ // Step 12.
+ Rooted<PlainObject*> untilOptions(cx,
+ SnapshotOwnProperties(cx, maybeOptions));
+ if (!untilOptions) {
+ return false;
+ }
+
+ // Step 13.
+ Rooted<Value> largestUnitValue(
+ cx, StringValue(TemporalUnitToString(cx, dateLargestUnit)));
+ if (!DefineDataProperty(cx, untilOptions, cx->names().largestUnit,
+ largestUnitValue)) {
+ return false;
+ }
+
+ // Step 14.
+ if (!DifferenceDate(cx, calendar, date1, date2, untilOptions,
+ &dateDifference)) {
+ return false;
+ }
+ } else {
+ // Steps 12-14.
+ if (!DifferenceDate(cx, calendar, date1, date2, dateLargestUnit,
+ &dateDifference)) {
+ return false;
+ }
+ }
+
+ // Step 15.
+ TimeDuration balanceResult;
+ if (!BalanceTimeDuration(cx,
+ {
+ 0,
+ 0,
+ 0,
+ dateDifference.days,
+ timeDifference.hours,
+ timeDifference.minutes,
+ timeDifference.seconds,
+ timeDifference.milliseconds,
+ timeDifference.microseconds,
+ timeDifference.nanoseconds,
+ },
+ largestUnit, &balanceResult)) {
+ return false;
+ }
+
+ // Step 16.
+ *result = {dateDifference.years, dateDifference.months,
+ dateDifference.weeks, balanceResult.days,
+ balanceResult.hours, balanceResult.minutes,
+ balanceResult.seconds, balanceResult.milliseconds,
+ balanceResult.microseconds, balanceResult.nanoseconds};
+ MOZ_ASSERT(IsValidDuration(*result));
+ return true;
+}
+
+/**
+ * DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2,
+ * d2, h2, min2, s2, ms2, mus2, ns2, calendarRec, largestUnit, options )
+ */
+bool js::temporal::DifferenceISODateTime(JSContext* cx,
+ const PlainDateTime& one,
+ const PlainDateTime& two,
+ Handle<CalendarRecord> calendar,
+ TemporalUnit largestUnit,
+ Duration* result) {
+ return ::DifferenceISODateTime(cx, one, two, calendar, largestUnit, nullptr,
+ result);
+}
+
+/**
+ * DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2,
+ * d2, h2, min2, s2, ms2, mus2, ns2, calendarRec, largestUnit, options )
+ */
+bool js::temporal::DifferenceISODateTime(
+ JSContext* cx, const PlainDateTime& one, const PlainDateTime& two,
+ Handle<CalendarRecord> calendar, TemporalUnit largestUnit,
+ Handle<PlainObject*> options, Duration* result) {
+ return ::DifferenceISODateTime(cx, one, two, calendar, largestUnit, options,
+ result);
+}
+
+/**
+ * RoundISODateTime ( year, month, day, hour, minute, second, millisecond,
+ * microsecond, nanosecond, increment, unit, roundingMode [ , dayLength ] )
+ */
+static PlainDateTime RoundISODateTime(const PlainDateTime& dateTime,
+ Increment increment, TemporalUnit unit,
+ TemporalRoundingMode roundingMode) {
+ const auto& [date, time] = dateTime;
+
+ // Step 1.
+ MOZ_ASSERT(IsValidISODateTime(dateTime));
+ MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
+
+ // Step 2. (Not applicable in our implementation.)
+
+ // Step 3.
+ auto roundedTime = RoundTime(time, increment, unit, roundingMode);
+ MOZ_ASSERT(0 <= roundedTime.days && roundedTime.days <= 1);
+
+ // Step 4.
+ auto balanceResult =
+ BalanceISODate(date.year, date.month, date.day + roundedTime.days);
+
+ // Step 5.
+ return {balanceResult, roundedTime.time};
+}
+
+/**
+ * DifferenceTemporalPlainDateTime ( operation, dateTime, other, options )
+ */
+static bool DifferenceTemporalPlainDateTime(JSContext* cx,
+ TemporalDifference operation,
+ const CallArgs& args) {
+ Rooted<PlainDateTimeWithCalendar> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ Rooted<PlainDateTimeWithCalendar> other(cx);
+ if (!::ToTemporalDateTime(cx, args.get(0), &other)) {
+ return false;
+ }
+
+ // Step 3.
+ if (!CalendarEqualsOrThrow(cx, dateTime.calendar(), other.calendar())) {
+ return false;
+ }
+
+ // Steps 4-5.
+ DifferenceSettings settings;
+ Rooted<PlainObject*> resolvedOptions(cx);
+ if (args.hasDefined(1)) {
+ Rooted<JSObject*> options(
+ cx, RequireObjectArg(cx, "options", ToName(operation), args[1]));
+ if (!options) {
+ return false;
+ }
+
+ // Step 4.
+ resolvedOptions = SnapshotOwnProperties(cx, options);
+ if (!resolvedOptions) {
+ return false;
+ }
+
+ // Step 5.
+ if (!GetDifferenceSettings(
+ cx, operation, resolvedOptions, TemporalUnitGroup::DateTime,
+ TemporalUnit::Nanosecond, TemporalUnit::Day, &settings)) {
+ return false;
+ }
+ } else {
+ // Steps 4-5.
+ settings = {
+ TemporalUnit::Nanosecond,
+ TemporalUnit::Day,
+ TemporalRoundingMode::Trunc,
+ Increment{1},
+ };
+ }
+
+ // Steps 6-7.
+ bool datePartsIdentical = dateTime.date() == other.date();
+
+ // Step 8.
+ if (datePartsIdentical && dateTime.time() == other.time()) {
+ auto* obj = CreateTemporalDuration(cx, {});
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+ }
+
+ // Step 9.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, dateTime.calendar(),
+ {
+ CalendarMethod::DateAdd,
+ CalendarMethod::DateUntil,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Step 10.
+ Duration diff;
+ if (!::DifferenceISODateTime(cx, dateTime, other, calendar,
+ settings.largestUnit, resolvedOptions, &diff)) {
+ return false;
+ }
+
+ // Step 11.
+ bool roundingGranularityIsNoop =
+ settings.smallestUnit == TemporalUnit::Nanosecond &&
+ settings.roundingIncrement == Increment{1};
+
+ // Step 12.
+ if (roundingGranularityIsNoop) {
+ if (operation == TemporalDifference::Since) {
+ diff = diff.negate();
+ }
+
+ auto* obj = CreateTemporalDuration(cx, diff);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+ }
+
+ // Step 13.
+ Rooted<PlainDateObject*> relativeTo(
+ cx, CreateTemporalDate(cx, dateTime.date(), dateTime.calendar()));
+ if (!relativeTo) {
+ return false;
+ }
+
+ // Steps 14-15.
+ Duration roundResult;
+ if (!temporal::RoundDuration(cx, diff, settings.roundingIncrement,
+ settings.smallestUnit, settings.roundingMode,
+ relativeTo, calendar, &roundResult)) {
+ return false;
+ }
+
+ // Step 16.
+ TimeDuration result;
+ if (!BalanceTimeDuration(cx, roundResult, settings.largestUnit, &result)) {
+ return false;
+ }
+
+ // Step 17.
+ auto toBalance = Duration{
+ roundResult.years,
+ roundResult.months,
+ roundResult.weeks,
+ result.days,
+ };
+ DateDuration balanceResult;
+ if (!temporal::BalanceDateDurationRelative(
+ cx, toBalance, settings.largestUnit, settings.smallestUnit,
+ relativeTo, calendar, &balanceResult)) {
+ return false;
+ }
+
+ // Step 18.
+ Duration duration = {
+ balanceResult.years, balanceResult.months, balanceResult.weeks,
+ balanceResult.days, result.hours, result.minutes,
+ result.seconds, result.milliseconds, result.microseconds,
+ result.nanoseconds,
+ };
+ if (operation == TemporalDifference::Since) {
+ duration = duration.negate();
+ }
+
+ auto* obj = CreateTemporalDuration(cx, duration);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+enum class PlainDateTimeDuration { Add, Subtract };
+
+/**
+ * AddDurationToOrSubtractDurationFromPlainDateTime ( operation, dateTime,
+ * temporalDurationLike, options )
+ */
+static bool AddDurationToOrSubtractDurationFromPlainDateTime(
+ JSContext* cx, PlainDateTimeDuration operation, const CallArgs& args) {
+ Rooted<PlainDateTimeWithCalendar> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ Duration duration;
+ if (!ToTemporalDurationRecord(cx, args.get(0), &duration)) {
+ return false;
+ }
+
+ // Step 3.
+ Rooted<JSObject*> options(cx);
+ if (args.hasDefined(1)) {
+ const char* name =
+ operation == PlainDateTimeDuration::Add ? "add" : "subtract";
+ options = RequireObjectArg(cx, "options", name, args[1]);
+ } else {
+ options = NewPlainObjectWithProto(cx, nullptr);
+ }
+ if (!options) {
+ return false;
+ }
+
+ // Step 4.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, dateTime.calendar(),
+ {
+ CalendarMethod::DateAdd,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Step 5.
+ if (operation == PlainDateTimeDuration::Subtract) {
+ duration = duration.negate();
+ }
+
+ PlainDateTime result;
+ if (!AddDateTime(cx, dateTime, calendar, duration, options, &result)) {
+ return false;
+ }
+
+ // Steps 6-7.
+ MOZ_ASSERT(IsValidISODateTime(result));
+
+ // Step 8.
+ auto* obj = CreateTemporalDateTime(cx, result, dateTime.calendar());
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime ( isoYear, isoMonth, isoDay [ , hour [ , minute [ ,
+ * second [ , millisecond [ , microsecond [ , nanosecond [ , calendarLike ] ] ]
+ * ] ] ] ] )
+ */
+static bool PlainDateTimeConstructor(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ if (!ThrowIfNotConstructing(cx, args, "Temporal.PlainDateTime")) {
+ return false;
+ }
+
+ // Step 2.
+ double isoYear;
+ if (!ToIntegerWithTruncation(cx, args.get(0), "year", &isoYear)) {
+ return false;
+ }
+
+ // Step 3.
+ double isoMonth;
+ if (!ToIntegerWithTruncation(cx, args.get(1), "month", &isoMonth)) {
+ return false;
+ }
+
+ // Step 4.
+ double isoDay;
+ if (!ToIntegerWithTruncation(cx, args.get(2), "day", &isoDay)) {
+ return false;
+ }
+
+ // Step 5.
+ double hour = 0;
+ if (args.hasDefined(3)) {
+ if (!ToIntegerWithTruncation(cx, args[3], "hour", &hour)) {
+ return false;
+ }
+ }
+
+ // Step 6.
+ double minute = 0;
+ if (args.hasDefined(4)) {
+ if (!ToIntegerWithTruncation(cx, args[4], "minute", &minute)) {
+ return false;
+ }
+ }
+
+ // Step 7.
+ double second = 0;
+ if (args.hasDefined(5)) {
+ if (!ToIntegerWithTruncation(cx, args[5], "second", &second)) {
+ return false;
+ }
+ }
+
+ // Step 8.
+ double millisecond = 0;
+ if (args.hasDefined(6)) {
+ if (!ToIntegerWithTruncation(cx, args[6], "millisecond", &millisecond)) {
+ return false;
+ }
+ }
+
+ // Step 9.
+ double microsecond = 0;
+ if (args.hasDefined(7)) {
+ if (!ToIntegerWithTruncation(cx, args[7], "microsecond", &microsecond)) {
+ return false;
+ }
+ }
+
+ // Step 10.
+ double nanosecond = 0;
+ if (args.hasDefined(8)) {
+ if (!ToIntegerWithTruncation(cx, args[8], "nanosecond", &nanosecond)) {
+ return false;
+ }
+ }
+
+ // Step 11.
+ Rooted<CalendarValue> calendar(cx);
+ if (!ToTemporalCalendarWithISODefault(cx, args.get(9), &calendar)) {
+ return false;
+ }
+
+ // Step 12.
+ auto* temporalDateTime = CreateTemporalDateTime(
+ cx, args, isoYear, isoMonth, isoDay, hour, minute, second, millisecond,
+ microsecond, nanosecond, calendar);
+ if (!temporalDateTime) {
+ return false;
+ }
+
+ args.rval().setObject(*temporalDateTime);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.from ( item [ , options ] )
+ */
+static bool PlainDateTime_from(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ Rooted<JSObject*> options(cx);
+ if (args.hasDefined(1)) {
+ options = RequireObjectArg(cx, "options", "from", args[1]);
+ if (!options) {
+ return false;
+ }
+ }
+
+ // Step 2.
+ if (args.get(0).isObject()) {
+ JSObject* item = &args[0].toObject();
+ if (auto* temporalDateTime = item->maybeUnwrapIf<PlainDateTimeObject>()) {
+ auto dateTime = ToPlainDateTime(temporalDateTime);
+
+ Rooted<CalendarValue> calendar(cx, temporalDateTime->calendar());
+ if (!calendar.wrap(cx)) {
+ return false;
+ }
+
+ if (options) {
+ // Step 2.a.
+ TemporalOverflow ignored;
+ if (!ToTemporalOverflow(cx, options, &ignored)) {
+ return false;
+ }
+ }
+
+ // Step 2.b.
+ auto* result = CreateTemporalDateTime(cx, dateTime, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+ }
+ }
+
+ // Step 3.
+ auto result = ToTemporalDateTime(cx, args.get(0), options);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.compare ( one, two )
+ */
+static bool PlainDateTime_compare(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ PlainDateTime one;
+ if (!ToTemporalDateTime(cx, args.get(0), &one)) {
+ return false;
+ }
+
+ // Step 2.
+ PlainDateTime two;
+ if (!ToTemporalDateTime(cx, args.get(1), &two)) {
+ return false;
+ }
+
+ // Step 3.
+ args.rval().setInt32(CompareISODateTime(one, two));
+ return true;
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.calendarId
+ */
+static bool PlainDateTime_calendarId(JSContext* cx, const CallArgs& args) {
+ auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+
+ // Step 3.
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+ auto* calendarId = ToTemporalCalendarIdentifier(cx, calendar);
+ if (!calendarId) {
+ return false;
+ }
+
+ args.rval().setString(calendarId);
+ return true;
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.calendarId
+ */
+static bool PlainDateTime_calendarId(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_calendarId>(cx,
+ args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.year
+ */
+static bool PlainDateTime_year(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ // Step 4.
+ return CalendarYear(cx, calendar, dateTime, args.rval());
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.year
+ */
+static bool PlainDateTime_year(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_year>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.month
+ */
+static bool PlainDateTime_month(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ // Step 4.
+ return CalendarMonth(cx, calendar, dateTime, args.rval());
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.month
+ */
+static bool PlainDateTime_month(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_month>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.monthCode
+ */
+static bool PlainDateTime_monthCode(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ // Step 4.
+ return CalendarMonthCode(cx, calendar, dateTime, args.rval());
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.monthCode
+ */
+static bool PlainDateTime_monthCode(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_monthCode>(cx,
+ args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.day
+ */
+static bool PlainDateTime_day(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ // Step 4.
+ return CalendarDay(cx, calendar, dateTime, args.rval());
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.day
+ */
+static bool PlainDateTime_day(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_day>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.hour
+ */
+static bool PlainDateTime_hour(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+ args.rval().setInt32(dateTime->isoHour());
+ return true;
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.hour
+ */
+static bool PlainDateTime_hour(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_hour>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.minute
+ */
+static bool PlainDateTime_minute(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+ args.rval().setInt32(dateTime->isoMinute());
+ return true;
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.minute
+ */
+static bool PlainDateTime_minute(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_minute>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.second
+ */
+static bool PlainDateTime_second(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+ args.rval().setInt32(dateTime->isoSecond());
+ return true;
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.second
+ */
+static bool PlainDateTime_second(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_second>(cx, args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.millisecond
+ */
+static bool PlainDateTime_millisecond(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+ args.rval().setInt32(dateTime->isoMillisecond());
+ return true;
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.millisecond
+ */
+static bool PlainDateTime_millisecond(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_millisecond>(cx,
+ args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.microsecond
+ */
+static bool PlainDateTime_microsecond(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+ args.rval().setInt32(dateTime->isoMicrosecond());
+ return true;
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.microsecond
+ */
+static bool PlainDateTime_microsecond(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_microsecond>(cx,
+ args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.nanosecond
+ */
+static bool PlainDateTime_nanosecond(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+ args.rval().setInt32(dateTime->isoNanosecond());
+ return true;
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.nanosecond
+ */
+static bool PlainDateTime_nanosecond(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_nanosecond>(cx,
+ args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.dayOfWeek
+ */
+static bool PlainDateTime_dayOfWeek(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ // Step 4.
+ return CalendarDayOfWeek(cx, calendar, dateTime, args.rval());
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.dayOfWeek
+ */
+static bool PlainDateTime_dayOfWeek(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_dayOfWeek>(cx,
+ args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.dayOfYear
+ */
+static bool PlainDateTime_dayOfYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ // Step 4.
+ return CalendarDayOfYear(cx, calendar, dateTime, args.rval());
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.dayOfYear
+ */
+static bool PlainDateTime_dayOfYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_dayOfYear>(cx,
+ args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.weekOfYear
+ */
+static bool PlainDateTime_weekOfYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ // Step 4.
+ return CalendarWeekOfYear(cx, calendar, dateTime, args.rval());
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.weekOfYear
+ */
+static bool PlainDateTime_weekOfYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_weekOfYear>(cx,
+ args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.yearOfWeek
+ */
+static bool PlainDateTime_yearOfWeek(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ // Step 4.
+ return CalendarYearOfWeek(cx, calendar, dateTime, args.rval());
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.yearOfWeek
+ */
+static bool PlainDateTime_yearOfWeek(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_yearOfWeek>(cx,
+ args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.daysInWeek
+ */
+static bool PlainDateTime_daysInWeek(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ // Step 4.
+ return CalendarDaysInWeek(cx, calendar, dateTime, args.rval());
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.daysInWeek
+ */
+static bool PlainDateTime_daysInWeek(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_daysInWeek>(cx,
+ args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.daysInMonth
+ */
+static bool PlainDateTime_daysInMonth(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ // Step 4.
+ return CalendarDaysInMonth(cx, calendar, dateTime, args.rval());
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.daysInMonth
+ */
+static bool PlainDateTime_daysInMonth(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_daysInMonth>(cx,
+ args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.daysInYear
+ */
+static bool PlainDateTime_daysInYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ // Step 4.
+ return CalendarDaysInYear(cx, calendar, dateTime, args.rval());
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.daysInYear
+ */
+static bool PlainDateTime_daysInYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_daysInYear>(cx,
+ args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.monthsInYear
+ */
+static bool PlainDateTime_monthsInYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ // Step 4.
+ return CalendarMonthsInYear(cx, calendar, dateTime, args.rval());
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.monthsInYear
+ */
+static bool PlainDateTime_monthsInYear(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_monthsInYear>(
+ cx, args);
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.inLeapYear
+ */
+static bool PlainDateTime_inLeapYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ // Step 4.
+ return CalendarInLeapYear(cx, calendar, dateTime, args.rval());
+}
+
+/**
+ * get Temporal.PlainDateTime.prototype.inLeapYear
+ */
+static bool PlainDateTime_inLeapYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_inLeapYear>(cx,
+ args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.with ( temporalDateTimeLike [ , options ] )
+ */
+static bool PlainDateTime_with(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+
+ // Step 3.
+ Rooted<JSObject*> temporalDateTimeLike(
+ cx, RequireObjectArg(cx, "temporalDateTimeLike", "with", args.get(0)));
+ if (!temporalDateTimeLike) {
+ return false;
+ }
+
+ // Step 4.
+ if (!RejectTemporalLikeObject(cx, temporalDateTimeLike)) {
+ return false;
+ }
+
+ // Step 5.
+ Rooted<PlainObject*> resolvedOptions(cx);
+ if (args.hasDefined(1)) {
+ Rooted<JSObject*> options(cx,
+ RequireObjectArg(cx, "options", "with", args[1]));
+ if (!options) {
+ return false;
+ }
+ resolvedOptions = SnapshotOwnProperties(cx, options);
+ } else {
+ resolvedOptions = NewPlainObjectWithProto(cx, nullptr);
+ }
+ if (!resolvedOptions) {
+ return false;
+ }
+
+ // Step 6.
+ Rooted<CalendarValue> calendarValue(cx, dateTime->calendar());
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendarValue,
+ {
+ CalendarMethod::DateFromFields,
+ CalendarMethod::Fields,
+ CalendarMethod::MergeFields,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Step 7.
+ JS::RootedVector<PropertyKey> fieldNames(cx);
+ if (!CalendarFields(cx, calendar,
+ {CalendarField::Day, CalendarField::Month,
+ CalendarField::MonthCode, CalendarField::Year},
+ &fieldNames)) {
+ return false;
+ }
+
+ // Step 8.
+ Rooted<PlainObject*> fields(cx,
+ PrepareTemporalFields(cx, dateTime, fieldNames));
+ if (!fields) {
+ return false;
+ }
+
+ // Steps 9-14.
+ struct TimeField {
+ using FieldName = ImmutableTenuredPtr<PropertyName*> JSAtomState::*;
+
+ FieldName name;
+ int32_t value;
+ } timeFields[] = {
+ {&JSAtomState::hour, dateTime->isoHour()},
+ {&JSAtomState::minute, dateTime->isoMinute()},
+ {&JSAtomState::second, dateTime->isoSecond()},
+ {&JSAtomState::millisecond, dateTime->isoMillisecond()},
+ {&JSAtomState::microsecond, dateTime->isoMicrosecond()},
+ {&JSAtomState::nanosecond, dateTime->isoNanosecond()},
+ };
+
+ Rooted<Value> timeFieldValue(cx);
+ for (const auto& timeField : timeFields) {
+ Handle<PropertyName*> name = cx->names().*(timeField.name);
+ timeFieldValue.setInt32(timeField.value);
+
+ if (!DefineDataProperty(cx, fields, name, timeFieldValue)) {
+ return false;
+ }
+ }
+
+ // Step 15.
+ if (!AppendSorted(cx, fieldNames.get(),
+ {
+ TemporalField::Hour,
+ TemporalField::Microsecond,
+ TemporalField::Millisecond,
+ TemporalField::Minute,
+ TemporalField::Nanosecond,
+ TemporalField::Second,
+ })) {
+ return false;
+ }
+
+ // Step 16.
+ Rooted<PlainObject*> partialDateTime(
+ cx, PreparePartialTemporalFields(cx, temporalDateTimeLike, fieldNames));
+ if (!partialDateTime) {
+ return false;
+ }
+
+ // Step 17.
+ Rooted<JSObject*> mergedFields(
+ cx, CalendarMergeFields(cx, calendar, fields, partialDateTime));
+ if (!mergedFields) {
+ return false;
+ }
+
+ // Step 18.
+ fields = PrepareTemporalFields(cx, mergedFields, fieldNames);
+ if (!fields) {
+ return false;
+ }
+
+ // Step 19.
+ PlainDateTime result;
+ if (!InterpretTemporalDateTimeFields(cx, calendar, fields, resolvedOptions,
+ &result)) {
+ return false;
+ }
+
+ // Steps 20-21.
+ MOZ_ASSERT(IsValidISODateTime(result));
+
+ // Step 22.
+ auto* obj = CreateTemporalDateTime(cx, result, calendar.receiver());
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.with ( temporalDateTimeLike [ , options ] )
+ */
+static bool PlainDateTime_with(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_with>(cx, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.withPlainTime ( [ plainTimeLike ] )
+ */
+static bool PlainDateTime_withPlainTime(JSContext* cx, const CallArgs& args) {
+ auto* temporalDateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+ auto date = ToPlainDate(temporalDateTime);
+ Rooted<CalendarValue> calendar(cx, temporalDateTime->calendar());
+
+ // Step 4.
+ PlainTime time = {};
+ if (args.hasDefined(0)) {
+ if (!ToTemporalTime(cx, args[0], &time)) {
+ return false;
+ }
+ }
+
+ // Steps 3 and 5.
+ auto* obj = CreateTemporalDateTime(cx, {date, time}, calendar);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.withPlainTime ( [ plainTimeLike ] )
+ */
+static bool PlainDateTime_withPlainTime(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_withPlainTime>(
+ cx, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.withPlainDate ( plainDateLike )
+ */
+static bool PlainDateTime_withPlainDate(JSContext* cx, const CallArgs& args) {
+ auto* temporalDateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+ auto time = ToPlainTime(temporalDateTime);
+ Rooted<CalendarValue> calendar(cx, temporalDateTime->calendar());
+
+ // Step 3.
+ Rooted<PlainDateWithCalendar> plainDate(cx);
+ if (!ToTemporalDate(cx, args.get(0), &plainDate)) {
+ return false;
+ }
+ auto date = plainDate.date();
+
+ // Step 4.
+ if (!ConsolidateCalendars(cx, calendar, plainDate.calendar(), &calendar)) {
+ return false;
+ }
+
+ // Step 5.
+ auto* obj = CreateTemporalDateTime(cx, {date, time}, calendar);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.withPlainDate ( plainDateLike )
+ */
+static bool PlainDateTime_withPlainDate(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_withPlainDate>(
+ cx, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.withCalendar ( calendar )
+ */
+static bool PlainDateTime_withCalendar(JSContext* cx, const CallArgs& args) {
+ auto* temporalDateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+ auto dateTime = ToPlainDateTime(temporalDateTime);
+
+ // Step 3.
+ Rooted<CalendarValue> calendar(cx);
+ if (!ToTemporalCalendar(cx, args.get(0), &calendar)) {
+ return false;
+ }
+
+ // Step 4.
+ auto* result = CreateTemporalDateTime(cx, dateTime, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.withCalendar ( calendar )
+ */
+static bool PlainDateTime_withCalendar(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_withCalendar>(
+ cx, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.add ( temporalDurationLike [ , options ] )
+ */
+static bool PlainDateTime_add(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ return AddDurationToOrSubtractDurationFromPlainDateTime(
+ cx, PlainDateTimeDuration::Add, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.add ( temporalDurationLike [ , options ] )
+ */
+static bool PlainDateTime_add(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_add>(cx, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.subtract ( temporalDurationLike [ , options
+ * ] )
+ */
+static bool PlainDateTime_subtract(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ return AddDurationToOrSubtractDurationFromPlainDateTime(
+ cx, PlainDateTimeDuration::Subtract, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.subtract ( temporalDurationLike [ , options
+ * ] )
+ */
+static bool PlainDateTime_subtract(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_subtract>(cx,
+ args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.until ( other [ , options ] )
+ */
+static bool PlainDateTime_until(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ return DifferenceTemporalPlainDateTime(cx, TemporalDifference::Until, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.until ( other [ , options ] )
+ */
+static bool PlainDateTime_until(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_until>(cx, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.since ( other [ , options ] )
+ */
+static bool PlainDateTime_since(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ return DifferenceTemporalPlainDateTime(cx, TemporalDifference::Since, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.since ( other [ , options ] )
+ */
+static bool PlainDateTime_since(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_since>(cx, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.round ( roundTo )
+ */
+static bool PlainDateTime_round(JSContext* cx, const CallArgs& args) {
+ auto* temporalDateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+ auto dateTime = ToPlainDateTime(temporalDateTime);
+ Rooted<CalendarValue> calendar(cx, temporalDateTime->calendar());
+
+ // Steps 3-12.
+ auto smallestUnit = TemporalUnit::Auto;
+ auto roundingMode = TemporalRoundingMode::HalfExpand;
+ auto roundingIncrement = Increment{1};
+ if (args.get(0).isString()) {
+ // Step 4. (Not applicable in our implementation.)
+
+ // Step 9.
+ Rooted<JSString*> paramString(cx, args[0].toString());
+ if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::SmallestUnit,
+ TemporalUnitGroup::DayTime, &smallestUnit)) {
+ return false;
+ }
+
+ MOZ_ASSERT(TemporalUnit::Day <= smallestUnit &&
+ smallestUnit <= TemporalUnit::Nanosecond);
+
+ // Steps 6-8 and 10-12. (Implicit)
+ } else {
+ // Steps 3 and 5.
+ Rooted<JSObject*> roundTo(
+ cx, RequireObjectArg(cx, "roundTo", "round", args.get(0)));
+ if (!roundTo) {
+ return false;
+ }
+
+ // Steps 6-7.
+ if (!ToTemporalRoundingIncrement(cx, roundTo, &roundingIncrement)) {
+ return false;
+ }
+
+ // Step 8.
+ if (!ToTemporalRoundingMode(cx, roundTo, &roundingMode)) {
+ return false;
+ }
+
+ // Step 9.
+ if (!GetTemporalUnit(cx, roundTo, TemporalUnitKey::SmallestUnit,
+ TemporalUnitGroup::DayTime, &smallestUnit)) {
+ return false;
+ }
+
+ if (smallestUnit == TemporalUnit::Auto) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_MISSING_OPTION, "smallestUnit");
+ return false;
+ }
+
+ MOZ_ASSERT(TemporalUnit::Day <= smallestUnit &&
+ smallestUnit <= TemporalUnit::Nanosecond);
+
+ // Steps 10-11.
+ auto maximum = Increment{1};
+ bool inclusive = true;
+ if (smallestUnit > TemporalUnit::Day) {
+ maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
+ inclusive = false;
+ }
+
+ // Step 12.
+ if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
+ inclusive)) {
+ return false;
+ }
+ }
+
+ // Step 13.
+ if (smallestUnit == TemporalUnit::Nanosecond &&
+ roundingIncrement == Increment{1}) {
+ auto* obj = CreateTemporalDateTime(cx, dateTime, calendar);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+ }
+
+ // Step 14.
+ auto result =
+ RoundISODateTime(dateTime, roundingIncrement, smallestUnit, roundingMode);
+
+ // Step 15.
+ auto* obj = CreateTemporalDateTime(cx, result, calendar);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.round ( roundTo )
+ */
+static bool PlainDateTime_round(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_round>(cx, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.equals ( other )
+ */
+static bool PlainDateTime_equals(JSContext* cx, const CallArgs& args) {
+ auto* temporalDateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+ auto dateTime = ToPlainDateTime(temporalDateTime);
+ Rooted<CalendarValue> calendar(cx, temporalDateTime->calendar());
+
+ // Step 3.
+ Rooted<PlainDateTimeWithCalendar> other(cx);
+ if (!::ToTemporalDateTime(cx, args.get(0), &other)) {
+ return false;
+ }
+
+ // Steps 4-13.
+ bool equals = dateTime == other.dateTime();
+ if (equals && !CalendarEquals(cx, calendar, other.calendar(), &equals)) {
+ return false;
+ }
+
+ args.rval().setBoolean(equals);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.equals ( other )
+ */
+static bool PlainDateTime_equals(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_equals>(cx, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.toString ( [ options ] )
+ */
+static bool PlainDateTime_toString(JSContext* cx, const CallArgs& args) {
+ auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+ auto dt = ToPlainDateTime(dateTime);
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ SecondsStringPrecision precision = {Precision::Auto(),
+ TemporalUnit::Nanosecond, Increment{1}};
+ auto roundingMode = TemporalRoundingMode::Trunc;
+ auto showCalendar = CalendarOption::Auto;
+ if (args.hasDefined(0)) {
+ // Step 3.
+ Rooted<JSObject*> options(
+ cx, RequireObjectArg(cx, "options", "toString", args[0]));
+ if (!options) {
+ return false;
+ }
+
+ // Steps 4-5.
+ if (!ToCalendarNameOption(cx, options, &showCalendar)) {
+ return false;
+ }
+
+ // Step 6.
+ auto digits = Precision::Auto();
+ if (!ToFractionalSecondDigits(cx, options, &digits)) {
+ return false;
+ }
+
+ // Step 7.
+ if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
+ return false;
+ }
+
+ // Step 8.
+ auto smallestUnit = TemporalUnit::Auto;
+ if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
+ TemporalUnitGroup::Time, &smallestUnit)) {
+ return false;
+ }
+
+ // Step 9.
+ if (smallestUnit == TemporalUnit::Hour) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_UNIT_OPTION, "hour",
+ "smallestUnit");
+ return false;
+ }
+
+ // Step 10.
+ precision = ToSecondsStringPrecision(smallestUnit, digits);
+ }
+
+ // Step 11.
+ auto result =
+ RoundISODateTime(dt, precision.increment, precision.unit, roundingMode);
+
+ // Step 12.
+ JSString* str = ::TemporalDateTimeToString(cx, result, calendar,
+ precision.precision, showCalendar);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.toString ( [ options ] )
+ */
+static bool PlainDateTime_toString(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_toString>(cx,
+ args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.toLocaleString ( [ locales [ , options ] ] )
+ */
+static bool PlainDateTime_toLocaleString(JSContext* cx, const CallArgs& args) {
+ auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+ auto dt = ToPlainDateTime(dateTime);
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ // Step 3.
+ JSString* str = ::TemporalDateTimeToString(
+ cx, dt, calendar, Precision::Auto(), CalendarOption::Auto);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.toLocaleString ( [ locales [ , options ] ] )
+ */
+static bool PlainDateTime_toLocaleString(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_toLocaleString>(
+ cx, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.toJSON ( )
+ */
+static bool PlainDateTime_toJSON(JSContext* cx, const CallArgs& args) {
+ auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+ auto dt = ToPlainDateTime(dateTime);
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ // Step 3.
+ JSString* str = ::TemporalDateTimeToString(
+ cx, dt, calendar, Precision::Auto(), CalendarOption::Auto);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.toJSON ( )
+ */
+static bool PlainDateTime_toJSON(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_toJSON>(cx, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.valueOf ( )
+ */
+static bool PlainDateTime_valueOf(JSContext* cx, unsigned argc, Value* vp) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
+ "PlainDateTime", "primitive type");
+ return false;
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.getISOFields ( )
+ */
+static bool PlainDateTime_getISOFields(JSContext* cx, const CallArgs& args) {
+ auto* temporalDateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+ auto dateTime = ToPlainDateTime(temporalDateTime);
+ auto calendar = temporalDateTime->calendar();
+
+ // Step 3.
+ Rooted<IdValueVector> fields(cx, IdValueVector(cx));
+
+ // Step 4.
+ if (!fields.emplaceBack(NameToId(cx->names().calendar), calendar.toValue())) {
+ return false;
+ }
+
+ // Step 5.
+ if (!fields.emplaceBack(NameToId(cx->names().isoDay),
+ Int32Value(dateTime.date.day))) {
+ return false;
+ }
+
+ // Step 6.
+ if (!fields.emplaceBack(NameToId(cx->names().isoHour),
+ Int32Value(dateTime.time.hour))) {
+ return false;
+ }
+
+ // Step 7.
+ if (!fields.emplaceBack(NameToId(cx->names().isoMicrosecond),
+ Int32Value(dateTime.time.microsecond))) {
+ return false;
+ }
+
+ // Step 8.
+ if (!fields.emplaceBack(NameToId(cx->names().isoMillisecond),
+ Int32Value(dateTime.time.millisecond))) {
+ return false;
+ }
+
+ // Step 9.
+ if (!fields.emplaceBack(NameToId(cx->names().isoMinute),
+ Int32Value(dateTime.time.minute))) {
+ return false;
+ }
+
+ // Step 10.
+ if (!fields.emplaceBack(NameToId(cx->names().isoMonth),
+ Int32Value(dateTime.date.month))) {
+ return false;
+ }
+
+ // Step 11.
+ if (!fields.emplaceBack(NameToId(cx->names().isoNanosecond),
+ Int32Value(dateTime.time.nanosecond))) {
+ return false;
+ }
+
+ // Step 12.
+ if (!fields.emplaceBack(NameToId(cx->names().isoSecond),
+ Int32Value(dateTime.time.second))) {
+ return false;
+ }
+
+ // Step 13.
+ if (!fields.emplaceBack(NameToId(cx->names().isoYear),
+ Int32Value(dateTime.date.year))) {
+ return false;
+ }
+
+ // Step 14.
+ auto* obj =
+ NewPlainObjectWithUniqueNames(cx, fields.begin(), fields.length());
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.getISOFields ( )
+ */
+static bool PlainDateTime_getISOFields(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_getISOFields>(
+ cx, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.getCalendar ( )
+ */
+static bool PlainDateTime_getCalendar(JSContext* cx, const CallArgs& args) {
+ auto* temporalDateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+ Rooted<CalendarValue> calendar(cx, temporalDateTime->calendar());
+
+ // Step 3.
+ auto* obj = ToTemporalCalendarObject(cx, calendar);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.getCalendar ( )
+ */
+static bool PlainDateTime_getCalendar(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_getCalendar>(cx,
+ args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.toZonedDateTime ( temporalTimeZoneLike [ ,
+ * options ] )
+ */
+static bool PlainDateTime_toZonedDateTime(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ // Step 3.
+ Rooted<TimeZoneValue> timeZone(cx);
+ if (!ToTemporalTimeZone(cx, args.get(0), &timeZone)) {
+ return false;
+ }
+
+ auto disambiguation = TemporalDisambiguation::Compatible;
+ if (args.hasDefined(1)) {
+ // Step 4.
+ Rooted<JSObject*> options(
+ cx, RequireObjectArg(cx, "options", "toZonedDateTime", args[1]));
+ if (!options) {
+ return false;
+ }
+
+ // Step 5.
+ if (!ToTemporalDisambiguation(cx, options, &disambiguation)) {
+ return false;
+ }
+ }
+
+ // Steps 6-7.
+ Instant instant;
+ if (!GetInstantFor(cx, timeZone, dateTime, disambiguation, &instant)) {
+ return false;
+ }
+
+ // Step 8.
+ auto* result = CreateTemporalZonedDateTime(cx, instant, timeZone, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.toZonedDateTime ( temporalTimeZoneLike [ ,
+ * options ] )
+ */
+static bool PlainDateTime_toZonedDateTime(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_toZonedDateTime>(
+ cx, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.toPlainDate ( )
+ */
+static bool PlainDateTime_toPlainDate(JSContext* cx, const CallArgs& args) {
+ auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+ Rooted<CalendarValue> calendar(cx, dateTime->calendar());
+
+ // Step 3.
+ auto* obj = CreateTemporalDate(cx, ToPlainDate(dateTime), calendar);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.toPlainDate ( )
+ */
+static bool PlainDateTime_toPlainDate(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_toPlainDate>(cx,
+ args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.toPlainYearMonth ( )
+ */
+static bool PlainDateTime_toPlainYearMonth(JSContext* cx,
+ const CallArgs& args) {
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+ Rooted<CalendarValue> calendarValue(cx, dateTime->calendar());
+
+ // Step 3.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendarValue,
+ {
+ CalendarMethod::Fields,
+ CalendarMethod::YearMonthFromFields,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Step 4.
+ JS::RootedVector<PropertyKey> fieldNames(cx);
+ if (!CalendarFields(cx, calendar,
+ {CalendarField::MonthCode, CalendarField::Year},
+ &fieldNames)) {
+ return false;
+ }
+
+ // Step 4.
+ Rooted<PlainObject*> fields(cx,
+ PrepareTemporalFields(cx, dateTime, fieldNames));
+ if (!fields) {
+ return false;
+ }
+
+ // Step 5.
+ auto obj = CalendarYearMonthFromFields(cx, calendar, fields);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.toPlainYearMonth ( )
+ */
+static bool PlainDateTime_toPlainYearMonth(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_toPlainYearMonth>(
+ cx, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.toPlainMonthDay ( )
+ */
+static bool PlainDateTime_toPlainMonthDay(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx, &args.thisv().toObject().as<PlainDateTimeObject>());
+ Rooted<CalendarValue> calendarValue(cx, dateTime->calendar());
+
+ // Step 3.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendarValue,
+ {
+ CalendarMethod::Fields,
+ CalendarMethod::MonthDayFromFields,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Step 4.
+ JS::RootedVector<PropertyKey> fieldNames(cx);
+ if (!CalendarFields(cx, calendar,
+ {CalendarField::Day, CalendarField::MonthCode},
+ &fieldNames)) {
+ return false;
+ }
+
+ // Step 5.
+ Rooted<PlainObject*> fields(cx,
+ PrepareTemporalFields(cx, dateTime, fieldNames));
+ if (!fields) {
+ return false;
+ }
+
+ // Steps 6-7.
+ auto obj = CalendarMonthDayFromFields(cx, calendar, fields);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.toPlainMonthDay ( )
+ */
+static bool PlainDateTime_toPlainMonthDay(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_toPlainMonthDay>(
+ cx, args);
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.toPlainTime ( )
+ */
+static bool PlainDateTime_toPlainTime(JSContext* cx, const CallArgs& args) {
+ auto* dateTime = &args.thisv().toObject().as<PlainDateTimeObject>();
+
+ // Step 3.
+ auto* obj = CreateTemporalTime(cx, ToPlainTime(dateTime));
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainDateTime.prototype.toPlainTime ( )
+ */
+static bool PlainDateTime_toPlainTime(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainDateTime, PlainDateTime_toPlainTime>(cx,
+ args);
+}
+
+const JSClass PlainDateTimeObject::class_ = {
+ "Temporal.PlainDateTime",
+ JSCLASS_HAS_RESERVED_SLOTS(PlainDateTimeObject::SLOT_COUNT) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_PlainDateTime),
+ JS_NULL_CLASS_OPS,
+ &PlainDateTimeObject::classSpec_,
+};
+
+const JSClass& PlainDateTimeObject::protoClass_ = PlainObject::class_;
+
+static const JSFunctionSpec PlainDateTime_methods[] = {
+ JS_FN("from", PlainDateTime_from, 1, 0),
+ JS_FN("compare", PlainDateTime_compare, 2, 0),
+ JS_FS_END,
+};
+
+static const JSFunctionSpec PlainDateTime_prototype_methods[] = {
+ JS_FN("with", PlainDateTime_with, 1, 0),
+ JS_FN("withPlainTime", PlainDateTime_withPlainTime, 0, 0),
+ JS_FN("withPlainDate", PlainDateTime_withPlainDate, 1, 0),
+ JS_FN("withCalendar", PlainDateTime_withCalendar, 1, 0),
+ JS_FN("add", PlainDateTime_add, 1, 0),
+ JS_FN("subtract", PlainDateTime_subtract, 1, 0),
+ JS_FN("until", PlainDateTime_until, 1, 0),
+ JS_FN("since", PlainDateTime_since, 1, 0),
+ JS_FN("round", PlainDateTime_round, 1, 0),
+ JS_FN("equals", PlainDateTime_equals, 1, 0),
+ JS_FN("toString", PlainDateTime_toString, 0, 0),
+ JS_FN("toLocaleString", PlainDateTime_toLocaleString, 0, 0),
+ JS_FN("toJSON", PlainDateTime_toJSON, 0, 0),
+ JS_FN("valueOf", PlainDateTime_valueOf, 0, 0),
+ JS_FN("toZonedDateTime", PlainDateTime_toZonedDateTime, 1, 0),
+ JS_FN("toPlainDate", PlainDateTime_toPlainDate, 0, 0),
+ JS_FN("toPlainYearMonth", PlainDateTime_toPlainYearMonth, 0, 0),
+ JS_FN("toPlainMonthDay", PlainDateTime_toPlainMonthDay, 0, 0),
+ JS_FN("toPlainTime", PlainDateTime_toPlainTime, 0, 0),
+ JS_FN("getISOFields", PlainDateTime_getISOFields, 0, 0),
+ JS_FN("getCalendar", PlainDateTime_getCalendar, 0, 0),
+ JS_FS_END,
+};
+
+static const JSPropertySpec PlainDateTime_prototype_properties[] = {
+ JS_PSG("calendarId", PlainDateTime_calendarId, 0),
+ JS_PSG("year", PlainDateTime_year, 0),
+ JS_PSG("month", PlainDateTime_month, 0),
+ JS_PSG("monthCode", PlainDateTime_monthCode, 0),
+ JS_PSG("day", PlainDateTime_day, 0),
+ JS_PSG("hour", PlainDateTime_hour, 0),
+ JS_PSG("minute", PlainDateTime_minute, 0),
+ JS_PSG("second", PlainDateTime_second, 0),
+ JS_PSG("millisecond", PlainDateTime_millisecond, 0),
+ JS_PSG("microsecond", PlainDateTime_microsecond, 0),
+ JS_PSG("nanosecond", PlainDateTime_nanosecond, 0),
+ JS_PSG("dayOfWeek", PlainDateTime_dayOfWeek, 0),
+ JS_PSG("dayOfYear", PlainDateTime_dayOfYear, 0),
+ JS_PSG("weekOfYear", PlainDateTime_weekOfYear, 0),
+ JS_PSG("yearOfWeek", PlainDateTime_yearOfWeek, 0),
+ JS_PSG("daysInWeek", PlainDateTime_daysInWeek, 0),
+ JS_PSG("daysInMonth", PlainDateTime_daysInMonth, 0),
+ JS_PSG("daysInYear", PlainDateTime_daysInYear, 0),
+ JS_PSG("monthsInYear", PlainDateTime_monthsInYear, 0),
+ JS_PSG("inLeapYear", PlainDateTime_inLeapYear, 0),
+ JS_STRING_SYM_PS(toStringTag, "Temporal.PlainDateTime", JSPROP_READONLY),
+ JS_PS_END,
+};
+
+const ClassSpec PlainDateTimeObject::classSpec_ = {
+ GenericCreateConstructor<PlainDateTimeConstructor, 3,
+ gc::AllocKind::FUNCTION>,
+ GenericCreatePrototype<PlainDateTimeObject>,
+ PlainDateTime_methods,
+ nullptr,
+ PlainDateTime_prototype_methods,
+ PlainDateTime_prototype_properties,
+ nullptr,
+ ClassSpec::DontDefineConstructor,
+};
diff --git a/js/src/builtin/temporal/PlainDateTime.h b/js/src/builtin/temporal/PlainDateTime.h
new file mode 100644
index 0000000000..3546fca903
--- /dev/null
+++ b/js/src/builtin/temporal/PlainDateTime.h
@@ -0,0 +1,263 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_PlainDateTime_h
+#define builtin_temporal_PlainDateTime_h
+
+#include "mozilla/Assertions.h"
+
+#include <stdint.h>
+
+#include "builtin/temporal/Calendar.h"
+#include "builtin/temporal/TemporalTypes.h"
+#include "builtin/temporal/Wrapped.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "vm/NativeObject.h"
+
+class JS_PUBLIC_API JSTracer;
+
+namespace js {
+struct ClassSpec;
+class PlainObject;
+} // namespace js
+
+namespace js::temporal {
+
+class PlainDateTimeObject : public NativeObject {
+ public:
+ static const JSClass class_;
+ static const JSClass& protoClass_;
+
+ // TODO: Consider compacting fields to reduce object size.
+ //
+ // See also PlainDateObject and PlainTimeObject.
+
+ static constexpr uint32_t ISO_YEAR_SLOT = 0;
+ static constexpr uint32_t ISO_MONTH_SLOT = 1;
+ static constexpr uint32_t ISO_DAY_SLOT = 2;
+ static constexpr uint32_t ISO_HOUR_SLOT = 3;
+ static constexpr uint32_t ISO_MINUTE_SLOT = 4;
+ static constexpr uint32_t ISO_SECOND_SLOT = 5;
+ static constexpr uint32_t ISO_MILLISECOND_SLOT = 6;
+ static constexpr uint32_t ISO_MICROSECOND_SLOT = 7;
+ static constexpr uint32_t ISO_NANOSECOND_SLOT = 8;
+ static constexpr uint32_t CALENDAR_SLOT = 9;
+ static constexpr uint32_t SLOT_COUNT = 10;
+
+ int32_t isoYear() const { return getFixedSlot(ISO_YEAR_SLOT).toInt32(); }
+
+ int32_t isoMonth() const { return getFixedSlot(ISO_MONTH_SLOT).toInt32(); }
+
+ int32_t isoDay() const { return getFixedSlot(ISO_DAY_SLOT).toInt32(); }
+
+ int32_t isoHour() const { return getFixedSlot(ISO_HOUR_SLOT).toInt32(); }
+
+ int32_t isoMinute() const { return getFixedSlot(ISO_MINUTE_SLOT).toInt32(); }
+
+ int32_t isoSecond() const { return getFixedSlot(ISO_SECOND_SLOT).toInt32(); }
+
+ int32_t isoMillisecond() const {
+ return getFixedSlot(ISO_MILLISECOND_SLOT).toInt32();
+ }
+
+ int32_t isoMicrosecond() const {
+ return getFixedSlot(ISO_MICROSECOND_SLOT).toInt32();
+ }
+
+ int32_t isoNanosecond() const {
+ return getFixedSlot(ISO_NANOSECOND_SLOT).toInt32();
+ }
+
+ CalendarValue calendar() const {
+ return CalendarValue(getFixedSlot(CALENDAR_SLOT));
+ }
+
+ private:
+ static const ClassSpec classSpec_;
+};
+
+/**
+ * Extract the date fields from the PlainDateTime object.
+ */
+inline PlainDate ToPlainDate(const PlainDateTimeObject* dateTime) {
+ return {dateTime->isoYear(), dateTime->isoMonth(), dateTime->isoDay()};
+}
+
+/**
+ * Extract the time fields from the PlainDateTime object.
+ */
+inline PlainTime ToPlainTime(const PlainDateTimeObject* dateTime) {
+ return {dateTime->isoHour(), dateTime->isoMinute(),
+ dateTime->isoSecond(), dateTime->isoMillisecond(),
+ dateTime->isoMicrosecond(), dateTime->isoNanosecond()};
+}
+
+/**
+ * Extract the date-time fields from the PlainDateTime object.
+ */
+inline PlainDateTime ToPlainDateTime(const PlainDateTimeObject* dateTime) {
+ return {ToPlainDate(dateTime), ToPlainTime(dateTime)};
+}
+
+enum class TemporalUnit;
+
+#ifdef DEBUG
+/**
+ * IsValidISODateTime ( year, month, day, hour, minute, second, millisecond,
+ * microsecond, nanosecond )
+ */
+bool IsValidISODateTime(const PlainDateTime& dateTime);
+#endif
+
+/**
+ * ISODateTimeWithinLimits ( year, month, day, hour, minute, second,
+ * millisecond, microsecond, nanosecond )
+ */
+bool ISODateTimeWithinLimits(const PlainDateTime& dateTime);
+
+/**
+ * ISODateTimeWithinLimits ( year, month, day, hour, minute, second,
+ * millisecond, microsecond, nanosecond )
+ */
+bool ISODateTimeWithinLimits(const PlainDate& date);
+
+/**
+ * ISODateTimeWithinLimits ( year, month, day, hour, minute, second,
+ * millisecond, microsecond, nanosecond )
+ */
+bool ISODateTimeWithinLimits(double year, double month, double day);
+
+/**
+ * CreateTemporalDateTime ( isoYear, isoMonth, isoDay, hour, minute, second,
+ * millisecond, microsecond, nanosecond, calendar [ , newTarget ] )
+ */
+PlainDateTimeObject* CreateTemporalDateTime(JSContext* cx,
+ const PlainDateTime& dateTime,
+ JS::Handle<CalendarValue> calendar);
+
+/**
+ * ToTemporalDateTime ( item [ , options ] )
+ */
+Wrapped<PlainDateTimeObject*> ToTemporalDateTime(JSContext* cx,
+ JS::Handle<JS::Value> item);
+
+/**
+ * ToTemporalDateTime ( item [ , options ] )
+ */
+bool ToTemporalDateTime(JSContext* cx, JS::Handle<JS::Value> item,
+ PlainDateTime* result);
+
+/**
+ * InterpretTemporalDateTimeFields ( calendarRec, fields, options )
+ */
+bool InterpretTemporalDateTimeFields(JSContext* cx,
+ JS::Handle<CalendarRecord> calendar,
+ JS::Handle<PlainObject*> fields,
+ JS::Handle<PlainObject*> options,
+ PlainDateTime* result);
+
+/**
+ * InterpretTemporalDateTimeFields ( calendarRec, fields, options )
+ */
+bool InterpretTemporalDateTimeFields(JSContext* cx,
+ JS::Handle<CalendarRecord> calendar,
+ JS::Handle<PlainObject*> fields,
+ PlainDateTime* result);
+
+/**
+ * DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2,
+ * d2, h2, min2, s2, ms2, mus2, ns2, calendarRec, largestUnit, options )
+ */
+bool DifferenceISODateTime(JSContext* cx, const PlainDateTime& one,
+ const PlainDateTime& two,
+ JS::Handle<CalendarRecord> calendar,
+ TemporalUnit largestUnit, Duration* result);
+
+/**
+ * DifferenceISODateTime ( y1, mon1, d1, h1, min1, s1, ms1, mus1, ns1, y2, mon2,
+ * d2, h2, min2, s2, ms2, mus2, ns2, calendarRec, largestUnit, options )
+ */
+bool DifferenceISODateTime(JSContext* cx, const PlainDateTime& one,
+ const PlainDateTime& two,
+ JS::Handle<CalendarRecord> calendar,
+ TemporalUnit largestUnit,
+ JS::Handle<PlainObject*> options, Duration* result);
+
+class PlainDateTimeWithCalendar {
+ PlainDateTime dateTime_;
+ CalendarValue calendar_;
+
+ public:
+ PlainDateTimeWithCalendar() = default;
+
+ PlainDateTimeWithCalendar(const PlainDateTime& dateTime,
+ const CalendarValue& calendar)
+ : dateTime_(dateTime), calendar_(calendar) {
+ MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
+ }
+
+ explicit PlainDateTimeWithCalendar(const PlainDateTimeObject* dateTime)
+ : PlainDateTimeWithCalendar(ToPlainDateTime(dateTime),
+ dateTime->calendar()) {}
+
+ const auto& dateTime() const { return dateTime_; }
+ const auto& date() const { return dateTime_.date; }
+ const auto& time() const { return dateTime_.time; }
+ const auto& calendar() const { return calendar_; }
+
+ // Allow implicit conversion to a calendar-less PlainDateTime.
+ operator const PlainDateTime&() const { return dateTime(); }
+
+ void trace(JSTracer* trc) { calendar_.trace(trc); }
+
+ const auto* calendarDoNotUse() const { return &calendar_; }
+};
+
+/**
+ * Extract the date-time fields from the PlainDateTimeWithCalendar object.
+ */
+inline const auto& ToPlainDateTime(const PlainDateTimeWithCalendar& dateTime) {
+ return dateTime.dateTime();
+}
+
+/**
+ * CreateTemporalDateTime ( isoYear, isoMonth, isoDay, hour, minute, second,
+ * millisecond, microsecond, nanosecond, calendar [ , newTarget ] )
+ */
+bool CreateTemporalDateTime(
+ JSContext* cx, const PlainDateTime& dateTime,
+ JS::Handle<CalendarValue> calendar,
+ JS::MutableHandle<PlainDateTimeWithCalendar> result);
+
+} /* namespace js::temporal */
+
+namespace js {
+
+template <typename Wrapper>
+class WrappedPtrOperations<temporal::PlainDateTimeWithCalendar, Wrapper> {
+ const auto& container() const {
+ return static_cast<const Wrapper*>(this)->get();
+ }
+
+ public:
+ const auto& dateTime() const { return container().dateTime(); }
+ const auto& date() const { return container().date(); }
+ const auto& time() const { return container().time(); }
+
+ auto calendar() const {
+ return JS::Handle<temporal::CalendarValue>::fromMarkedLocation(
+ container().calendarDoNotUse());
+ }
+
+ // Allow implicit conversion to a calendar-less PlainDateTime.
+ operator const temporal::PlainDateTime&() const { return dateTime(); }
+};
+
+} // namespace js
+
+#endif /* builtin_temporal_PlainDateTime_h */
diff --git a/js/src/builtin/temporal/PlainMonthDay.cpp b/js/src/builtin/temporal/PlainMonthDay.cpp
new file mode 100644
index 0000000000..f97b7ad68c
--- /dev/null
+++ b/js/src/builtin/temporal/PlainMonthDay.cpp
@@ -0,0 +1,1000 @@
+/* -*- 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/PlainMonthDay.h"
+
+#include "mozilla/Assertions.h"
+
+#include <utility>
+
+#include "jsnum.h"
+#include "jspubtd.h"
+#include "NamespaceImports.h"
+
+#include "builtin/temporal/Calendar.h"
+#include "builtin/temporal/PlainDate.h"
+#include "builtin/temporal/PlainDateTime.h"
+#include "builtin/temporal/PlainYearMonth.h"
+#include "builtin/temporal/Temporal.h"
+#include "builtin/temporal/TemporalFields.h"
+#include "builtin/temporal/TemporalParser.h"
+#include "builtin/temporal/TemporalTypes.h"
+#include "builtin/temporal/ToString.h"
+#include "builtin/temporal/Wrapped.h"
+#include "builtin/temporal/ZonedDateTime.h"
+#include "ds/IdValuePair.h"
+#include "gc/AllocKind.h"
+#include "gc/Barrier.h"
+#include "js/AllocPolicy.h"
+#include "js/CallArgs.h"
+#include "js/CallNonGenericMethod.h"
+#include "js/Class.h"
+#include "js/ErrorReport.h"
+#include "js/friend/ErrorMessages.h"
+#include "js/GCVector.h"
+#include "js/Id.h"
+#include "js/PropertyDescriptor.h"
+#include "js/PropertySpec.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/GlobalObject.h"
+#include "vm/JSAtomState.h"
+#include "vm/JSContext.h"
+#include "vm/JSObject.h"
+#include "vm/PlainObject.h"
+#include "vm/StringType.h"
+
+#include "vm/JSObject-inl.h"
+#include "vm/NativeObject-inl.h"
+#include "vm/ObjectOperations-inl.h"
+
+using namespace js;
+using namespace js::temporal;
+
+static inline bool IsPlainMonthDay(Handle<Value> v) {
+ return v.isObject() && v.toObject().is<PlainMonthDayObject>();
+}
+
+/**
+ * CreateTemporalMonthDay ( isoMonth, isoDay, calendar, referenceISOYear [ ,
+ * newTarget ] )
+ */
+static PlainMonthDayObject* CreateTemporalMonthDay(
+ 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_MONTH_DAY_INVALID);
+ return nullptr;
+ }
+
+ // Steps 3-4.
+ Rooted<JSObject*> proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_PlainMonthDay,
+ &proto)) {
+ return nullptr;
+ }
+
+ auto* obj = NewObjectWithClassProto<PlainMonthDayObject>(cx, proto);
+ if (!obj) {
+ return nullptr;
+ }
+
+ // Step 5.
+ obj->setFixedSlot(PlainMonthDayObject::ISO_MONTH_SLOT, Int32Value(isoMonth));
+
+ // Step 6.
+ obj->setFixedSlot(PlainMonthDayObject::ISO_DAY_SLOT, Int32Value(isoDay));
+
+ // Step 7.
+ obj->setFixedSlot(PlainMonthDayObject::CALENDAR_SLOT, calendar.toValue());
+
+ // Step 8.
+ obj->setFixedSlot(PlainMonthDayObject::ISO_YEAR_SLOT, Int32Value(isoYear));
+
+ // Step 9.
+ return obj;
+}
+
+/**
+ * CreateTemporalMonthDay ( isoMonth, isoDay, calendar, referenceISOYear [ ,
+ * newTarget ] )
+ */
+PlainMonthDayObject* js::temporal::CreateTemporalMonthDay(
+ 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_MONTH_DAY_INVALID);
+ return nullptr;
+ }
+
+ // Steps 3-4.
+ auto* obj = NewBuiltinClassInstance<PlainMonthDayObject>(cx);
+ if (!obj) {
+ return nullptr;
+ }
+
+ // Step 5.
+ obj->setFixedSlot(PlainMonthDayObject::ISO_MONTH_SLOT, Int32Value(isoMonth));
+
+ // Step 6.
+ obj->setFixedSlot(PlainMonthDayObject::ISO_DAY_SLOT, Int32Value(isoDay));
+
+ // Step 7.
+ obj->setFixedSlot(PlainMonthDayObject::CALENDAR_SLOT, calendar.toValue());
+
+ // Step 8.
+ obj->setFixedSlot(PlainMonthDayObject::ISO_YEAR_SLOT, Int32Value(isoYear));
+
+ // Step 9.
+ return obj;
+}
+
+template <typename T, typename... Ts>
+static bool ToTemporalCalendarForMonthDay(JSContext* cx,
+ Handle<JSObject*> object,
+ MutableHandle<CalendarValue> result) {
+ if (auto* unwrapped = object->maybeUnwrapIf<T>()) {
+ result.set(unwrapped->calendar());
+ return result.wrap(cx);
+ }
+
+ if constexpr (sizeof...(Ts) > 0) {
+ return ToTemporalCalendarForMonthDay<Ts...>(cx, object, result);
+ }
+
+ result.set(CalendarValue());
+ return true;
+}
+
+/**
+ * ToTemporalMonthDay ( item [ , options ] )
+ */
+static Wrapped<PlainMonthDayObject*> ToTemporalMonthDay(
+ JSContext* cx, Handle<Value> item,
+ Handle<JSObject*> maybeOptions = nullptr) {
+ // 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());
+
+ // Step 3.a.
+ if (itemObj->canUnwrapAs<PlainMonthDayObject>()) {
+ return itemObj;
+ }
+
+ // Steps 3.b-c.
+ Rooted<CalendarValue> calendarValue(cx);
+ if (!::ToTemporalCalendarForMonthDay<PlainDateObject, PlainDateTimeObject,
+ PlainYearMonthObject,
+ ZonedDateTimeObject>(cx, itemObj,
+ &calendarValue)) {
+ return nullptr;
+ }
+ if (!calendarValue) {
+ // Step 3.c.i.
+ Rooted<Value> calendarLike(cx);
+ if (!GetProperty(cx, itemObj, itemObj, cx->names().calendar,
+ &calendarLike)) {
+ return nullptr;
+ }
+
+ // Step 3.c.ii.
+ if (!ToTemporalCalendarWithISODefault(cx, calendarLike, &calendarValue)) {
+ return nullptr;
+ }
+ }
+
+ // Step 3.d.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendarValue,
+ {
+ CalendarMethod::Fields,
+ CalendarMethod::MonthDayFromFields,
+ },
+ &calendar)) {
+ return nullptr;
+ }
+
+ // Step 3.e.
+ JS::RootedVector<PropertyKey> fieldNames(cx);
+ if (!CalendarFields(cx, calendar,
+ {CalendarField::Day, CalendarField::Month,
+ CalendarField::MonthCode, CalendarField::Year},
+ &fieldNames)) {
+ return nullptr;
+ }
+
+ // Step 3.f.
+ Rooted<PlainObject*> fields(cx,
+ PrepareTemporalFields(cx, itemObj, fieldNames));
+ if (!fields) {
+ return nullptr;
+ }
+
+ // Step 3.g.
+ if (maybeResolvedOptions) {
+ return js::temporal::CalendarMonthDayFromFields(cx, calendar, fields,
+ maybeResolvedOptions);
+ }
+ return js::temporal::CalendarMonthDayFromFields(cx, calendar, fields);
+ }
+
+ // 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;
+ bool hasYear;
+ Rooted<JSString*> calendarString(cx);
+ if (!ParseTemporalMonthDayString(cx, string, &result, &hasYear,
+ &calendarString)) {
+ return nullptr;
+ }
+
+ // Steps 6-9.
+ Rooted<CalendarValue> calendarValue(cx, CalendarValue(cx->names().iso8601));
+ if (calendarString) {
+ if (!ToBuiltinCalendar(cx, calendarString, &calendarValue)) {
+ return nullptr;
+ }
+ }
+
+ // Step 10.
+ if (maybeResolvedOptions) {
+ TemporalOverflow ignored;
+ if (!ToTemporalOverflow(cx, maybeResolvedOptions, &ignored)) {
+ return nullptr;
+ }
+ }
+
+ // Step 11.
+ if (!hasYear) {
+ // Step 11.a.
+ MOZ_ASSERT(calendarValue.isString() &&
+ EqualStrings(calendarValue.toString(), cx->names().iso8601));
+
+ // Step 11.b.
+ constexpr int32_t referenceISOYear = 1972;
+
+ // Step 11.a.
+ return CreateTemporalMonthDay(
+ cx, {referenceISOYear, result.month, result.day}, calendarValue);
+ }
+
+ // Step 12.
+ Rooted<PlainMonthDayObject*> obj(
+ cx, CreateTemporalMonthDay(cx, result, calendarValue));
+ if (!obj) {
+ return nullptr;
+ }
+
+ // FIXME: spec bug - missing call to CreateCalendarMethodsRecord
+
+ // Step 13.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendarValue,
+ {
+ CalendarMethod::MonthDayFromFields,
+ },
+ &calendar)) {
+ return nullptr;
+ }
+
+ // Steps 14-15.
+ return CalendarMonthDayFromFields(cx, calendar, obj);
+}
+
+/**
+ * ToTemporalMonthDay ( item [ , options ] )
+ */
+static bool ToTemporalMonthDay(JSContext* cx, Handle<Value> item,
+ PlainDate* result,
+ MutableHandle<CalendarValue> calendar) {
+ auto* obj = ToTemporalMonthDay(cx, item).unwrapOrNull();
+ if (!obj) {
+ return false;
+ }
+
+ *result = ToPlainDate(obj);
+ calendar.set(obj->calendar());
+ return calendar.wrap(cx);
+}
+
+/**
+ * Temporal.PlainMonthDay ( isoMonth, isoDay [ , calendarLike [ ,
+ * referenceISOYear ] ] )
+ */
+static bool PlainMonthDayConstructor(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ if (!ThrowIfNotConstructing(cx, args, "Temporal.PlainMonthDay")) {
+ return false;
+ }
+
+ // Step 3.
+ double isoMonth;
+ if (!ToIntegerWithTruncation(cx, args.get(0), "month", &isoMonth)) {
+ return false;
+ }
+
+ // Step 4.
+ double isoDay;
+ if (!ToIntegerWithTruncation(cx, args.get(1), "day", &isoDay)) {
+ return false;
+ }
+
+ // Step 5.
+ Rooted<CalendarValue> calendar(cx);
+ if (!ToTemporalCalendarWithISODefault(cx, args.get(2), &calendar)) {
+ return false;
+ }
+
+ // Steps 2 and 6.
+ double isoYear = 1972;
+ if (args.hasDefined(3)) {
+ if (!ToIntegerWithTruncation(cx, args[3], "year", &isoYear)) {
+ return false;
+ }
+ }
+
+ // Step 7.
+ auto* monthDay =
+ CreateTemporalMonthDay(cx, args, isoYear, isoMonth, isoDay, calendar);
+ if (!monthDay) {
+ return false;
+ }
+
+ args.rval().setObject(*monthDay);
+ return true;
+}
+
+/**
+ * Temporal.PlainMonthDay.from ( item [ , options ] )
+ */
+static bool PlainMonthDay_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* monthDay = item->maybeUnwrapIf<PlainMonthDayObject>()) {
+ auto date = ToPlainDate(monthDay);
+
+ Rooted<CalendarValue> calendar(cx, monthDay->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* obj = CreateTemporalMonthDay(cx, date, calendar);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+ }
+ }
+
+ // Step 3.
+ auto obj = ToTemporalMonthDay(cx, args.get(0), options);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * get Temporal.PlainMonthDay.prototype.calendarId
+ */
+static bool PlainMonthDay_calendarId(JSContext* cx, const CallArgs& args) {
+ auto* monthDay = &args.thisv().toObject().as<PlainMonthDayObject>();
+
+ // Step 3.
+ Rooted<CalendarValue> calendar(cx, monthDay->calendar());
+ auto* calendarId = ToTemporalCalendarIdentifier(cx, calendar);
+ if (!calendarId) {
+ return false;
+ }
+
+ args.rval().setString(calendarId);
+ return true;
+}
+
+/**
+ * get Temporal.PlainMonthDay.prototype.calendarId
+ */
+static bool PlainMonthDay_calendarId(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainMonthDay, PlainMonthDay_calendarId>(cx,
+ args);
+}
+
+/**
+ * get Temporal.PlainMonthDay.prototype.monthCode
+ */
+static bool PlainMonthDay_monthCode(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainMonthDayObject*> monthDay(
+ cx, &args.thisv().toObject().as<PlainMonthDayObject>());
+ Rooted<CalendarValue> calendar(cx, monthDay->calendar());
+
+ // Step 4.
+ return CalendarMonthCode(cx, calendar, monthDay, args.rval());
+}
+
+/**
+ * get Temporal.PlainMonthDay.prototype.monthCode
+ */
+static bool PlainMonthDay_monthCode(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainMonthDay, PlainMonthDay_monthCode>(cx,
+ args);
+}
+
+/**
+ * get Temporal.PlainMonthDay.prototype.day
+ */
+static bool PlainMonthDay_day(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainMonthDayObject*> monthDay(
+ cx, &args.thisv().toObject().as<PlainMonthDayObject>());
+ Rooted<CalendarValue> calendar(cx, monthDay->calendar());
+
+ // Step 4.
+ return CalendarDay(cx, calendar, monthDay, args.rval());
+}
+
+/**
+ * get Temporal.PlainMonthDay.prototype.day
+ */
+static bool PlainMonthDay_day(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainMonthDay, PlainMonthDay_day>(cx, args);
+}
+
+/**
+ * Temporal.PlainMonthDay.prototype.with ( temporalMonthDayLike [ , options ] )
+ */
+static bool PlainMonthDay_with(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainMonthDayObject*> monthDay(
+ cx, &args.thisv().toObject().as<PlainMonthDayObject>());
+ Rooted<CalendarValue> calendarValue(cx, monthDay->calendar());
+
+ // Step 3.
+ Rooted<JSObject*> temporalMonthDayLike(
+ cx, RequireObjectArg(cx, "temporalMonthDayLike", "with", args.get(0)));
+ if (!temporalMonthDayLike) {
+ return false;
+ }
+
+ // Step 4.
+ if (!RejectTemporalLikeObject(cx, temporalMonthDayLike)) {
+ return false;
+ }
+
+ // Step 5.
+ Rooted<PlainObject*> resolvedOptions(cx);
+ if (args.hasDefined(1)) {
+ Rooted<JSObject*> options(cx,
+ RequireObjectArg(cx, "options", "with", args[1]));
+ if (!options) {
+ return false;
+ }
+ resolvedOptions = SnapshotOwnProperties(cx, options);
+ } else {
+ resolvedOptions = NewPlainObjectWithProto(cx, nullptr);
+ }
+ if (!resolvedOptions) {
+ return false;
+ }
+
+ // Step 6.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendarValue,
+ {
+ CalendarMethod::Fields,
+ CalendarMethod::MergeFields,
+ CalendarMethod::MonthDayFromFields,
+ },
+ &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, monthDay, fieldNames));
+ if (!fields) {
+ return false;
+ }
+
+ // Step 9.
+ Rooted<PlainObject*> partialMonthDay(
+ cx, PreparePartialTemporalFields(cx, temporalMonthDayLike, fieldNames));
+ if (!partialMonthDay) {
+ return false;
+ }
+
+ // Step 10.
+ Rooted<JSObject*> mergedFields(
+ cx, CalendarMergeFields(cx, calendar, fields, partialMonthDay));
+ if (!mergedFields) {
+ return false;
+ }
+
+ // Step 11.
+ fields = PrepareTemporalFields(cx, mergedFields, fieldNames);
+ if (!fields) {
+ return false;
+ }
+
+ // Step 12.
+ auto obj = js::temporal::CalendarMonthDayFromFields(cx, calendar, fields,
+ resolvedOptions);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainMonthDay.prototype.with ( temporalMonthDayLike [ , options ] )
+ */
+static bool PlainMonthDay_with(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainMonthDay, PlainMonthDay_with>(cx, args);
+}
+
+/**
+ * Temporal.PlainMonthDay.prototype.equals ( other )
+ */
+static bool PlainMonthDay_equals(JSContext* cx, const CallArgs& args) {
+ auto* monthDay = &args.thisv().toObject().as<PlainMonthDayObject>();
+ auto date = ToPlainDate(monthDay);
+ Rooted<CalendarValue> calendar(cx, monthDay->calendar());
+
+ // Step 3.
+ PlainDate other;
+ Rooted<CalendarValue> otherCalendar(cx);
+ if (!ToTemporalMonthDay(cx, args.get(0), &other, &otherCalendar)) {
+ return false;
+ }
+
+ // Steps 4-7.
+ bool equals = date == other;
+ if (equals && !CalendarEquals(cx, calendar, otherCalendar, &equals)) {
+ return false;
+ }
+
+ args.rval().setBoolean(equals);
+ return true;
+}
+
+/**
+ * Temporal.PlainMonthDay.prototype.equals ( other )
+ */
+static bool PlainMonthDay_equals(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainMonthDay, PlainMonthDay_equals>(cx, args);
+}
+
+/**
+ * Temporal.PlainMonthDay.prototype.toString ( [ options ] )
+ */
+static bool PlainMonthDay_toString(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainMonthDayObject*> monthDay(
+ cx, &args.thisv().toObject().as<PlainMonthDayObject>());
+
+ 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 = TemporalMonthDayToString(cx, monthDay, showCalendar);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.PlainMonthDay.prototype.toString ( [ options ] )
+ */
+static bool PlainMonthDay_toString(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainMonthDay, PlainMonthDay_toString>(cx,
+ args);
+}
+
+/**
+ * Temporal.PlainMonthDay.prototype.toLocaleString ( [ locales [ , options ] ] )
+ */
+static bool PlainMonthDay_toLocaleString(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainMonthDayObject*> monthDay(
+ cx, &args.thisv().toObject().as<PlainMonthDayObject>());
+
+ // Step 3.
+ JSString* str = TemporalMonthDayToString(cx, monthDay, CalendarOption::Auto);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.PlainMonthDay.prototype.toLocaleString ( [ locales [ , options ] ] )
+ */
+static bool PlainMonthDay_toLocaleString(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainMonthDay, PlainMonthDay_toLocaleString>(
+ cx, args);
+}
+
+/**
+ * Temporal.PlainMonthDay.prototype.toJSON ( )
+ */
+static bool PlainMonthDay_toJSON(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainMonthDayObject*> monthDay(
+ cx, &args.thisv().toObject().as<PlainMonthDayObject>());
+
+ // Step 3.
+ JSString* str = TemporalMonthDayToString(cx, monthDay, CalendarOption::Auto);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.PlainMonthDay.prototype.toJSON ( )
+ */
+static bool PlainMonthDay_toJSON(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainMonthDay, PlainMonthDay_toJSON>(cx, args);
+}
+
+/**
+ * Temporal.PlainMonthDay.prototype.valueOf ( )
+ */
+static bool PlainMonthDay_valueOf(JSContext* cx, unsigned argc, Value* vp) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
+ "PlainMonthDay", "primitive type");
+ return false;
+}
+
+/**
+ * Temporal.PlainMonthDay.prototype.toPlainDate ( item )
+ */
+static bool PlainMonthDay_toPlainDate(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainMonthDayObject*> monthDay(
+ cx, &args.thisv().toObject().as<PlainMonthDayObject>());
+
+ // Step 3.
+ Rooted<JSObject*> item(
+ cx, RequireObjectArg(cx, "item", "toPlainDate", args.get(0)));
+ if (!item) {
+ return false;
+ }
+
+ // Step 4.
+ Rooted<CalendarValue> calendarValue(cx, monthDay->calendar());
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendarValue,
+ {
+ CalendarMethod::DateFromFields,
+ CalendarMethod::Fields,
+ CalendarMethod::MergeFields,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Step 5.
+ JS::RootedVector<PropertyKey> receiverFieldNames(cx);
+ if (!CalendarFields(cx, calendar,
+ {CalendarField::Day, CalendarField::MonthCode},
+ &receiverFieldNames)) {
+ return false;
+ }
+
+ // Step 6.
+ Rooted<PlainObject*> fields(
+ cx, PrepareTemporalFields(cx, monthDay, receiverFieldNames));
+ if (!fields) {
+ return false;
+ }
+
+ // Step 7.
+ JS::RootedVector<PropertyKey> inputFieldNames(cx);
+ if (!CalendarFields(cx, calendar, {CalendarField::Year}, &inputFieldNames)) {
+ return false;
+ }
+
+ // Step 8.
+ Rooted<PlainObject*> inputFields(
+ cx, PrepareTemporalFields(cx, item, inputFieldNames));
+ if (!inputFields) {
+ return false;
+ }
+
+ // Step 9.
+ Rooted<JSObject*> mergedFields(
+ cx, CalendarMergeFields(cx, calendar, fields, inputFields));
+ if (!mergedFields) {
+ return false;
+ }
+
+ // Step 10.
+ JS::RootedVector<PropertyKey> concatenatedFieldNames(cx);
+ if (!ConcatTemporalFieldNames(receiverFieldNames, inputFieldNames,
+ concatenatedFieldNames.get())) {
+ return false;
+ }
+
+ // Step 11.
+ Rooted<PlainObject*> mergedFromConcatenatedFields(
+ cx, PrepareTemporalFields(cx, mergedFields, concatenatedFieldNames));
+ if (!mergedFromConcatenatedFields) {
+ return false;
+ }
+
+ // Step 12.
+ Rooted<PlainObject*> options(cx, NewPlainObjectWithProto(cx, nullptr));
+ if (!options) {
+ return false;
+ }
+
+ // Step 13.
+ Rooted<Value> overflow(cx, StringValue(cx->names().constrain));
+ if (!DefineDataProperty(cx, options, cx->names().overflow, overflow)) {
+ return false;
+ }
+
+ // Step 14.
+ auto obj = js::temporal::CalendarDateFromFields(
+ cx, calendar, mergedFromConcatenatedFields, options);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainMonthDay.prototype.toPlainDate ( item )
+ */
+static bool PlainMonthDay_toPlainDate(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainMonthDay, PlainMonthDay_toPlainDate>(cx,
+ args);
+}
+
+/**
+ * Temporal.PlainMonthDay.prototype.getISOFields ( )
+ */
+static bool PlainMonthDay_getISOFields(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainMonthDayObject*> monthDay(
+ cx, &args.thisv().toObject().as<PlainMonthDayObject>());
+
+ // Step 3.
+ Rooted<IdValueVector> fields(cx, IdValueVector(cx));
+
+ // Step 4.
+ if (!fields.emplaceBack(NameToId(cx->names().calendar),
+ monthDay->calendar().toValue())) {
+ return false;
+ }
+
+ // Step 5.
+ if (!fields.emplaceBack(NameToId(cx->names().isoDay),
+ Int32Value(monthDay->isoDay()))) {
+ return false;
+ }
+
+ // Step 6.
+ if (!fields.emplaceBack(NameToId(cx->names().isoMonth),
+ Int32Value(monthDay->isoMonth()))) {
+ return false;
+ }
+
+ // Step 7.
+ if (!fields.emplaceBack(NameToId(cx->names().isoYear),
+ Int32Value(monthDay->isoYear()))) {
+ return false;
+ }
+
+ // Step 8.
+ auto* obj =
+ NewPlainObjectWithUniqueNames(cx, fields.begin(), fields.length());
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainMonthDay.prototype.getISOFields ( )
+ */
+static bool PlainMonthDay_getISOFields(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainMonthDay, PlainMonthDay_getISOFields>(
+ cx, args);
+}
+
+/**
+ * Temporal.PlainMonthDay.prototype.getCalendar ( )
+ */
+static bool PlainMonthDay_getCalendar(JSContext* cx, const CallArgs& args) {
+ auto* monthDay = &args.thisv().toObject().as<PlainMonthDayObject>();
+ Rooted<CalendarValue> calendar(cx, monthDay->calendar());
+
+ // Step 3.
+ auto* obj = ToTemporalCalendarObject(cx, calendar);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainMonthDay.prototype.getCalendar ( )
+ */
+static bool PlainMonthDay_getCalendar(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainMonthDay, PlainMonthDay_getCalendar>(cx,
+ args);
+}
+
+const JSClass PlainMonthDayObject::class_ = {
+ "Temporal.PlainMonthDay",
+ JSCLASS_HAS_RESERVED_SLOTS(PlainMonthDayObject::SLOT_COUNT) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_PlainMonthDay),
+ JS_NULL_CLASS_OPS,
+ &PlainMonthDayObject::classSpec_,
+};
+
+const JSClass& PlainMonthDayObject::protoClass_ = PlainObject::class_;
+
+static const JSFunctionSpec PlainMonthDay_methods[] = {
+ JS_FN("from", PlainMonthDay_from, 1, 0),
+ JS_FS_END,
+};
+
+static const JSFunctionSpec PlainMonthDay_prototype_methods[] = {
+ JS_FN("with", PlainMonthDay_with, 1, 0),
+ JS_FN("equals", PlainMonthDay_equals, 1, 0),
+ JS_FN("toString", PlainMonthDay_toString, 0, 0),
+ JS_FN("toLocaleString", PlainMonthDay_toLocaleString, 0, 0),
+ JS_FN("toJSON", PlainMonthDay_toJSON, 0, 0),
+ JS_FN("valueOf", PlainMonthDay_valueOf, 0, 0),
+ JS_FN("toPlainDate", PlainMonthDay_toPlainDate, 1, 0),
+ JS_FN("getISOFields", PlainMonthDay_getISOFields, 0, 0),
+ JS_FN("getCalendar", PlainMonthDay_getCalendar, 0, 0),
+ JS_FS_END,
+};
+
+static const JSPropertySpec PlainMonthDay_prototype_properties[] = {
+ JS_PSG("calendarId", PlainMonthDay_calendarId, 0),
+ JS_PSG("monthCode", PlainMonthDay_monthCode, 0),
+ JS_PSG("day", PlainMonthDay_day, 0),
+ JS_STRING_SYM_PS(toStringTag, "Temporal.PlainMonthDay", JSPROP_READONLY),
+ JS_PS_END,
+};
+
+const ClassSpec PlainMonthDayObject::classSpec_ = {
+ GenericCreateConstructor<PlainMonthDayConstructor, 2,
+ gc::AllocKind::FUNCTION>,
+ GenericCreatePrototype<PlainMonthDayObject>,
+ PlainMonthDay_methods,
+ nullptr,
+ PlainMonthDay_prototype_methods,
+ PlainMonthDay_prototype_properties,
+ nullptr,
+ ClassSpec::DontDefineConstructor,
+};
diff --git a/js/src/builtin/temporal/PlainMonthDay.h b/js/src/builtin/temporal/PlainMonthDay.h
new file mode 100644
index 0000000000..1a48513387
--- /dev/null
+++ b/js/src/builtin/temporal/PlainMonthDay.h
@@ -0,0 +1,66 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_PlainMonthDay_h
+#define builtin_temporal_PlainMonthDay_h
+
+#include <stdint.h>
+
+#include "builtin/temporal/Calendar.h"
+#include "builtin/temporal/TemporalTypes.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "vm/NativeObject.h"
+
+namespace js {
+struct ClassSpec;
+}
+
+namespace js::temporal {
+
+class PlainMonthDayObject : public NativeObject {
+ public:
+ static const JSClass class_;
+ static const JSClass& protoClass_;
+
+ static constexpr uint32_t ISO_YEAR_SLOT = 0;
+ static constexpr uint32_t ISO_MONTH_SLOT = 1;
+ static constexpr uint32_t ISO_DAY_SLOT = 2;
+ static constexpr uint32_t CALENDAR_SLOT = 3;
+ static constexpr uint32_t SLOT_COUNT = 4;
+
+ int32_t isoYear() const { return getFixedSlot(ISO_YEAR_SLOT).toInt32(); }
+
+ int32_t isoMonth() const { return getFixedSlot(ISO_MONTH_SLOT).toInt32(); }
+
+ int32_t isoDay() const { return getFixedSlot(ISO_DAY_SLOT).toInt32(); }
+
+ CalendarValue calendar() const {
+ return CalendarValue(getFixedSlot(CALENDAR_SLOT));
+ }
+
+ private:
+ static const ClassSpec classSpec_;
+};
+
+/**
+ * Extract the date fields from the PlainMonthDay object.
+ */
+inline PlainDate ToPlainDate(const PlainMonthDayObject* monthDay) {
+ return {monthDay->isoYear(), monthDay->isoMonth(), monthDay->isoDay()};
+}
+
+/**
+ * CreateTemporalMonthDay ( isoMonth, isoDay, calendar, referenceISOYear [ ,
+ * newTarget ] )
+ */
+PlainMonthDayObject* CreateTemporalMonthDay(JSContext* cx,
+ const PlainDate& date,
+ JS::Handle<CalendarValue> calendar);
+
+} /* namespace js::temporal */
+
+#endif /* builtin_temporal_PlainMonthDay_h */
diff --git a/js/src/builtin/temporal/PlainTime.cpp b/js/src/builtin/temporal/PlainTime.cpp
new file mode 100644
index 0000000000..9501b5853b
--- /dev/null
+++ b/js/src/builtin/temporal/PlainTime.cpp
@@ -0,0 +1,2641 @@
+/* -*- 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/PlainTime.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Maybe.h"
+
+#include <algorithm>
+#include <cmath>
+#include <cstdlib>
+#include <type_traits>
+#include <utility>
+
+#include "jsnum.h"
+#include "jspubtd.h"
+#include "NamespaceImports.h"
+
+#include "builtin/temporal/Duration.h"
+#include "builtin/temporal/Instant.h"
+#include "builtin/temporal/PlainDate.h"
+#include "builtin/temporal/PlainDateTime.h"
+#include "builtin/temporal/Temporal.h"
+#include "builtin/temporal/TemporalParser.h"
+#include "builtin/temporal/TemporalRoundingMode.h"
+#include "builtin/temporal/TemporalTypes.h"
+#include "builtin/temporal/TemporalUnit.h"
+#include "builtin/temporal/TimeZone.h"
+#include "builtin/temporal/ToString.h"
+#include "builtin/temporal/ZonedDateTime.h"
+#include "ds/IdValuePair.h"
+#include "gc/AllocKind.h"
+#include "gc/Barrier.h"
+#include "js/AllocPolicy.h"
+#include "js/CallArgs.h"
+#include "js/CallNonGenericMethod.h"
+#include "js/Class.h"
+#include "js/ErrorReport.h"
+#include "js/friend/ErrorMessages.h"
+#include "js/PropertyDescriptor.h"
+#include "js/PropertySpec.h"
+#include "js/RootingAPI.h"
+#include "js/Value.h"
+#include "vm/BigIntType.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/GlobalObject.h"
+#include "vm/JSAtomState.h"
+#include "vm/JSContext.h"
+#include "vm/JSObject.h"
+#include "vm/PlainObject.h"
+#include "vm/StringType.h"
+
+#include "vm/JSObject-inl.h"
+#include "vm/NativeObject-inl.h"
+#include "vm/ObjectOperations-inl.h"
+
+using namespace js;
+using namespace js::temporal;
+
+static inline bool IsPlainTime(Handle<Value> v) {
+ return v.isObject() && v.toObject().is<PlainTimeObject>();
+}
+
+#ifdef DEBUG
+/**
+ * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
+ */
+template <typename T>
+static bool IsValidTime(T hour, T minute, T second, T millisecond,
+ T microsecond, T nanosecond) {
+ static_assert(std::is_same_v<T, int32_t> || std::is_same_v<T, double>);
+
+ // Step 1.
+ MOZ_ASSERT(IsInteger(hour));
+ MOZ_ASSERT(IsInteger(minute));
+ MOZ_ASSERT(IsInteger(second));
+ MOZ_ASSERT(IsInteger(millisecond));
+ MOZ_ASSERT(IsInteger(microsecond));
+ MOZ_ASSERT(IsInteger(nanosecond));
+
+ // Step 2.
+ if (hour < 0 || hour > 23) {
+ return false;
+ }
+
+ // Step 3.
+ if (minute < 0 || minute > 59) {
+ return false;
+ }
+
+ // Step 4.
+ if (second < 0 || second > 59) {
+ return false;
+ }
+
+ // Step 5.
+ if (millisecond < 0 || millisecond > 999) {
+ return false;
+ }
+
+ // Step 6.
+ if (microsecond < 0 || microsecond > 999) {
+ return false;
+ }
+
+ // Step 7.
+ if (nanosecond < 0 || nanosecond > 999) {
+ return false;
+ }
+
+ // Step 8.
+ return true;
+}
+
+/**
+ * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
+ */
+bool js::temporal::IsValidTime(const PlainTime& time) {
+ auto& [hour, minute, second, millisecond, microsecond, nanosecond] = time;
+ return ::IsValidTime(hour, minute, second, millisecond, microsecond,
+ nanosecond);
+}
+
+/**
+ * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
+ */
+bool js::temporal::IsValidTime(double hour, double minute, double second,
+ double millisecond, double microsecond,
+ double nanosecond) {
+ return ::IsValidTime(hour, minute, second, millisecond, microsecond,
+ nanosecond);
+}
+#endif
+
+static void ReportInvalidTimeValue(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_TIME_INVALID_VALUE, name,
+ minStr, maxStr, numStr);
+}
+
+template <typename T>
+static inline bool ThrowIfInvalidTimeValue(JSContext* cx, const char* name,
+ int32_t min, int32_t max, T num) {
+ if (min <= num && num <= max) {
+ return true;
+ }
+ ReportInvalidTimeValue(cx, name, min, max, num);
+ return false;
+}
+
+/**
+ * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
+ */
+template <typename T>
+static bool ThrowIfInvalidTime(JSContext* cx, T hour, T minute, T second,
+ T millisecond, T microsecond, T nanosecond) {
+ static_assert(std::is_same_v<T, int32_t> || std::is_same_v<T, double>);
+
+ // Step 1.
+ MOZ_ASSERT(IsInteger(hour));
+ MOZ_ASSERT(IsInteger(minute));
+ MOZ_ASSERT(IsInteger(second));
+ MOZ_ASSERT(IsInteger(millisecond));
+ MOZ_ASSERT(IsInteger(microsecond));
+ MOZ_ASSERT(IsInteger(nanosecond));
+
+ // Step 2.
+ if (!ThrowIfInvalidTimeValue(cx, "hour", 0, 23, hour)) {
+ return false;
+ }
+
+ // Step 3.
+ if (!ThrowIfInvalidTimeValue(cx, "minute", 0, 59, minute)) {
+ return false;
+ }
+
+ // Step 4.
+ if (!ThrowIfInvalidTimeValue(cx, "second", 0, 59, second)) {
+ return false;
+ }
+
+ // Step 5.
+ if (!ThrowIfInvalidTimeValue(cx, "millisecond", 0, 999, millisecond)) {
+ return false;
+ }
+
+ // Step 6.
+ if (!ThrowIfInvalidTimeValue(cx, "microsecond", 0, 999, microsecond)) {
+ return false;
+ }
+
+ // Step 7.
+ if (!ThrowIfInvalidTimeValue(cx, "nanosecond", 0, 999, nanosecond)) {
+ return false;
+ }
+
+ // Step 8.
+ return true;
+}
+
+/**
+ * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
+ */
+bool js::temporal::ThrowIfInvalidTime(JSContext* cx, const PlainTime& time) {
+ auto& [hour, minute, second, millisecond, microsecond, nanosecond] = time;
+ return ::ThrowIfInvalidTime(cx, hour, minute, second, millisecond,
+ microsecond, nanosecond);
+}
+
+/**
+ * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
+ */
+bool js::temporal::ThrowIfInvalidTime(JSContext* cx, double hour, double minute,
+ double second, double millisecond,
+ double microsecond, double nanosecond) {
+ return ::ThrowIfInvalidTime(cx, hour, minute, second, millisecond,
+ microsecond, nanosecond);
+}
+
+/**
+ * ConstrainTime ( hour, minute, second, millisecond, microsecond, nanosecond )
+ */
+static PlainTime ConstrainTime(double hour, double minute, double second,
+ double millisecond, double microsecond,
+ double nanosecond) {
+ // Step 1.
+ MOZ_ASSERT(IsInteger(hour));
+ MOZ_ASSERT(IsInteger(minute));
+ MOZ_ASSERT(IsInteger(second));
+ MOZ_ASSERT(IsInteger(millisecond));
+ MOZ_ASSERT(IsInteger(microsecond));
+ MOZ_ASSERT(IsInteger(nanosecond));
+
+ // Steps 2-8.
+ return {
+ int32_t(std::clamp(hour, 0.0, 23.0)),
+ int32_t(std::clamp(minute, 0.0, 59.0)),
+ int32_t(std::clamp(second, 0.0, 59.0)),
+ int32_t(std::clamp(millisecond, 0.0, 999.0)),
+ int32_t(std::clamp(microsecond, 0.0, 999.0)),
+ int32_t(std::clamp(nanosecond, 0.0, 999.0)),
+ };
+}
+
+/**
+ * RegulateTime ( hour, minute, second, millisecond, microsecond, nanosecond,
+ * overflow )
+ */
+bool js::temporal::RegulateTime(JSContext* cx, const TimeRecord& time,
+ TemporalOverflow overflow, PlainTime* result) {
+ auto& [hour, minute, second, millisecond, microsecond, nanosecond] = time;
+
+ // Step 1.
+ MOZ_ASSERT(IsInteger(hour));
+ MOZ_ASSERT(IsInteger(minute));
+ MOZ_ASSERT(IsInteger(second));
+ MOZ_ASSERT(IsInteger(millisecond));
+ MOZ_ASSERT(IsInteger(microsecond));
+ MOZ_ASSERT(IsInteger(nanosecond));
+
+ // Step 2. (Not applicable in our implementation.)
+
+ // Step 3.
+ if (overflow == TemporalOverflow::Constrain) {
+ *result = ConstrainTime(hour, minute, second, millisecond, microsecond,
+ nanosecond);
+ return true;
+ }
+
+ // Step 4.a.
+ MOZ_ASSERT(overflow == TemporalOverflow::Reject);
+
+ // Step 4.b.
+ if (!ThrowIfInvalidTime(cx, hour, minute, second, millisecond, microsecond,
+ nanosecond)) {
+ return false;
+ }
+
+ // Step 4.c.
+ *result = {
+ int32_t(hour), int32_t(minute), int32_t(second),
+ int32_t(millisecond), int32_t(microsecond), int32_t(nanosecond),
+ };
+ return true;
+}
+
+/**
+ * CreateTemporalTime ( hour, minute, second, millisecond, microsecond,
+ * nanosecond [ , newTarget ] )
+ */
+static PlainTimeObject* CreateTemporalTime(JSContext* cx, const CallArgs& args,
+ double hour, double minute,
+ double second, double millisecond,
+ double microsecond,
+ double nanosecond) {
+ MOZ_ASSERT(IsInteger(hour));
+ MOZ_ASSERT(IsInteger(minute));
+ MOZ_ASSERT(IsInteger(second));
+ MOZ_ASSERT(IsInteger(millisecond));
+ MOZ_ASSERT(IsInteger(microsecond));
+ MOZ_ASSERT(IsInteger(nanosecond));
+
+ // Step 1.
+ if (!ThrowIfInvalidTime(cx, hour, minute, second, millisecond, microsecond,
+ nanosecond)) {
+ return nullptr;
+ }
+
+ // Steps 2-3.
+ Rooted<JSObject*> proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_PlainTime,
+ &proto)) {
+ return nullptr;
+ }
+
+ auto* object = NewObjectWithClassProto<PlainTimeObject>(cx, proto);
+ if (!object) {
+ return nullptr;
+ }
+
+ // Step 4.
+ object->setFixedSlot(PlainTimeObject::ISO_HOUR_SLOT, Int32Value(hour));
+
+ // Step 5.
+ object->setFixedSlot(PlainTimeObject::ISO_MINUTE_SLOT, Int32Value(minute));
+
+ // Step 6.
+ object->setFixedSlot(PlainTimeObject::ISO_SECOND_SLOT, Int32Value(second));
+
+ // Step 7.
+ object->setFixedSlot(PlainTimeObject::ISO_MILLISECOND_SLOT,
+ Int32Value(millisecond));
+
+ // Step 8.
+ object->setFixedSlot(PlainTimeObject::ISO_MICROSECOND_SLOT,
+ Int32Value(microsecond));
+
+ // Step 9.
+ object->setFixedSlot(PlainTimeObject::ISO_NANOSECOND_SLOT,
+ Int32Value(nanosecond));
+
+ // Step 10.
+ return object;
+}
+
+/**
+ * CreateTemporalTime ( hour, minute, second, millisecond, microsecond,
+ * nanosecond [ , newTarget ] )
+ */
+PlainTimeObject* js::temporal::CreateTemporalTime(JSContext* cx,
+ const PlainTime& time) {
+ auto& [hour, minute, second, millisecond, microsecond, nanosecond] = time;
+
+ // Step 1.
+ if (!ThrowIfInvalidTime(cx, time)) {
+ return nullptr;
+ }
+
+ // Steps 2-3.
+ auto* object = NewBuiltinClassInstance<PlainTimeObject>(cx);
+ if (!object) {
+ return nullptr;
+ }
+
+ // Step 4.
+ object->setFixedSlot(PlainTimeObject::ISO_HOUR_SLOT, Int32Value(hour));
+
+ // Step 5.
+ object->setFixedSlot(PlainTimeObject::ISO_MINUTE_SLOT, Int32Value(minute));
+
+ // Step 6.
+ object->setFixedSlot(PlainTimeObject::ISO_SECOND_SLOT, Int32Value(second));
+
+ // Step 7.
+ object->setFixedSlot(PlainTimeObject::ISO_MILLISECOND_SLOT,
+ Int32Value(millisecond));
+
+ // Step 8.
+ object->setFixedSlot(PlainTimeObject::ISO_MICROSECOND_SLOT,
+ Int32Value(microsecond));
+
+ // Step 9.
+ object->setFixedSlot(PlainTimeObject::ISO_NANOSECOND_SLOT,
+ Int32Value(nanosecond));
+
+ // Step 10.
+ return object;
+}
+
+/**
+ * CreateTimeDurationRecord ( days, hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds )
+ */
+static TimeDuration CreateTimeDurationRecord(double days, int32_t hours,
+ int32_t minutes, int32_t seconds,
+ int32_t milliseconds,
+ int32_t microseconds,
+ int32_t nanoseconds) {
+ // Step 1.
+ MOZ_ASSERT(IsValidDuration({0, 0, 0, days, double(hours), double(minutes),
+ double(seconds), double(microseconds),
+ double(nanoseconds)}));
+
+ // Step 2.
+ return {
+ days,
+ double(hours),
+ double(minutes),
+ double(seconds),
+ double(milliseconds),
+ double(microseconds),
+ double(nanoseconds),
+ };
+}
+
+/**
+ * DurationSign ( years, months, weeks, days, hours, minutes, seconds,
+ * milliseconds, microseconds, nanoseconds )
+ */
+static int32_t DurationSign(int32_t hours, int32_t minutes, int32_t seconds,
+ int32_t milliseconds, int32_t microseconds,
+ int32_t nanoseconds) {
+ // Step 1. (Loop unrolled)
+ if (hours) {
+ return hours > 0 ? 1 : -1;
+ }
+ if (minutes) {
+ return minutes > 0 ? 1 : -1;
+ }
+ if (seconds) {
+ return seconds > 0 ? 1 : -1;
+ }
+ if (milliseconds) {
+ return milliseconds > 0 ? 1 : -1;
+ }
+ if (microseconds) {
+ return microseconds > 0 ? 1 : -1;
+ }
+ if (nanoseconds) {
+ return nanoseconds > 0 ? 1 : -1;
+ }
+
+ // Step 2.
+ return 0;
+}
+
+/**
+ * BalanceTime ( hour, minute, second, millisecond, microsecond, nanosecond )
+ */
+template <typename IntT>
+static BalancedTime BalanceTime(IntT hour, IntT minute, IntT second,
+ IntT millisecond, IntT microsecond,
+ IntT nanosecond) {
+ // Combined floor'ed division and modulo operation.
+ auto divmod = [](IntT dividend, int32_t divisor, int32_t* remainder) {
+ MOZ_ASSERT(divisor > 0);
+
+ IntT quotient = dividend / divisor;
+ *remainder = dividend % divisor;
+
+ // The remainder is negative, add the divisor and simulate a floor instead
+ // of trunc division.
+ if (*remainder < 0) {
+ *remainder += divisor;
+ quotient -= 1;
+ }
+
+ return quotient;
+ };
+
+ PlainTime time = {};
+
+ // Steps 1-2.
+ microsecond += divmod(nanosecond, 1000, &time.nanosecond);
+
+ // Steps 3-4.
+ millisecond += divmod(microsecond, 1000, &time.microsecond);
+
+ // Steps 5-6.
+ second += divmod(millisecond, 1000, &time.millisecond);
+
+ // Steps 7-8.
+ minute += divmod(second, 60, &time.second);
+
+ // Steps 9-10.
+ hour += divmod(minute, 60, &time.minute);
+
+ // Steps 11-12.
+ int32_t days = divmod(hour, 24, &time.hour);
+
+ // Step 13.
+ MOZ_ASSERT(IsValidTime(time));
+ return {days, time};
+}
+
+/**
+ * BalanceTime ( hour, minute, second, millisecond, microsecond, nanosecond )
+ */
+static BalancedTime BalanceTime(int32_t hour, int32_t minute, int32_t second,
+ int32_t millisecond, int32_t microsecond,
+ int32_t nanosecond) {
+ MOZ_ASSERT(-24 < hour && hour < 2 * 24);
+ MOZ_ASSERT(-60 < minute && minute < 2 * 60);
+ MOZ_ASSERT(-60 < second && second < 2 * 60);
+ MOZ_ASSERT(-1000 < millisecond && millisecond < 2 * 1000);
+ MOZ_ASSERT(-1000 < microsecond && microsecond < 2 * 1000);
+ MOZ_ASSERT(-1000 < nanosecond && nanosecond < 2 * 1000);
+
+ return BalanceTime<int32_t>(hour, minute, second, millisecond, microsecond,
+ nanosecond);
+}
+
+/**
+ * BalanceTime ( hour, minute, second, millisecond, microsecond, nanosecond )
+ */
+BalancedTime js::temporal::BalanceTime(const PlainTime& time,
+ int64_t nanoseconds) {
+ MOZ_ASSERT(IsValidTime(time));
+ MOZ_ASSERT(std::abs(nanoseconds) <= 2 * ToNanoseconds(TemporalUnit::Day));
+
+ return ::BalanceTime<int64_t>(time.hour, time.minute, time.second,
+ time.millisecond, time.microsecond,
+ time.nanosecond + nanoseconds);
+}
+
+/**
+ * DifferenceTime ( h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2, ns2 )
+ */
+TimeDuration js::temporal::DifferenceTime(const PlainTime& time1,
+ const PlainTime& time2) {
+ MOZ_ASSERT(IsValidTime(time1));
+ MOZ_ASSERT(IsValidTime(time2));
+
+ // Step 1.
+ int32_t hours = time2.hour - time1.hour;
+
+ // Step 2.
+ int32_t minutes = time2.minute - time1.minute;
+
+ // Step 3.
+ int32_t seconds = time2.second - time1.second;
+
+ // Step 4.
+ int32_t milliseconds = time2.millisecond - time1.millisecond;
+
+ // Step 5.
+ int32_t microseconds = time2.microsecond - time1.microsecond;
+
+ // Step 6.
+ int32_t nanoseconds = time2.nanosecond - time1.nanosecond;
+
+ // Step 7.
+ int32_t sign = ::DurationSign(hours, minutes, seconds, milliseconds,
+ microseconds, nanoseconds);
+
+ // Step 8.
+ auto balanced = ::BalanceTime(hours * sign, minutes * sign, seconds * sign,
+ milliseconds * sign, microseconds * sign,
+ nanoseconds * sign);
+
+ // Step 9.
+ MOZ_ASSERT(balanced.days == 0);
+
+ // Step 10.
+ return CreateTimeDurationRecord(
+ 0, balanced.time.hour * sign, balanced.time.minute * sign,
+ balanced.time.second * sign, balanced.time.millisecond * sign,
+ balanced.time.microsecond * sign, balanced.time.nanosecond * sign);
+}
+
+/**
+ * ToTemporalTime ( item [ , overflow ] )
+ */
+static bool ToTemporalTime(JSContext* cx, Handle<Value> item,
+ TemporalOverflow overflow, PlainTime* result) {
+ // Steps 1-2. (Not applicable in our implementation.)
+
+ // Steps 3-4.
+ if (item.isObject()) {
+ // Step 3.
+ Rooted<JSObject*> itemObj(cx, &item.toObject());
+
+ // Step 3.a.
+ if (auto* time = itemObj->maybeUnwrapIf<PlainTimeObject>()) {
+ *result = ToPlainTime(time);
+ return true;
+ }
+
+ // Step 3.b.
+ if (auto* zonedDateTime = itemObj->maybeUnwrapIf<ZonedDateTimeObject>()) {
+ auto epochInstant = ToInstant(zonedDateTime);
+ Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
+
+ if (!timeZone.wrap(cx)) {
+ return false;
+ }
+
+ // Steps 3.b.i-iii.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, timeZone, epochInstant, &dateTime)) {
+ return false;
+ }
+
+ // Step 3.b.iv.
+ *result = dateTime.time;
+ return true;
+ }
+
+ // Step 3.c.
+ if (auto* dateTime = itemObj->maybeUnwrapIf<PlainDateTimeObject>()) {
+ *result = ToPlainTime(dateTime);
+ return true;
+ }
+
+ // Step 3.d.
+ TimeRecord timeResult;
+ if (!ToTemporalTimeRecord(cx, itemObj, &timeResult)) {
+ return false;
+ }
+
+ // Step 3.e.
+ if (!RegulateTime(cx, timeResult, overflow, result)) {
+ return false;
+ }
+ } else {
+ // Step 4.
+
+ // Step 4.a.
+ if (!item.isString()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, item,
+ nullptr, "not a string");
+ return false;
+ }
+ Rooted<JSString*> string(cx, item.toString());
+
+ // Step 4.b.
+ if (!ParseTemporalTimeString(cx, string, result)) {
+ return false;
+ }
+
+ // Step 4.c.
+ MOZ_ASSERT(IsValidTime(*result));
+ }
+
+ // Step 5.
+ return true;
+}
+
+/**
+ * ToTemporalTime ( item [ , overflow ] )
+ */
+static PlainTimeObject* ToTemporalTime(JSContext* cx, Handle<Value> item,
+ TemporalOverflow overflow) {
+ PlainTime time;
+ if (!ToTemporalTime(cx, item, overflow, &time)) {
+ return nullptr;
+ }
+ MOZ_ASSERT(IsValidTime(time));
+
+ return CreateTemporalTime(cx, time);
+}
+
+/**
+ * ToTemporalTime ( item [ , overflow ] )
+ */
+bool js::temporal::ToTemporalTime(JSContext* cx, Handle<Value> item,
+ PlainTime* result) {
+ return ToTemporalTime(cx, item, TemporalOverflow::Constrain, result);
+}
+
+/**
+ * TotalDurationNanoseconds ( hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds )
+ */
+static int64_t TotalDurationNanoseconds(const Duration& duration) {
+ // This function is only called from BalanceTime. The difference between two
+ // plain times can't exceed the number of nanoseconds in a day.
+ MOZ_ASSERT(IsValidDuration(duration));
+ MOZ_ASSERT(std::abs(duration.hours) <= 24);
+ MOZ_ASSERT(std::abs(duration.minutes) <= 60);
+ MOZ_ASSERT(std::abs(duration.seconds) <= 60);
+ MOZ_ASSERT(std::abs(duration.milliseconds) <= 1000);
+ MOZ_ASSERT(std::abs(duration.microseconds) <= 1000);
+ MOZ_ASSERT(std::abs(duration.nanoseconds) <= 1000);
+
+ // Step 1.
+ auto minutes = int64_t(duration.minutes) + int64_t(duration.hours) * 60;
+
+ // Step 2.
+ auto seconds = int64_t(duration.seconds) + minutes * 60;
+
+ // Step 3.
+ auto milliseconds = int64_t(duration.milliseconds) + seconds * 1000;
+
+ // Step 4.
+ auto microseconds = int64_t(duration.microseconds) + milliseconds * 1000;
+
+ // Steps 5.
+ return int64_t(duration.nanoseconds) + microseconds * 1000;
+}
+
+/**
+ * BalanceTimeDuration ( days, hours, minutes, seconds, milliseconds,
+ * microseconds, nanoseconds, largestUnit [ , relativeTo ] )
+ */
+static Duration BalanceTimeDuration(const Duration& duration,
+ TemporalUnit largestUnit) {
+ MOZ_ASSERT(IsValidDuration(duration));
+ MOZ_ASSERT(largestUnit > TemporalUnit::Day);
+
+ // We only handle time components here.
+ MOZ_ASSERT(duration.years == 0);
+ MOZ_ASSERT(duration.months == 0);
+ MOZ_ASSERT(duration.weeks == 0);
+ MOZ_ASSERT(duration.days == 0);
+
+ // Step 1. (Not applicable)
+
+ // Step 2.
+ int64_t nanoseconds = TotalDurationNanoseconds(duration);
+ MOZ_ASSERT(std::abs(nanoseconds) <= ToNanoseconds(TemporalUnit::Day));
+
+ // Steps 3-4. (Not applicable)
+
+ // Step 5.
+ int64_t hours = 0;
+ int64_t minutes = 0;
+ int64_t seconds = 0;
+ int64_t milliseconds = 0;
+ int64_t microseconds = 0;
+
+ // Step 6.
+ int32_t sign = nanoseconds < 0 ? -1 : +1;
+
+ // Step 7.
+ nanoseconds = std::abs(nanoseconds);
+
+ // Steps 8-13.
+ switch (largestUnit) {
+ case TemporalUnit::Auto:
+ case TemporalUnit::Year:
+ case TemporalUnit::Month:
+ case TemporalUnit::Week:
+ case TemporalUnit::Day:
+ MOZ_CRASH("Unexpected temporal unit");
+
+ case TemporalUnit::Hour: {
+ // Step 8.
+
+ // Step 8.a.
+ microseconds = nanoseconds / 1000;
+
+ // Step 8.b.
+ nanoseconds = nanoseconds % 1000;
+
+ // Step 8.c.
+ milliseconds = microseconds / 1000;
+
+ // Step 8.d.
+ microseconds = microseconds % 1000;
+
+ // Step 8.e.
+ seconds = milliseconds / 1000;
+
+ // Step 8.f.
+ milliseconds = milliseconds % 1000;
+
+ // Step 8.g.
+ minutes = seconds / 60;
+
+ // Step 8.h.
+ seconds = seconds % 60;
+
+ // Step 8.i.
+ hours = minutes / 60;
+
+ // Step 8.j.
+ minutes = minutes % 60;
+
+ break;
+ }
+ case TemporalUnit::Minute: {
+ // Step 9.
+
+ // Step 9.a.
+ microseconds = nanoseconds / 1000;
+
+ // Step 9.b.
+ nanoseconds = nanoseconds % 1000;
+
+ // Step 9.c.
+ milliseconds = microseconds / 1000;
+
+ // Step 9.d.
+ microseconds = microseconds % 1000;
+
+ // Step 9.e.
+ seconds = milliseconds / 1000;
+
+ // Step 9.f.
+ milliseconds = milliseconds % 1000;
+
+ // Step 9.g.
+ minutes = seconds / 60;
+
+ // Step 9.h.
+ seconds = seconds % 60;
+
+ break;
+ }
+ case TemporalUnit::Second: {
+ // Step 10.
+
+ // Step 10.a.
+ microseconds = nanoseconds / 1000;
+
+ // Step 10.b.
+ nanoseconds = nanoseconds % 1000;
+
+ // Step 10.c.
+ milliseconds = microseconds / 1000;
+
+ // Step 10.d.
+ microseconds = microseconds % 1000;
+
+ // Step 10.e.
+ seconds = milliseconds / 1000;
+
+ // Step 10.f.
+ milliseconds = milliseconds % 1000;
+
+ break;
+ }
+ case TemporalUnit::Millisecond: {
+ // Step 11.
+
+ // Step 11.a.
+ microseconds = nanoseconds / 1000;
+
+ // Step 11.b.
+ nanoseconds = nanoseconds % 1000;
+
+ // Step 11.c.
+ milliseconds = microseconds / 1000;
+
+ // Step 11.d.
+ microseconds = microseconds % 1000;
+
+ break;
+ }
+ case TemporalUnit::Microsecond: {
+ // Step 12.
+
+ // Step 12.a.
+ microseconds = nanoseconds / 1000;
+
+ // Step 12.b.
+ nanoseconds = nanoseconds % 1000;
+
+ break;
+ }
+ case TemporalUnit::Nanosecond: {
+ // Step 13.
+ break;
+ }
+ }
+
+ // Step 14.
+ return {
+ 0,
+ 0,
+ 0,
+ 0,
+ double(hours * sign),
+ double(minutes * sign),
+ double(seconds * sign),
+ double(milliseconds * sign),
+ double(microseconds * sign),
+ double(nanoseconds * sign),
+ };
+}
+
+/**
+ * CompareTemporalTime ( h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2,
+ * ns2 )
+ */
+int32_t js::temporal::CompareTemporalTime(const PlainTime& one,
+ const PlainTime& two) {
+ // Steps 1-2.
+ if (int32_t diff = one.hour - two.hour) {
+ return diff < 0 ? -1 : 1;
+ }
+
+ // Steps 3-4.
+ if (int32_t diff = one.minute - two.minute) {
+ return diff < 0 ? -1 : 1;
+ }
+
+ // Steps 5-6.
+ if (int32_t diff = one.second - two.second) {
+ return diff < 0 ? -1 : 1;
+ }
+
+ // Steps 7-8.
+ if (int32_t diff = one.millisecond - two.millisecond) {
+ return diff < 0 ? -1 : 1;
+ }
+
+ // Steps 9-10.
+ if (int32_t diff = one.microsecond - two.microsecond) {
+ return diff < 0 ? -1 : 1;
+ }
+
+ // Steps 11-12.
+ if (int32_t diff = one.nanosecond - two.nanosecond) {
+ return diff < 0 ? -1 : 1;
+ }
+
+ // Step 13.
+ return 0;
+}
+
+/**
+ * ToTemporalTimeRecord ( temporalTimeLike [ , completeness ] )
+ */
+static bool ToTemporalTimeRecord(JSContext* cx,
+ Handle<JSObject*> temporalTimeLike,
+ TimeRecord* result) {
+ // Steps 1 and 3-4. (Not applicable in our implementation.)
+
+ // Step 2. (Inlined call to PrepareTemporalFields.)
+ // PrepareTemporalFields, step 1. (Not applicable in our implementation.)
+
+ // PrepareTemporalFields, step 2.
+ bool any = false;
+
+ // PrepareTemporalFields, steps 3-4. (Loop unrolled)
+ Rooted<Value> value(cx);
+ auto getTimeProperty = [&](Handle<PropertyName*> property, const char* name,
+ double* num) {
+ // Step 4.a.
+ if (!GetProperty(cx, temporalTimeLike, temporalTimeLike, property,
+ &value)) {
+ return false;
+ }
+
+ // Step 4.b.
+ if (!value.isUndefined()) {
+ // Step 4.b.i.
+ any = true;
+
+ // Step 4.b.ii.2.
+ if (!ToIntegerWithTruncation(cx, value, name, num)) {
+ return false;
+ }
+ }
+ return true;
+ };
+
+ if (!getTimeProperty(cx->names().hour, "hour", &result->hour)) {
+ return false;
+ }
+ if (!getTimeProperty(cx->names().microsecond, "microsecond",
+ &result->microsecond)) {
+ return false;
+ }
+ if (!getTimeProperty(cx->names().millisecond, "millisecond",
+ &result->millisecond)) {
+ return false;
+ }
+ if (!getTimeProperty(cx->names().minute, "minute", &result->minute)) {
+ return false;
+ }
+ if (!getTimeProperty(cx->names().nanosecond, "nanosecond",
+ &result->nanosecond)) {
+ return false;
+ }
+ if (!getTimeProperty(cx->names().second, "second", &result->second)) {
+ return false;
+ }
+
+ // PrepareTemporalFields, step 5.
+ if (!any) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PLAIN_TIME_MISSING_UNIT);
+ return false;
+ }
+
+ // Steps 5-16. (Performed implicitly in our implementation.)
+
+ // Step 17.
+ return true;
+}
+
+/**
+ * ToTemporalTimeRecord ( temporalTimeLike [ , completeness ] )
+ */
+bool js::temporal::ToTemporalTimeRecord(JSContext* cx,
+ Handle<JSObject*> temporalTimeLike,
+ TimeRecord* result) {
+ // Step 3.a. (Set all fields to zero.)
+ *result = {};
+
+ // Steps 1-2 and 4-17.
+ return ::ToTemporalTimeRecord(cx, temporalTimeLike, result);
+}
+
+/**
+ * RoundNumberToIncrement ( x, increment, roundingMode )
+ */
+static int64_t RoundNumberToIncrement(int64_t x, TemporalUnit unit,
+ Increment increment,
+ TemporalRoundingMode roundingMode) {
+ MOZ_ASSERT(x >= 0);
+ MOZ_ASSERT(x < ToNanoseconds(TemporalUnit::Day));
+
+ MOZ_ASSERT(unit >= TemporalUnit::Day);
+ MOZ_ASSERT_IF(unit == TemporalUnit::Day, increment == Increment{1});
+ MOZ_ASSERT_IF(unit > TemporalUnit::Day,
+ increment <= MaximumTemporalDurationRoundingIncrement(unit));
+
+ int64_t divisor = ToNanoseconds(unit) * increment.value();
+ MOZ_ASSERT(divisor > 0);
+ MOZ_ASSERT(divisor <= ToNanoseconds(TemporalUnit::Day));
+
+ // Division by one has no remainder.
+ if (divisor == 1) {
+ MOZ_ASSERT(increment == Increment{1});
+ return x;
+ }
+
+ // Steps 1-8.
+ int64_t rounded = Divide(x, divisor, roundingMode);
+
+ // Step 9.
+ mozilla::CheckedInt64 result = rounded;
+ result *= increment.value();
+
+ MOZ_ASSERT(result.isValid(), "can't overflow when inputs are all in range");
+
+ return result.value();
+}
+
+/**
+ * RoundNumberToIncrement ( x, increment, roundingMode )
+ */
+static int64_t RoundNumberToIncrement(int64_t x, int64_t divisor,
+ Increment increment,
+ TemporalRoundingMode roundingMode) {
+ MOZ_ASSERT(x >= 0);
+ MOZ_ASSERT(x < ToNanoseconds(TemporalUnit::Day));
+ MOZ_ASSERT(divisor > 0);
+ MOZ_ASSERT(increment == Increment{1}, "Rounding increment for 'day' is 1");
+
+ // Steps 1-2. (Not applicable in our implementation)
+
+ // Steps 3-8.
+ return Divide(x, divisor, roundingMode);
+}
+
+static int64_t TimeToNanos(const PlainTime& time) {
+ // No overflow possible because the input is a valid time.
+ MOZ_ASSERT(IsValidTime(time));
+
+ int64_t hour = time.hour;
+ int64_t minute = time.minute;
+ int64_t second = time.second;
+ int64_t millisecond = time.millisecond;
+ int64_t microsecond = time.microsecond;
+ int64_t nanosecond = time.nanosecond;
+
+ int64_t millis = ((hour * 60 + minute) * 60 + second) * 1000 + millisecond;
+ return (millis * 1000 + microsecond) * 1000 + nanosecond;
+}
+
+/**
+ * RoundTime ( hour, minute, second, millisecond, microsecond, nanosecond,
+ * increment, unit, roundingMode [ , dayLengthNs ] )
+ */
+RoundedTime js::temporal::RoundTime(const PlainTime& time, Increment increment,
+ TemporalUnit unit,
+ TemporalRoundingMode roundingMode) {
+ MOZ_ASSERT(IsValidTime(time));
+ MOZ_ASSERT(unit >= TemporalUnit::Day);
+ MOZ_ASSERT_IF(unit > TemporalUnit::Day,
+ increment <= MaximumTemporalDurationRoundingIncrement(unit));
+ MOZ_ASSERT_IF(unit == TemporalUnit::Day, increment == Increment{1});
+
+ int32_t days = 0;
+ auto [hour, minute, second, millisecond, microsecond, nanosecond] = time;
+
+ // Take the same approach as used in RoundDuration() to perform exact
+ // mathematical operations without possible loss of precision.
+
+ // Steps 1-8.
+ PlainTime quantity;
+ int32_t* result;
+ switch (unit) {
+ case TemporalUnit::Day:
+ quantity = time;
+ result = &days;
+ break;
+ case TemporalUnit::Hour:
+ quantity = time;
+ result = &hour;
+ minute = 0;
+ second = 0;
+ millisecond = 0;
+ microsecond = 0;
+ nanosecond = 0;
+ break;
+ case TemporalUnit::Minute:
+ quantity = {0, minute, second, millisecond, microsecond, nanosecond};
+ result = &minute;
+ second = 0;
+ millisecond = 0;
+ microsecond = 0;
+ nanosecond = 0;
+ break;
+ case TemporalUnit::Second:
+ quantity = {0, 0, second, millisecond, microsecond, nanosecond};
+ result = &second;
+ millisecond = 0;
+ microsecond = 0;
+ nanosecond = 0;
+ break;
+ case TemporalUnit::Millisecond:
+ quantity = {0, 0, 0, millisecond, microsecond, nanosecond};
+ result = &millisecond;
+ microsecond = 0;
+ nanosecond = 0;
+ break;
+ case TemporalUnit::Microsecond:
+ quantity = {0, 0, 0, 0, microsecond, nanosecond};
+ result = &microsecond;
+ nanosecond = 0;
+ break;
+ case TemporalUnit::Nanosecond:
+ quantity = {0, 0, 0, 0, 0, nanosecond};
+ result = &nanosecond;
+ break;
+
+ case TemporalUnit::Auto:
+ case TemporalUnit::Year:
+ case TemporalUnit::Month:
+ case TemporalUnit::Week:
+ MOZ_CRASH("unexpected temporal unit");
+ }
+
+ // Step 9.
+ int64_t r = ::RoundNumberToIncrement(TimeToNanos(quantity), unit, increment,
+ roundingMode);
+ MOZ_ASSERT(r == int64_t(int32_t(r)),
+ "no overflow possible due to limited range of arguments");
+ *result = r;
+
+ // Step 10.
+ if (unit == TemporalUnit::Day) {
+ return {int64_t(days), {0, 0, 0, 0, 0, 0}};
+ }
+
+ // Steps 11-17.
+ auto balanced =
+ ::BalanceTime(hour, minute, second, millisecond, microsecond, nanosecond);
+ return {int64_t(balanced.days), balanced.time};
+}
+
+/**
+ * RoundTime ( hour, minute, second, millisecond, microsecond, nanosecond,
+ * increment, unit, roundingMode [ , dayLengthNs ] )
+ */
+RoundedTime js::temporal::RoundTime(const PlainTime& time, Increment increment,
+ TemporalUnit unit,
+ TemporalRoundingMode roundingMode,
+ const InstantSpan& dayLengthNs) {
+ MOZ_ASSERT(IsValidTime(time));
+ MOZ_ASSERT(IsValidInstantSpan(dayLengthNs));
+ MOZ_ASSERT(dayLengthNs > (InstantSpan{}));
+
+ if (unit != TemporalUnit::Day) {
+ return RoundTime(time, increment, unit, roundingMode);
+ }
+
+ // Step 1. (Not applicable)
+
+ // Step 2.
+ int64_t quantity = TimeToNanos(time);
+ MOZ_ASSERT(quantity < ToNanoseconds(TemporalUnit::Day));
+
+ // Steps 3-8. (Not applicable)
+
+ // Step 9.
+ int64_t divisor;
+ if (auto checkedDiv = dayLengthNs.toNanoseconds(); checkedDiv.isValid()) {
+ divisor = checkedDiv.value();
+ } else {
+ // When the divisor is too large, the expression `quantity / divisor` is a
+ // value near zero. Substitute |divisor| with an equivalent expression.
+ // Choose |86'400'000'000'000| which will give a similar result because
+ // |quantity| is guaranteed to be lower than |86'400'000'000'000|.
+ divisor = ToNanoseconds(TemporalUnit::Day);
+ }
+ MOZ_ASSERT(divisor > 0);
+
+ int64_t result =
+ ::RoundNumberToIncrement(quantity, divisor, increment, roundingMode);
+
+ // Step 10.
+ return {result, {0, 0, 0, 0, 0, 0}};
+}
+
+/**
+ * AddTime ( hour, minute, second, millisecond, microsecond, nanosecond, hours,
+ * minutes, seconds, milliseconds, microseconds, nanoseconds )
+ */
+static PlainTime AddTime(const PlainTime& time, const Duration& duration) {
+ MOZ_ASSERT(IsValidTime(time));
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ // Balance the duration so we don't have to worry about imprecise Number
+ // computations below.
+
+ // Use either int64_t or int32_t below. Assert the total combined amount of
+ // the units can be expressed in either int64_t or int32_t.
+ static_assert(1 * UnitsPerDay(TemporalUnit::Nanosecond) > INT32_MAX,
+ "total combined nanoseconds per day");
+ static_assert(2 * UnitsPerDay(TemporalUnit::Microsecond) > INT32_MAX,
+ "total combined microseconds per day");
+ static_assert(3 * UnitsPerDay(TemporalUnit::Millisecond) <= INT32_MAX,
+ "total combined milliseconds per day");
+ static_assert(4 * UnitsPerDay(TemporalUnit::Second) <= INT32_MAX,
+ "total combined seconds per day");
+ static_assert(5 * UnitsPerDay(TemporalUnit::Minute) <= INT32_MAX,
+ "total combined minutes per day");
+ static_assert(6 * UnitsPerDay(TemporalUnit::Hour) <= INT32_MAX,
+ "total combined hours per day");
+
+ // We ignore the days overflow in this function, therefore it's possible
+ // to restrict each unit to units-per-day.
+ int64_t nanoseconds = int64_t(
+ std::fmod(duration.nanoseconds, UnitsPerDay(TemporalUnit::Nanosecond)));
+ int64_t microseconds = int64_t(
+ std::fmod(duration.microseconds, UnitsPerDay(TemporalUnit::Microsecond)));
+ int32_t milliseconds = int32_t(
+ std::fmod(duration.milliseconds, UnitsPerDay(TemporalUnit::Millisecond)));
+ int32_t seconds =
+ int32_t(std::fmod(duration.seconds, UnitsPerDay(TemporalUnit::Second)));
+ int32_t minutes =
+ int32_t(std::fmod(duration.minutes, UnitsPerDay(TemporalUnit::Minute)));
+ int32_t hours =
+ int32_t(std::fmod(duration.hours, UnitsPerDay(TemporalUnit::Hour)));
+
+ // Each unit is now less than the units-per-day.
+ MOZ_ASSERT(std::abs(nanoseconds) < UnitsPerDay(TemporalUnit::Nanosecond));
+ MOZ_ASSERT(std::abs(microseconds) < UnitsPerDay(TemporalUnit::Microsecond));
+ MOZ_ASSERT(std::abs(milliseconds) < UnitsPerDay(TemporalUnit::Millisecond));
+ MOZ_ASSERT(std::abs(seconds) < UnitsPerDay(TemporalUnit::Second));
+ MOZ_ASSERT(std::abs(minutes) < UnitsPerDay(TemporalUnit::Minute));
+ MOZ_ASSERT(std::abs(hours) < UnitsPerDay(TemporalUnit::Hour));
+
+ microseconds += nanoseconds / 1000;
+ nanoseconds %= 1000;
+ MOZ_ASSERT(microseconds < 2 * UnitsPerDay(TemporalUnit::Microsecond));
+
+ milliseconds += microseconds / 1000;
+ microseconds %= 1000;
+ MOZ_ASSERT(milliseconds < 3 * UnitsPerDay(TemporalUnit::Millisecond));
+
+ seconds += milliseconds / 1000;
+ milliseconds %= 1000;
+ MOZ_ASSERT(seconds < 4 * UnitsPerDay(TemporalUnit::Second));
+
+ minutes += seconds / 60;
+ seconds %= 60;
+ MOZ_ASSERT(minutes < 5 * UnitsPerDay(TemporalUnit::Minute));
+
+ hours += minutes / 60;
+ minutes %= 60;
+ MOZ_ASSERT(hours < 6 * UnitsPerDay(TemporalUnit::Hour));
+
+ hours %= 24;
+
+ MOZ_ASSERT(std::abs(hours) <= 23);
+ MOZ_ASSERT(std::abs(minutes) <= 59);
+ MOZ_ASSERT(std::abs(seconds) <= 59);
+ MOZ_ASSERT(std::abs(milliseconds) <= 999);
+ MOZ_ASSERT(std::abs(microseconds) <= 999);
+ MOZ_ASSERT(std::abs(nanoseconds) <= 999);
+
+ // Step 1.
+ int32_t hour = time.hour + hours;
+
+ // Step 2.
+ int32_t minute = time.minute + minutes;
+
+ // Step 3.
+ int32_t second = time.second + seconds;
+
+ // Step 4.
+ int32_t millisecond = time.millisecond + milliseconds;
+
+ // Step 5.
+ int32_t microsecond = time.microsecond + int32_t(microseconds);
+
+ // Step 6.
+ int32_t nanosecond = time.nanosecond + int32_t(nanoseconds);
+
+ // Step 7.
+ auto balanced =
+ ::BalanceTime(hour, minute, second, millisecond, microsecond, nanosecond);
+ return balanced.time;
+}
+
+static BigInt* FloorDiv(JSContext* cx, Handle<BigInt*> dividend,
+ int32_t divisor) {
+ MOZ_ASSERT(divisor > 0);
+
+ Rooted<BigInt*> div(cx, BigInt::createFromInt64(cx, divisor));
+ if (!div) {
+ return nullptr;
+ }
+
+ Rooted<BigInt*> quotient(cx);
+ Rooted<BigInt*> remainder(cx);
+ if (!BigInt::divmod(cx, dividend, div, &quotient, &remainder)) {
+ return nullptr;
+ }
+ if (remainder->isNegative()) {
+ return BigInt::dec(cx, quotient);
+ }
+ return quotient;
+}
+
+static bool AddTimeDaysSlow(JSContext* cx, const PlainTime& time,
+ const Duration& duration, double* result) {
+ MOZ_ASSERT(IsValidTime(time));
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ Rooted<BigInt*> days(cx, BigInt::createFromDouble(cx, duration.days));
+ if (!days) {
+ return false;
+ }
+
+ Rooted<BigInt*> hours(cx, BigInt::createFromDouble(cx, duration.hours));
+ if (!hours) {
+ return false;
+ }
+
+ Rooted<BigInt*> minutes(cx, BigInt::createFromDouble(cx, duration.minutes));
+ if (!minutes) {
+ return false;
+ }
+
+ Rooted<BigInt*> seconds(cx, BigInt::createFromDouble(cx, duration.seconds));
+ if (!seconds) {
+ return false;
+ }
+
+ Rooted<BigInt*> milliseconds(
+ cx, BigInt::createFromDouble(cx, duration.milliseconds));
+ if (!milliseconds) {
+ return false;
+ }
+
+ Rooted<BigInt*> microseconds(
+ cx, BigInt::createFromDouble(cx, duration.microseconds));
+ if (!microseconds) {
+ return false;
+ }
+
+ Rooted<BigInt*> nanoseconds(
+ cx, BigInt::createFromDouble(cx, duration.nanoseconds));
+ if (!nanoseconds) {
+ return false;
+ }
+
+ auto addWithInt32 = [cx](Handle<BigInt*> left, int32_t right) -> BigInt* {
+ Rooted<BigInt*> rightBigInt(cx, BigInt::createFromInt64(cx, right));
+ if (!rightBigInt) {
+ return nullptr;
+ }
+ return BigInt::add(cx, left, rightBigInt);
+ };
+
+ // Step 1.
+ Rooted<BigInt*> hour(cx, addWithInt32(hours, time.hour));
+ if (!hour) {
+ return false;
+ }
+
+ // Step 2.
+ Rooted<BigInt*> minute(cx, addWithInt32(minutes, time.minute));
+ if (!minute) {
+ return false;
+ }
+
+ // Step 3.
+ Rooted<BigInt*> second(cx, addWithInt32(seconds, time.second));
+ if (!second) {
+ return false;
+ }
+
+ // Step 4.
+ Rooted<BigInt*> millisecond(cx, addWithInt32(milliseconds, time.millisecond));
+ if (!millisecond) {
+ return false;
+ }
+
+ // Step 5.
+ Rooted<BigInt*> microsecond(cx, addWithInt32(microseconds, time.microsecond));
+ if (!microsecond) {
+ return false;
+ }
+
+ // Step 6.
+ Rooted<BigInt*> nanosecond(cx, addWithInt32(nanoseconds, time.nanosecond));
+ if (!nanosecond) {
+ return false;
+ }
+
+ // Step 7. (Inlined BalanceTime)
+
+ auto addFloorDiv = [cx](Handle<BigInt*> left, Handle<BigInt*> right,
+ int32_t divisor) -> BigInt* {
+ Rooted<BigInt*> quotient(cx, FloorDiv(cx, right, divisor));
+ if (!quotient) {
+ return nullptr;
+ }
+ return BigInt::add(cx, left, quotient);
+ };
+
+ // BalanceTime, steps 1-2.
+ microsecond = addFloorDiv(microsecond, nanosecond, 1000);
+ if (!microsecond) {
+ return false;
+ }
+
+ // BalanceTime, steps 3-4.
+ millisecond = addFloorDiv(millisecond, microsecond, 1000);
+ if (!millisecond) {
+ return false;
+ }
+
+ // BalanceTime, steps 5-6.
+ second = addFloorDiv(second, millisecond, 1000);
+ if (!second) {
+ return false;
+ }
+
+ // BalanceTime, steps 7-8.
+ minute = addFloorDiv(minute, second, 60);
+ if (!minute) {
+ return false;
+ }
+
+ // BalanceTime, steps 9-10.
+ hour = addFloorDiv(hour, minute, 60);
+ if (!hour) {
+ return false;
+ }
+
+ // BalanceTime, steps 11-13.
+ days = addFloorDiv(days, hour, 24);
+ if (!days) {
+ return false;
+ }
+
+ // The days number is used as the input for a duration. Throw if the BigInt
+ // when converted to a Number can't be represented in a duration.
+ double daysNumber = BigInt::numberValue(days);
+ if (!ThrowIfInvalidDuration(cx, {0, 0, 0, daysNumber})) {
+ return false;
+ }
+ MOZ_ASSERT(IsInteger(daysNumber));
+
+ *result = daysNumber;
+ return true;
+}
+
+static mozilla::Maybe<int64_t> AddTimeDays(const PlainTime& time,
+ const Duration& duration) {
+ MOZ_ASSERT(IsValidTime(time));
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ int64_t days;
+ if (!mozilla::NumberEqualsInt64(duration.days, &days)) {
+ return mozilla::Nothing();
+ }
+
+ int64_t hours;
+ if (!mozilla::NumberEqualsInt64(duration.hours, &hours)) {
+ return mozilla::Nothing();
+ }
+
+ int64_t minutes;
+ if (!mozilla::NumberEqualsInt64(duration.minutes, &minutes)) {
+ return mozilla::Nothing();
+ }
+
+ int64_t seconds;
+ if (!mozilla::NumberEqualsInt64(duration.seconds, &seconds)) {
+ return mozilla::Nothing();
+ }
+
+ int64_t milliseconds;
+ if (!mozilla::NumberEqualsInt64(duration.milliseconds, &milliseconds)) {
+ return mozilla::Nothing();
+ }
+
+ int64_t microseconds;
+ if (!mozilla::NumberEqualsInt64(duration.microseconds, &microseconds)) {
+ return mozilla::Nothing();
+ }
+
+ int64_t nanoseconds;
+ if (!mozilla::NumberEqualsInt64(duration.nanoseconds, &nanoseconds)) {
+ return mozilla::Nothing();
+ }
+
+ // Step 1.
+ auto hour = mozilla::CheckedInt64(time.hour) + hours;
+ if (!hour.isValid()) {
+ return mozilla::Nothing();
+ }
+
+ // Step 2.
+ auto minute = mozilla::CheckedInt64(time.minute) + minutes;
+ if (!minute.isValid()) {
+ return mozilla::Nothing();
+ }
+
+ // Step 3.
+ auto second = mozilla::CheckedInt64(time.second) + seconds;
+ if (!second.isValid()) {
+ return mozilla::Nothing();
+ }
+
+ // Step 4.
+ auto millisecond = mozilla::CheckedInt64(time.millisecond) + milliseconds;
+ if (!millisecond.isValid()) {
+ return mozilla::Nothing();
+ }
+
+ // Step 5.
+ auto microsecond = mozilla::CheckedInt64(time.microsecond) + microseconds;
+ if (!microsecond.isValid()) {
+ return mozilla::Nothing();
+ }
+
+ // Step 6.
+ auto nanosecond = mozilla::CheckedInt64(time.nanosecond) + nanoseconds;
+ if (!nanosecond.isValid()) {
+ return mozilla::Nothing();
+ }
+
+ // Step 7. (Inlined BalanceTime)
+
+ // BalanceTime, steps 1-2.
+ microsecond += FloorDiv(nanosecond.value(), 1000);
+ if (!microsecond.isValid()) {
+ return mozilla::Nothing();
+ }
+
+ // BalanceTime, steps 3-4.
+ millisecond += FloorDiv(microsecond.value(), 1000);
+ if (!millisecond.isValid()) {
+ return mozilla::Nothing();
+ }
+
+ // BalanceTime, steps 5-6.
+ second += FloorDiv(millisecond.value(), 1000);
+ if (!second.isValid()) {
+ return mozilla::Nothing();
+ }
+
+ // BalanceTime, steps 7-8.
+ minute += FloorDiv(second.value(), 60);
+ if (!minute.isValid()) {
+ return mozilla::Nothing();
+ }
+
+ // BalanceTime, steps 9-10.
+ hour += FloorDiv(minute.value(), 60);
+ if (!hour.isValid()) {
+ return mozilla::Nothing();
+ }
+
+ // BalanceTime, steps 11-13.
+ auto result = mozilla::CheckedInt64(days) + FloorDiv(hour.value(), 24);
+ if (!result.isValid()) {
+ return mozilla::Nothing();
+ }
+ return mozilla::Some(result.value());
+}
+
+static bool AddTimeDays(JSContext* cx, const PlainTime& time,
+ const Duration& duration, double* result) {
+ // Fast-path when we can perform the whole computation with int64 values.
+ if (auto days = AddTimeDays(time, duration)) {
+ *result = *days;
+ return true;
+ }
+ return AddTimeDaysSlow(cx, time, duration, result);
+}
+
+/**
+ * AddTime ( hour, minute, second, millisecond, microsecond, nanosecond, hours,
+ * minutes, seconds, milliseconds, microseconds, nanoseconds )
+ */
+bool js::temporal::AddTime(JSContext* cx, const PlainTime& time,
+ const Duration& duration, PlainTime* result,
+ double* daysResult) {
+ MOZ_ASSERT(IsValidTime(time));
+ MOZ_ASSERT(IsValidDuration(duration));
+
+ // Steps 1-7.
+ auto balanced = ::AddTime(time, duration);
+
+ // Compute |days| separately to ensure no loss of precision occurs.
+ //
+ // The single caller of this |AddTime| function also needs to compute the
+ // addition of |duration.days| and the balanced days. Perform this addition
+ // here, so we don't need to pass around BigInt values for exact mathematical
+ // results.
+ double days;
+ if (!AddTimeDays(cx, time, duration, &days)) {
+ return false;
+ }
+ MOZ_ASSERT(IsInteger(days));
+
+ *result = balanced;
+ *daysResult = days;
+ return true;
+}
+
+/**
+ * DifferenceTemporalPlainTime ( operation, temporalTime, other, options )
+ */
+static bool DifferenceTemporalPlainTime(JSContext* cx,
+ TemporalDifference operation,
+ const CallArgs& args) {
+ auto temporalTime =
+ ToPlainTime(&args.thisv().toObject().as<PlainTimeObject>());
+
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ PlainTime other;
+ if (!ToTemporalTime(cx, args.get(0), &other)) {
+ return false;
+ }
+
+ // Steps 3-4.
+ DifferenceSettings settings;
+ if (args.hasDefined(1)) {
+ Rooted<JSObject*> options(
+ cx, RequireObjectArg(cx, "options", ToName(operation), args[1]));
+ if (!options) {
+ return false;
+ }
+
+ // Step 3.
+ Rooted<PlainObject*> resolvedOptions(cx,
+ SnapshotOwnProperties(cx, options));
+ if (!resolvedOptions) {
+ return false;
+ }
+
+ // Step 4.
+ if (!GetDifferenceSettings(
+ cx, operation, resolvedOptions, TemporalUnitGroup::Time,
+ TemporalUnit::Nanosecond, TemporalUnit::Hour, &settings)) {
+ return false;
+ }
+ } else {
+ // Steps 3-4.
+ settings = {
+ TemporalUnit::Nanosecond,
+ TemporalUnit::Hour,
+ TemporalRoundingMode::Trunc,
+ Increment{1},
+ };
+ }
+
+ // Step 5.
+ auto diff = DifferenceTime(temporalTime, other);
+ MOZ_ASSERT(diff.days == 0);
+
+ // Step 6.
+ auto roundedDuration = diff.toDuration();
+ if (settings.smallestUnit != TemporalUnit::Nanosecond ||
+ settings.roundingIncrement != Increment{1}) {
+ // Steps 6.a-b.
+ if (!RoundDuration(cx, roundedDuration.time(), settings.roundingIncrement,
+ settings.smallestUnit, settings.roundingMode,
+ &roundedDuration)) {
+ return false;
+ }
+ }
+
+ // Step 7.
+ auto balancedDuration =
+ BalanceTimeDuration(roundedDuration, settings.largestUnit);
+
+ // Step 8.
+ if (operation == TemporalDifference::Since) {
+ balancedDuration = balancedDuration.negate();
+ }
+
+ auto* result = CreateTemporalDuration(cx, balancedDuration);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+enum class PlainTimeDuration { Add, Subtract };
+
+/**
+ * AddDurationToOrSubtractDurationFromPlainTime ( operation, temporalTime,
+ * temporalDurationLike )
+ */
+static bool AddDurationToOrSubtractDurationFromPlainTime(
+ JSContext* cx, PlainTimeDuration operation, const CallArgs& args) {
+ auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
+ auto time = ToPlainTime(temporalTime);
+
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ Duration duration;
+ if (!ToTemporalDurationRecord(cx, args.get(0), &duration)) {
+ return false;
+ }
+
+ // Step 3.
+ if (operation == PlainTimeDuration::Subtract) {
+ duration = duration.negate();
+ }
+ auto result = AddTime(time, duration);
+
+ // Step 4.
+ MOZ_ASSERT(IsValidTime(result));
+
+ // Step 5.
+ auto* obj = CreateTemporalTime(cx, result);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainTime ( [ hour [ , minute [ , second [ , millisecond [ ,
+ * microsecond [ , nanosecond ] ] ] ] ] ] )
+ */
+static bool PlainTimeConstructor(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ if (!ThrowIfNotConstructing(cx, args, "Temporal.PlainTime")) {
+ return false;
+ }
+
+ // Step 2.
+ double hour = 0;
+ if (args.hasDefined(0)) {
+ if (!ToIntegerWithTruncation(cx, args[0], "hour", &hour)) {
+ return false;
+ }
+ }
+
+ // Step 3.
+ double minute = 0;
+ if (args.hasDefined(1)) {
+ if (!ToIntegerWithTruncation(cx, args[1], "minute", &minute)) {
+ return false;
+ }
+ }
+
+ // Step 4.
+ double second = 0;
+ if (args.hasDefined(2)) {
+ if (!ToIntegerWithTruncation(cx, args[2], "second", &second)) {
+ return false;
+ }
+ }
+
+ // Step 5.
+ double millisecond = 0;
+ if (args.hasDefined(3)) {
+ if (!ToIntegerWithTruncation(cx, args[3], "millisecond", &millisecond)) {
+ return false;
+ }
+ }
+
+ // Step 6.
+ double microsecond = 0;
+ if (args.hasDefined(4)) {
+ if (!ToIntegerWithTruncation(cx, args[4], "microsecond", &microsecond)) {
+ return false;
+ }
+ }
+
+ // Step 7.
+ double nanosecond = 0;
+ if (args.hasDefined(5)) {
+ if (!ToIntegerWithTruncation(cx, args[5], "nanosecond", &nanosecond)) {
+ return false;
+ }
+ }
+
+ // Step 8.
+ auto* temporalTime = CreateTemporalTime(cx, args, hour, minute, second,
+ millisecond, microsecond, nanosecond);
+ if (!temporalTime) {
+ return false;
+ }
+
+ args.rval().setObject(*temporalTime);
+ return true;
+}
+
+/**
+ * Temporal.PlainTime.from ( item [ , options ] )
+ */
+static bool PlainTime_from(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1. (Not applicable)
+
+ auto overflow = TemporalOverflow::Constrain;
+ if (args.hasDefined(1)) {
+ // Step 2.
+ Rooted<JSObject*> options(cx,
+ RequireObjectArg(cx, "options", "from", args[1]));
+ if (!options) {
+ return false;
+ }
+
+ // Step 3.
+ if (!ToTemporalOverflow(cx, options, &overflow)) {
+ return false;
+ }
+ }
+
+ // Steps 4-5.
+ auto result = ToTemporalTime(cx, args.get(0), overflow);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.PlainTime.compare ( one, two )
+ */
+static bool PlainTime_compare(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ PlainTime one;
+ if (!ToTemporalTime(cx, args.get(0), &one)) {
+ return false;
+ }
+
+ // Step 2.
+ PlainTime two;
+ if (!ToTemporalTime(cx, args.get(1), &two)) {
+ return false;
+ }
+
+ // Step 3.
+ args.rval().setInt32(CompareTemporalTime(one, two));
+ return true;
+}
+
+/**
+ * get Temporal.PlainTime.prototype.hour
+ */
+static bool PlainTime_hour(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
+ args.rval().setInt32(temporalTime->isoHour());
+ return true;
+}
+
+/**
+ * get Temporal.PlainTime.prototype.hour
+ */
+static bool PlainTime_hour(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_hour>(cx, args);
+}
+
+/**
+ * get Temporal.PlainTime.prototype.minute
+ */
+static bool PlainTime_minute(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
+ args.rval().setInt32(temporalTime->isoMinute());
+ return true;
+}
+
+/**
+ * get Temporal.PlainTime.prototype.minute
+ */
+static bool PlainTime_minute(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_minute>(cx, args);
+}
+
+/**
+ * get Temporal.PlainTime.prototype.second
+ */
+static bool PlainTime_second(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
+ args.rval().setInt32(temporalTime->isoSecond());
+ return true;
+}
+
+/**
+ * get Temporal.PlainTime.prototype.second
+ */
+static bool PlainTime_second(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_second>(cx, args);
+}
+
+/**
+ * get Temporal.PlainTime.prototype.millisecond
+ */
+static bool PlainTime_millisecond(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
+ args.rval().setInt32(temporalTime->isoMillisecond());
+ return true;
+}
+
+/**
+ * get Temporal.PlainTime.prototype.millisecond
+ */
+static bool PlainTime_millisecond(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_millisecond>(cx, args);
+}
+
+/**
+ * get Temporal.PlainTime.prototype.microsecond
+ */
+static bool PlainTime_microsecond(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
+ args.rval().setInt32(temporalTime->isoMicrosecond());
+ return true;
+}
+
+/**
+ * get Temporal.PlainTime.prototype.microsecond
+ */
+static bool PlainTime_microsecond(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_microsecond>(cx, args);
+}
+
+/**
+ * get Temporal.PlainTime.prototype.nanosecond
+ */
+static bool PlainTime_nanosecond(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
+ args.rval().setInt32(temporalTime->isoNanosecond());
+ return true;
+}
+
+/**
+ * get Temporal.PlainTime.prototype.nanosecond
+ */
+static bool PlainTime_nanosecond(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_nanosecond>(cx, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.add ( temporalDurationLike )
+ */
+static bool PlainTime_add(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ return AddDurationToOrSubtractDurationFromPlainTime(
+ cx, PlainTimeDuration::Add, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.add ( temporalDurationLike )
+ */
+static bool PlainTime_add(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_add>(cx, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.subtract ( temporalDurationLike )
+ */
+static bool PlainTime_subtract(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ return AddDurationToOrSubtractDurationFromPlainTime(
+ cx, PlainTimeDuration::Subtract, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.subtract ( temporalDurationLike )
+ */
+static bool PlainTime_subtract(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_subtract>(cx, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.with ( temporalTimeLike [ , options ] )
+ */
+static bool PlainTime_with(JSContext* cx, const CallArgs& args) {
+ auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
+ auto time = ToPlainTime(temporalTime);
+
+ // Step 3.
+ Rooted<JSObject*> temporalTimeLike(
+ cx, RequireObjectArg(cx, "temporalTimeLike", "with", args.get(0)));
+ if (!temporalTimeLike) {
+ return false;
+ }
+
+ // Step 4.
+ if (!RejectTemporalLikeObject(cx, temporalTimeLike)) {
+ return false;
+ }
+
+ auto overflow = TemporalOverflow::Constrain;
+ if (args.hasDefined(1)) {
+ // Step 5.
+ Rooted<JSObject*> options(cx,
+ RequireObjectArg(cx, "options", "with", args[1]));
+ if (!options) {
+ return false;
+ }
+
+ // Step 6.
+ if (!ToTemporalOverflow(cx, options, &overflow)) {
+ return false;
+ }
+ }
+
+ // Steps 7-19.
+ TimeRecord partialTime = {
+ double(time.hour), double(time.minute),
+ double(time.second), double(time.millisecond),
+ double(time.microsecond), double(time.nanosecond),
+ };
+ if (!::ToTemporalTimeRecord(cx, temporalTimeLike, &partialTime)) {
+ return false;
+ }
+
+ // Step 20.
+ PlainTime result;
+ if (!RegulateTime(cx, partialTime, overflow, &result)) {
+ return false;
+ }
+
+ // Step 21.
+ auto* obj = CreateTemporalTime(cx, result);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainTime.prototype.with ( temporalTimeLike [ , options ] )
+ */
+static bool PlainTime_with(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_with>(cx, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.until ( other [ , options ] )
+ */
+static bool PlainTime_until(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ return DifferenceTemporalPlainTime(cx, TemporalDifference::Until, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.until ( other [ , options ] )
+ */
+static bool PlainTime_until(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_until>(cx, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.since ( other [ , options ] )
+ */
+static bool PlainTime_since(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ return DifferenceTemporalPlainTime(cx, TemporalDifference::Since, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.since ( other [ , options ] )
+ */
+static bool PlainTime_since(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_since>(cx, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.round ( roundTo )
+ */
+static bool PlainTime_round(JSContext* cx, const CallArgs& args) {
+ auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
+ auto time = ToPlainTime(temporalTime);
+
+ // Steps 3-12.
+ auto smallestUnit = TemporalUnit::Auto;
+ auto roundingMode = TemporalRoundingMode::HalfExpand;
+ auto roundingIncrement = Increment{1};
+ if (args.get(0).isString()) {
+ // Step 4. (Not applicable in our implementation.)
+
+ // Step 9.
+ Rooted<JSString*> paramString(cx, args[0].toString());
+ if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::SmallestUnit,
+ TemporalUnitGroup::Time, &smallestUnit)) {
+ return false;
+ }
+
+ // Steps 6-8 and 10-12. (Implicit)
+ } else {
+ // Steps 3 and 5.
+ Rooted<JSObject*> options(
+ cx, RequireObjectArg(cx, "roundTo", "round", args.get(0)));
+ if (!options) {
+ return false;
+ }
+
+ // Steps 6-7.
+ if (!ToTemporalRoundingIncrement(cx, options, &roundingIncrement)) {
+ return false;
+ }
+
+ // Step 8.
+ if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
+ return false;
+ }
+
+ // Step 9.
+ if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
+ TemporalUnitGroup::Time, &smallestUnit)) {
+ return false;
+ }
+
+ if (smallestUnit == TemporalUnit::Auto) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_MISSING_OPTION, "smallestUnit");
+ return false;
+ }
+
+ // Steps 10-11.
+ auto maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
+
+ // Step 12.
+ if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
+ false)) {
+ return false;
+ }
+ }
+
+ // Step 13.
+ auto result = RoundTime(time, roundingIncrement, smallestUnit, roundingMode);
+
+ // Step 14.
+ auto* obj = CreateTemporalTime(cx, result.time);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainTime.prototype.round ( roundTo )
+ */
+static bool PlainTime_round(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_round>(cx, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.equals ( other )
+ */
+static bool PlainTime_equals(JSContext* cx, const CallArgs& args) {
+ auto temporalTime =
+ ToPlainTime(&args.thisv().toObject().as<PlainTimeObject>());
+
+ // Step 3.
+ PlainTime other;
+ if (!ToTemporalTime(cx, args.get(0), &other)) {
+ return false;
+ }
+
+ // Steps 4-10.
+ args.rval().setBoolean(temporalTime == other);
+ return true;
+}
+
+/**
+ * Temporal.PlainTime.prototype.equals ( other )
+ */
+static bool PlainTime_equals(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_equals>(cx, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.toPlainDateTime ( temporalDate )
+ */
+static bool PlainTime_toPlainDateTime(JSContext* cx, const CallArgs& args) {
+ auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
+ auto time = ToPlainTime(temporalTime);
+
+ // Step 3.
+ Rooted<PlainDateWithCalendar> plainDate(cx);
+ if (!ToTemporalDate(cx, args.get(0), &plainDate)) {
+ return false;
+ }
+ auto date = plainDate.date();
+ auto calendar = plainDate.calendar();
+
+ // Step 4.
+ auto* result = CreateTemporalDateTime(cx, {date, time}, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.PlainTime.prototype.toPlainDateTime ( temporalDate )
+ */
+static bool PlainTime_toPlainDateTime(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_toPlainDateTime>(cx, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.toZonedDateTime ( item )
+ *
+ * |item| is an options object with `plainDate` and `timeZone` properties.
+ */
+static bool PlainTime_toZonedDateTime(JSContext* cx, const CallArgs& args) {
+ auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
+ auto time = ToPlainTime(temporalTime);
+
+ // Step 3.
+ Rooted<JSObject*> itemObj(
+ cx, RequireObjectArg(cx, "item", "toZonedDateTime", args.get(0)));
+ if (!itemObj) {
+ return false;
+ }
+
+ // Step 4.
+ Rooted<Value> temporalDateLike(cx);
+ if (!GetProperty(cx, itemObj, args[0], cx->names().plainDate,
+ &temporalDateLike)) {
+ return false;
+ }
+
+ // Step 5.
+ if (temporalDateLike.isUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_MISSING_PROPERTY, "plainDate");
+ return false;
+ }
+
+ // Step 6.
+ Rooted<PlainDateWithCalendar> plainDate(cx);
+ if (!ToTemporalDate(cx, temporalDateLike, &plainDate)) {
+ return false;
+ }
+ auto date = plainDate.date();
+ auto calendar = plainDate.calendar();
+
+ // Step 7.
+ Rooted<Value> temporalTimeZoneLike(cx);
+ if (!GetProperty(cx, itemObj, itemObj, cx->names().timeZone,
+ &temporalTimeZoneLike)) {
+ return false;
+ }
+
+ // Step 8.
+ if (temporalTimeZoneLike.isUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_MISSING_PROPERTY, "timeZone");
+ return false;
+ }
+
+ // Step 9.
+ Rooted<TimeZoneValue> timeZone(cx);
+ if (!ToTemporalTimeZone(cx, temporalTimeZoneLike, &timeZone)) {
+ return false;
+ }
+
+ // Step 10.
+ Rooted<PlainDateTimeWithCalendar> temporalDateTime(cx);
+ if (!CreateTemporalDateTime(cx, {date, time}, calendar, &temporalDateTime)) {
+ return false;
+ }
+
+ // Steps 11-12.
+ Instant instant;
+ if (!GetInstantFor(cx, timeZone, temporalDateTime,
+ TemporalDisambiguation::Compatible, &instant)) {
+ return false;
+ }
+
+ // Step 13.
+ auto* result = CreateTemporalZonedDateTime(cx, instant, timeZone, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.PlainTime.prototype.toZonedDateTime ( item )
+ */
+static bool PlainTime_toZonedDateTime(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_toZonedDateTime>(cx, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.getISOFields ( )
+ */
+static bool PlainTime_getISOFields(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainTimeObject*> temporalTime(
+ cx, &args.thisv().toObject().as<PlainTimeObject>());
+ auto time = ToPlainTime(temporalTime);
+
+ // Step 3.
+ Rooted<IdValueVector> fields(cx, IdValueVector(cx));
+
+ // Step 4.
+ if (!fields.emplaceBack(NameToId(cx->names().isoHour),
+ Int32Value(time.hour))) {
+ return false;
+ }
+
+ // Step 5.
+ if (!fields.emplaceBack(NameToId(cx->names().isoMicrosecond),
+ Int32Value(time.microsecond))) {
+ return false;
+ }
+
+ // Step 6.
+ if (!fields.emplaceBack(NameToId(cx->names().isoMillisecond),
+ Int32Value(time.millisecond))) {
+ return false;
+ }
+
+ // Step 7.
+ if (!fields.emplaceBack(NameToId(cx->names().isoMinute),
+ Int32Value(time.minute))) {
+ return false;
+ }
+
+ // Step 8.
+ if (!fields.emplaceBack(NameToId(cx->names().isoNanosecond),
+ Int32Value(time.nanosecond))) {
+ return false;
+ }
+
+ // Step 9.
+ if (!fields.emplaceBack(NameToId(cx->names().isoSecond),
+ Int32Value(time.second))) {
+ return false;
+ }
+
+ // Step 10.
+ auto* obj =
+ NewPlainObjectWithUniqueNames(cx, fields.begin(), fields.length());
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainTime.prototype.getISOFields ( )
+ */
+static bool PlainTime_getISOFields(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_getISOFields>(cx, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.toString ( [ options ] )
+ */
+static bool PlainTime_toString(JSContext* cx, const CallArgs& args) {
+ auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
+ auto time = ToPlainTime(temporalTime);
+
+ SecondsStringPrecision precision = {Precision::Auto(),
+ TemporalUnit::Nanosecond, Increment{1}};
+ auto roundingMode = TemporalRoundingMode::Trunc;
+ if (args.hasDefined(0)) {
+ // Step 3.
+ Rooted<JSObject*> options(
+ cx, RequireObjectArg(cx, "options", "toString", args[0]));
+ if (!options) {
+ return false;
+ }
+
+ // Steps 4-5.
+ auto digits = Precision::Auto();
+ if (!ToFractionalSecondDigits(cx, options, &digits)) {
+ return false;
+ }
+
+ // Step 6.
+ if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
+ return false;
+ }
+
+ // Step 7.
+ auto smallestUnit = TemporalUnit::Auto;
+ if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
+ TemporalUnitGroup::Time, &smallestUnit)) {
+ return false;
+ }
+
+ // Step 8.
+ if (smallestUnit == TemporalUnit::Hour) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_UNIT_OPTION, "hour",
+ "smallestUnit");
+ return false;
+ }
+
+ // Step 9.
+ precision = ToSecondsStringPrecision(smallestUnit, digits);
+ }
+
+ // Step 10.
+ auto roundedTime =
+ RoundTime(time, precision.increment, precision.unit, roundingMode);
+
+ // Step 11.
+ JSString* str =
+ TemporalTimeToString(cx, roundedTime.time, precision.precision);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.PlainTime.prototype.toString ( [ options ] )
+ */
+static bool PlainTime_toString(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_toString>(cx, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.toLocaleString ( [ locales [ , options ] ] )
+ */
+static bool PlainTime_toLocaleString(JSContext* cx, const CallArgs& args) {
+ auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
+ auto time = ToPlainTime(temporalTime);
+
+ // Step 3.
+ JSString* str = TemporalTimeToString(cx, time, Precision::Auto());
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.PlainTime.prototype.toLocaleString ( [ locales [ , options ] ] )
+ */
+static bool PlainTime_toLocaleString(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_toLocaleString>(cx, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.toJSON ( )
+ */
+static bool PlainTime_toJSON(JSContext* cx, const CallArgs& args) {
+ auto* temporalTime = &args.thisv().toObject().as<PlainTimeObject>();
+ auto time = ToPlainTime(temporalTime);
+
+ // Step 3.
+ JSString* str = TemporalTimeToString(cx, time, Precision::Auto());
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.PlainTime.prototype.toJSON ( )
+ */
+static bool PlainTime_toJSON(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainTime, PlainTime_toJSON>(cx, args);
+}
+
+/**
+ * Temporal.PlainTime.prototype.valueOf ( )
+ */
+static bool PlainTime_valueOf(JSContext* cx, unsigned argc, Value* vp) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
+ "PlainTime", "primitive type");
+ return false;
+}
+
+const JSClass PlainTimeObject::class_ = {
+ "Temporal.PlainTime",
+ JSCLASS_HAS_RESERVED_SLOTS(PlainTimeObject::SLOT_COUNT) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_PlainTime),
+ JS_NULL_CLASS_OPS,
+ &PlainTimeObject::classSpec_,
+};
+
+const JSClass& PlainTimeObject::protoClass_ = PlainObject::class_;
+
+static const JSFunctionSpec PlainTime_methods[] = {
+ JS_FN("from", PlainTime_from, 1, 0),
+ JS_FN("compare", PlainTime_compare, 2, 0),
+ JS_FS_END,
+};
+
+static const JSFunctionSpec PlainTime_prototype_methods[] = {
+ JS_FN("add", PlainTime_add, 1, 0),
+ JS_FN("subtract", PlainTime_subtract, 1, 0),
+ JS_FN("with", PlainTime_with, 1, 0),
+ JS_FN("until", PlainTime_until, 1, 0),
+ JS_FN("since", PlainTime_since, 1, 0),
+ JS_FN("round", PlainTime_round, 1, 0),
+ JS_FN("equals", PlainTime_equals, 1, 0),
+ JS_FN("toPlainDateTime", PlainTime_toPlainDateTime, 1, 0),
+ JS_FN("toZonedDateTime", PlainTime_toZonedDateTime, 1, 0),
+ JS_FN("getISOFields", PlainTime_getISOFields, 0, 0),
+ JS_FN("toString", PlainTime_toString, 0, 0),
+ JS_FN("toLocaleString", PlainTime_toLocaleString, 0, 0),
+ JS_FN("toJSON", PlainTime_toJSON, 0, 0),
+ JS_FN("valueOf", PlainTime_valueOf, 0, 0),
+ JS_FS_END,
+};
+
+static const JSPropertySpec PlainTime_prototype_properties[] = {
+ JS_PSG("hour", PlainTime_hour, 0),
+ JS_PSG("minute", PlainTime_minute, 0),
+ JS_PSG("second", PlainTime_second, 0),
+ JS_PSG("millisecond", PlainTime_millisecond, 0),
+ JS_PSG("microsecond", PlainTime_microsecond, 0),
+ JS_PSG("nanosecond", PlainTime_nanosecond, 0),
+ JS_STRING_SYM_PS(toStringTag, "Temporal.PlainTime", JSPROP_READONLY),
+ JS_PS_END,
+};
+
+const ClassSpec PlainTimeObject::classSpec_ = {
+ GenericCreateConstructor<PlainTimeConstructor, 0, gc::AllocKind::FUNCTION>,
+ GenericCreatePrototype<PlainTimeObject>,
+ PlainTime_methods,
+ nullptr,
+ PlainTime_prototype_methods,
+ PlainTime_prototype_properties,
+ nullptr,
+ ClassSpec::DontDefineConstructor,
+};
diff --git a/js/src/builtin/temporal/PlainTime.h b/js/src/builtin/temporal/PlainTime.h
new file mode 100644
index 0000000000..b6da469913
--- /dev/null
+++ b/js/src/builtin/temporal/PlainTime.h
@@ -0,0 +1,187 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_PlainTime_h
+#define builtin_temporal_PlainTime_h
+
+#include <stdint.h>
+
+#include "builtin/temporal/TemporalTypes.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "vm/NativeObject.h"
+
+namespace js {
+struct ClassSpec;
+}
+
+namespace js::temporal {
+
+class PlainTimeObject : public NativeObject {
+ public:
+ static const JSClass class_;
+ static const JSClass& protoClass_;
+
+ // TODO: Consider compacting fields to reduce object size.
+ //
+ // ceil(log2(24)) + 2 * ceil(log2(60)) + 3 * ceil(log2(1000)) = 47 bits are
+ // needed to store a time value in a single int64. 47 bits can be stored as
+ // raw bits in a JS::Value.
+
+ static constexpr uint32_t ISO_HOUR_SLOT = 0;
+ static constexpr uint32_t ISO_MINUTE_SLOT = 1;
+ static constexpr uint32_t ISO_SECOND_SLOT = 2;
+ static constexpr uint32_t ISO_MILLISECOND_SLOT = 3;
+ static constexpr uint32_t ISO_MICROSECOND_SLOT = 4;
+ static constexpr uint32_t ISO_NANOSECOND_SLOT = 5;
+ static constexpr uint32_t SLOT_COUNT = 6;
+
+ int32_t isoHour() const { return getFixedSlot(ISO_HOUR_SLOT).toInt32(); }
+
+ int32_t isoMinute() const { return getFixedSlot(ISO_MINUTE_SLOT).toInt32(); }
+
+ int32_t isoSecond() const { return getFixedSlot(ISO_SECOND_SLOT).toInt32(); }
+
+ int32_t isoMillisecond() const {
+ return getFixedSlot(ISO_MILLISECOND_SLOT).toInt32();
+ }
+
+ int32_t isoMicrosecond() const {
+ return getFixedSlot(ISO_MICROSECOND_SLOT).toInt32();
+ }
+
+ int32_t isoNanosecond() const {
+ return getFixedSlot(ISO_NANOSECOND_SLOT).toInt32();
+ }
+
+ private:
+ static const ClassSpec classSpec_;
+};
+
+/**
+ * Extract the time fields from the PlainTime object.
+ */
+inline PlainTime ToPlainTime(const PlainTimeObject* time) {
+ return {time->isoHour(), time->isoMinute(),
+ time->isoSecond(), time->isoMillisecond(),
+ time->isoMicrosecond(), time->isoNanosecond()};
+}
+
+class Increment;
+enum class TemporalOverflow;
+enum class TemporalRoundingMode;
+enum class TemporalUnit;
+
+#ifdef DEBUG
+/**
+ * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
+ */
+bool IsValidTime(const PlainTime& time);
+
+/**
+ * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
+ */
+bool IsValidTime(double hour, double minute, double second, double millisecond,
+ double microsecond, double nanosecond);
+#endif
+
+/**
+ * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
+ */
+bool ThrowIfInvalidTime(JSContext* cx, const PlainTime& time);
+
+/**
+ * IsValidTime ( hour, minute, second, millisecond, microsecond, nanosecond )
+ */
+bool ThrowIfInvalidTime(JSContext* cx, double hour, double minute,
+ double second, double millisecond, double microsecond,
+ double nanosecond);
+
+/**
+ * CreateTemporalTime ( hour, minute, second, millisecond, microsecond,
+ * nanosecond [ , newTarget ] )
+ */
+PlainTimeObject* CreateTemporalTime(JSContext* cx, const PlainTime& time);
+
+/**
+ * ToTemporalTime ( item [ , overflow ] )
+ */
+bool ToTemporalTime(JSContext* cx, JS::Handle<JS::Value> item,
+ PlainTime* result);
+
+/**
+ * AddTime ( hour, minute, second, millisecond, microsecond, nanosecond, hours,
+ * minutes, seconds, milliseconds, microseconds, nanoseconds )
+ */
+bool AddTime(JSContext* cx, const PlainTime& time, const Duration& duration,
+ PlainTime* result, double* daysResult);
+
+/**
+ * DifferenceTime ( h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2, ns2 )
+ */
+TimeDuration DifferenceTime(const PlainTime& time1, const PlainTime& time2);
+
+struct TimeRecord final {
+ double hour = 0;
+ double minute = 0;
+ double second = 0;
+ double millisecond = 0;
+ double microsecond = 0;
+ double nanosecond = 0;
+};
+
+/**
+ * ToTemporalTimeRecord ( temporalTimeLike [ , completeness ] )
+ */
+bool ToTemporalTimeRecord(JSContext* cx, JS::Handle<JSObject*> temporalTimeLike,
+ TimeRecord* result);
+
+/**
+ * RegulateTime ( hour, minute, second, millisecond, microsecond, nanosecond,
+ * overflow )
+ */
+bool RegulateTime(JSContext* cx, const TimeRecord& time,
+ TemporalOverflow overflow, PlainTime* result);
+
+/**
+ * CompareTemporalTime ( h1, min1, s1, ms1, mus1, ns1, h2, min2, s2, ms2, mus2,
+ * ns2 )
+ */
+int32_t CompareTemporalTime(const PlainTime& one, const PlainTime& two);
+
+struct BalancedTime final {
+ int32_t days = 0;
+ PlainTime time;
+};
+
+/**
+ * BalanceTime ( hour, minute, second, millisecond, microsecond, nanosecond )
+ */
+BalancedTime BalanceTime(const PlainTime& time, int64_t nanoseconds);
+
+struct RoundedTime final {
+ int64_t days = 0;
+ PlainTime time;
+};
+
+/**
+ * RoundTime ( hour, minute, second, millisecond, microsecond, nanosecond,
+ * increment, unit, roundingMode [ , dayLengthNs ] )
+ */
+RoundedTime RoundTime(const PlainTime& time, Increment increment,
+ TemporalUnit unit, TemporalRoundingMode roundingMode);
+
+/**
+ * RoundTime ( hour, minute, second, millisecond, microsecond, nanosecond,
+ * increment, unit, roundingMode [ , dayLengthNs ] )
+ */
+RoundedTime RoundTime(const PlainTime& time, Increment increment,
+ TemporalUnit unit, TemporalRoundingMode roundingMode,
+ const InstantSpan& dayLengthNs);
+
+} /* namespace js::temporal */
+
+#endif /* builtin_temporal_PlainTime_h */
diff --git a/js/src/builtin/temporal/PlainYearMonth.cpp b/js/src/builtin/temporal/PlainYearMonth.cpp
new file mode 100644
index 0000000000..a4e2f8f9e4
--- /dev/null
+++ b/js/src/builtin/temporal/PlainYearMonth.cpp
@@ -0,0 +1,1642 @@
+/* -*- 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/PlainYearMonth.h"
+
+#include "mozilla/Assertions.h"
+
+#include <type_traits>
+#include <utility>
+
+#include "jsnum.h"
+#include "jspubtd.h"
+#include "NamespaceImports.h"
+
+#include "builtin/temporal/Calendar.h"
+#include "builtin/temporal/Duration.h"
+#include "builtin/temporal/PlainDate.h"
+#include "builtin/temporal/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/ToString.h"
+#include "builtin/temporal/Wrapped.h"
+#include "ds/IdValuePair.h"
+#include "gc/AllocKind.h"
+#include "gc/Barrier.h"
+#include "js/AllocPolicy.h"
+#include "js/CallArgs.h"
+#include "js/CallNonGenericMethod.h"
+#include "js/Class.h"
+#include "js/ErrorReport.h"
+#include "js/friend/ErrorMessages.h"
+#include "js/GCVector.h"
+#include "js/Id.h"
+#include "js/PropertyDescriptor.h"
+#include "js/PropertySpec.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/GlobalObject.h"
+#include "vm/JSAtomState.h"
+#include "vm/JSContext.h"
+#include "vm/JSObject.h"
+#include "vm/ObjectOperations.h"
+#include "vm/PlainObject.h"
+#include "vm/StringType.h"
+
+#include "vm/JSObject-inl.h"
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+using namespace js::temporal;
+
+static inline bool IsPlainYearMonth(Handle<Value> v) {
+ return v.isObject() && v.toObject().is<PlainYearMonthObject>();
+}
+
+/**
+ * ISOYearMonthWithinLimits ( year, month )
+ */
+template <typename T>
+static bool ISOYearMonthWithinLimits(T year, int32_t month) {
+ static_assert(std::is_same_v<T, int32_t> || std::is_same_v<T, double>);
+
+ // Step 1.
+ MOZ_ASSERT(IsInteger(year));
+ MOZ_ASSERT(1 <= month && month <= 12);
+
+ // Step 2.
+ if (year < -271821 || year > 275760) {
+ return false;
+ }
+
+ // Step 3.
+ if (year == -271821 && month < 4) {
+ return false;
+ }
+
+ // Step 4.
+ if (year == 275760 && month > 9) {
+ return false;
+ }
+
+ // Step 5.
+ return true;
+}
+
+/**
+ * CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ ,
+ * newTarget ] )
+ */
+static PlainYearMonthObject* CreateTemporalYearMonth(
+ 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;
+ }
+
+ // FIXME: spec issue - Consider calling ISODateTimeWithinLimits to include
+ // testing |referenceISODay|?
+
+ // Step 2.
+ if (!ISOYearMonthWithinLimits(isoYear, isoMonth)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PLAIN_YEAR_MONTH_INVALID);
+ return nullptr;
+ }
+
+ // Steps 3-4.
+ Rooted<JSObject*> proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_PlainYearMonth,
+ &proto)) {
+ return nullptr;
+ }
+
+ auto* obj = NewObjectWithClassProto<PlainYearMonthObject>(cx, proto);
+ if (!obj) {
+ return nullptr;
+ }
+
+ // Step 5.
+ obj->setFixedSlot(PlainYearMonthObject::ISO_YEAR_SLOT, Int32Value(isoYear));
+
+ // Step 6.
+ obj->setFixedSlot(PlainYearMonthObject::ISO_MONTH_SLOT, Int32Value(isoMonth));
+
+ // Step 7.
+ obj->setFixedSlot(PlainYearMonthObject::CALENDAR_SLOT, calendar.toValue());
+
+ // Step 8.
+ obj->setFixedSlot(PlainYearMonthObject::ISO_DAY_SLOT, Int32Value(isoDay));
+
+ // Step 9.
+ return obj;
+}
+
+/**
+ * CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ ,
+ * newTarget ] )
+ */
+PlainYearMonthObject* js::temporal::CreateTemporalYearMonth(
+ JSContext* cx, const PlainDate& date, Handle<CalendarValue> calendar) {
+ auto& [isoYear, isoMonth, isoDay] = date;
+
+ // Step 1.
+ if (!ThrowIfInvalidISODate(cx, date)) {
+ return nullptr;
+ }
+
+ // FIXME: spec issue - Consider calling ISODateTimeWithinLimits to include
+ // testing |referenceISODay|?
+
+ // Step 2.
+ if (!ISOYearMonthWithinLimits(isoYear, isoMonth)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PLAIN_YEAR_MONTH_INVALID);
+ return nullptr;
+ }
+
+ // Steps 3-4.
+ auto* obj = NewBuiltinClassInstance<PlainYearMonthObject>(cx);
+ if (!obj) {
+ return nullptr;
+ }
+
+ // Step 5.
+ obj->setFixedSlot(PlainYearMonthObject::ISO_YEAR_SLOT, Int32Value(isoYear));
+
+ // Step 6.
+ obj->setFixedSlot(PlainYearMonthObject::ISO_MONTH_SLOT, Int32Value(isoMonth));
+
+ // Step 7.
+ obj->setFixedSlot(PlainYearMonthObject::CALENDAR_SLOT, calendar.toValue());
+
+ // Step 8.
+ obj->setFixedSlot(PlainYearMonthObject::ISO_DAY_SLOT, Int32Value(isoDay));
+
+ // Step 9.
+ return obj;
+}
+
+/**
+ * ToTemporalYearMonth ( item [ , options ] )
+ */
+static Wrapped<PlainYearMonthObject*> ToTemporalYearMonth(
+ JSContext* cx, Handle<Value> item,
+ Handle<JSObject*> maybeOptions = nullptr) {
+ // 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());
+
+ // Step 3.a.
+ if (itemObj->canUnwrapAs<PlainYearMonthObject>()) {
+ return itemObj;
+ }
+
+ // Step 3.b.
+ Rooted<CalendarValue> calendarValue(cx);
+ if (!GetTemporalCalendarWithISODefault(cx, itemObj, &calendarValue)) {
+ return nullptr;
+ }
+
+ // Step 3.c.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendarValue,
+ {
+ CalendarMethod::Fields,
+ CalendarMethod::YearMonthFromFields,
+ },
+ &calendar)) {
+ return nullptr;
+ }
+
+ // Step 3.d.
+ JS::RootedVector<PropertyKey> fieldNames(cx);
+ if (!CalendarFields(cx, calendar,
+ {CalendarField::Month, CalendarField::MonthCode,
+ CalendarField::Year},
+ &fieldNames)) {
+ return nullptr;
+ }
+
+ // Step 3.e.
+ Rooted<PlainObject*> fields(cx,
+ PrepareTemporalFields(cx, itemObj, fieldNames));
+ if (!fields) {
+ return nullptr;
+ }
+
+ // Step 3.f.
+ if (maybeResolvedOptions) {
+ return CalendarYearMonthFromFields(cx, calendar, fields,
+ maybeResolvedOptions);
+ }
+ return CalendarYearMonthFromFields(cx, calendar, fields);
+ }
+
+ // 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 (!ParseTemporalYearMonthString(cx, string, &result, &calendarString)) {
+ return nullptr;
+ }
+
+ // Steps 6-9.
+ Rooted<CalendarValue> calendarValue(cx, CalendarValue(cx->names().iso8601));
+ if (calendarString) {
+ if (!ToBuiltinCalendar(cx, calendarString, &calendarValue)) {
+ return nullptr;
+ }
+ }
+
+ // Step 10.
+ if (maybeResolvedOptions) {
+ TemporalOverflow ignored;
+ if (!ToTemporalOverflow(cx, maybeResolvedOptions, &ignored)) {
+ return nullptr;
+ }
+ }
+
+ // Step 11.
+ Rooted<PlainYearMonthObject*> obj(
+ cx, CreateTemporalYearMonth(cx, result, calendarValue));
+ if (!obj) {
+ return nullptr;
+ }
+
+ // Step 12.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendarValue,
+ {
+ CalendarMethod::YearMonthFromFields,
+ },
+ &calendar)) {
+ return nullptr;
+ }
+
+ // FIXME: spec issue - reorder note to appear directly before
+ // CalendarYearMonthFromFields
+
+ // Steps 13-14.
+ return CalendarYearMonthFromFields(cx, calendar, obj);
+}
+
+/**
+ * ToTemporalYearMonth ( item [ , options ] )
+ */
+static bool ToTemporalYearMonth(JSContext* cx, Handle<Value> item,
+ PlainDate* result) {
+ auto obj = ToTemporalYearMonth(cx, item);
+ if (!obj) {
+ return false;
+ }
+
+ *result = ToPlainDate(&obj.unwrap());
+ return true;
+}
+
+/**
+ * ToTemporalYearMonth ( item [ , options ] )
+ */
+static bool ToTemporalYearMonth(JSContext* cx, Handle<Value> item,
+ PlainDate* result,
+ MutableHandle<CalendarValue> calendar) {
+ auto* obj = ToTemporalYearMonth(cx, item).unwrapOrNull();
+ if (!obj) {
+ return false;
+ }
+
+ *result = ToPlainDate(obj);
+ calendar.set(obj->calendar());
+ return calendar.wrap(cx);
+}
+
+/**
+ * DifferenceTemporalPlainYearMonth ( operation, yearMonth, other, options )
+ */
+static bool DifferenceTemporalPlainYearMonth(JSContext* cx,
+ TemporalDifference operation,
+ const CallArgs& args) {
+ Rooted<PlainYearMonthObject*> yearMonth(
+ cx, &args.thisv().toObject().as<PlainYearMonthObject>());
+
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ auto otherYearMonth = ToTemporalYearMonth(cx, args.get(0));
+ if (!otherYearMonth) {
+ return false;
+ }
+ auto* unwrappedOtherYearMonth = &otherYearMonth.unwrap();
+ auto otherYearMonthDate = ToPlainDate(unwrappedOtherYearMonth);
+
+ Rooted<Wrapped<PlainYearMonthObject*>> other(cx, otherYearMonth);
+ Rooted<CalendarValue> otherCalendar(cx, unwrappedOtherYearMonth->calendar());
+ if (!otherCalendar.wrap(cx)) {
+ return false;
+ }
+
+ // Step 3.
+ Rooted<CalendarValue> calendar(cx, yearMonth->calendar());
+
+ // Step 4.
+ if (!CalendarEqualsOrThrow(cx, calendar, otherCalendar)) {
+ return false;
+ }
+
+ // Steps 5-6.
+ 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 5.
+ resolvedOptions = SnapshotOwnProperties(cx, options);
+ if (!resolvedOptions) {
+ return false;
+ }
+
+ // Step 6.
+ if (!GetDifferenceSettings(cx, operation, resolvedOptions,
+ TemporalUnitGroup::Date, TemporalUnit::Month,
+ TemporalUnit::Month, TemporalUnit::Year,
+ &settings)) {
+ return false;
+ }
+ } else {
+ // Steps 5-6.
+ settings = {
+ TemporalUnit::Month,
+ TemporalUnit::Year,
+ TemporalRoundingMode::Trunc,
+ Increment{1},
+ };
+ }
+
+ // Step 7.
+ if (ToPlainDate(yearMonth) == otherYearMonthDate) {
+ auto* obj = CreateTemporalDuration(cx, {});
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+ }
+
+ // Step 8.
+ // FIXME: spec issue - duplicate CreateDataPropertyOrThrow for "largestUnit".
+
+ // Step 9.
+ Rooted<CalendarRecord> calendarRec(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendar,
+ {
+ CalendarMethod::DateAdd,
+ CalendarMethod::DateFromFields,
+ CalendarMethod::DateUntil,
+ CalendarMethod::Fields,
+ },
+ &calendarRec)) {
+ return false;
+ }
+
+ // Step 10.
+ JS::RootedVector<PropertyKey> fieldNames(cx);
+ if (!CalendarFields(cx, calendarRec,
+ {CalendarField::MonthCode, CalendarField::Year},
+ &fieldNames)) {
+ return false;
+ }
+
+ // Step 11.
+ Rooted<PlainObject*> thisFields(
+ cx, PrepareTemporalFields(cx, yearMonth, fieldNames));
+ if (!thisFields) {
+ return false;
+ }
+
+ // Step 12.
+ Value one = Int32Value(1);
+ auto handleOne = Handle<Value>::fromMarkedLocation(&one);
+ if (!DefineDataProperty(cx, thisFields, cx->names().day, handleOne)) {
+ return false;
+ }
+
+ // Step 13.
+ Rooted<Wrapped<PlainDateObject*>> thisDate(
+ cx, CalendarDateFromFields(cx, calendarRec, thisFields));
+ if (!thisDate) {
+ return false;
+ }
+
+ // Step 14.
+ Rooted<PlainObject*> otherFields(
+ cx, PrepareTemporalFields(cx, other, fieldNames));
+ if (!otherFields) {
+ return false;
+ }
+
+ // Step 15.
+ if (!DefineDataProperty(cx, otherFields, cx->names().day, handleOne)) {
+ return false;
+ }
+
+ // Step 16.
+ Rooted<Wrapped<PlainDateObject*>> otherDate(
+ cx, CalendarDateFromFields(cx, calendarRec, otherFields));
+ if (!otherDate) {
+ return false;
+ }
+
+ // Steps 17-18.
+ Duration result;
+ if (resolvedOptions) {
+ // Step 17.
+ Rooted<Value> largestUnitValue(
+ cx, StringValue(TemporalUnitToString(cx, settings.largestUnit)));
+ if (!DefineDataProperty(cx, resolvedOptions, cx->names().largestUnit,
+ largestUnitValue)) {
+ return false;
+ }
+
+ // Step 18.
+ if (!CalendarDateUntil(cx, calendarRec, thisDate, otherDate,
+ resolvedOptions, &result)) {
+ return false;
+ }
+ } else {
+ // Steps 17-18.
+ if (!CalendarDateUntil(cx, calendarRec, thisDate, otherDate,
+ settings.largestUnit, &result)) {
+ return false;
+ }
+ }
+
+ // We only care about years and months here, all other fields are set to zero.
+ Duration duration = {result.years, result.months};
+
+ // Step 19.
+ if (settings.smallestUnit != TemporalUnit::Month ||
+ settings.roundingIncrement != Increment{1}) {
+ // Steps 19.a-b.
+ Duration roundResult;
+ if (!RoundDuration(cx, duration, settings.roundingIncrement,
+ settings.smallestUnit, settings.roundingMode, thisDate,
+ calendarRec, &roundResult)) {
+ return false;
+ }
+
+ // Step 19.c.
+ auto toBalance = Duration{roundResult.years, roundResult.months};
+ DateDuration balanceResult;
+ if (!temporal::BalanceDateDurationRelative(
+ cx, toBalance, settings.largestUnit, settings.smallestUnit,
+ thisDate, calendarRec, &balanceResult)) {
+ return false;
+ }
+ duration = balanceResult.toDuration();
+ }
+
+ // Step 20.
+ if (operation == TemporalDifference::Since) {
+ duration = duration.negate();
+ }
+
+ auto* obj = CreateTemporalDuration(cx, {duration.years, duration.months});
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+enum class PlainYearMonthDuration { Add, Subtract };
+
+/**
+ * AddDurationToOrSubtractDurationFromPlainYearMonth ( operation, yearMonth,
+ * temporalDurationLike, options )
+ */
+static bool AddDurationToOrSubtractDurationFromPlainYearMonth(
+ JSContext* cx, PlainYearMonthDuration operation, const CallArgs& args) {
+ Rooted<PlainYearMonthObject*> yearMonth(
+ cx, &args.thisv().toObject().as<PlainYearMonthObject>());
+
+ // Step 1.
+ Duration duration;
+ if (!ToTemporalDurationRecord(cx, args.get(0), &duration)) {
+ return false;
+ }
+
+ // Step 2.
+ if (operation == PlainYearMonthDuration::Subtract) {
+ duration = duration.negate();
+ }
+
+ // Step 3.
+ TimeDuration balanceResult;
+ if (!BalanceTimeDuration(cx, duration, TemporalUnit::Day, &balanceResult)) {
+ return false;
+ }
+
+ // Step 4.
+ int32_t sign = DurationSign(
+ {duration.years, duration.months, duration.weeks, balanceResult.days});
+
+ // Step 5.
+ Rooted<CalendarValue> calendarValue(cx, yearMonth->calendar());
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendarValue,
+ {
+ CalendarMethod::DateAdd,
+ CalendarMethod::DateFromFields,
+ CalendarMethod::Day,
+ CalendarMethod::Fields,
+ CalendarMethod::YearMonthFromFields,
+ },
+ &calendar)) {
+ return false;
+ };
+
+ // Step 6.
+ JS::RootedVector<PropertyKey> fieldNames(cx);
+ if (!CalendarFields(cx, calendar,
+ {CalendarField::MonthCode, CalendarField::Year},
+ &fieldNames)) {
+ return false;
+ }
+
+ // Step 7.
+ Rooted<PlainObject*> fields(cx,
+ PrepareTemporalFields(cx, yearMonth, fieldNames));
+ if (!fields) {
+ return false;
+ }
+
+ // Step 8.
+ Rooted<PlainObject*> fieldsCopy(cx, SnapshotOwnProperties(cx, fields));
+ if (!fieldsCopy) {
+ return false;
+ }
+
+ // Step 9.
+ Value one = Int32Value(1);
+ auto handleOne = Handle<Value>::fromMarkedLocation(&one);
+ if (!DefineDataProperty(cx, fields, cx->names().day, handleOne)) {
+ return false;
+ }
+
+ // Step 10.
+ Rooted<Wrapped<PlainDateObject*>> intermediateDate(
+ cx, CalendarDateFromFields(cx, calendar, fields));
+ if (!intermediateDate) {
+ return false;
+ }
+
+ // Steps 11-12.
+ Rooted<Wrapped<PlainDateObject*>> date(cx);
+ if (sign < 0) {
+ // |intermediateDate| is initialized to the first day of |yearMonth|'s
+ // month. Compute the last day of |yearMonth|'s month by first adding one
+ // month and then subtracting one day.
+ //
+ // This is roughly equivalent to these calls:
+ //
+ // js> var ym = new Temporal.PlainYearMonth(2023, 1);
+ // js> ym.toPlainDate({day: 1}).add({months: 1}).subtract({days: 1}).day
+ // 31
+ //
+ // For many calendars this is equivalent to `ym.daysInMonth`, except when
+ // some days are skipped, for example consider the Julian-to-Gregorian
+ // calendar transition.
+
+ // Step 11.a.
+ Duration oneMonthDuration = {0, 1};
+
+ // Step 11.b.
+ Rooted<Wrapped<PlainDateObject*>> nextMonth(
+ cx, CalendarDateAdd(cx, calendar, intermediateDate, oneMonthDuration));
+ if (!nextMonth) {
+ return false;
+ }
+
+ auto* unwrappedNextMonth = nextMonth.unwrap(cx);
+ if (!unwrappedNextMonth) {
+ return false;
+ }
+ auto nextMonthDate = ToPlainDate(unwrappedNextMonth);
+
+ // Step 11.c.
+ PlainDate endOfMonthISO;
+ if (!AddISODate(cx, nextMonthDate, {0, 0, 0, -1},
+ TemporalOverflow::Constrain, &endOfMonthISO)) {
+ return false;
+ }
+
+ // Step 11.d.
+ Rooted<PlainDateWithCalendar> endOfMonth(cx);
+ if (!CreateTemporalDate(cx, endOfMonthISO, calendar.receiver(),
+ &endOfMonth)) {
+ return false;
+ }
+
+ // Step 11.e.
+ Rooted<Value> day(cx);
+ if (!CalendarDay(cx, calendar, endOfMonth.date(), &day)) {
+ return false;
+ }
+
+ // Step 11.f.
+ if (!DefineDataProperty(cx, fieldsCopy, cx->names().day, day)) {
+ return false;
+ }
+
+ // Step 11.g.
+ date = CalendarDateFromFields(cx, calendar, fieldsCopy);
+ if (!date) {
+ return false;
+ }
+ } else {
+ // Step 12.a.
+ date = intermediateDate;
+ }
+
+ // Step 13.
+ Duration durationToAdd = {duration.years, duration.months, duration.weeks,
+ balanceResult.days};
+
+ // FIXME: spec issue - GetOptionsObject should be called after
+ // ToTemporalDurationRecord to validate the input type before performing any
+ // other user-visible operations.
+ // https://github.com/tc39/proposal-temporal/issues/2721
+
+ // Step 14.
+ Rooted<JSObject*> options(cx);
+ if (args.hasDefined(1)) {
+ const char* name =
+ operation == PlainYearMonthDuration::Add ? "add" : "subtract";
+ options = RequireObjectArg(cx, "options", name, args[1]);
+ } else {
+ // TODO: Avoid creating an options object if not necessary.
+ options = NewPlainObjectWithProto(cx, nullptr);
+ }
+ if (!options) {
+ return false;
+ }
+
+ // Step 15.
+ Rooted<PlainObject*> optionsCopy(cx, SnapshotOwnProperties(cx, options));
+ if (!optionsCopy) {
+ return false;
+ }
+
+ // Step 16.
+ Rooted<Wrapped<PlainDateObject*>> addedDate(
+ cx, AddDate(cx, calendar, date, durationToAdd, options));
+ if (!addedDate) {
+ return false;
+ }
+
+ // Step 17.
+ Rooted<PlainObject*> addedDateFields(
+ cx, PrepareTemporalFields(cx, addedDate, fieldNames));
+ if (!addedDateFields) {
+ return false;
+ }
+
+ // Step 18.
+ auto obj =
+ CalendarYearMonthFromFields(cx, calendar, addedDateFields, optionsCopy);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainYearMonth ( isoYear, isoMonth [ , calendarLike [ ,
+ * referenceISODay ] ] )
+ */
+static bool PlainYearMonthConstructor(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ if (!ThrowIfNotConstructing(cx, args, "Temporal.PlainYearMonth")) {
+ return false;
+ }
+
+ // Step 3.
+ double isoYear;
+ if (!ToIntegerWithTruncation(cx, args.get(0), "year", &isoYear)) {
+ return false;
+ }
+
+ // Step 4.
+ double isoMonth;
+ if (!ToIntegerWithTruncation(cx, args.get(1), "month", &isoMonth)) {
+ return false;
+ }
+
+ // Step 5.
+ Rooted<CalendarValue> calendar(cx);
+ if (!ToTemporalCalendarWithISODefault(cx, args.get(2), &calendar)) {
+ return false;
+ }
+
+ // Steps 2 and 6.
+ double isoDay = 1;
+ if (args.hasDefined(3)) {
+ if (!ToIntegerWithTruncation(cx, args[3], "day", &isoDay)) {
+ return false;
+ }
+ }
+
+ // Step 7.
+ auto* yearMonth =
+ CreateTemporalYearMonth(cx, args, isoYear, isoMonth, isoDay, calendar);
+ if (!yearMonth) {
+ return false;
+ }
+
+ args.rval().setObject(*yearMonth);
+ return true;
+}
+
+/**
+ * Temporal.PlainYearMonth.from ( item [ , options ] )
+ */
+static bool PlainYearMonth_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* yearMonth = item->maybeUnwrapIf<PlainYearMonthObject>()) {
+ auto date = ToPlainDate(yearMonth);
+
+ Rooted<CalendarValue> calendar(cx, yearMonth->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* obj = CreateTemporalYearMonth(cx, date, calendar);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+ }
+ }
+
+ // Step 3.
+ auto obj = ToTemporalYearMonth(cx, args.get(0), options);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainYearMonth.compare ( one, two )
+ */
+static bool PlainYearMonth_compare(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ PlainDate one;
+ if (!ToTemporalYearMonth(cx, args.get(0), &one)) {
+ return false;
+ }
+
+ // Step 2.
+ PlainDate two;
+ if (!ToTemporalYearMonth(cx, args.get(1), &two)) {
+ return false;
+ }
+
+ // Step 3.
+ args.rval().setInt32(CompareISODate(one, two));
+ return true;
+}
+
+/**
+ * get Temporal.PlainYearMonth.prototype.calendarId
+ */
+static bool PlainYearMonth_calendarId(JSContext* cx, const CallArgs& args) {
+ auto* yearMonth = &args.thisv().toObject().as<PlainYearMonthObject>();
+ Rooted<CalendarValue> calendar(cx, yearMonth->calendar());
+
+ // Step 3.
+ auto* calendarId = ToTemporalCalendarIdentifier(cx, calendar);
+ if (!calendarId) {
+ return false;
+ }
+
+ args.rval().setString(calendarId);
+ return true;
+}
+
+/**
+ * get Temporal.PlainYearMonth.prototype.calendarId
+ */
+static bool PlainYearMonth_calendarId(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_calendarId>(
+ cx, args);
+}
+
+/**
+ * get Temporal.PlainYearMonth.prototype.year
+ */
+static bool PlainYearMonth_year(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainYearMonthObject*> yearMonth(
+ cx, &args.thisv().toObject().as<PlainYearMonthObject>());
+ Rooted<CalendarValue> calendar(cx, yearMonth->calendar());
+
+ // Step 4.
+ return CalendarYear(cx, calendar, yearMonth, args.rval());
+}
+
+/**
+ * get Temporal.PlainYearMonth.prototype.year
+ */
+static bool PlainYearMonth_year(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_year>(cx, args);
+}
+
+/**
+ * get Temporal.PlainYearMonth.prototype.month
+ */
+static bool PlainYearMonth_month(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainYearMonthObject*> yearMonth(
+ cx, &args.thisv().toObject().as<PlainYearMonthObject>());
+ Rooted<CalendarValue> calendar(cx, yearMonth->calendar());
+
+ // Step 4.
+ return CalendarMonth(cx, calendar, yearMonth, args.rval());
+}
+
+/**
+ * get Temporal.PlainYearMonth.prototype.month
+ */
+static bool PlainYearMonth_month(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_month>(cx, args);
+}
+
+/**
+ * get Temporal.PlainYearMonth.prototype.monthCode
+ */
+static bool PlainYearMonth_monthCode(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainYearMonthObject*> yearMonth(
+ cx, &args.thisv().toObject().as<PlainYearMonthObject>());
+ Rooted<CalendarValue> calendar(cx, yearMonth->calendar());
+
+ // Step 4.
+ return CalendarMonthCode(cx, calendar, yearMonth, args.rval());
+}
+
+/**
+ * get Temporal.PlainYearMonth.prototype.monthCode
+ */
+static bool PlainYearMonth_monthCode(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_monthCode>(cx,
+ args);
+}
+
+/**
+ * get Temporal.PlainYearMonth.prototype.daysInYear
+ */
+static bool PlainYearMonth_daysInYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainYearMonthObject*> yearMonth(
+ cx, &args.thisv().toObject().as<PlainYearMonthObject>());
+ Rooted<CalendarValue> calendar(cx, yearMonth->calendar());
+
+ // Step 4.
+ return CalendarDaysInYear(cx, calendar, yearMonth, args.rval());
+}
+
+/**
+ * get Temporal.PlainYearMonth.prototype.daysInYear
+ */
+static bool PlainYearMonth_daysInYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_daysInYear>(
+ cx, args);
+}
+
+/**
+ * get Temporal.PlainYearMonth.prototype.daysInMonth
+ */
+static bool PlainYearMonth_daysInMonth(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainYearMonthObject*> yearMonth(
+ cx, &args.thisv().toObject().as<PlainYearMonthObject>());
+ Rooted<CalendarValue> calendar(cx, yearMonth->calendar());
+
+ // Step 4.
+ return CalendarDaysInMonth(cx, calendar, yearMonth, args.rval());
+}
+
+/**
+ * get Temporal.PlainYearMonth.prototype.daysInMonth
+ */
+static bool PlainYearMonth_daysInMonth(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_daysInMonth>(
+ cx, args);
+}
+
+/**
+ * get Temporal.PlainYearMonth.prototype.monthsInYear
+ */
+static bool PlainYearMonth_monthsInYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainYearMonthObject*> yearMonth(
+ cx, &args.thisv().toObject().as<PlainYearMonthObject>());
+ Rooted<CalendarValue> calendar(cx, yearMonth->calendar());
+
+ // Step 4.
+ return CalendarMonthsInYear(cx, calendar, yearMonth, args.rval());
+}
+
+/**
+ * get Temporal.PlainYearMonth.prototype.monthsInYear
+ */
+static bool PlainYearMonth_monthsInYear(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_monthsInYear>(
+ cx, args);
+}
+
+/**
+ * get Temporal.PlainYearMonth.prototype.inLeapYear
+ */
+static bool PlainYearMonth_inLeapYear(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ Rooted<PlainYearMonthObject*> yearMonth(
+ cx, &args.thisv().toObject().as<PlainYearMonthObject>());
+ Rooted<CalendarValue> calendar(cx, yearMonth->calendar());
+
+ // Step 4.
+ return CalendarInLeapYear(cx, calendar, yearMonth, args.rval());
+}
+
+/**
+ * get Temporal.PlainYearMonth.prototype.inLeapYear
+ */
+static bool PlainYearMonth_inLeapYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_inLeapYear>(
+ cx, args);
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.with ( temporalYearMonthLike [ , options ]
+ * )
+ */
+static bool PlainYearMonth_with(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainYearMonthObject*> yearMonth(
+ cx, &args.thisv().toObject().as<PlainYearMonthObject>());
+ Rooted<CalendarValue> calendarValue(cx, yearMonth->calendar());
+
+ // Step 3.
+ Rooted<JSObject*> temporalYearMonthLike(
+ cx, RequireObjectArg(cx, "temporalYearMonthLike", "with", args.get(0)));
+ if (!temporalYearMonthLike) {
+ return false;
+ }
+
+ // Step 4.
+ if (!RejectTemporalLikeObject(cx, temporalYearMonthLike)) {
+ return false;
+ }
+
+ // Step 5.
+ Rooted<PlainObject*> resolvedOptions(cx);
+ if (args.hasDefined(1)) {
+ Rooted<JSObject*> options(cx,
+ RequireObjectArg(cx, "options", "with", args[1]));
+ if (!options) {
+ return false;
+ }
+ resolvedOptions = SnapshotOwnProperties(cx, options);
+ } else {
+ resolvedOptions = NewPlainObjectWithProto(cx, nullptr);
+ }
+ if (!resolvedOptions) {
+ return false;
+ }
+
+ // Step 6.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendarValue,
+ {
+ CalendarMethod::Fields,
+ CalendarMethod::MergeFields,
+ CalendarMethod::YearMonthFromFields,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Step 7.
+ JS::RootedVector<PropertyKey> fieldNames(cx);
+ if (!CalendarFields(
+ cx, calendar,
+ {CalendarField::Month, CalendarField::MonthCode, CalendarField::Year},
+ &fieldNames)) {
+ return false;
+ }
+
+ // Step 8.
+ Rooted<PlainObject*> fields(cx,
+ PrepareTemporalFields(cx, yearMonth, fieldNames));
+ if (!fields) {
+ return false;
+ }
+
+ // Step 9.
+ Rooted<PlainObject*> partialYearMonth(
+ cx, PreparePartialTemporalFields(cx, temporalYearMonthLike, fieldNames));
+ if (!partialYearMonth) {
+ return false;
+ }
+
+ // Step 10.
+ Rooted<JSObject*> mergedFields(
+ cx, CalendarMergeFields(cx, calendar, fields, partialYearMonth));
+ if (!mergedFields) {
+ return false;
+ }
+
+ // Step 11.
+ fields = PrepareTemporalFields(cx, mergedFields, fieldNames);
+ if (!fields) {
+ return false;
+ }
+
+ // Step 12.
+ auto obj = CalendarYearMonthFromFields(cx, calendar, fields, resolvedOptions);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.with ( temporalYearMonthLike [ , options ]
+ * )
+ */
+static bool PlainYearMonth_with(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_with>(cx, args);
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.add ( temporalDurationLike [ , options ] )
+ */
+static bool PlainYearMonth_add(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ return AddDurationToOrSubtractDurationFromPlainYearMonth(
+ cx, PlainYearMonthDuration::Add, args);
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.add ( temporalDurationLike [ , options ] )
+ */
+static bool PlainYearMonth_add(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_add>(cx, args);
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.subtract ( temporalDurationLike [ , options
+ * ] )
+ */
+static bool PlainYearMonth_subtract(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ return AddDurationToOrSubtractDurationFromPlainYearMonth(
+ cx, PlainYearMonthDuration::Subtract, args);
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.subtract ( temporalDurationLike [ , options
+ * ] )
+ */
+static bool PlainYearMonth_subtract(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_subtract>(cx,
+ args);
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.until ( other [ , options ] )
+ */
+static bool PlainYearMonth_until(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ return DifferenceTemporalPlainYearMonth(cx, TemporalDifference::Until, args);
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.until ( other [ , options ] )
+ */
+static bool PlainYearMonth_until(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_until>(cx, args);
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.since ( other [ , options ] )
+ */
+static bool PlainYearMonth_since(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ return DifferenceTemporalPlainYearMonth(cx, TemporalDifference::Since, args);
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.since ( other [ , options ] )
+ */
+static bool PlainYearMonth_since(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_since>(cx, args);
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.equals ( other )
+ */
+static bool PlainYearMonth_equals(JSContext* cx, const CallArgs& args) {
+ auto* yearMonth = &args.thisv().toObject().as<PlainYearMonthObject>();
+ auto date = ToPlainDate(yearMonth);
+ Rooted<CalendarValue> calendar(cx, yearMonth->calendar());
+
+ // Step 3.
+ PlainDate other;
+ Rooted<CalendarValue> otherCalendar(cx);
+ if (!ToTemporalYearMonth(cx, args.get(0), &other, &otherCalendar)) {
+ return false;
+ }
+
+ // Steps 4-7.
+ bool equals = date == other;
+ if (equals && !CalendarEquals(cx, calendar, otherCalendar, &equals)) {
+ return false;
+ }
+
+ args.rval().setBoolean(equals);
+ return true;
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.equals ( other )
+ */
+static bool PlainYearMonth_equals(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_equals>(cx,
+ args);
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.toString ( [ options ] )
+ */
+static bool PlainYearMonth_toString(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainYearMonthObject*> yearMonth(
+ cx, &args.thisv().toObject().as<PlainYearMonthObject>());
+
+ 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 = TemporalYearMonthToString(cx, yearMonth, showCalendar);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.toString ( [ options ] )
+ */
+static bool PlainYearMonth_toString(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_toString>(cx,
+ args);
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.toLocaleString ( [ locales [ , options ] ]
+ * )
+ */
+static bool PlainYearMonth_toLocaleString(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainYearMonthObject*> yearMonth(
+ cx, &args.thisv().toObject().as<PlainYearMonthObject>());
+
+ // Step 3.
+ JSString* str =
+ TemporalYearMonthToString(cx, yearMonth, CalendarOption::Auto);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.toLocaleString ( [ locales [ , options ] ]
+ * )
+ */
+static bool PlainYearMonth_toLocaleString(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_toLocaleString>(
+ cx, args);
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.toJSON ( )
+ */
+static bool PlainYearMonth_toJSON(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainYearMonthObject*> yearMonth(
+ cx, &args.thisv().toObject().as<PlainYearMonthObject>());
+
+ // Step 3.
+ JSString* str =
+ TemporalYearMonthToString(cx, yearMonth, CalendarOption::Auto);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.toJSON ( )
+ */
+static bool PlainYearMonth_toJSON(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_toJSON>(cx,
+ args);
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.valueOf ( )
+ */
+static bool PlainYearMonth_valueOf(JSContext* cx, unsigned argc, Value* vp) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
+ "PlainYearMonth", "primitive type");
+ return false;
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.toPlainDate ( item )
+ */
+static bool PlainYearMonth_toPlainDate(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainYearMonthObject*> yearMonth(
+ cx, &args.thisv().toObject().as<PlainYearMonthObject>());
+
+ // Step 3.
+ Rooted<JSObject*> item(
+ cx, RequireObjectArg(cx, "item", "toPlainDate", args.get(0)));
+ if (!item) {
+ return false;
+ }
+
+ // Step 4.
+ Rooted<CalendarValue> calendarValue(cx, yearMonth->calendar());
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendarValue,
+ {
+ CalendarMethod::DateFromFields,
+ CalendarMethod::Fields,
+ CalendarMethod::MergeFields,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Step 5.
+ JS::RootedVector<PropertyKey> receiverFieldNames(cx);
+ if (!CalendarFields(cx, calendar,
+ {CalendarField::MonthCode, CalendarField::Year},
+ &receiverFieldNames)) {
+ return false;
+ }
+
+ // Step 6.
+ Rooted<PlainObject*> fields(
+ cx, PrepareTemporalFields(cx, yearMonth, receiverFieldNames));
+ if (!fields) {
+ return false;
+ }
+
+ // Step 7.
+ JS::RootedVector<PropertyKey> inputFieldNames(cx);
+ if (!CalendarFields(cx, calendar, {CalendarField::Day}, &inputFieldNames)) {
+ return false;
+ }
+
+ // Step 8.
+ Rooted<PlainObject*> inputFields(
+ cx, PrepareTemporalFields(cx, item, inputFieldNames));
+ if (!inputFields) {
+ return false;
+ }
+
+ // Step 9.
+ Rooted<JSObject*> mergedFields(
+ cx, CalendarMergeFields(cx, calendar, fields, inputFields));
+ if (!mergedFields) {
+ return false;
+ }
+
+ // Step 10.
+ JS::RootedVector<PropertyKey> concatenatedFieldNames(cx);
+ if (!ConcatTemporalFieldNames(receiverFieldNames, inputFieldNames,
+ concatenatedFieldNames.get())) {
+ return false;
+ }
+
+ // Step 11.
+ Rooted<PlainObject*> mergedFromConcatenatedFields(
+ cx, PrepareTemporalFields(cx, mergedFields, concatenatedFieldNames));
+ if (!mergedFromConcatenatedFields) {
+ return false;
+ }
+
+ // Step 12.
+ Rooted<PlainObject*> options(cx, NewPlainObjectWithProto(cx, nullptr));
+ if (!options) {
+ return false;
+ }
+
+ // Step 13.
+ Rooted<Value> overflow(cx, StringValue(cx->names().constrain));
+ if (!DefineDataProperty(cx, options, cx->names().overflow, overflow)) {
+ return false;
+ }
+
+ // Step 14.
+ auto obj = CalendarDateFromFields(cx, calendar, mergedFromConcatenatedFields,
+ options);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.toPlainDate ( item )
+ */
+static bool PlainYearMonth_toPlainDate(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_toPlainDate>(
+ cx, args);
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.getISOFields ( )
+ */
+static bool PlainYearMonth_getISOFields(JSContext* cx, const CallArgs& args) {
+ Rooted<PlainYearMonthObject*> yearMonth(
+ cx, &args.thisv().toObject().as<PlainYearMonthObject>());
+
+ // Step 3.
+ Rooted<IdValueVector> fields(cx, IdValueVector(cx));
+
+ // Step 4.
+ if (!fields.emplaceBack(NameToId(cx->names().calendar),
+ yearMonth->calendar().toValue())) {
+ return false;
+ }
+
+ // Step 5.
+ if (!fields.emplaceBack(NameToId(cx->names().isoDay),
+ Int32Value(yearMonth->isoDay()))) {
+ return false;
+ }
+
+ // Step 6.
+ if (!fields.emplaceBack(NameToId(cx->names().isoMonth),
+ Int32Value(yearMonth->isoMonth()))) {
+ return false;
+ }
+
+ // Step 7.
+ if (!fields.emplaceBack(NameToId(cx->names().isoYear),
+ Int32Value(yearMonth->isoYear()))) {
+ return false;
+ }
+
+ // Step 8.
+ auto* obj =
+ NewPlainObjectWithUniqueNames(cx, fields.begin(), fields.length());
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.getISOFields ( )
+ */
+static bool PlainYearMonth_getISOFields(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_getISOFields>(
+ cx, args);
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.getCalendar ( )
+ */
+static bool PlainYearMonth_getCalendar(JSContext* cx, const CallArgs& args) {
+ auto* yearMonth = &args.thisv().toObject().as<PlainYearMonthObject>();
+ Rooted<CalendarValue> calendar(cx, yearMonth->calendar());
+
+ // Step 3.
+ auto* obj = ToTemporalCalendarObject(cx, calendar);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.PlainYearMonth.prototype.getCalendar ( )
+ */
+static bool PlainYearMonth_getCalendar(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsPlainYearMonth, PlainYearMonth_getCalendar>(
+ cx, args);
+}
+
+const JSClass PlainYearMonthObject::class_ = {
+ "Temporal.PlainYearMonth",
+ JSCLASS_HAS_RESERVED_SLOTS(PlainYearMonthObject::SLOT_COUNT) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_PlainYearMonth),
+ JS_NULL_CLASS_OPS,
+ &PlainYearMonthObject::classSpec_,
+};
+
+const JSClass& PlainYearMonthObject::protoClass_ = PlainObject::class_;
+
+static const JSFunctionSpec PlainYearMonth_methods[] = {
+ JS_FN("from", PlainYearMonth_from, 1, 0),
+ JS_FN("compare", PlainYearMonth_compare, 2, 0),
+ JS_FS_END,
+};
+
+static const JSFunctionSpec PlainYearMonth_prototype_methods[] = {
+ JS_FN("with", PlainYearMonth_with, 1, 0),
+ JS_FN("add", PlainYearMonth_add, 1, 0),
+ JS_FN("subtract", PlainYearMonth_subtract, 1, 0),
+ JS_FN("until", PlainYearMonth_until, 1, 0),
+ JS_FN("since", PlainYearMonth_since, 1, 0),
+ JS_FN("equals", PlainYearMonth_equals, 1, 0),
+ JS_FN("toString", PlainYearMonth_toString, 0, 0),
+ JS_FN("toLocaleString", PlainYearMonth_toLocaleString, 0, 0),
+ JS_FN("toJSON", PlainYearMonth_toJSON, 0, 0),
+ JS_FN("valueOf", PlainYearMonth_valueOf, 0, 0),
+ JS_FN("toPlainDate", PlainYearMonth_toPlainDate, 1, 0),
+ JS_FN("getISOFields", PlainYearMonth_getISOFields, 0, 0),
+ JS_FN("getCalendar", PlainYearMonth_getCalendar, 0, 0),
+ JS_FS_END,
+};
+
+static const JSPropertySpec PlainYearMonth_prototype_properties[] = {
+ JS_PSG("calendarId", PlainYearMonth_calendarId, 0),
+ JS_PSG("year", PlainYearMonth_year, 0),
+ JS_PSG("month", PlainYearMonth_month, 0),
+ JS_PSG("monthCode", PlainYearMonth_monthCode, 0),
+ JS_PSG("daysInYear", PlainYearMonth_daysInYear, 0),
+ JS_PSG("daysInMonth", PlainYearMonth_daysInMonth, 0),
+ JS_PSG("monthsInYear", PlainYearMonth_monthsInYear, 0),
+ JS_PSG("inLeapYear", PlainYearMonth_inLeapYear, 0),
+ JS_STRING_SYM_PS(toStringTag, "Temporal.PlainYearMonth", JSPROP_READONLY),
+ JS_PS_END,
+};
+
+const ClassSpec PlainYearMonthObject::classSpec_ = {
+ GenericCreateConstructor<PlainYearMonthConstructor, 2,
+ gc::AllocKind::FUNCTION>,
+ GenericCreatePrototype<PlainYearMonthObject>,
+ PlainYearMonth_methods,
+ nullptr,
+ PlainYearMonth_prototype_methods,
+ PlainYearMonth_prototype_properties,
+ nullptr,
+ ClassSpec::DontDefineConstructor,
+};
diff --git a/js/src/builtin/temporal/PlainYearMonth.h b/js/src/builtin/temporal/PlainYearMonth.h
new file mode 100644
index 0000000000..e9adda9b08
--- /dev/null
+++ b/js/src/builtin/temporal/PlainYearMonth.h
@@ -0,0 +1,65 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_PlainYearMonth_h
+#define builtin_temporal_PlainYearMonth_h
+
+#include <stdint.h>
+
+#include "builtin/temporal/Calendar.h"
+#include "builtin/temporal/TemporalTypes.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "vm/NativeObject.h"
+
+namespace js {
+struct ClassSpec;
+}
+
+namespace js::temporal {
+
+class PlainYearMonthObject : public NativeObject {
+ public:
+ static const JSClass class_;
+ static const JSClass& protoClass_;
+
+ static constexpr uint32_t ISO_YEAR_SLOT = 0;
+ static constexpr uint32_t ISO_MONTH_SLOT = 1;
+ static constexpr uint32_t ISO_DAY_SLOT = 2;
+ static constexpr uint32_t CALENDAR_SLOT = 3;
+ static constexpr uint32_t SLOT_COUNT = 4;
+
+ int32_t isoYear() const { return getFixedSlot(ISO_YEAR_SLOT).toInt32(); }
+
+ int32_t isoMonth() const { return getFixedSlot(ISO_MONTH_SLOT).toInt32(); }
+
+ int32_t isoDay() const { return getFixedSlot(ISO_DAY_SLOT).toInt32(); }
+
+ CalendarValue calendar() const {
+ return CalendarValue(getFixedSlot(CALENDAR_SLOT));
+ }
+
+ private:
+ static const ClassSpec classSpec_;
+};
+
+/**
+ * Extract the date fields from the PlainYearMonth object.
+ */
+inline PlainDate ToPlainDate(const PlainYearMonthObject* yearMonth) {
+ return {yearMonth->isoYear(), yearMonth->isoMonth(), yearMonth->isoDay()};
+}
+
+/**
+ * CreateTemporalYearMonth ( isoYear, isoMonth, calendar, referenceISODay [ ,
+ * newTarget ] )
+ */
+PlainYearMonthObject* CreateTemporalYearMonth(
+ JSContext* cx, const PlainDate& date, JS::Handle<CalendarValue> calendar);
+
+} /* namespace js::temporal */
+
+#endif /* builtin_temporal_PlainYearMonth_h */
diff --git a/js/src/builtin/temporal/Temporal.cpp b/js/src/builtin/temporal/Temporal.cpp
new file mode 100644
index 0000000000..3960a2832d
--- /dev/null
+++ b/js/src/builtin/temporal/Temporal.cpp
@@ -0,0 +1,1850 @@
+/* -*- 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/Temporal.h"
+
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Maybe.h"
+
+#include <algorithm>
+#include <cmath>
+#include <cstdlib>
+#include <initializer_list>
+#include <iterator>
+#include <stdint.h>
+#include <string_view>
+#include <utility>
+
+#include "jsfriendapi.h"
+#include "jsnum.h"
+#include "jspubtd.h"
+#include "NamespaceImports.h"
+
+#include "builtin/temporal/Instant.h"
+#include "builtin/temporal/PlainDate.h"
+#include "builtin/temporal/PlainDateTime.h"
+#include "builtin/temporal/PlainMonthDay.h"
+#include "builtin/temporal/PlainTime.h"
+#include "builtin/temporal/PlainYearMonth.h"
+#include "builtin/temporal/TemporalRoundingMode.h"
+#include "builtin/temporal/TemporalTypes.h"
+#include "builtin/temporal/TemporalUnit.h"
+#include "builtin/temporal/ZonedDateTime.h"
+#include "gc/Barrier.h"
+#include "js/Class.h"
+#include "js/Conversions.h"
+#include "js/ErrorReport.h"
+#include "js/friend/ErrorMessages.h"
+#include "js/GCVector.h"
+#include "js/Id.h"
+#include "js/Printer.h"
+#include "js/PropertyDescriptor.h"
+#include "js/PropertySpec.h"
+#include "js/RootingAPI.h"
+#include "js/String.h"
+#include "js/Value.h"
+#include "vm/BigIntType.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/GlobalObject.h"
+#include "vm/JSAtomState.h"
+#include "vm/JSAtomUtils.h"
+#include "vm/JSContext.h"
+#include "vm/JSObject.h"
+#include "vm/ObjectOperations.h"
+#include "vm/PIC.h"
+#include "vm/PlainObject.h"
+#include "vm/Realm.h"
+#include "vm/StringType.h"
+
+#include "vm/JSObject-inl.h"
+#include "vm/ObjectOperations-inl.h"
+
+using namespace js;
+using namespace js::temporal;
+
+/**
+ * GetOption ( options, property, type, values, default )
+ *
+ * GetOption specialization when `type=string`. Default value handling must
+ * happen in the caller, so we don't provide the `default` parameter here.
+ */
+static bool GetStringOption(JSContext* cx, Handle<JSObject*> options,
+ Handle<PropertyName*> property,
+ MutableHandle<JSString*> string) {
+ // Step 1.
+ Rooted<Value> value(cx);
+ if (!GetProperty(cx, options, options, property, &value)) {
+ return false;
+ }
+
+ // Step 2. (Caller should fill in the fallback.)
+ if (value.isUndefined()) {
+ return true;
+ }
+
+ // Steps 3-4. (Not applicable when type=string)
+
+ // Step 5.
+ string.set(JS::ToString(cx, value));
+ if (!string) {
+ return false;
+ }
+
+ // Step 6. (Not applicable in our implementation)
+
+ // Step 7.
+ return true;
+}
+
+/**
+ * GetOption ( options, property, type, values, default )
+ */
+static bool GetNumberOption(JSContext* cx, Handle<JSObject*> options,
+ Handle<PropertyName*> property, double* number) {
+ // Step 1.
+ Rooted<Value> value(cx);
+ if (!GetProperty(cx, options, options, property, &value)) {
+ return false;
+ }
+
+ // Step 2. (Caller should fill in the fallback.)
+ if (value.isUndefined()) {
+ return true;
+ }
+
+ // Steps 3 and 5. (Not applicable in our implementation)
+
+ // Step 4.a.
+ if (!JS::ToNumber(cx, value, number)) {
+ return false;
+ }
+
+ // Step 4.b. (Caller must check for NaN values.)
+
+ // Step 7. (Not applicable in our implementation)
+
+ // Step 8.
+ return true;
+}
+
+/**
+ * ToTemporalRoundingIncrement ( normalizedOptions, dividend, inclusive )
+ */
+bool js::temporal::ToTemporalRoundingIncrement(JSContext* cx,
+ Handle<JSObject*> options,
+ Increment* increment) {
+ // Step 1.
+ double number = 1;
+ if (!GetNumberOption(cx, options, cx->names().roundingIncrement, &number)) {
+ return false;
+ }
+
+ // Step 3. (Reordered)
+ number = std::trunc(number);
+
+ // Steps 2 and 4.
+ if (!std::isfinite(number) || number < 1 || number > 1'000'000'000) {
+ ToCStringBuf cbuf;
+ const char* numStr = NumberToCString(&cbuf, number);
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_INVALID_OPTION_VALUE, "roundingIncrement",
+ numStr);
+ return false;
+ }
+
+ *increment = Increment{uint32_t(number)};
+ return true;
+}
+
+/**
+ * ValidateTemporalRoundingIncrement ( increment, dividend, inclusive )
+ */
+bool js::temporal::ValidateTemporalRoundingIncrement(JSContext* cx,
+ Increment increment,
+ int64_t dividend,
+ bool inclusive) {
+ MOZ_ASSERT(dividend > 0);
+ MOZ_ASSERT_IF(!inclusive, dividend > 1);
+
+ // Steps 1-2.
+ int64_t maximum = inclusive ? dividend : dividend - 1;
+
+ // Steps 3-4.
+ if (increment.value() > maximum || dividend % increment.value() != 0) {
+ Int32ToCStringBuf cbuf;
+ const char* numStr = Int32ToCString(&cbuf, increment.value());
+
+ // TODO: Better error message could be helpful.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_INVALID_OPTION_VALUE, "roundingIncrement",
+ numStr);
+ return false;
+ }
+
+ // Step 5.
+ return true;
+}
+
+PropertyName* js::temporal::TemporalUnitToString(JSContext* cx,
+ TemporalUnit unit) {
+ switch (unit) {
+ case TemporalUnit::Auto:
+ break;
+ case TemporalUnit::Year:
+ return cx->names().year;
+ case TemporalUnit::Month:
+ return cx->names().month;
+ case TemporalUnit::Week:
+ return cx->names().week;
+ case TemporalUnit::Day:
+ return cx->names().day;
+ case TemporalUnit::Hour:
+ return cx->names().hour;
+ case TemporalUnit::Minute:
+ return cx->names().minute;
+ case TemporalUnit::Second:
+ return cx->names().second;
+ case TemporalUnit::Millisecond:
+ return cx->names().millisecond;
+ case TemporalUnit::Microsecond:
+ return cx->names().microsecond;
+ case TemporalUnit::Nanosecond:
+ return cx->names().nanosecond;
+ }
+ MOZ_CRASH("invalid temporal unit");
+}
+
+static Handle<PropertyName*> ToPropertyName(JSContext* cx,
+ TemporalUnitKey key) {
+ switch (key) {
+ case TemporalUnitKey::SmallestUnit:
+ return cx->names().smallestUnit;
+ case TemporalUnitKey::LargestUnit:
+ return cx->names().largestUnit;
+ case TemporalUnitKey::Unit:
+ return cx->names().unit;
+ }
+ MOZ_CRASH("invalid temporal unit group");
+}
+
+static const char* ToCString(TemporalUnitKey key) {
+ switch (key) {
+ case TemporalUnitKey::SmallestUnit:
+ return "smallestUnit";
+ case TemporalUnitKey::LargestUnit:
+ return "largestUnit";
+ case TemporalUnitKey::Unit:
+ return "unit";
+ }
+ MOZ_CRASH("invalid temporal unit group");
+}
+
+static bool ToTemporalUnit(JSContext* cx, JSLinearString* str,
+ TemporalUnitKey key, TemporalUnit* unit) {
+ struct UnitMap {
+ std::string_view name;
+ TemporalUnit unit;
+ };
+
+ static constexpr UnitMap mapping[] = {
+ {"year", TemporalUnit::Year},
+ {"years", TemporalUnit::Year},
+ {"month", TemporalUnit::Month},
+ {"months", TemporalUnit::Month},
+ {"week", TemporalUnit::Week},
+ {"weeks", TemporalUnit::Week},
+ {"day", TemporalUnit::Day},
+ {"days", TemporalUnit::Day},
+ {"hour", TemporalUnit::Hour},
+ {"hours", TemporalUnit::Hour},
+ {"minute", TemporalUnit::Minute},
+ {"minutes", TemporalUnit::Minute},
+ {"second", TemporalUnit::Second},
+ {"seconds", TemporalUnit::Second},
+ {"millisecond", TemporalUnit::Millisecond},
+ {"milliseconds", TemporalUnit::Millisecond},
+ {"microsecond", TemporalUnit::Microsecond},
+ {"microseconds", TemporalUnit::Microsecond},
+ {"nanosecond", TemporalUnit::Nanosecond},
+ {"nanoseconds", TemporalUnit::Nanosecond},
+ };
+
+ // Compute the length of the longest name.
+ constexpr size_t maxNameLength =
+ std::max_element(std::begin(mapping), std::end(mapping),
+ [](const auto& x, const auto& y) {
+ return x.name.length() < y.name.length();
+ })
+ ->name.length();
+
+ // Twenty StringEqualsLiteral calls for each possible combination seems a bit
+ // expensive, so let's instead copy the input name into a char array and rely
+ // on the compiler to generate optimized code for the comparisons.
+
+ size_t length = str->length();
+ if (length <= maxNameLength && StringIsAscii(str)) {
+ char chars[maxNameLength] = {};
+ JS::LossyCopyLinearStringChars(chars, str, length);
+
+ for (const auto& m : mapping) {
+ if (m.name == std::string_view(chars, length)) {
+ *unit = m.unit;
+ return true;
+ }
+ }
+ }
+
+ if (auto chars = QuoteString(cx, str, '"')) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_INVALID_OPTION_VALUE, ToCString(key),
+ chars.get());
+ }
+ return false;
+}
+
+static std::pair<TemporalUnit, TemporalUnit> AllowedValues(
+ TemporalUnitGroup unitGroup) {
+ switch (unitGroup) {
+ case TemporalUnitGroup::Date:
+ return {TemporalUnit::Year, TemporalUnit::Day};
+ case TemporalUnitGroup::Time:
+ return {TemporalUnit::Hour, TemporalUnit::Nanosecond};
+ case TemporalUnitGroup::DateTime:
+ return {TemporalUnit::Year, TemporalUnit::Nanosecond};
+ case TemporalUnitGroup::DayTime:
+ return {TemporalUnit::Day, TemporalUnit::Nanosecond};
+ }
+ MOZ_CRASH("invalid temporal unit group");
+}
+
+/**
+ * GetTemporalUnit ( normalizedOptions, key, unitGroup, default [ , extraValues
+ * ] )
+ */
+bool js::temporal::GetTemporalUnit(JSContext* cx, Handle<JSObject*> options,
+ TemporalUnitKey key,
+ TemporalUnitGroup unitGroup,
+ TemporalUnit* unit) {
+ // Steps 1-8. (Not applicable in our implementation.)
+
+ // Step 9.
+ Rooted<JSString*> value(cx);
+ if (!GetStringOption(cx, options, ToPropertyName(cx, key), &value)) {
+ return false;
+ }
+
+ // Caller should fill in the fallback.
+ if (!value) {
+ return true;
+ }
+
+ return GetTemporalUnit(cx, value, key, unitGroup, unit);
+}
+
+/**
+ * GetTemporalUnit ( normalizedOptions, key, unitGroup, default [ , extraValues
+ * ] )
+ */
+bool js::temporal::GetTemporalUnit(JSContext* cx, Handle<JSString*> value,
+ TemporalUnitKey key,
+ TemporalUnitGroup unitGroup,
+ TemporalUnit* unit) {
+ // Steps 1-9. (Not applicable in our implementation.)
+
+ // Step 10. (Handled in caller.)
+
+ Rooted<JSLinearString*> linear(cx, value->ensureLinear(cx));
+ if (!linear) {
+ return false;
+ }
+
+ // Caller should fill in the fallback.
+ if (key == TemporalUnitKey::LargestUnit) {
+ if (StringEqualsLiteral(linear, "auto")) {
+ return true;
+ }
+ }
+
+ // Step 11.
+ if (!ToTemporalUnit(cx, linear, key, unit)) {
+ return false;
+ }
+
+ auto allowedValues = AllowedValues(unitGroup);
+ if (*unit < allowedValues.first || *unit > allowedValues.second) {
+ if (auto chars = QuoteString(cx, linear, '"')) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_INVALID_OPTION_VALUE, ToCString(key),
+ chars.get());
+ }
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * ToTemporalRoundingMode ( normalizedOptions, fallback )
+ */
+bool js::temporal::ToTemporalRoundingMode(JSContext* cx,
+ Handle<JSObject*> options,
+ TemporalRoundingMode* mode) {
+ // Step 1.
+ Rooted<JSString*> string(cx);
+ if (!GetStringOption(cx, options, cx->names().roundingMode, &string)) {
+ return false;
+ }
+
+ // Caller should fill in the fallback.
+ if (!string) {
+ return true;
+ }
+
+ JSLinearString* linear = string->ensureLinear(cx);
+ if (!linear) {
+ return false;
+ }
+
+ if (StringEqualsLiteral(linear, "ceil")) {
+ *mode = TemporalRoundingMode::Ceil;
+ } else if (StringEqualsLiteral(linear, "floor")) {
+ *mode = TemporalRoundingMode::Floor;
+ } else if (StringEqualsLiteral(linear, "expand")) {
+ *mode = TemporalRoundingMode::Expand;
+ } else if (StringEqualsLiteral(linear, "trunc")) {
+ *mode = TemporalRoundingMode::Trunc;
+ } else if (StringEqualsLiteral(linear, "halfCeil")) {
+ *mode = TemporalRoundingMode::HalfCeil;
+ } else if (StringEqualsLiteral(linear, "halfFloor")) {
+ *mode = TemporalRoundingMode::HalfFloor;
+ } else if (StringEqualsLiteral(linear, "halfExpand")) {
+ *mode = TemporalRoundingMode::HalfExpand;
+ } else if (StringEqualsLiteral(linear, "halfTrunc")) {
+ *mode = TemporalRoundingMode::HalfTrunc;
+ } else if (StringEqualsLiteral(linear, "halfEven")) {
+ *mode = TemporalRoundingMode::HalfEven;
+ } else {
+ if (auto chars = QuoteString(cx, linear, '"')) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_INVALID_OPTION_VALUE, "roundingMode",
+ chars.get());
+ }
+ return false;
+ }
+ return true;
+}
+
+static BigInt* Divide(JSContext* cx, Handle<BigInt*> dividend, int64_t divisor,
+ TemporalRoundingMode roundingMode) {
+ MOZ_ASSERT(divisor > 0);
+
+ Rooted<BigInt*> div(cx, BigInt::createFromInt64(cx, divisor));
+ if (!div) {
+ return nullptr;
+ }
+
+ Rooted<BigInt*> quotient(cx);
+ Rooted<BigInt*> remainder(cx);
+ if (!BigInt::divmod(cx, dividend, div, &quotient, &remainder)) {
+ return nullptr;
+ }
+
+ // No rounding needed when the remainder is zero.
+ if (remainder->isZero()) {
+ return quotient;
+ }
+
+ switch (roundingMode) {
+ case TemporalRoundingMode::Ceil: {
+ if (!remainder->isNegative()) {
+ return BigInt::inc(cx, quotient);
+ }
+ return quotient;
+ }
+ case TemporalRoundingMode::Floor: {
+ if (remainder->isNegative()) {
+ return BigInt::dec(cx, quotient);
+ }
+ return quotient;
+ }
+ case TemporalRoundingMode::Trunc:
+ // BigInt division truncates.
+ return quotient;
+ case TemporalRoundingMode::Expand: {
+ if (!remainder->isNegative()) {
+ return BigInt::inc(cx, quotient);
+ }
+ return BigInt::dec(cx, quotient);
+ }
+ case TemporalRoundingMode::HalfCeil: {
+ int64_t rem;
+ MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem));
+
+ if (!remainder->isNegative()) {
+ if (uint64_t(std::abs(rem)) * 2 >= uint64_t(divisor)) {
+ return BigInt::inc(cx, quotient);
+ }
+ } else {
+ if (uint64_t(std::abs(rem)) * 2 > uint64_t(divisor)) {
+ return BigInt::dec(cx, quotient);
+ }
+ }
+ return quotient;
+ }
+ case TemporalRoundingMode::HalfFloor: {
+ int64_t rem;
+ MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem));
+
+ if (remainder->isNegative()) {
+ if (uint64_t(std::abs(rem)) * 2 >= uint64_t(divisor)) {
+ return BigInt::dec(cx, quotient);
+ }
+ } else {
+ if (uint64_t(std::abs(rem)) * 2 > uint64_t(divisor)) {
+ return BigInt::inc(cx, quotient);
+ }
+ }
+ return quotient;
+ }
+ case TemporalRoundingMode::HalfExpand: {
+ int64_t rem;
+ MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem));
+
+ if (uint64_t(std::abs(rem)) * 2 >= uint64_t(divisor)) {
+ if (!dividend->isNegative()) {
+ return BigInt::inc(cx, quotient);
+ }
+ return BigInt::dec(cx, quotient);
+ }
+ return quotient;
+ }
+ case TemporalRoundingMode::HalfTrunc: {
+ int64_t rem;
+ MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem));
+
+ if (uint64_t(std::abs(rem)) * 2 > uint64_t(divisor)) {
+ if (!dividend->isNegative()) {
+ return BigInt::inc(cx, quotient);
+ }
+ return BigInt::dec(cx, quotient);
+ }
+ return quotient;
+ }
+ case TemporalRoundingMode::HalfEven: {
+ int64_t rem;
+ MOZ_ALWAYS_TRUE(BigInt::isInt64(remainder, &rem));
+
+ if (uint64_t(std::abs(rem)) * 2 == uint64_t(divisor)) {
+ bool isOdd = !quotient->isZero() && (quotient->digit(0) & 1) == 1;
+ if (isOdd) {
+ if (!dividend->isNegative()) {
+ return BigInt::inc(cx, quotient);
+ }
+ return BigInt::dec(cx, quotient);
+ }
+ }
+ if (uint64_t(std::abs(rem)) * 2 > uint64_t(divisor)) {
+ if (!dividend->isNegative()) {
+ return BigInt::inc(cx, quotient);
+ }
+ return BigInt::dec(cx, quotient);
+ }
+ return quotient;
+ }
+ }
+
+ MOZ_CRASH("invalid rounding mode");
+}
+
+static BigInt* Divide(JSContext* cx, Handle<BigInt*> dividend,
+ Handle<BigInt*> divisor,
+ TemporalRoundingMode roundingMode) {
+ MOZ_ASSERT(!divisor->isNegative());
+ MOZ_ASSERT(!divisor->isZero());
+
+ Rooted<BigInt*> quotient(cx);
+ Rooted<BigInt*> remainder(cx);
+ if (!BigInt::divmod(cx, dividend, divisor, &quotient, &remainder)) {
+ return nullptr;
+ }
+
+ // No rounding needed when the remainder is zero.
+ if (remainder->isZero()) {
+ return quotient;
+ }
+
+ switch (roundingMode) {
+ case TemporalRoundingMode::Ceil: {
+ if (!remainder->isNegative()) {
+ return BigInt::inc(cx, quotient);
+ }
+ return quotient;
+ }
+ case TemporalRoundingMode::Floor: {
+ if (remainder->isNegative()) {
+ return BigInt::dec(cx, quotient);
+ }
+ return quotient;
+ }
+ case TemporalRoundingMode::Trunc:
+ // BigInt division truncates.
+ return quotient;
+ case TemporalRoundingMode::Expand: {
+ if (!remainder->isNegative()) {
+ return BigInt::inc(cx, quotient);
+ }
+ return BigInt::dec(cx, quotient);
+ }
+ case TemporalRoundingMode::HalfCeil: {
+ BigInt* rem = BigInt::add(cx, remainder, remainder);
+ if (!rem) {
+ return nullptr;
+ }
+
+ if (!remainder->isNegative()) {
+ if (BigInt::absoluteCompare(rem, divisor) >= 0) {
+ return BigInt::inc(cx, quotient);
+ }
+ } else {
+ if (BigInt::absoluteCompare(rem, divisor) > 0) {
+ return BigInt::dec(cx, quotient);
+ }
+ }
+ return quotient;
+ }
+ case TemporalRoundingMode::HalfFloor: {
+ BigInt* rem = BigInt::add(cx, remainder, remainder);
+ if (!rem) {
+ return nullptr;
+ }
+
+ if (remainder->isNegative()) {
+ if (BigInt::absoluteCompare(rem, divisor) >= 0) {
+ return BigInt::dec(cx, quotient);
+ }
+ } else {
+ if (BigInt::absoluteCompare(rem, divisor) > 0) {
+ return BigInt::inc(cx, quotient);
+ }
+ }
+ return quotient;
+ }
+ case TemporalRoundingMode::HalfExpand: {
+ BigInt* rem = BigInt::add(cx, remainder, remainder);
+ if (!rem) {
+ return nullptr;
+ }
+
+ if (BigInt::absoluteCompare(rem, divisor) >= 0) {
+ if (!dividend->isNegative()) {
+ return BigInt::inc(cx, quotient);
+ }
+ return BigInt::dec(cx, quotient);
+ }
+ return quotient;
+ }
+ case TemporalRoundingMode::HalfTrunc: {
+ BigInt* rem = BigInt::add(cx, remainder, remainder);
+ if (!rem) {
+ return nullptr;
+ }
+
+ if (BigInt::absoluteCompare(rem, divisor) > 0) {
+ if (!dividend->isNegative()) {
+ return BigInt::inc(cx, quotient);
+ }
+ return BigInt::dec(cx, quotient);
+ }
+ return quotient;
+ }
+ case TemporalRoundingMode::HalfEven: {
+ BigInt* rem = BigInt::add(cx, remainder, remainder);
+ if (!rem) {
+ return nullptr;
+ }
+
+ if (BigInt::absoluteCompare(rem, divisor) == 0) {
+ bool isOdd = !quotient->isZero() && (quotient->digit(0) & 1) == 1;
+ if (isOdd) {
+ if (!dividend->isNegative()) {
+ return BigInt::inc(cx, quotient);
+ }
+ return BigInt::dec(cx, quotient);
+ }
+ }
+ if (BigInt::absoluteCompare(rem, divisor) > 0) {
+ if (!dividend->isNegative()) {
+ return BigInt::inc(cx, quotient);
+ }
+ return BigInt::dec(cx, quotient);
+ }
+ return quotient;
+ }
+ }
+
+ MOZ_CRASH("invalid rounding mode");
+}
+
+static BigInt* RoundNumberToIncrementSlow(JSContext* cx, Handle<BigInt*> x,
+ int64_t divisor, int64_t increment,
+ TemporalRoundingMode roundingMode) {
+ // Steps 1-8.
+ Rooted<BigInt*> rounded(cx, Divide(cx, x, divisor, roundingMode));
+ if (!rounded) {
+ return nullptr;
+ }
+
+ // We can skip the next step when |increment=1|.
+ if (increment == 1) {
+ return rounded;
+ }
+
+ // Step 9.
+ Rooted<BigInt*> inc(cx, BigInt::createFromInt64(cx, increment));
+ if (!inc) {
+ return nullptr;
+ }
+ return BigInt::mul(cx, rounded, inc);
+}
+
+static BigInt* RoundNumberToIncrementSlow(JSContext* cx, Handle<BigInt*> x,
+ int64_t increment,
+ TemporalRoundingMode roundingMode) {
+ return RoundNumberToIncrementSlow(cx, x, increment, increment, roundingMode);
+}
+
+/**
+ * RoundNumberToIncrement ( x, increment, roundingMode )
+ */
+bool js::temporal::RoundNumberToIncrement(JSContext* cx, const Instant& x,
+ int64_t increment,
+ TemporalRoundingMode roundingMode,
+ Instant* result) {
+ MOZ_ASSERT(temporal::IsValidEpochInstant(x));
+ MOZ_ASSERT(increment > 0);
+ MOZ_ASSERT(increment <= ToNanoseconds(TemporalUnit::Day));
+
+ // Fast path for the default case.
+ if (increment == 1) {
+ *result = x;
+ return true;
+ }
+
+ // Dividing zero is always zero.
+ if (x == Instant{}) {
+ *result = x;
+ return true;
+ }
+
+ // Fast-path when we can perform the whole computation with int64 values.
+ if (auto num = x.toNanoseconds(); MOZ_LIKELY(num.isValid())) {
+ // Steps 1-8.
+ int64_t rounded = Divide(num.value(), increment, roundingMode);
+
+ // Step 9.
+ mozilla::CheckedInt64 checked = rounded;
+ checked *= increment;
+ if (MOZ_LIKELY(checked.isValid())) {
+ *result = Instant::fromNanoseconds(checked.value());
+ return true;
+ }
+ }
+
+ Rooted<BigInt*> bi(cx, ToEpochNanoseconds(cx, x));
+ if (!bi) {
+ return false;
+ }
+
+ auto* rounded = RoundNumberToIncrementSlow(cx, bi, increment, roundingMode);
+ if (!rounded) {
+ return false;
+ }
+
+ *result = ToInstant(rounded);
+ return true;
+}
+
+/**
+ * RoundNumberToIncrement ( x, increment, roundingMode )
+ */
+bool js::temporal::RoundNumberToIncrement(JSContext* cx, int64_t numerator,
+ TemporalUnit unit,
+ Increment increment,
+ TemporalRoundingMode roundingMode,
+ double* result) {
+ MOZ_ASSERT(unit >= TemporalUnit::Day);
+ MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max());
+
+ // Take the slow path when the increment is too large.
+ if (MOZ_UNLIKELY(increment > Increment{100'000})) {
+ Rooted<BigInt*> bi(cx, BigInt::createFromInt64(cx, numerator));
+ if (!bi) {
+ return false;
+ }
+
+ Rooted<BigInt*> denominator(
+ cx, BigInt::createFromInt64(cx, ToNanoseconds(unit)));
+ if (!denominator) {
+ return false;
+ }
+
+ return RoundNumberToIncrement(cx, bi, denominator, increment, roundingMode,
+ result);
+ }
+
+ int64_t divisor = ToNanoseconds(unit) * increment.value();
+ MOZ_ASSERT(divisor > 0);
+ MOZ_ASSERT(divisor <= 8'640'000'000'000'000'000);
+
+ // Division by one has no remainder.
+ if (divisor == 1) {
+ MOZ_ASSERT(increment == Increment{1});
+ *result = double(numerator);
+ return true;
+ }
+
+ // Steps 1-8.
+ int64_t rounded = Divide(numerator, divisor, roundingMode);
+
+ // Step 9.
+ mozilla::CheckedInt64 checked = rounded;
+ checked *= increment.value();
+ if (checked.isValid()) {
+ *result = double(checked.value());
+ return true;
+ }
+
+ Rooted<BigInt*> bi(cx, BigInt::createFromInt64(cx, numerator));
+ if (!bi) {
+ return false;
+ }
+ return RoundNumberToIncrement(cx, bi, unit, increment, roundingMode, result);
+}
+
+/**
+ * RoundNumberToIncrement ( x, increment, roundingMode )
+ */
+bool js::temporal::RoundNumberToIncrement(
+ JSContext* cx, Handle<BigInt*> numerator, TemporalUnit unit,
+ Increment increment, TemporalRoundingMode roundingMode, double* result) {
+ MOZ_ASSERT(unit >= TemporalUnit::Day);
+ MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max());
+
+ // Take the slow path when the increment is too large.
+ if (MOZ_UNLIKELY(increment > Increment{100'000})) {
+ Rooted<BigInt*> denominator(
+ cx, BigInt::createFromInt64(cx, ToNanoseconds(unit)));
+ if (!denominator) {
+ return false;
+ }
+
+ return RoundNumberToIncrement(cx, numerator, denominator, increment,
+ roundingMode, result);
+ }
+
+ int64_t divisor = ToNanoseconds(unit) * increment.value();
+ MOZ_ASSERT(divisor > 0);
+ MOZ_ASSERT(divisor <= 8'640'000'000'000'000'000);
+
+ // Division by one has no remainder.
+ if (divisor == 1) {
+ MOZ_ASSERT(increment == Increment{1});
+ *result = BigInt::numberValue(numerator);
+ return true;
+ }
+
+ // Dividing zero is always zero.
+ if (numerator->isZero()) {
+ *result = 0;
+ return true;
+ }
+
+ // All callers are already in the slow path, so we don't need to fast-path the
+ // case when |x| can be represented by an int64 value.
+
+ // Steps 1-9.
+ auto* rounded = RoundNumberToIncrementSlow(cx, numerator, divisor,
+ increment.value(), roundingMode);
+ if (!rounded) {
+ return false;
+ }
+
+ *result = BigInt::numberValue(rounded);
+ return true;
+}
+
+/**
+ * RoundNumberToIncrement ( x, increment, roundingMode )
+ */
+bool js::temporal::RoundNumberToIncrement(JSContext* cx, int64_t numerator,
+ int64_t denominator,
+ Increment increment,
+ TemporalRoundingMode roundingMode,
+ double* result) {
+ MOZ_ASSERT(denominator > 0);
+ MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max());
+
+ // Dividing zero is always zero.
+ if (numerator == 0) {
+ *result = 0;
+ return true;
+ }
+
+ // We don't have to adjust the divisor when |increment=1|.
+ if (increment == Increment{1}) {
+ int64_t divisor = denominator;
+ int64_t rounded = Divide(numerator, divisor, roundingMode);
+
+ *result = double(rounded);
+ return true;
+ }
+
+ auto divisor = mozilla::CheckedInt64(denominator) * increment.value();
+ if (MOZ_LIKELY(divisor.isValid())) {
+ MOZ_ASSERT(divisor.value() > 0);
+
+ // Steps 1-8.
+ int64_t rounded = Divide(numerator, divisor.value(), roundingMode);
+
+ // Step 9.
+ auto adjusted = mozilla::CheckedInt64(rounded) * increment.value();
+ if (MOZ_LIKELY(adjusted.isValid())) {
+ *result = double(adjusted.value());
+ return true;
+ }
+ }
+
+ // Slow path on overflow.
+
+ Rooted<BigInt*> bi(cx, BigInt::createFromInt64(cx, numerator));
+ if (!bi) {
+ return false;
+ }
+
+ Rooted<BigInt*> denom(cx, BigInt::createFromInt64(cx, denominator));
+ if (!denom) {
+ return false;
+ }
+
+ return RoundNumberToIncrement(cx, bi, denom, increment, roundingMode, result);
+}
+
+/**
+ * RoundNumberToIncrement ( x, increment, roundingMode )
+ */
+bool js::temporal::RoundNumberToIncrement(
+ JSContext* cx, Handle<BigInt*> numerator, Handle<BigInt*> denominator,
+ Increment increment, TemporalRoundingMode roundingMode, double* result) {
+ MOZ_ASSERT(!denominator->isNegative());
+ MOZ_ASSERT(!denominator->isZero());
+ MOZ_ASSERT(Increment::min() <= increment && increment <= Increment::max());
+
+ // Dividing zero is always zero.
+ if (numerator->isZero()) {
+ *result = 0;
+ return true;
+ }
+
+ // We don't have to adjust the divisor when |increment=1|.
+ if (increment == Increment{1}) {
+ auto divisor = denominator;
+
+ auto* rounded = Divide(cx, numerator, divisor, roundingMode);
+ if (!rounded) {
+ return false;
+ }
+
+ *result = BigInt::numberValue(rounded);
+ return true;
+ }
+
+ Rooted<BigInt*> inc(cx, BigInt::createFromUint64(cx, increment.value()));
+ if (!inc) {
+ return false;
+ }
+
+ Rooted<BigInt*> divisor(cx, BigInt::mul(cx, denominator, inc));
+ if (!divisor) {
+ return false;
+ }
+ MOZ_ASSERT(!divisor->isNegative());
+ MOZ_ASSERT(!divisor->isZero());
+
+ // Steps 1-8.
+ Rooted<BigInt*> rounded(cx, Divide(cx, numerator, divisor, roundingMode));
+ if (!rounded) {
+ return false;
+ }
+
+ // Step 9.
+ auto* adjusted = BigInt::mul(cx, rounded, inc);
+ if (!adjusted) {
+ return false;
+ }
+
+ *result = BigInt::numberValue(adjusted);
+ return true;
+}
+
+/**
+ * ToCalendarNameOption ( normalizedOptions )
+ */
+bool js::temporal::ToCalendarNameOption(JSContext* cx,
+ Handle<JSObject*> options,
+ CalendarOption* result) {
+ // Step 1.
+ Rooted<JSString*> calendarName(cx);
+ if (!GetStringOption(cx, options, cx->names().calendarName, &calendarName)) {
+ return false;
+ }
+
+ // Caller should fill in the fallback.
+ if (!calendarName) {
+ return true;
+ }
+
+ JSLinearString* linear = calendarName->ensureLinear(cx);
+ if (!linear) {
+ return false;
+ }
+
+ if (StringEqualsLiteral(linear, "auto")) {
+ *result = CalendarOption::Auto;
+ } else if (StringEqualsLiteral(linear, "always")) {
+ *result = CalendarOption::Always;
+ } else if (StringEqualsLiteral(linear, "never")) {
+ *result = CalendarOption::Never;
+ } else if (StringEqualsLiteral(linear, "critical")) {
+ *result = CalendarOption::Critical;
+ } else {
+ if (auto chars = QuoteString(cx, linear, '"')) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_INVALID_OPTION_VALUE, "calendarName",
+ chars.get());
+ }
+ return false;
+ }
+ return true;
+}
+
+/**
+ * ToFractionalSecondDigits ( normalizedOptions )
+ */
+bool js::temporal::ToFractionalSecondDigits(JSContext* cx,
+ Handle<JSObject*> options,
+ Precision* precision) {
+ // Step 1.
+ Rooted<Value> digitsValue(cx);
+ if (!GetProperty(cx, options, options, cx->names().fractionalSecondDigits,
+ &digitsValue)) {
+ return false;
+ }
+
+ // Step 2.
+ if (digitsValue.isUndefined()) {
+ *precision = Precision::Auto();
+ return true;
+ }
+
+ // Step 3.
+ if (!digitsValue.isNumber()) {
+ // Step 3.a.
+ JSString* string = JS::ToString(cx, digitsValue);
+ if (!string) {
+ return false;
+ }
+
+ JSLinearString* linear = string->ensureLinear(cx);
+ if (!linear) {
+ return false;
+ }
+
+ if (!StringEqualsLiteral(linear, "auto")) {
+ if (auto chars = QuoteString(cx, linear, '"')) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_INVALID_OPTION_VALUE,
+ "fractionalSecondDigits", chars.get());
+ }
+ return false;
+ }
+
+ // Step 3.b.
+ *precision = Precision::Auto();
+ return true;
+ }
+
+ // Step 4.
+ double digitCount = digitsValue.toNumber();
+ if (!std::isfinite(digitCount)) {
+ ToCStringBuf cbuf;
+ const char* numStr = NumberToCString(&cbuf, digitCount);
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_INVALID_OPTION_VALUE,
+ "fractionalSecondDigits", numStr);
+ return false;
+ }
+
+ // Step 5.
+ digitCount = std::floor(digitCount);
+
+ // Step 6.
+ if (digitCount < 0 || digitCount > 9) {
+ ToCStringBuf cbuf;
+ const char* numStr = NumberToCString(&cbuf, digitCount);
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_INVALID_OPTION_VALUE,
+ "fractionalSecondDigits", numStr);
+ return false;
+ }
+
+ // Step 7.
+ *precision = Precision{uint8_t(digitCount)};
+ return true;
+}
+
+/**
+ * ToSecondsStringPrecisionRecord ( smallestUnit, fractionalDigitCount )
+ */
+SecondsStringPrecision js::temporal::ToSecondsStringPrecision(
+ TemporalUnit smallestUnit, Precision fractionalDigitCount) {
+ MOZ_ASSERT(smallestUnit == TemporalUnit::Auto ||
+ smallestUnit >= TemporalUnit::Minute);
+ MOZ_ASSERT(fractionalDigitCount == Precision::Auto() ||
+ fractionalDigitCount.value() <= 9);
+
+ // Steps 1-5.
+ switch (smallestUnit) {
+ // Step 1.
+ case TemporalUnit::Minute:
+ return {Precision::Minute(), TemporalUnit::Minute, Increment{1}};
+
+ // Step 2.
+ case TemporalUnit::Second:
+ return {Precision{0}, TemporalUnit::Second, Increment{1}};
+
+ // Step 3.
+ case TemporalUnit::Millisecond:
+ return {Precision{3}, TemporalUnit::Millisecond, Increment{1}};
+
+ // Step 4.
+ case TemporalUnit::Microsecond:
+ return {Precision{6}, TemporalUnit::Microsecond, Increment{1}};
+
+ // Step 5.
+ case TemporalUnit::Nanosecond:
+ return {Precision{9}, TemporalUnit::Nanosecond, Increment{1}};
+
+ case TemporalUnit::Auto:
+ break;
+
+ case TemporalUnit::Year:
+ case TemporalUnit::Month:
+ case TemporalUnit::Week:
+ case TemporalUnit::Day:
+ case TemporalUnit::Hour:
+ MOZ_CRASH("Unexpected temporal unit");
+ }
+
+ // Step 6. (Not applicable in our implementation.)
+
+ // Step 7.
+ if (fractionalDigitCount == Precision::Auto()) {
+ return {Precision::Auto(), TemporalUnit::Nanosecond, Increment{1}};
+ }
+
+ static constexpr Increment increments[] = {
+ Increment{1},
+ Increment{10},
+ Increment{100},
+ };
+
+ uint8_t digitCount = fractionalDigitCount.value();
+
+ // Step 8.
+ if (digitCount == 0) {
+ return {Precision{0}, TemporalUnit::Second, Increment{1}};
+ }
+
+ // Step 9.
+ if (digitCount <= 3) {
+ return {fractionalDigitCount, TemporalUnit::Millisecond,
+ increments[3 - digitCount]};
+ }
+
+ // Step 10.
+ if (digitCount <= 6) {
+ return {fractionalDigitCount, TemporalUnit::Microsecond,
+ increments[6 - digitCount]};
+ }
+
+ // Step 11.
+ MOZ_ASSERT(digitCount <= 9);
+
+ // Step 12.
+ return {fractionalDigitCount, TemporalUnit::Nanosecond,
+ increments[9 - digitCount]};
+}
+
+/**
+ * ToTemporalOverflow ( normalizedOptions )
+ */
+bool js::temporal::ToTemporalOverflow(JSContext* cx, Handle<JSObject*> options,
+ TemporalOverflow* result) {
+ // Step 1.
+ Rooted<JSString*> overflow(cx);
+ if (!GetStringOption(cx, options, cx->names().overflow, &overflow)) {
+ return false;
+ }
+
+ // Caller should fill in the fallback.
+ if (!overflow) {
+ return true;
+ }
+
+ JSLinearString* linear = overflow->ensureLinear(cx);
+ if (!linear) {
+ return false;
+ }
+
+ if (StringEqualsLiteral(linear, "constrain")) {
+ *result = TemporalOverflow::Constrain;
+ } else if (StringEqualsLiteral(linear, "reject")) {
+ *result = TemporalOverflow::Reject;
+ } else {
+ if (auto chars = QuoteString(cx, linear, '"')) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_INVALID_OPTION_VALUE, "overflow",
+ chars.get());
+ }
+ return false;
+ }
+ return true;
+}
+
+/**
+ * ToTemporalDisambiguation ( options )
+ */
+bool js::temporal::ToTemporalDisambiguation(
+ JSContext* cx, Handle<JSObject*> options,
+ TemporalDisambiguation* disambiguation) {
+ // Step 1. (Not applicable)
+
+ // Step 2.
+ Rooted<JSString*> string(cx);
+ if (!GetStringOption(cx, options, cx->names().disambiguation, &string)) {
+ return false;
+ }
+
+ // Caller should fill in the fallback.
+ if (!string) {
+ return true;
+ }
+
+ JSLinearString* linear = string->ensureLinear(cx);
+ if (!linear) {
+ return false;
+ }
+
+ if (StringEqualsLiteral(linear, "compatible")) {
+ *disambiguation = TemporalDisambiguation::Compatible;
+ } else if (StringEqualsLiteral(linear, "earlier")) {
+ *disambiguation = TemporalDisambiguation::Earlier;
+ } else if (StringEqualsLiteral(linear, "later")) {
+ *disambiguation = TemporalDisambiguation::Later;
+ } else if (StringEqualsLiteral(linear, "reject")) {
+ *disambiguation = TemporalDisambiguation::Reject;
+ } else {
+ if (auto chars = QuoteString(cx, linear, '"')) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_INVALID_OPTION_VALUE, "disambiguation",
+ chars.get());
+ }
+ return false;
+ }
+ return true;
+}
+
+/**
+ * ToTemporalOffset ( options, fallback )
+ */
+bool js::temporal::ToTemporalOffset(JSContext* cx, Handle<JSObject*> options,
+ TemporalOffset* offset) {
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ Rooted<JSString*> string(cx);
+ if (!GetStringOption(cx, options, cx->names().offset, &string)) {
+ return false;
+ }
+
+ // Caller should fill in the fallback.
+ if (!string) {
+ return true;
+ }
+
+ JSLinearString* linear = string->ensureLinear(cx);
+ if (!linear) {
+ return false;
+ }
+
+ if (StringEqualsLiteral(linear, "prefer")) {
+ *offset = TemporalOffset::Prefer;
+ } else if (StringEqualsLiteral(linear, "use")) {
+ *offset = TemporalOffset::Use;
+ } else if (StringEqualsLiteral(linear, "ignore")) {
+ *offset = TemporalOffset::Ignore;
+ } else if (StringEqualsLiteral(linear, "reject")) {
+ *offset = TemporalOffset::Reject;
+ } else {
+ if (auto chars = QuoteString(cx, linear, '"')) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_INVALID_OPTION_VALUE, "offset",
+ chars.get());
+ }
+ return false;
+ }
+ return true;
+}
+
+/**
+ * ToTimeZoneNameOption ( normalizedOptions )
+ */
+bool js::temporal::ToTimeZoneNameOption(JSContext* cx,
+ Handle<JSObject*> options,
+ TimeZoneNameOption* result) {
+ // Step 1.
+ Rooted<JSString*> timeZoneName(cx);
+ if (!GetStringOption(cx, options, cx->names().timeZoneName, &timeZoneName)) {
+ return false;
+ }
+
+ // Caller should fill in the fallback.
+ if (!timeZoneName) {
+ return true;
+ }
+
+ JSLinearString* linear = timeZoneName->ensureLinear(cx);
+ if (!linear) {
+ return false;
+ }
+
+ if (StringEqualsLiteral(linear, "auto")) {
+ *result = TimeZoneNameOption::Auto;
+ } else if (StringEqualsLiteral(linear, "never")) {
+ *result = TimeZoneNameOption::Never;
+ } else if (StringEqualsLiteral(linear, "critical")) {
+ *result = TimeZoneNameOption::Critical;
+ } else {
+ if (auto chars = QuoteString(cx, linear, '"')) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_INVALID_OPTION_VALUE, "timeZoneName",
+ chars.get());
+ }
+ return false;
+ }
+ return true;
+}
+
+/**
+ * ToShowOffsetOption ( normalizedOptions )
+ */
+bool js::temporal::ToShowOffsetOption(JSContext* cx, Handle<JSObject*> options,
+ ShowOffsetOption* result) {
+ // FIXME: spec issue - should be renamed to ToOffsetOption to match the other
+ // operations ToCalendarNameOption and ToTimeZoneNameOption.
+ //
+ // https://github.com/tc39/proposal-temporal/issues/2441
+
+ // Step 1.
+ Rooted<JSString*> offset(cx);
+ if (!GetStringOption(cx, options, cx->names().offset, &offset)) {
+ return false;
+ }
+
+ // Caller should fill in the fallback.
+ if (!offset) {
+ return true;
+ }
+
+ JSLinearString* linear = offset->ensureLinear(cx);
+ if (!linear) {
+ return false;
+ }
+
+ if (StringEqualsLiteral(linear, "auto")) {
+ *result = ShowOffsetOption::Auto;
+ } else if (StringEqualsLiteral(linear, "never")) {
+ *result = ShowOffsetOption::Never;
+ } else {
+ if (auto chars = QuoteString(cx, linear, '"')) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_INVALID_OPTION_VALUE, "offset",
+ chars.get());
+ }
+ return false;
+ }
+ return true;
+}
+
+template <typename T, typename... Ts>
+static JSObject* MaybeUnwrapIf(JSObject* object) {
+ if (auto* unwrapped = object->maybeUnwrapIf<T>()) {
+ return unwrapped;
+ }
+ if constexpr (sizeof...(Ts) > 0) {
+ return MaybeUnwrapIf<Ts...>(object);
+ }
+ return nullptr;
+}
+
+// FIXME: spec issue - "Reject" is exclusively used for Promise rejection. The
+// existing `RejectPromise` abstract operation unconditionally rejects, whereas
+// this operation conditionally rejects.
+// https://github.com/tc39/proposal-temporal/issues/2534
+
+/**
+ * RejectTemporalLikeObject ( object )
+ */
+bool js::temporal::RejectTemporalLikeObject(JSContext* cx,
+ Handle<JSObject*> object) {
+ // Step 1.
+ if (auto* unwrapped =
+ MaybeUnwrapIf<PlainDateObject, PlainDateTimeObject,
+ PlainMonthDayObject, PlainTimeObject,
+ PlainYearMonthObject, ZonedDateTimeObject>(object)) {
+ Rooted<Value> value(cx, ObjectValue(*object));
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, value,
+ nullptr, unwrapped->getClass()->name);
+ return false;
+ }
+
+ Rooted<Value> property(cx);
+
+ // Step 2.
+ if (!GetProperty(cx, object, object, cx->names().calendar, &property)) {
+ return false;
+ }
+
+ // Step 3.
+ if (!property.isUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_UNEXPECTED_PROPERTY, "calendar");
+ return false;
+ }
+
+ // Step 4.
+ if (!GetProperty(cx, object, object, cx->names().timeZone, &property)) {
+ return false;
+ }
+
+ // Step 5.
+ if (!property.isUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_UNEXPECTED_PROPERTY, "timeZone");
+ return false;
+ }
+
+ // Step 6.
+ return true;
+}
+
+/**
+ * ToPositiveIntegerWithTruncation ( argument )
+ */
+bool js::temporal::ToPositiveIntegerWithTruncation(JSContext* cx,
+ Handle<Value> value,
+ const char* name,
+ double* result) {
+ // Step 1.
+ double number;
+ if (!ToIntegerWithTruncation(cx, value, name, &number)) {
+ return false;
+ }
+
+ // Step 2.
+ if (number <= 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_NUMBER, name);
+ return false;
+ }
+
+ // Step 3.
+ *result = number;
+ return true;
+}
+
+/**
+ * ToIntegerWithTruncation ( argument )
+ */
+bool js::temporal::ToIntegerWithTruncation(JSContext* cx, Handle<Value> value,
+ const char* name, double* result) {
+ // Step 1.
+ double number;
+ if (!JS::ToNumber(cx, value, &number)) {
+ return false;
+ }
+
+ // Step 2.
+ if (!std::isfinite(number)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_INTEGER, name);
+ return false;
+ }
+
+ // Step 3.
+ *result = std::trunc(number) + (+0.0); // Add zero to convert -0 to +0.
+ return true;
+}
+
+/**
+ * GetMethod ( V, P )
+ */
+JSObject* js::temporal::GetMethod(JSContext* cx, Handle<JSObject*> object,
+ Handle<PropertyName*> name) {
+ // Step 1.
+ Rooted<Value> value(cx);
+ if (!GetProperty(cx, object, object, name, &value)) {
+ return nullptr;
+ }
+
+ // Steps 2-3.
+ if (!IsCallable(value)) {
+ if (auto chars = StringToNewUTF8CharsZ(cx, *name)) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_PROPERTY_NOT_CALLABLE, chars.get());
+ }
+ return nullptr;
+ }
+
+ // Step 4.
+ return &value.toObject();
+}
+
+/**
+ * CopyDataProperties ( target, source, excludedKeys [ , excludedValues ] )
+ *
+ * Implementation when |excludedKeys| and |excludedValues| are both empty lists.
+ */
+bool js::temporal::CopyDataProperties(JSContext* cx,
+ Handle<PlainObject*> target,
+ Handle<JSObject*> source) {
+ // Optimization for the common case when |source| is a native object.
+ if (source->is<NativeObject>()) {
+ bool optimized = false;
+ if (!CopyDataPropertiesNative(cx, target, source.as<NativeObject>(),
+ nullptr, &optimized)) {
+ return false;
+ }
+ if (optimized) {
+ return true;
+ }
+ }
+
+ // Step 1-2. (Not applicable)
+
+ // Step 3.
+ JS::RootedVector<PropertyKey> keys(cx);
+ if (!GetPropertyKeys(
+ cx, source, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &keys)) {
+ return false;
+ }
+
+ // Step 4.
+ Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
+ Rooted<Value> propValue(cx);
+ for (size_t i = 0; i < keys.length(); i++) {
+ Handle<PropertyKey> key = keys[i];
+
+ // Steps 4.a-b. (Not applicable)
+
+ // Step 4.c.i.
+ if (!GetOwnPropertyDescriptor(cx, source, key, &desc)) {
+ return false;
+ }
+
+ // Step 4.c.ii.
+ if (desc.isNothing() || !desc->enumerable()) {
+ continue;
+ }
+
+ // Step 4.c.ii.1.
+ if (!GetProperty(cx, source, source, key, &propValue)) {
+ return false;
+ }
+
+ // Step 4.c.ii.2. (Not applicable)
+
+ // Step 4.c.ii.3.
+ if (!DefineDataProperty(cx, target, key, propValue)) {
+ return false;
+ }
+ }
+
+ // Step 5.
+ return true;
+}
+
+/**
+ * CopyDataProperties ( target, source, excludedKeys [ , excludedValues ] )
+ *
+ * Implementation when |excludedKeys| is an empty list and |excludedValues| is
+ * the list ยซundefinedยป.
+ */
+static bool CopyDataPropertiesIgnoreUndefined(JSContext* cx,
+ Handle<PlainObject*> target,
+ Handle<JSObject*> source) {
+ // Step 1-2. (Not applicable)
+
+ // Step 3.
+ JS::RootedVector<PropertyKey> keys(cx);
+ if (!GetPropertyKeys(
+ cx, source, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &keys)) {
+ return false;
+ }
+
+ // Step 4.
+ Rooted<mozilla::Maybe<PropertyDescriptor>> desc(cx);
+ Rooted<Value> propValue(cx);
+ for (size_t i = 0; i < keys.length(); i++) {
+ Handle<PropertyKey> key = keys[i];
+
+ // Steps 4.a-b. (Not applicable)
+
+ // Step 4.c.i.
+ if (!GetOwnPropertyDescriptor(cx, source, key, &desc)) {
+ return false;
+ }
+
+ // Step 4.c.ii.
+ if (desc.isNothing() || !desc->enumerable()) {
+ continue;
+ }
+
+ // Step 4.c.ii.1.
+ if (!GetProperty(cx, source, source, key, &propValue)) {
+ return false;
+ }
+
+ // Step 4.c.ii.2.
+ if (propValue.isUndefined()) {
+ continue;
+ }
+
+ // Step 4.c.ii.3.
+ if (!DefineDataProperty(cx, target, key, propValue)) {
+ return false;
+ }
+ }
+
+ // Step 5.
+ return true;
+}
+
+/**
+ * SnapshotOwnProperties ( source, proto [, excludedKeys [, excludedValues ] ] )
+ */
+PlainObject* js::temporal::SnapshotOwnProperties(JSContext* cx,
+ Handle<JSObject*> source) {
+ // Step 1.
+ Rooted<PlainObject*> copy(cx, NewPlainObjectWithProto(cx, nullptr));
+ if (!copy) {
+ return nullptr;
+ }
+
+ // Steps 2-4.
+ if (!CopyDataProperties(cx, copy, source)) {
+ return nullptr;
+ }
+
+ // Step 3.
+ return copy;
+}
+
+/**
+ * SnapshotOwnProperties ( source, proto [, excludedKeys [, excludedValues ] ] )
+ *
+ * Implementation when |excludedKeys| is an empty list and |excludedValues| is
+ * the list ยซundefinedยป.
+ */
+PlainObject* js::temporal::SnapshotOwnPropertiesIgnoreUndefined(
+ JSContext* cx, Handle<JSObject*> source) {
+ // Step 1.
+ Rooted<PlainObject*> copy(cx, NewPlainObjectWithProto(cx, nullptr));
+ if (!copy) {
+ return nullptr;
+ }
+
+ // Steps 2-4.
+ if (!CopyDataPropertiesIgnoreUndefined(cx, copy, source)) {
+ return nullptr;
+ }
+
+ // Step 3.
+ return copy;
+}
+
+/**
+ * GetDifferenceSettings ( operation, options, unitGroup, disallowedUnits,
+ * fallbackSmallestUnit, smallestLargestDefaultUnit )
+ */
+bool js::temporal::GetDifferenceSettings(
+ JSContext* cx, TemporalDifference operation, Handle<PlainObject*> options,
+ TemporalUnitGroup unitGroup, TemporalUnit smallestAllowedUnit,
+ TemporalUnit fallbackSmallestUnit, TemporalUnit smallestLargestDefaultUnit,
+ DifferenceSettings* result) {
+ // Steps 1-2.
+ auto largestUnit = TemporalUnit::Auto;
+ if (!GetTemporalUnit(cx, options, TemporalUnitKey::LargestUnit, unitGroup,
+ &largestUnit)) {
+ return false;
+ }
+
+ // Step 3.
+ if (largestUnit > smallestAllowedUnit) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_UNIT_OPTION,
+ TemporalUnitToString(largestUnit), "largestUnit");
+ return false;
+ }
+
+ // Step 4.
+ auto roundingIncrement = Increment{1};
+ if (!ToTemporalRoundingIncrement(cx, options, &roundingIncrement)) {
+ return false;
+ }
+
+ // Step 5.
+ auto roundingMode = TemporalRoundingMode::Trunc;
+ if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
+ return false;
+ }
+
+ // Step 6.
+ if (operation == TemporalDifference::Since) {
+ roundingMode = NegateTemporalRoundingMode(roundingMode);
+ }
+
+ // Step 7.
+ auto smallestUnit = fallbackSmallestUnit;
+ if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit, unitGroup,
+ &smallestUnit)) {
+ return false;
+ }
+
+ // Step 8.
+ if (smallestUnit > smallestAllowedUnit) {
+ JS_ReportErrorNumberASCII(
+ cx, GetErrorMessage, nullptr, JSMSG_TEMPORAL_INVALID_UNIT_OPTION,
+ TemporalUnitToString(smallestUnit), "smallestUnit");
+ return false;
+ }
+
+ // Step 9. (Inlined call to LargerOfTwoTemporalUnits)
+ auto defaultLargestUnit = std::min(smallestLargestDefaultUnit, smallestUnit);
+
+ // Step 10.
+ if (largestUnit == TemporalUnit::Auto) {
+ largestUnit = defaultLargestUnit;
+ }
+
+ // Step 11.
+ if (largestUnit > smallestUnit) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_UNIT_RANGE);
+ return false;
+ }
+
+ // Steps 12-13.
+ if (smallestUnit > TemporalUnit::Day) {
+ // Step 12.
+ auto maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
+
+ // Step 13.
+ if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
+ false)) {
+ return false;
+ }
+ }
+
+ // Step 14.
+ *result = {smallestUnit, largestUnit, roundingMode, roundingIncrement};
+ return true;
+}
+
+bool temporal::IsArrayIterationSane(JSContext* cx, bool* result) {
+ auto* stubChain = ForOfPIC::getOrCreate(cx);
+ if (!stubChain) {
+ return false;
+ }
+ return stubChain->tryOptimizeArray(cx, result);
+}
+
+static JSObject* CreateTemporalObject(JSContext* cx, JSProtoKey key) {
+ Rooted<JSObject*> proto(cx, &cx->global()->getObjectPrototype());
+
+ // The |Temporal| object is just a plain object with some "static" data
+ // properties and some constructor properties.
+ return NewTenuredObjectWithGivenProto<TemporalObject>(cx, proto);
+}
+
+/**
+ * Initializes the Temporal Object and its standard built-in properties.
+ */
+static bool TemporalClassFinish(JSContext* cx, Handle<JSObject*> temporal,
+ Handle<JSObject*> proto) {
+ Rooted<PropertyKey> ctorId(cx);
+ Rooted<Value> ctorValue(cx);
+ auto defineProperty = [&](JSProtoKey protoKey, Handle<PropertyName*> name) {
+ JSObject* ctor = GlobalObject::getOrCreateConstructor(cx, protoKey);
+ if (!ctor) {
+ return false;
+ }
+
+ ctorId = NameToId(name);
+ ctorValue.setObject(*ctor);
+ return DefineDataProperty(cx, temporal, ctorId, ctorValue, 0);
+ };
+
+ // Add the constructor properties.
+ for (const auto& protoKey :
+ {JSProto_Calendar, JSProto_Duration, JSProto_Instant, JSProto_PlainDate,
+ JSProto_PlainDateTime, JSProto_PlainMonthDay, JSProto_PlainTime,
+ JSProto_PlainYearMonth, JSProto_TimeZone, JSProto_ZonedDateTime}) {
+ if (!defineProperty(protoKey, ClassName(protoKey, cx))) {
+ return false;
+ }
+ }
+
+ // ClassName(JSProto_TemporalNow) returns "TemporalNow", so we need to handle
+ // it separately.
+ if (!defineProperty(JSProto_TemporalNow, cx->names().Now)) {
+ return false;
+ }
+
+ return true;
+}
+
+const JSClass TemporalObject::class_ = {
+ "Temporal",
+ JSCLASS_HAS_CACHED_PROTO(JSProto_Temporal),
+ JS_NULL_CLASS_OPS,
+ &TemporalObject::classSpec_,
+};
+
+static const JSPropertySpec Temporal_properties[] = {
+ JS_STRING_SYM_PS(toStringTag, "Temporal", JSPROP_READONLY),
+ JS_PS_END,
+};
+
+const ClassSpec TemporalObject::classSpec_ = {
+ CreateTemporalObject, nullptr, nullptr,
+ Temporal_properties, nullptr, nullptr,
+ TemporalClassFinish,
+};
diff --git a/js/src/builtin/temporal/Temporal.h b/js/src/builtin/temporal/Temporal.h
new file mode 100644
index 0000000000..3d014bfaa7
--- /dev/null
+++ b/js/src/builtin/temporal/Temporal.h
@@ -0,0 +1,397 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_Temporal_h
+#define builtin_temporal_Temporal_h
+
+#include "mozilla/Assertions.h"
+
+#include <stdint.h>
+
+#include "jstypes.h"
+
+#include "builtin/temporal/TemporalRoundingMode.h"
+#include "builtin/temporal/TemporalUnit.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "vm/NativeObject.h"
+
+namespace js {
+struct ClassSpec;
+class PlainObject;
+class PropertyName;
+} // namespace js
+
+namespace js::temporal {
+
+class TemporalObject : public NativeObject {
+ public:
+ static const JSClass class_;
+
+ private:
+ static const ClassSpec classSpec_;
+};
+
+struct Instant;
+struct PlainTime;
+
+/**
+ * Rounding increment, which is an integer in the range [1, 1'000'000'000].
+ *
+ * Temporal units are rounded to a multiple of the specified increment value.
+ */
+class Increment final {
+ uint32_t value_;
+
+ public:
+ constexpr explicit Increment(uint32_t value) : value_(value) {
+ MOZ_ASSERT(1 <= value && value <= 1'000'000'000);
+ }
+
+ /**
+ * Minimum allowed rounding increment.
+ */
+ static constexpr auto min() { return Increment{1}; }
+
+ /**
+ * Maximum allowed rounding increment.
+ */
+ static constexpr auto max() { return Increment{1'000'000'000}; }
+
+ /**
+ * The rounding increment's value.
+ */
+ uint32_t value() const { return value_; }
+
+ bool operator==(const Increment& other) const {
+ return value_ == other.value_;
+ }
+
+ bool operator<(const Increment& other) const { return value_ < other.value_; }
+
+ // Other operators are implemented in terms of operator== and operator<.
+ bool operator!=(const Increment& other) const { return !(*this == other); }
+ bool operator>(const Increment& other) const { return other < *this; }
+ bool operator<=(const Increment& other) const { return !(other < *this); }
+ bool operator>=(const Increment& other) const { return !(*this < other); }
+};
+
+/**
+ * ToTemporalRoundingIncrement ( normalizedOptions, dividend, inclusive )
+ */
+bool ToTemporalRoundingIncrement(JSContext* cx, JS::Handle<JSObject*> options,
+ Increment* increment);
+
+/**
+ * ValidateTemporalRoundingIncrement ( increment, dividend, inclusive )
+ */
+bool ValidateTemporalRoundingIncrement(JSContext* cx, Increment increment,
+ int64_t dividend, bool inclusive);
+
+/**
+ * ValidateTemporalRoundingIncrement ( increment, dividend, inclusive )
+ */
+inline bool ValidateTemporalRoundingIncrement(JSContext* cx,
+ Increment increment,
+ Increment dividend,
+ bool inclusive) {
+ return ValidateTemporalRoundingIncrement(cx, increment, dividend.value(),
+ inclusive);
+}
+
+/**
+ * MaximumTemporalDurationRoundingIncrement ( unit )
+ */
+constexpr Increment MaximumTemporalDurationRoundingIncrement(
+ TemporalUnit unit) {
+ // Step 1. (Not applicable in our implementation.)
+ MOZ_ASSERT(unit > TemporalUnit::Day);
+
+ // Step 2.
+ if (unit == TemporalUnit::Hour) {
+ return Increment{24};
+ }
+
+ // Step 3.
+ if (unit <= TemporalUnit::Second) {
+ return Increment{60};
+ }
+
+ // Steps 4-5.
+ return Increment{1000};
+}
+
+PropertyName* TemporalUnitToString(JSContext* cx, TemporalUnit unit);
+
+enum class TemporalUnitGroup {
+ // Allow date units: "year", "month", "week", "day".
+ Date,
+
+ // Allow time units: "hour", "minute", "second", "milli-/micro-/nanoseconds".
+ Time,
+
+ // Allow date and time units.
+ DateTime,
+
+ // Allow "day" and time units.
+ DayTime,
+};
+
+enum class TemporalUnitKey {
+ SmallestUnit,
+ LargestUnit,
+ Unit,
+};
+
+/**
+ * GetTemporalUnit ( normalizedOptions, key, unitGroup, default [ , extraValues
+ * ] )
+ */
+bool GetTemporalUnit(JSContext* cx, JS::Handle<JSObject*> options,
+ TemporalUnitKey key, TemporalUnitGroup unitGroup,
+ TemporalUnit* unit);
+
+/**
+ * GetTemporalUnit ( normalizedOptions, key, unitGroup, default [ , extraValues
+ * ] )
+ */
+bool GetTemporalUnit(JSContext* cx, JS::Handle<JSString*> value,
+ TemporalUnitKey key, TemporalUnitGroup unitGroup,
+ TemporalUnit* unit);
+
+/**
+ * ToTemporalRoundingMode ( normalizedOptions, fallback )
+ */
+bool ToTemporalRoundingMode(JSContext* cx, JS::Handle<JSObject*> options,
+ TemporalRoundingMode* mode);
+
+/**
+ * RoundNumberToIncrement ( x, increment, roundingMode )
+ */
+bool RoundNumberToIncrement(JSContext* cx, const Instant& x, int64_t increment,
+ TemporalRoundingMode roundingMode, Instant* result);
+
+/**
+ * RoundNumberToIncrement ( x, increment, roundingMode )
+ */
+bool RoundNumberToIncrement(JSContext* cx, int64_t numerator, TemporalUnit unit,
+ Increment increment,
+ TemporalRoundingMode roundingMode, double* result);
+
+/**
+ * RoundNumberToIncrement ( x, increment, roundingMode )
+ */
+bool RoundNumberToIncrement(JSContext* cx, JS::Handle<JS::BigInt*> numerator,
+ TemporalUnit unit, Increment increment,
+ TemporalRoundingMode roundingMode, double* result);
+
+/**
+ * RoundNumberToIncrement ( x, increment, roundingMode )
+ */
+bool RoundNumberToIncrement(JSContext* cx, int64_t numerator,
+ int64_t denominator, Increment increment,
+ TemporalRoundingMode roundingMode, double* result);
+
+/**
+ * RoundNumberToIncrement ( x, increment, roundingMode )
+ */
+bool RoundNumberToIncrement(JSContext* cx, JS::Handle<JS::BigInt*> numerator,
+ JS::Handle<JS::BigInt*> denominator,
+ Increment increment,
+ TemporalRoundingMode roundingMode, double* result);
+
+enum class CalendarOption { Auto, Always, Never, Critical };
+
+/**
+ * ToCalendarNameOption ( normalizedOptions )
+ */
+bool ToCalendarNameOption(JSContext* cx, JS::Handle<JSObject*> options,
+ CalendarOption* result);
+
+/**
+ * Precision when displaying fractional seconds.
+ */
+class Precision final {
+ int8_t value_;
+
+ enum class Tag {};
+ constexpr Precision(int8_t value, Tag) : value_(value) {}
+
+ public:
+ constexpr explicit Precision(uint8_t value) : value_(value) {
+ MOZ_ASSERT(value < 10);
+ }
+
+ bool operator==(const Precision& other) const {
+ return value_ == other.value_;
+ }
+
+ bool operator!=(const Precision& other) const { return !(*this == other); }
+
+ /**
+ * Return the number of fractional second digits.
+ */
+ uint8_t value() const {
+ MOZ_ASSERT(value_ >= 0, "auto and minute precision don't have a value");
+ return uint8_t(value_);
+ }
+
+ /**
+ * Limit the precision to trim off any trailing zeros.
+ */
+ static constexpr Precision Auto() { return {-1, Tag{}}; }
+
+ /**
+ * Limit the precision to minutes, i.e. don't display seconds and sub-seconds.
+ */
+ static constexpr Precision Minute() { return {-2, Tag{}}; }
+};
+
+/**
+ * ToFractionalSecondDigits ( normalizedOptions )
+ */
+bool ToFractionalSecondDigits(JSContext* cx, JS::Handle<JSObject*> options,
+ Precision* precision);
+
+struct SecondsStringPrecision final {
+ Precision precision = Precision{0};
+ TemporalUnit unit = TemporalUnit::Auto;
+ Increment increment = Increment{1};
+};
+
+/**
+ * ToSecondsStringPrecisionRecord ( smallestUnit, fractionalDigitCount )
+ */
+SecondsStringPrecision ToSecondsStringPrecision(TemporalUnit smallestUnit,
+ Precision fractionalDigitCount);
+
+enum class TemporalOverflow { Constrain, Reject };
+
+/**
+ * ToTemporalOverflow ( normalizedOptions )
+ */
+bool ToTemporalOverflow(JSContext* cx, JS::Handle<JSObject*> options,
+ TemporalOverflow* result);
+
+enum class TemporalDisambiguation { Compatible, Earlier, Later, Reject };
+
+/**
+ * ToTemporalDisambiguation ( options )
+ */
+bool ToTemporalDisambiguation(JSContext* cx, JS::Handle<JSObject*> options,
+ TemporalDisambiguation* disambiguation);
+
+enum class TemporalOffset { Prefer, Use, Ignore, Reject };
+
+/**
+ * ToTemporalOffset ( options, fallback )
+ */
+bool ToTemporalOffset(JSContext* cx, JS::Handle<JSObject*> options,
+ TemporalOffset* offset);
+
+enum class TimeZoneNameOption { Auto, Never, Critical };
+
+bool ToTimeZoneNameOption(JSContext* cx, JS::Handle<JSObject*> options,
+ TimeZoneNameOption* result);
+
+enum class ShowOffsetOption { Auto, Never };
+
+/**
+ * ToShowOffsetOption ( normalizedOptions )
+ */
+bool ToShowOffsetOption(JSContext* cx, JS::Handle<JSObject*> options,
+ ShowOffsetOption* result);
+
+/**
+ * RejectTemporalLikeObject ( object )
+ */
+bool RejectTemporalLikeObject(JSContext* cx, JS::Handle<JSObject*> object);
+
+/**
+ * ToPositiveIntegerWithTruncation ( argument )
+ */
+bool ToPositiveIntegerWithTruncation(JSContext* cx, JS::Handle<JS::Value> value,
+ const char* name, double* result);
+
+/**
+ * ToIntegerWithTruncation ( argument )
+ */
+bool ToIntegerWithTruncation(JSContext* cx, JS::Handle<JS::Value> value,
+ const char* name, double* result);
+
+/**
+ * GetMethod ( V, P )
+ */
+JSObject* GetMethod(JSContext* cx, JS::Handle<JSObject*> object,
+ JS::Handle<PropertyName*> name);
+
+/**
+ * SnapshotOwnProperties ( source, proto [ , excludedKeys [ , excludedValues ] ]
+ * )
+ */
+PlainObject* SnapshotOwnProperties(JSContext* cx, JS::Handle<JSObject*> source);
+
+/**
+ * SnapshotOwnProperties ( source, proto [ , excludedKeys [ , excludedValues ] ]
+ * )
+ */
+PlainObject* SnapshotOwnPropertiesIgnoreUndefined(JSContext* cx,
+ JS::Handle<JSObject*> source);
+
+/**
+ * CopyDataProperties ( target, source, excludedKeys [ , excludedValues ] )
+ */
+bool CopyDataProperties(JSContext* cx, JS::Handle<PlainObject*> target,
+ JS::Handle<JSObject*> source);
+
+enum class TemporalDifference { Since, Until };
+
+inline const char* ToName(TemporalDifference difference) {
+ return difference == TemporalDifference::Since ? "since" : "until";
+}
+
+struct DifferenceSettings final {
+ TemporalUnit smallestUnit = TemporalUnit::Auto;
+ TemporalUnit largestUnit = TemporalUnit::Auto;
+ TemporalRoundingMode roundingMode = TemporalRoundingMode::Trunc;
+ Increment roundingIncrement = Increment{1};
+};
+
+/**
+ * GetDifferenceSettings ( operation, options, unitGroup, disallowedUnits,
+ * fallbackSmallestUnit, smallestLargestDefaultUnit )
+ */
+bool GetDifferenceSettings(JSContext* cx, TemporalDifference operation,
+ JS::Handle<PlainObject*> options,
+ TemporalUnitGroup unitGroup,
+ TemporalUnit smallestAllowedUnit,
+ TemporalUnit fallbackSmallestUnit,
+ TemporalUnit smallestLargestDefaultUnit,
+ DifferenceSettings* result);
+
+/**
+ * GetDifferenceSettings ( operation, options, unitGroup, disallowedUnits,
+ * fallbackSmallestUnit, smallestLargestDefaultUnit )
+ */
+inline bool GetDifferenceSettings(JSContext* cx, TemporalDifference operation,
+ JS::Handle<PlainObject*> options,
+ TemporalUnitGroup unitGroup,
+ TemporalUnit fallbackSmallestUnit,
+ TemporalUnit smallestLargestDefaultUnit,
+ DifferenceSettings* result) {
+ return GetDifferenceSettings(cx, operation, options, unitGroup,
+ TemporalUnit::Nanosecond, fallbackSmallestUnit,
+ smallestLargestDefaultUnit, result);
+}
+
+/**
+ * Sets |result| to `true` when array iteration is still in its initial state.
+ */
+bool IsArrayIterationSane(JSContext* cx, bool* result);
+
+} /* namespace js::temporal */
+
+#endif /* builtin_temporal_Temporal_h */
diff --git a/js/src/builtin/temporal/TemporalFields.cpp b/js/src/builtin/temporal/TemporalFields.cpp
new file mode 100644
index 0000000000..9ac2e44639
--- /dev/null
+++ b/js/src/builtin/temporal/TemporalFields.cpp
@@ -0,0 +1,939 @@
+/* -*- 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/TemporalFields.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Range.h"
+#include "mozilla/RangedPtr.h"
+
+#include <algorithm>
+#include <cstring>
+#include <iterator>
+#include <stdint.h>
+#include <utility>
+
+#include "jsnum.h"
+#include "jspubtd.h"
+#include "NamespaceImports.h"
+
+#include "builtin/temporal/Temporal.h"
+#include "ds/Sort.h"
+#include "gc/Barrier.h"
+#include "gc/Tracer.h"
+#include "js/AllocPolicy.h"
+#include "js/ComparisonOperators.h"
+#include "js/ErrorReport.h"
+#include "js/friend/ErrorMessages.h"
+#include "js/GCVector.h"
+#include "js/Id.h"
+#include "js/Printer.h"
+#include "js/RootingAPI.h"
+#include "js/TracingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Utility.h"
+#include "js/Value.h"
+#include "util/Text.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/JSAtomState.h"
+#include "vm/JSContext.h"
+#include "vm/JSObject.h"
+#include "vm/PlainObject.h"
+#include "vm/StringType.h"
+#include "vm/SymbolType.h"
+
+#include "vm/JSAtomUtils-inl.h"
+#include "vm/ObjectOperations-inl.h"
+
+using namespace js;
+using namespace js::temporal;
+
+void TemporalFields::trace(JSTracer* trc) {
+ TraceNullableRoot(trc, &monthCode, "TemporalFields::monthCode");
+ TraceNullableRoot(trc, &offset, "TemporalFields::offset");
+ TraceNullableRoot(trc, &era, "TemporalFields::era");
+ TraceRoot(trc, &timeZone, "TemporalFields::timeZone");
+}
+
+static PropertyName* ToPropertyName(JSContext* cx, TemporalField field) {
+ switch (field) {
+ case TemporalField::Year:
+ return cx->names().year;
+ case TemporalField::Month:
+ return cx->names().month;
+ case TemporalField::MonthCode:
+ return cx->names().monthCode;
+ case TemporalField::Day:
+ return cx->names().day;
+ case TemporalField::Hour:
+ return cx->names().hour;
+ case TemporalField::Minute:
+ return cx->names().minute;
+ case TemporalField::Second:
+ return cx->names().second;
+ case TemporalField::Millisecond:
+ return cx->names().millisecond;
+ case TemporalField::Microsecond:
+ return cx->names().microsecond;
+ case TemporalField::Nanosecond:
+ return cx->names().nanosecond;
+ case TemporalField::Offset:
+ return cx->names().offset;
+ case TemporalField::Era:
+ return cx->names().era;
+ case TemporalField::EraYear:
+ return cx->names().eraYear;
+ case TemporalField::TimeZone:
+ return cx->names().timeZone;
+ }
+ MOZ_CRASH("invalid temporal field name");
+}
+
+static const char* ToCString(TemporalField field) {
+ switch (field) {
+ case TemporalField::Year:
+ return "year";
+ case TemporalField::Month:
+ return "month";
+ case TemporalField::MonthCode:
+ return "monthCode";
+ case TemporalField::Day:
+ return "day";
+ case TemporalField::Hour:
+ return "hour";
+ case TemporalField::Minute:
+ return "minute";
+ case TemporalField::Second:
+ return "second";
+ case TemporalField::Millisecond:
+ return "millisecond";
+ case TemporalField::Microsecond:
+ return "microsecond";
+ case TemporalField::Nanosecond:
+ return "nanosecond";
+ case TemporalField::Offset:
+ return "offset";
+ case TemporalField::Era:
+ return "era";
+ case TemporalField::EraYear:
+ return "eraYear";
+ case TemporalField::TimeZone:
+ return "timeZone";
+ }
+ MOZ_CRASH("invalid temporal field name");
+}
+
+static JS::UniqueChars QuoteString(JSContext* cx, const char* str) {
+ Sprinter sprinter(cx);
+ if (!sprinter.init()) {
+ return nullptr;
+ }
+ mozilla::Range range(reinterpret_cast<const Latin1Char*>(str),
+ std::strlen(str));
+ QuoteString<QuoteTarget::String>(&sprinter, range);
+ return sprinter.release();
+}
+
+static JS::UniqueChars QuoteString(JSContext* cx, PropertyKey key) {
+ if (key.isString()) {
+ return QuoteString(cx, key.toString());
+ }
+
+ if (key.isInt()) {
+ Int32ToCStringBuf buf;
+ size_t length;
+ const char* str = Int32ToCString(&buf, key.toInt(), &length);
+ return DuplicateString(cx, str, length);
+ }
+
+ MOZ_ASSERT(key.isSymbol());
+ return QuoteString(cx, key.toSymbol()->description());
+}
+
+static mozilla::Maybe<TemporalField> ToTemporalField(JSContext* cx,
+ PropertyKey property) {
+ static constexpr TemporalField fieldNames[] = {
+ TemporalField::Year, TemporalField::Month,
+ TemporalField::MonthCode, TemporalField::Day,
+ TemporalField::Hour, TemporalField::Minute,
+ TemporalField::Second, TemporalField::Millisecond,
+ TemporalField::Microsecond, TemporalField::Nanosecond,
+ TemporalField::Offset, TemporalField::Era,
+ TemporalField::EraYear, TemporalField::TimeZone,
+ };
+
+ for (const auto& fieldName : fieldNames) {
+ auto* name = ToPropertyName(cx, fieldName);
+ if (property.isAtom(name)) {
+ return mozilla::Some(fieldName);
+ }
+ }
+ return mozilla::Nothing();
+}
+
+static JSString* ToPrimitiveAndRequireString(JSContext* cx,
+ Handle<Value> value) {
+ Rooted<Value> primitive(cx, value);
+ if (!ToPrimitive(cx, JSTYPE_STRING, &primitive)) {
+ return nullptr;
+ }
+ if (!primitive.isString()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, primitive,
+ nullptr, "not a string");
+ return nullptr;
+ }
+ return primitive.toString();
+}
+
+static Value TemporalFieldDefaultValue(TemporalField field) {
+ switch (field) {
+ case TemporalField::Year:
+ case TemporalField::Month:
+ case TemporalField::MonthCode:
+ case TemporalField::Day:
+ case TemporalField::Offset:
+ case TemporalField::Era:
+ case TemporalField::EraYear:
+ case TemporalField::TimeZone:
+ return UndefinedValue();
+ case TemporalField::Hour:
+ case TemporalField::Minute:
+ case TemporalField::Second:
+ case TemporalField::Millisecond:
+ case TemporalField::Microsecond:
+ case TemporalField::Nanosecond:
+ return Int32Value(0);
+ }
+ MOZ_CRASH("invalid temporal field name");
+}
+
+static bool TemporalFieldConvertValue(JSContext* cx, TemporalField field,
+ MutableHandle<Value> value) {
+ auto* name = ToCString(field);
+ switch (field) {
+ case TemporalField::Year:
+ case TemporalField::Hour:
+ case TemporalField::Minute:
+ case TemporalField::Second:
+ case TemporalField::Millisecond:
+ case TemporalField::Microsecond:
+ case TemporalField::Nanosecond:
+ case TemporalField::EraYear: {
+ double num;
+ if (!ToIntegerWithTruncation(cx, value, name, &num)) {
+ return false;
+ }
+ value.setNumber(num);
+ return true;
+ }
+
+ case TemporalField::Month:
+ case TemporalField::Day: {
+ double num;
+ if (!ToPositiveIntegerWithTruncation(cx, value, name, &num)) {
+ return false;
+ }
+ value.setNumber(num);
+ return true;
+ }
+
+ case TemporalField::MonthCode:
+ case TemporalField::Offset:
+ case TemporalField::Era: {
+ JSString* str = ToPrimitiveAndRequireString(cx, value);
+ if (!str) {
+ return false;
+ }
+ value.setString(str);
+ return true;
+ }
+
+ case TemporalField::TimeZone:
+ // NB: timeZone has no conversion function.
+ return true;
+ }
+ MOZ_CRASH("invalid temporal field name");
+}
+
+static int32_t ComparePropertyKey(PropertyKey x, PropertyKey y) {
+ MOZ_ASSERT(x.isAtom() || x.isInt());
+ MOZ_ASSERT(y.isAtom() || y.isInt());
+
+ if (MOZ_LIKELY(x.isAtom() && y.isAtom())) {
+ return CompareStrings(x.toAtom(), y.toAtom());
+ }
+
+ if (x.isInt() && y.isInt()) {
+ return x.toInt() - y.toInt();
+ }
+
+ uint32_t index = uint32_t(x.isInt() ? x.toInt() : y.toInt());
+ JSAtom* str = x.isAtom() ? x.toAtom() : y.toAtom();
+
+ char16_t buf[UINT32_CHAR_BUFFER_LENGTH];
+ mozilla::RangedPtr<char16_t> end(std::end(buf), buf, std::end(buf));
+ mozilla::RangedPtr<char16_t> start = BackfillIndexInCharBuffer(index, end);
+
+ int32_t result = CompareChars(start.get(), end - start, str);
+ return x.isInt() ? result : -result;
+}
+
+#ifdef DEBUG
+static bool IsSorted(std::initializer_list<TemporalField> fieldNames) {
+ return std::is_sorted(fieldNames.begin(), fieldNames.end(),
+ [](auto x, auto y) {
+ auto* a = ToCString(x);
+ auto* b = ToCString(y);
+ return std::strcmp(a, b) < 0;
+ });
+}
+
+static bool IsSorted(const TemporalFieldNames& fieldNames) {
+ return std::is_sorted(
+ fieldNames.begin(), fieldNames.end(),
+ [](auto x, auto y) { return ComparePropertyKey(x, y) < 0; });
+}
+#endif
+
+// clang-format off
+//
+// TODO: |fields| is often a built-in Temporal type, so we likely want to
+// optimise for this case.
+//
+// Consider the case when PlainDate.prototype.toPlainMonthDay is called. The
+// following steps are applied:
+//
+// 1. CalendarFields(calendar, ยซ"day", "monthCode"ยป) is called to retrieve the
+// relevant calendar fields. For (most?) built-in calendars this will just
+// return the input list ยซ"day", "monthCode"ยป.
+// 2. PrepareTemporalFields(plainDate, ยซ"day", "monthCode"ยป) is called. This
+// will access the properties `plainDate.day` and `plainDate.monthCode`.
+// a. `plainDate.day` will call CalendarDay(calendar, plainDate).
+// b. For built-in calendars, this will simply access `plainDate.[[IsoDay]]`.
+// c. `plainDate.monthCode` will call CalendarMonthCode(calendar, plainDate).
+// d. For built-in calendars, ISOMonthCode(plainDate.[[IsoMonth]]) is called.
+// 3. CalendarMonthDayFromFields(calendar, {day, monthCode}) is called.
+// 4. For built-in calendars, this calls PrepareTemporalFields({day, monthCode},
+// ยซ"day", "month", "monthCode", "year"ยป, ยซ"day"ยป).
+// 5. The previous PrepareTemporalFields call is a no-op and returns {day, monthCode}.
+// 6. Then ISOMonthDayFromFields({day, monthCode}, "constrain") gets called.
+// 7. ResolveISOMonth(monthCode) is called to parse the just created `monthCode`.
+// 8. RegulateISODate(referenceISOYear, month, day, "constrain") is called.
+// 9. Finally CreateTemporalMonthDay is called to create the PlainMonthDay instance.
+//
+// All these steps could be simplified to just:
+// 1. CreateTemporalMonthDay(referenceISOYear, plainDate.[[IsoMonth]], plainDate.[[IsoDay]]).
+//
+// When the following conditions are true:
+// 1. The `plainDate` is a Temporal.PlainDate instance and has no overridden methods.
+// 2. The `calendar` is a Temporal.Calendar instance and has no overridden methods.
+// 3. Temporal.PlainDate.prototype and Temporal.Calendar.prototype are in their initial state.
+// 4. Array iteration is still in its initial state. (Required by CalendarFields)
+//
+// PlainDate_toPlainMonthDay has an example implementation for this optimisation.
+//
+// clang-format on
+
+/**
+ * PrepareTemporalFields ( fields, fieldNames, requiredFields )
+ */
+bool js::temporal::PrepareTemporalFields(
+ JSContext* cx, Handle<JSObject*> fields,
+ std::initializer_list<TemporalField> fieldNames,
+ std::initializer_list<TemporalField> requiredFields,
+ MutableHandle<TemporalFields> result) {
+ // Steps 1-3. (Not applicable in our implementation.)
+
+ // Step 4. (|fieldNames| is sorted in our implementation.)
+ MOZ_ASSERT(IsSorted(fieldNames));
+
+ // Step 5. (The list doesn't contain duplicates in our implementation.)
+ MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) ==
+ fieldNames.end());
+
+ // |requiredFields| is sorted and doesn't contain any duplicate elements.
+ MOZ_ASSERT(IsSorted(requiredFields));
+ MOZ_ASSERT(std::adjacent_find(requiredFields.begin(), requiredFields.end()) ==
+ requiredFields.end());
+
+ // Step 6.
+ Rooted<Value> value(cx);
+ for (auto fieldName : fieldNames) {
+ auto* property = ToPropertyName(cx, fieldName);
+ auto* cstr = ToCString(fieldName);
+
+ // Step 6.a. (Not applicable in our implementation.)
+
+ // Step 6.b.i.
+ if (!GetProperty(cx, fields, fields, property, &value)) {
+ return false;
+ }
+
+ // Steps 6.b.ii-iii.
+ if (!value.isUndefined()) {
+ // Step 6.b.ii.1. (Not applicable in our implementation.)
+
+ // Steps 6.b.ii.2-3.
+ switch (fieldName) {
+ case TemporalField::Year:
+ if (!ToIntegerWithTruncation(cx, value, cstr, &result.year())) {
+ return false;
+ }
+ break;
+ case TemporalField::Month:
+ if (!ToPositiveIntegerWithTruncation(cx, value, cstr,
+ &result.month())) {
+ return false;
+ }
+ break;
+ case TemporalField::MonthCode: {
+ JSString* str = ToPrimitiveAndRequireString(cx, value);
+ if (!str) {
+ return false;
+ }
+ result.monthCode().set(str);
+ break;
+ }
+ case TemporalField::Day:
+ if (!ToPositiveIntegerWithTruncation(cx, value, cstr,
+ &result.day())) {
+ return false;
+ }
+ break;
+ case TemporalField::Hour:
+ if (!ToIntegerWithTruncation(cx, value, cstr, &result.hour())) {
+ return false;
+ }
+ break;
+ case TemporalField::Minute:
+ if (!ToIntegerWithTruncation(cx, value, cstr, &result.minute())) {
+ return false;
+ }
+ break;
+ case TemporalField::Second:
+ if (!ToIntegerWithTruncation(cx, value, cstr, &result.second())) {
+ return false;
+ }
+ break;
+ case TemporalField::Millisecond:
+ if (!ToIntegerWithTruncation(cx, value, cstr,
+ &result.millisecond())) {
+ return false;
+ }
+ break;
+ case TemporalField::Microsecond:
+ if (!ToIntegerWithTruncation(cx, value, cstr,
+ &result.microsecond())) {
+ return false;
+ }
+ break;
+ case TemporalField::Nanosecond:
+ if (!ToIntegerWithTruncation(cx, value, cstr, &result.nanosecond())) {
+ return false;
+ }
+ break;
+ case TemporalField::Offset: {
+ JSString* str = ToPrimitiveAndRequireString(cx, value);
+ if (!str) {
+ return false;
+ }
+ result.offset().set(str);
+ break;
+ }
+ case TemporalField::Era: {
+ JSString* str = ToPrimitiveAndRequireString(cx, value);
+ if (!str) {
+ return false;
+ }
+ result.era().set(str);
+ break;
+ }
+ case TemporalField::EraYear:
+ if (!ToIntegerWithTruncation(cx, value, cstr, &result.eraYear())) {
+ return false;
+ }
+ break;
+ case TemporalField::TimeZone:
+ // NB: TemporalField::TimeZone has no conversion function.
+ result.timeZone().set(value);
+ break;
+ }
+ } else {
+ // Step 6.b.iii.1.
+ if (std::find(requiredFields.begin(), requiredFields.end(), fieldName) !=
+ requiredFields.end()) {
+ if (auto chars = QuoteString(cx, cstr)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_MISSING_PROPERTY,
+ chars.get());
+ }
+ return false;
+ }
+
+ // `const` can be changed to `constexpr` when we switch to C++20.
+ const TemporalFields FallbackValues{};
+
+ // Steps 6.b.iii.2-3.
+ switch (fieldName) {
+ case TemporalField::Year:
+ result.year() = FallbackValues.year;
+ break;
+ case TemporalField::Month:
+ result.month() = FallbackValues.month;
+ break;
+ case TemporalField::MonthCode:
+ result.monthCode().set(FallbackValues.monthCode);
+ break;
+ case TemporalField::Day:
+ result.day() = FallbackValues.day;
+ break;
+ case TemporalField::Hour:
+ result.hour() = FallbackValues.hour;
+ break;
+ case TemporalField::Minute:
+ result.minute() = FallbackValues.minute;
+ break;
+ case TemporalField::Second:
+ result.second() = FallbackValues.second;
+ break;
+ case TemporalField::Millisecond:
+ result.millisecond() = FallbackValues.millisecond;
+ break;
+ case TemporalField::Microsecond:
+ result.microsecond() = FallbackValues.microsecond;
+ break;
+ case TemporalField::Nanosecond:
+ result.nanosecond() = FallbackValues.nanosecond;
+ break;
+ case TemporalField::Offset:
+ result.offset().set(FallbackValues.offset);
+ break;
+ case TemporalField::Era:
+ result.era().set(FallbackValues.era);
+ break;
+ case TemporalField::EraYear:
+ result.eraYear() = FallbackValues.eraYear;
+ break;
+ case TemporalField::TimeZone:
+ result.timeZone().set(FallbackValues.timeZone);
+ break;
+ }
+ }
+
+ // Steps 6.c-d. (Not applicable in our implementation.)
+ }
+
+ // Step 7. (Not applicable in our implementation.)
+
+ // Step 8.
+ return true;
+}
+
+/**
+ * PrepareTemporalFields ( fields, fieldNames, requiredFields [ ,
+ * duplicateBehaviour ] )
+ */
+PlainObject* js::temporal::PrepareTemporalFields(
+ JSContext* cx, Handle<JSObject*> fields,
+ Handle<TemporalFieldNames> fieldNames) {
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ Rooted<PlainObject*> result(cx, NewPlainObjectWithProto(cx, nullptr));
+ if (!result) {
+ return nullptr;
+ }
+
+ // Step 3. (Not applicable in our implementation.)
+
+ // Step 4. (The list is already sorted in our implementation.)
+ MOZ_ASSERT(IsSorted(fieldNames));
+
+ // Step 5. (The list doesn't contain duplicates in our implementation.)
+ MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) ==
+ fieldNames.end());
+
+ // Step 6.
+ Rooted<Value> value(cx);
+ for (size_t i = 0; i < fieldNames.length(); i++) {
+ Handle<PropertyKey> property = fieldNames[i];
+
+ // Step 6.a.
+ MOZ_ASSERT(property != NameToId(cx->names().constructor));
+ MOZ_ASSERT(property != NameToId(cx->names().proto_));
+
+ // Step 6.b.i.
+ if (!GetProperty(cx, fields, fields, property, &value)) {
+ return nullptr;
+ }
+
+ // Steps 6.b.ii-iii.
+ if (auto fieldName = ToTemporalField(cx, property)) {
+ if (!value.isUndefined()) {
+ // Step 6.b.ii.1. (Not applicable in our implementation.)
+
+ // Step 6.b.ii.2.
+ if (!TemporalFieldConvertValue(cx, *fieldName, &value)) {
+ return nullptr;
+ }
+ } else {
+ // Step 6.b.iii.1. (Not applicable in our implementation.)
+
+ // Step 6.b.iii.2.
+ value = TemporalFieldDefaultValue(*fieldName);
+ }
+ }
+
+ // Steps 6.b.ii.3 and 6.b.iii.3.
+ if (!DefineDataProperty(cx, result, property, value)) {
+ return nullptr;
+ }
+
+ // Steps 6.c-d. (Not applicable in our implementation.)
+ }
+
+ // Step 7. (Not applicable in our implementation.)
+
+ // Step 8.
+ return result;
+}
+
+/**
+ * PrepareTemporalFields ( fields, fieldNames, requiredFields [ ,
+ * duplicateBehaviour ] )
+ */
+PlainObject* js::temporal::PrepareTemporalFields(
+ JSContext* cx, Handle<JSObject*> fields,
+ Handle<TemporalFieldNames> fieldNames,
+ std::initializer_list<TemporalField> requiredFields) {
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ Rooted<PlainObject*> result(cx, NewPlainObjectWithProto(cx, nullptr));
+ if (!result) {
+ return nullptr;
+ }
+
+ // Step 3. (Not applicable in our implementation.)
+
+ // Step 4. (The list is already sorted in our implementation.)
+ MOZ_ASSERT(IsSorted(fieldNames));
+
+ // Step 5. (The list doesn't contain duplicates in our implementation.)
+ MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) ==
+ fieldNames.end());
+
+ // |requiredFields| is sorted and doesn't include any duplicate elements.
+ MOZ_ASSERT(IsSorted(requiredFields));
+ MOZ_ASSERT(std::adjacent_find(requiredFields.begin(), requiredFields.end()) ==
+ requiredFields.end());
+
+ // Step 6.
+ Rooted<Value> value(cx);
+ for (size_t i = 0; i < fieldNames.length(); i++) {
+ Handle<PropertyKey> property = fieldNames[i];
+
+ // Step 6.a.
+ MOZ_ASSERT(property != NameToId(cx->names().constructor));
+ MOZ_ASSERT(property != NameToId(cx->names().proto_));
+
+ // Step 6.b.i.
+ if (!GetProperty(cx, fields, fields, property, &value)) {
+ return nullptr;
+ }
+
+ // Steps 6.b.ii-iii.
+ if (auto fieldName = ToTemporalField(cx, property)) {
+ if (!value.isUndefined()) {
+ // Step 6.b.ii.1. (Not applicable in our implementation.)
+
+ // Step 6.b.ii.2.
+ if (!TemporalFieldConvertValue(cx, *fieldName, &value)) {
+ return nullptr;
+ }
+ } else {
+ // Step 6.b.iii.1.
+ if (std::find(requiredFields.begin(), requiredFields.end(),
+ *fieldName) != requiredFields.end()) {
+ if (auto chars = QuoteString(cx, property.toString())) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_MISSING_PROPERTY,
+ chars.get());
+ }
+ return nullptr;
+ }
+
+ // Step 6.b.iii.2.
+ value = TemporalFieldDefaultValue(*fieldName);
+ }
+ }
+
+ // Steps 6.b.ii.3 and 6.b.iii.3.
+ if (!DefineDataProperty(cx, result, property, value)) {
+ return nullptr;
+ }
+
+ // Steps 6.c-d. (Not applicable in our implementation.)
+ }
+
+ // Step 7. (Not applicable in our implementation.)
+
+ // Step 8.
+ return result;
+}
+
+/**
+ * PrepareTemporalFields ( fields, fieldNames, requiredFields [ ,
+ * duplicateBehaviour ] )
+ */
+PlainObject* js::temporal::PreparePartialTemporalFields(
+ JSContext* cx, Handle<JSObject*> fields,
+ Handle<TemporalFieldNames> fieldNames) {
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ Rooted<PlainObject*> result(cx, NewPlainObjectWithProto(cx, nullptr));
+ if (!result) {
+ return nullptr;
+ }
+
+ // Step 3.
+ bool any = false;
+
+ // Step 4. (The list is already sorted in our implementation.)
+ MOZ_ASSERT(IsSorted(fieldNames));
+
+ // Step 5. (The list doesn't contain duplicates in our implementation.)
+ MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) ==
+ fieldNames.end());
+
+ // Step 6.
+ Rooted<Value> value(cx);
+ for (size_t i = 0; i < fieldNames.length(); i++) {
+ Handle<PropertyKey> property = fieldNames[i];
+
+ // Step 6.a.
+ MOZ_ASSERT(property != NameToId(cx->names().constructor));
+ MOZ_ASSERT(property != NameToId(cx->names().proto_));
+
+ // Step 6.b.i.
+ if (!GetProperty(cx, fields, fields, property, &value)) {
+ return nullptr;
+ }
+
+ // Steps 6.b.ii-iii.
+ if (!value.isUndefined()) {
+ // Step 6.b.ii.1.
+ any = true;
+
+ // Step 6.b.ii.2.
+ if (auto fieldName = ToTemporalField(cx, property)) {
+ if (!TemporalFieldConvertValue(cx, *fieldName, &value)) {
+ return nullptr;
+ }
+ }
+
+ // Steps 6.b.ii.3.
+ if (!DefineDataProperty(cx, result, property, value)) {
+ return nullptr;
+ }
+ } else {
+ // Step 6.b.iii. (Not applicable in our implementation.)
+ }
+
+ // Steps 6.c-d. (Not applicable in our implementation.)
+ }
+
+ // Step 7.
+ if (!any) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_MISSING_TEMPORAL_FIELDS);
+ return nullptr;
+ }
+
+ // Step 8.
+ return result;
+}
+
+/**
+ * Performs list-concatenation, removes any duplicates, and sorts the result.
+ */
+bool js::temporal::ConcatTemporalFieldNames(
+ const TemporalFieldNames& receiverFieldNames,
+ const TemporalFieldNames& inputFieldNames,
+ TemporalFieldNames& concatenatedFieldNames) {
+ MOZ_ASSERT(IsSorted(receiverFieldNames));
+ MOZ_ASSERT(IsSorted(inputFieldNames));
+ MOZ_ASSERT(concatenatedFieldNames.empty());
+
+ auto appendUnique = [&](auto key) {
+ if (concatenatedFieldNames.empty() ||
+ concatenatedFieldNames.back() != key) {
+ return concatenatedFieldNames.append(key);
+ }
+ return true;
+ };
+
+ size_t i = 0;
+ size_t j = 0;
+
+ // Append the names from |receiverFieldNames| and |inputFieldNames|.
+ while (i < receiverFieldNames.length() && j < inputFieldNames.length()) {
+ auto x = receiverFieldNames[i];
+ auto y = inputFieldNames[j];
+
+ PropertyKey z;
+ if (ComparePropertyKey(x, y) <= 0) {
+ z = x;
+ i++;
+ } else {
+ z = y;
+ j++;
+ }
+ if (!appendUnique(z)) {
+ return false;
+ }
+ }
+
+ // Append the remaining names from |receiverFieldNames|.
+ while (i < receiverFieldNames.length()) {
+ if (!appendUnique(receiverFieldNames[i++])) {
+ return false;
+ }
+ }
+
+ // Append the remaining names from |inputFieldNames|.
+ while (j < inputFieldNames.length()) {
+ if (!appendUnique(inputFieldNames[j++])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool js::temporal::AppendSorted(
+ JSContext* cx, TemporalFieldNames& fieldNames,
+ std::initializer_list<TemporalField> additionalNames) {
+ // |fieldNames| is sorted and doesn't include any duplicates
+ MOZ_ASSERT(IsSorted(fieldNames));
+ MOZ_ASSERT(std::adjacent_find(fieldNames.begin(), fieldNames.end()) ==
+ fieldNames.end());
+
+ // |additionalNames| is non-empty, sorted, and doesn't include any duplicates.
+ MOZ_ASSERT(additionalNames.size() > 0);
+ MOZ_ASSERT(IsSorted(additionalNames));
+ MOZ_ASSERT(
+ std::adjacent_find(additionalNames.begin(), additionalNames.end()) ==
+ additionalNames.end());
+
+ // Allocate space for entries from |additionalNames|.
+ if (!fieldNames.growBy(additionalNames.size())) {
+ return false;
+ }
+
+ auto* left = std::prev(fieldNames.end(), additionalNames.size());
+ auto* right = additionalNames.end();
+ auto* out = fieldNames.end();
+
+ // Write backwards into the newly allocated space.
+ while (left != fieldNames.begin() && right != additionalNames.begin()) {
+ MOZ_ASSERT(out != fieldNames.begin());
+ auto x = *std::prev(left);
+ auto y = NameToId(ToPropertyName(cx, *std::prev(right)));
+
+ int32_t r = ComparePropertyKey(x, y);
+
+ // Reject duplicates per PrepareTemporalFields, step 6.c.
+ if (r == 0) {
+ if (auto chars = QuoteString(cx, x)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DUPLICATE_PROPERTY,
+ chars.get());
+ }
+ return false;
+ }
+
+ // Insert the lexicographically greater key.
+ PropertyKey z;
+ if (r > 0) {
+ z = x;
+ left--;
+ } else {
+ z = y;
+ right--;
+ }
+ *--out = z;
+ }
+
+ // Avoid unnecessary copying if possible.
+ if (left == out) {
+ MOZ_ASSERT(right == additionalNames.begin());
+ return true;
+ }
+
+ // Prepend the remaining names from |fieldNames|.
+ while (left != fieldNames.begin()) {
+ MOZ_ASSERT(out != fieldNames.begin());
+ *--out = *--left;
+ }
+
+ // Prepend the remaining names from |additionalNames|.
+ while (right != additionalNames.begin()) {
+ MOZ_ASSERT(out != fieldNames.begin());
+ *--out = NameToId(ToPropertyName(cx, *--right));
+ }
+
+ // All field names were written into the result list.
+ MOZ_ASSERT(out == fieldNames.begin());
+
+ return true;
+}
+
+bool js::temporal::SortTemporalFieldNames(JSContext* cx,
+ TemporalFieldNames& fieldNames) {
+ // Create scratch space for MergeSort().
+ TemporalFieldNames scratch(cx);
+ if (!scratch.resize(fieldNames.length())) {
+ return false;
+ }
+
+ // Sort all field names in alphabetical order.
+ auto comparator = [](const auto& x, const auto& y, bool* lessOrEqual) {
+ *lessOrEqual = ComparePropertyKey(x, y) <= 0;
+ return true;
+ };
+ MOZ_ALWAYS_TRUE(MergeSort(fieldNames.begin(), fieldNames.length(),
+ scratch.begin(), comparator));
+
+ for (size_t i = 0; i < fieldNames.length(); i++) {
+ auto property = fieldNames[i];
+
+ // Reject "constructor" and "__proto__" per PrepareTemporalFields, step 6.a.
+ if (property == NameToId(cx->names().constructor) ||
+ property == NameToId(cx->names().proto_)) {
+ if (auto chars = QuoteString(cx, property)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_PROPERTY, chars.get());
+ }
+ return false;
+ }
+
+ // Reject duplicates per PrepareTemporalFields, step 6.c.
+ if (i > 0 && property == fieldNames[i - 1]) {
+ if (auto chars = QuoteString(cx, property)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_DUPLICATE_PROPERTY,
+ chars.get());
+ }
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/js/src/builtin/temporal/TemporalFields.h b/js/src/builtin/temporal/TemporalFields.h
new file mode 100644
index 0000000000..1af3631169
--- /dev/null
+++ b/js/src/builtin/temporal/TemporalFields.h
@@ -0,0 +1,192 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_TemporalFields_h
+#define builtin_temporal_TemporalFields_h
+
+#include "mozilla/FloatingPoint.h"
+
+#include <initializer_list>
+
+#include "jstypes.h"
+
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+
+class JS_PUBLIC_API JSTracer;
+
+namespace js {
+class PlainObject;
+}
+
+namespace js::temporal {
+enum class TemporalField {
+ Year,
+ Month,
+ MonthCode,
+ Day,
+ Hour,
+ Minute,
+ Second,
+ Millisecond,
+ Microsecond,
+ Nanosecond,
+ Offset,
+ Era,
+ EraYear,
+ TimeZone,
+};
+
+// Default values are specified in Table 15 [1]. `undefined` is replaced with
+// an appropriate value based on the type, for example `double` fields use
+// NaN whereas pointer fields use nullptr.
+//
+// [1] <https://tc39.es/proposal-temporal/#table-temporal-field-requirements>
+struct TemporalFields final {
+ double year = mozilla::UnspecifiedNaN<double>();
+ double month = mozilla::UnspecifiedNaN<double>();
+ JSString* monthCode = nullptr;
+ double day = mozilla::UnspecifiedNaN<double>();
+ double hour = 0;
+ double minute = 0;
+ double second = 0;
+ double millisecond = 0;
+ double microsecond = 0;
+ double nanosecond = 0;
+ JSString* offset = nullptr;
+ JSString* era = nullptr;
+ double eraYear = mozilla::UnspecifiedNaN<double>();
+ JS::Value timeZone = JS::UndefinedValue();
+
+ TemporalFields() = default;
+
+ void trace(JSTracer* trc);
+};
+} // namespace js::temporal
+
+namespace js {
+
+template <typename Wrapper>
+class WrappedPtrOperations<temporal::TemporalFields, Wrapper> {
+ const temporal::TemporalFields& fields() const {
+ return static_cast<const Wrapper*>(this)->get();
+ }
+
+ public:
+ double year() const { return fields().year; }
+ double month() const { return fields().month; }
+ double day() const { return fields().day; }
+ double hour() const { return fields().hour; }
+ double minute() const { return fields().minute; }
+ double second() const { return fields().second; }
+ double millisecond() const { return fields().millisecond; }
+ double microsecond() const { return fields().microsecond; }
+ double nanosecond() const { return fields().nanosecond; }
+ double eraYear() const { return fields().eraYear; }
+
+ JS::Handle<JSString*> monthCode() const {
+ return JS::Handle<JSString*>::fromMarkedLocation(&fields().monthCode);
+ }
+ JS::Handle<JSString*> offset() const {
+ return JS::Handle<JSString*>::fromMarkedLocation(&fields().offset);
+ }
+ JS::Handle<JSString*> era() const {
+ return JS::Handle<JSString*>::fromMarkedLocation(&fields().era);
+ }
+ JS::Handle<JS::Value> timeZone() const {
+ return JS::Handle<JS::Value>::fromMarkedLocation(&fields().timeZone);
+ }
+};
+
+template <typename Wrapper>
+class MutableWrappedPtrOperations<temporal::TemporalFields, Wrapper>
+ : public WrappedPtrOperations<temporal::TemporalFields, Wrapper> {
+ temporal::TemporalFields& fields() {
+ return static_cast<Wrapper*>(this)->get();
+ }
+
+ public:
+ double& year() { return fields().year; }
+ double& month() { return fields().month; }
+ double& day() { return fields().day; }
+ double& hour() { return fields().hour; }
+ double& minute() { return fields().minute; }
+ double& second() { return fields().second; }
+ double& millisecond() { return fields().millisecond; }
+ double& microsecond() { return fields().microsecond; }
+ double& nanosecond() { return fields().nanosecond; }
+ double& eraYear() { return fields().eraYear; }
+
+ JS::MutableHandle<JSString*> monthCode() {
+ return JS::MutableHandle<JSString*>::fromMarkedLocation(
+ &fields().monthCode);
+ }
+ JS::MutableHandle<JSString*> offset() {
+ return JS::MutableHandle<JSString*>::fromMarkedLocation(&fields().offset);
+ }
+ JS::MutableHandle<JSString*> era() {
+ return JS::MutableHandle<JSString*>::fromMarkedLocation(&fields().era);
+ }
+ JS::MutableHandle<JS::Value> timeZone() {
+ return JS::MutableHandle<JS::Value>::fromMarkedLocation(&fields().timeZone);
+ }
+};
+
+} // namespace js
+
+namespace js::temporal {
+
+/**
+ * PrepareTemporalFields ( fields, fieldNames, requiredFields [ ,
+ * duplicateBehaviour ] )
+ */
+bool PrepareTemporalFields(JSContext* cx, JS::Handle<JSObject*> fields,
+ std::initializer_list<TemporalField> fieldNames,
+ std::initializer_list<TemporalField> requiredFields,
+ JS::MutableHandle<TemporalFields> result);
+
+using TemporalFieldNames = JS::StackGCVector<JS::PropertyKey>;
+
+/**
+ * PrepareTemporalFields ( fields, fieldNames, requiredFields [ ,
+ * duplicateBehaviour ] )
+ */
+PlainObject* PrepareTemporalFields(JSContext* cx, JS::Handle<JSObject*> fields,
+ JS::Handle<TemporalFieldNames> fieldNames);
+
+/**
+ * PrepareTemporalFields ( fields, fieldNames, requiredFields [ ,
+ * duplicateBehaviour ] )
+ */
+PlainObject* PrepareTemporalFields(
+ JSContext* cx, JS::Handle<JSObject*> fields,
+ JS::Handle<TemporalFieldNames> fieldNames,
+ std::initializer_list<TemporalField> requiredFields);
+
+/**
+ * PrepareTemporalFields ( fields, fieldNames, requiredFields [ ,
+ * duplicateBehaviour ] )
+ */
+PlainObject* PreparePartialTemporalFields(
+ JSContext* cx, JS::Handle<JSObject*> fields,
+ JS::Handle<TemporalFieldNames> fieldNames);
+
+[[nodiscard]] bool ConcatTemporalFieldNames(
+ const TemporalFieldNames& receiverFieldNames,
+ const TemporalFieldNames& inputFieldNames,
+ TemporalFieldNames& concatenatedFieldNames);
+
+[[nodiscard]] bool AppendSorted(
+ JSContext* cx, TemporalFieldNames& fieldNames,
+ std::initializer_list<TemporalField> additionalNames);
+
+[[nodiscard]] bool SortTemporalFieldNames(JSContext* cx,
+ TemporalFieldNames& fieldNames);
+
+} /* namespace js::temporal */
+
+#endif /* builtin_temporal_TemporalFields_h */
diff --git a/js/src/builtin/temporal/TemporalNow.cpp b/js/src/builtin/temporal/TemporalNow.cpp
new file mode 100644
index 0000000000..adc84361af
--- /dev/null
+++ b/js/src/builtin/temporal/TemporalNow.cpp
@@ -0,0 +1,539 @@
+/* -*- 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/TemporalNow.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Result.h"
+
+#include <cstdlib>
+#include <stdint.h>
+#include <string_view>
+#include <utility>
+
+#include "jsdate.h"
+#include "jspubtd.h"
+#include "jstypes.h"
+#include "NamespaceImports.h"
+
+#include "builtin/intl/CommonFunctions.h"
+#include "builtin/intl/FormatBuffer.h"
+#include "builtin/temporal/Calendar.h"
+#include "builtin/temporal/Instant.h"
+#include "builtin/temporal/PlainDate.h"
+#include "builtin/temporal/PlainDateTime.h"
+#include "builtin/temporal/PlainTime.h"
+#include "builtin/temporal/TemporalParser.h"
+#include "builtin/temporal/TemporalTypes.h"
+#include "builtin/temporal/TimeZone.h"
+#include "builtin/temporal/ZonedDateTime.h"
+#include "gc/Barrier.h"
+#include "gc/GCEnum.h"
+#include "js/AllocPolicy.h"
+#include "js/CallArgs.h"
+#include "js/Class.h"
+#include "js/Date.h"
+#include "js/PropertyDescriptor.h"
+#include "js/PropertySpec.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "vm/DateTime.h"
+#include "vm/GlobalObject.h"
+#include "vm/JSAtomState.h"
+#include "vm/JSContext.h"
+#include "vm/Realm.h"
+#include "vm/StringType.h"
+
+#include "vm/JSObject-inl.h"
+
+using namespace js;
+using namespace js::temporal;
+
+static bool SystemTimeZoneOffset(JSContext* cx, int32_t* offset) {
+ auto rawOffset =
+ DateTimeInfo::getRawOffsetMs(DateTimeInfo::forceUTC(cx->realm()));
+ if (rawOffset.isErr()) {
+ intl::ReportInternalError(cx);
+ return false;
+ }
+
+ *offset = rawOffset.unwrap();
+ return true;
+}
+
+/**
+ * 6.4.3 DefaultTimeZone ()
+ *
+ * Returns the IANA time zone name for the host environment's current time zone.
+ *
+ * ES2017 Intl draft rev 4a23f407336d382ed5e3471200c690c9b020b5f3
+ */
+static JSString* SystemTimeZoneIdentifier(JSContext* cx) {
+ intl::FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> formatBuffer(cx);
+ auto result = DateTimeInfo::timeZoneId(DateTimeInfo::forceUTC(cx->realm()),
+ formatBuffer);
+ if (result.isErr()) {
+ intl::ReportInternalError(cx, result.unwrapErr());
+ return nullptr;
+ }
+
+ Rooted<JSString*> timeZone(cx, formatBuffer.toString(cx));
+ if (!timeZone) {
+ return nullptr;
+ }
+
+ Rooted<JSAtom*> validTimeZone(cx);
+ if (!IsValidTimeZoneName(cx, timeZone, &validTimeZone)) {
+ return nullptr;
+ }
+ if (validTimeZone) {
+ return CanonicalizeTimeZoneName(cx, validTimeZone);
+ }
+
+ // See DateTimeFormat.js for the JS implementation.
+ // TODO: Move the JS implementation into C++.
+
+ // Before defaulting to "UTC", try to represent the system time zone using
+ // the Etc/GMT + offset format. This format only accepts full hour offsets.
+ int32_t offset;
+ if (!SystemTimeZoneOffset(cx, &offset)) {
+ return nullptr;
+ }
+
+ constexpr int32_t msPerHour = 60 * 60 * 1000;
+ int32_t offsetHours = std::abs(offset / msPerHour);
+ int32_t offsetHoursFraction = offset % msPerHour;
+ if (offsetHoursFraction == 0 && offsetHours < 24) {
+ // Etc/GMT + offset uses POSIX-style signs, i.e. a positive offset
+ // means a location west of GMT.
+ constexpr std::string_view etcGMT = "Etc/GMT";
+
+ char offsetString[etcGMT.length() + 3];
+
+ size_t n = etcGMT.copy(offsetString, etcGMT.length());
+ offsetString[n++] = offset < 0 ? '+' : '-';
+ if (offsetHours >= 10) {
+ offsetString[n++] = '0' + (offsetHours / 10);
+ }
+ offsetString[n++] = '0' + (offsetHours % 10);
+
+ MOZ_ASSERT(n == etcGMT.length() + 2 || n == etcGMT.length() + 3);
+
+ timeZone = NewStringCopyN<CanGC>(cx, offsetString, n);
+ if (!timeZone) {
+ return nullptr;
+ }
+
+ // Check if the fallback is valid.
+ if (!IsValidTimeZoneName(cx, timeZone, &validTimeZone)) {
+ return nullptr;
+ }
+ if (validTimeZone) {
+ return CanonicalizeTimeZoneName(cx, validTimeZone);
+ }
+ }
+
+ // Fallback to "UTC" if everything else fails.
+ return cx->names().UTC;
+}
+
+static BuiltinTimeZoneObject* SystemTimeZoneObject(JSContext* cx) {
+ Rooted<JSString*> timeZoneIdentifier(cx, SystemTimeZoneIdentifier(cx));
+ if (!timeZoneIdentifier) {
+ return nullptr;
+ }
+
+ return CreateTemporalTimeZone(cx, timeZoneIdentifier);
+}
+
+/**
+ * SystemUTCEpochNanoseconds ( )
+ */
+static bool SystemUTCEpochNanoseconds(JSContext* cx, Instant* result) {
+ // Step 1.
+ JS::ClippedTime nowMillis = DateNow(cx);
+ MOZ_ASSERT(nowMillis.isValid());
+
+ // Step 2.
+ MOZ_ASSERT(nowMillis.toDouble() >= js::StartOfTime);
+ MOZ_ASSERT(nowMillis.toDouble() <= js::EndOfTime);
+
+ // Step 3.
+ *result = Instant::fromMilliseconds(int64_t(nowMillis.toDouble()));
+ return true;
+}
+
+/**
+ * SystemInstant ( )
+ */
+static bool SystemInstant(JSContext* cx, Instant* result) {
+ // Steps 1-2.
+ return SystemUTCEpochNanoseconds(cx, result);
+}
+
+/**
+ * SystemInstant ( )
+ */
+static InstantObject* SystemInstant(JSContext* cx) {
+ // Step 1.
+ Instant instant;
+ if (!SystemUTCEpochNanoseconds(cx, &instant)) {
+ return nullptr;
+ }
+
+ // Step 2.
+ return CreateTemporalInstant(cx, instant);
+}
+
+/**
+ * SystemDateTime ( temporalTimeZoneLike, calendarLike )
+ * SystemZonedDateTime ( temporalTimeZoneLike, calendarLike )
+ */
+static bool ToTemporalTimeZoneOrSystemTimeZone(
+ JSContext* cx, Handle<Value> temporalTimeZoneLike,
+ MutableHandle<TimeZoneValue> timeZone) {
+ // Step 1.
+ if (temporalTimeZoneLike.isUndefined()) {
+ auto* timeZoneObj = SystemTimeZoneObject(cx);
+ if (!timeZoneObj) {
+ return false;
+ }
+ timeZone.set(TimeZoneValue(timeZoneObj));
+ return true;
+ }
+
+ // Step 2.
+ return ToTemporalTimeZone(cx, temporalTimeZoneLike, timeZone);
+}
+
+/**
+ * SystemDateTime ( temporalTimeZoneLike, calendarLike )
+ */
+static bool SystemDateTime(JSContext* cx, Handle<TimeZoneValue> timeZone,
+ PlainDateTime* dateTime) {
+ // SystemDateTime, step 4.
+ Instant instant;
+ if (!SystemInstant(cx, &instant)) {
+ return false;
+ }
+
+ // SystemDateTime, steps 5-6.
+ return GetPlainDateTimeFor(cx, timeZone, instant, dateTime);
+}
+
+/**
+ * Temporal.Now.timeZoneId ( )
+ */
+static bool Temporal_Now_timeZoneId(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ auto* result = SystemTimeZoneIdentifier(cx);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setString(result);
+ return true;
+}
+
+/**
+ * Temporal.Now.instant ( )
+ */
+static bool Temporal_Now_instant(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ auto* result = SystemInstant(cx);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Now.plainDateTime ( calendar [ , temporalTimeZoneLike ] )
+ */
+static bool Temporal_Now_plainDateTime(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1. (Inlined call to SystemDateTime)
+
+ // SystemDateTime, steps 1-2.
+ Rooted<TimeZoneValue> timeZone(cx);
+ if (!ToTemporalTimeZoneOrSystemTimeZone(cx, args.get(1), &timeZone)) {
+ return false;
+ }
+
+ // SystemDateTime, step 3.
+ Rooted<CalendarValue> calendar(cx);
+ if (!ToTemporalCalendar(cx, args.get(0), &calendar)) {
+ return false;
+ }
+
+ // SystemDateTime, steps 4-5.
+ PlainDateTime dateTime;
+ if (!SystemDateTime(cx, timeZone, &dateTime)) {
+ return false;
+ }
+
+ auto* result = CreateTemporalDateTime(cx, dateTime, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Now.plainDateTimeISO ( [ temporalTimeZoneLike ] )
+ */
+static bool Temporal_Now_plainDateTimeISO(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1. (Inlined call to SystemDateTime)
+
+ // SystemDateTime, steps 1-2.
+ Rooted<TimeZoneValue> timeZone(cx);
+ if (!ToTemporalTimeZoneOrSystemTimeZone(cx, args.get(0), &timeZone)) {
+ return false;
+ }
+
+ // SystemDateTime, step 3.
+ Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
+
+ // SystemDateTime, steps 4-5.
+ PlainDateTime dateTime;
+ if (!SystemDateTime(cx, timeZone, &dateTime)) {
+ return false;
+ }
+
+ auto* result = CreateTemporalDateTime(cx, dateTime, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Now.zonedDateTime ( calendar [ , temporalTimeZoneLike ] )
+ */
+static bool Temporal_Now_zonedDateTime(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1. (Inlined call to SystemZonedDateTime)
+
+ // SystemZonedDateTime, steps 1-2.
+ Rooted<TimeZoneValue> timeZone(cx);
+ if (!ToTemporalTimeZoneOrSystemTimeZone(cx, args.get(1), &timeZone)) {
+ return false;
+ }
+
+ // SystemZonedDateTime, step 3.
+ Rooted<CalendarValue> calendar(cx);
+ if (!ToTemporalCalendar(cx, args.get(0), &calendar)) {
+ return false;
+ }
+
+ // SystemZonedDateTime, step 4.
+ Instant instant;
+ if (!SystemUTCEpochNanoseconds(cx, &instant)) {
+ return false;
+ }
+
+ // SystemZonedDateTime, step 5.
+ auto* result = CreateTemporalZonedDateTime(cx, instant, timeZone, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Now.zonedDateTimeISO ( [ temporalTimeZoneLike ] )
+ */
+static bool Temporal_Now_zonedDateTimeISO(JSContext* cx, unsigned argc,
+ Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1. (Inlined call to SystemZonedDateTime)
+
+ // SystemZonedDateTime, steps 1-2.
+ Rooted<TimeZoneValue> timeZone(cx);
+ if (!ToTemporalTimeZoneOrSystemTimeZone(cx, args.get(0), &timeZone)) {
+ return false;
+ }
+
+ // SystemZonedDateTime, step 3.
+ Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
+
+ // SystemZonedDateTime, step 4.
+ Instant instant;
+ if (!SystemUTCEpochNanoseconds(cx, &instant)) {
+ return false;
+ }
+
+ // SystemZonedDateTime, step 5.
+ auto* result = CreateTemporalZonedDateTime(cx, instant, timeZone, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Now.plainDate ( calendar [ , temporalTimeZoneLike ] )
+ */
+static bool Temporal_Now_plainDate(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1. (Inlined call to SystemDateTime)
+
+ // SystemDateTime, steps 1-2.
+ Rooted<TimeZoneValue> timeZone(cx);
+ if (!ToTemporalTimeZoneOrSystemTimeZone(cx, args.get(1), &timeZone)) {
+ return false;
+ }
+
+ // SystemDateTime, step 3.
+ Rooted<CalendarValue> calendar(cx);
+ if (!ToTemporalCalendar(cx, args.get(0), &calendar)) {
+ return false;
+ }
+
+ // SystemDateTime, steps 4-5.
+ PlainDateTime dateTime;
+ if (!SystemDateTime(cx, timeZone, &dateTime)) {
+ return false;
+ }
+
+ // Step 2.
+ auto* result = CreateTemporalDate(cx, dateTime.date, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Now.plainDateISO ( [ temporalTimeZoneLike ] )
+ */
+static bool Temporal_Now_plainDateISO(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1. (Inlined call to SystemDateTime)
+
+ // SystemDateTime, steps 1-2.
+ Rooted<TimeZoneValue> timeZone(cx);
+ if (!ToTemporalTimeZoneOrSystemTimeZone(cx, args.get(0), &timeZone)) {
+ return false;
+ }
+
+ // SystemDateTime, step 3.
+ Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
+
+ // SystemDateTime, steps 4-5.
+ PlainDateTime dateTime;
+ if (!SystemDateTime(cx, timeZone, &dateTime)) {
+ return false;
+ }
+
+ // Step 2.
+ auto* result = CreateTemporalDate(cx, dateTime.date, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.Now.plainTimeISO ( [ temporalTimeZoneLike ] )
+ */
+static bool Temporal_Now_plainTimeISO(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1. (Inlined call to SystemDateTime)
+
+ // SystemDateTime, steps 1-2.
+ Rooted<TimeZoneValue> timeZone(cx);
+ if (!ToTemporalTimeZoneOrSystemTimeZone(cx, args.get(0), &timeZone)) {
+ return false;
+ }
+
+ // SystemDateTime, step 3. (Not applicable)
+
+ // SystemDateTime, steps 4-5.
+ PlainDateTime dateTime;
+ if (!SystemDateTime(cx, timeZone, &dateTime)) {
+ return false;
+ }
+
+ // Step 2.
+ auto* result = CreateTemporalTime(cx, dateTime.time);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+const JSClass TemporalNowObject::class_ = {
+ "Temporal.Now",
+ JSCLASS_HAS_CACHED_PROTO(JSProto_TemporalNow),
+ JS_NULL_CLASS_OPS,
+ &TemporalNowObject::classSpec_,
+};
+
+static const JSFunctionSpec TemporalNow_methods[] = {
+ JS_FN("timeZoneId", Temporal_Now_timeZoneId, 0, 0),
+ JS_FN("instant", Temporal_Now_instant, 0, 0),
+ JS_FN("plainDateTime", Temporal_Now_plainDateTime, 1, 0),
+ JS_FN("plainDateTimeISO", Temporal_Now_plainDateTimeISO, 0, 0),
+ JS_FN("zonedDateTime", Temporal_Now_zonedDateTime, 1, 0),
+ JS_FN("zonedDateTimeISO", Temporal_Now_zonedDateTimeISO, 0, 0),
+ JS_FN("plainDate", Temporal_Now_plainDate, 1, 0),
+ JS_FN("plainDateISO", Temporal_Now_plainDateISO, 0, 0),
+ JS_FN("plainTimeISO", Temporal_Now_plainTimeISO, 0, 0),
+ JS_FS_END,
+};
+
+static const JSPropertySpec TemporalNow_properties[] = {
+ JS_STRING_SYM_PS(toStringTag, "Temporal.Now", JSPROP_READONLY),
+ JS_PS_END,
+};
+
+static JSObject* CreateTemporalNowObject(JSContext* cx, JSProtoKey key) {
+ Rooted<JSObject*> proto(cx, &cx->global()->getObjectPrototype());
+ return NewTenuredObjectWithGivenProto(cx, &TemporalNowObject::class_, proto);
+}
+
+const ClassSpec TemporalNowObject::classSpec_ = {
+ CreateTemporalNowObject,
+ nullptr,
+ TemporalNow_methods,
+ TemporalNow_properties,
+ nullptr,
+ nullptr,
+ nullptr,
+ ClassSpec::DontDefineConstructor,
+};
diff --git a/js/src/builtin/temporal/TemporalNow.h b/js/src/builtin/temporal/TemporalNow.h
new file mode 100644
index 0000000000..52ff68b8ce
--- /dev/null
+++ b/js/src/builtin/temporal/TemporalNow.h
@@ -0,0 +1,30 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_TemporalNow_h
+#define builtin_temporal_TemporalNow_h
+
+#include "vm/NativeObject.h"
+
+struct JSClass;
+
+namespace js {
+struct ClassSpec;
+}
+
+namespace js::temporal {
+
+class TemporalNowObject : public NativeObject {
+ public:
+ static const JSClass class_;
+
+ private:
+ static const ClassSpec classSpec_;
+};
+
+} /* namespace js::temporal */
+
+#endif /* builtin_temporal_TemporalNow_h */
diff --git a/js/src/builtin/temporal/TemporalParser.cpp b/js/src/builtin/temporal/TemporalParser.cpp
new file mode 100644
index 0000000000..c117e63b31
--- /dev/null
+++ b/js/src/builtin/temporal/TemporalParser.cpp
@@ -0,0 +1,3464 @@
+/* -*- 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/TemporalParser.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Range.h"
+#include "mozilla/Result.h"
+#include "mozilla/Span.h"
+#include "mozilla/TextUtils.h"
+
+#include <algorithm>
+#include <cstdlib>
+#include <initializer_list>
+#include <iterator>
+#include <limits>
+#include <stdint.h>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+
+#include "jsnum.h"
+#include "NamespaceImports.h"
+
+#include "builtin/temporal/Duration.h"
+#include "builtin/temporal/PlainDate.h"
+#include "builtin/temporal/PlainTime.h"
+#include "builtin/temporal/TemporalTypes.h"
+#include "builtin/temporal/TemporalUnit.h"
+#include "gc/Barrier.h"
+#include "gc/Tracer.h"
+#include "js/ErrorReport.h"
+#include "js/friend/ErrorMessages.h"
+#include "js/GCAPI.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "util/Text.h"
+#include "vm/JSAtomState.h"
+#include "vm/JSContext.h"
+#include "vm/StringType.h"
+
+using namespace js;
+using namespace js::temporal;
+
+// TODO: Better error message for empty strings?
+// TODO: Add string input to error message?
+// TODO: Better error messages, for example display current character?
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1839676
+
+struct StringName final {
+ // Start position and length of this name.
+ size_t start = 0;
+ size_t length = 0;
+
+ bool present() const { return length > 0; }
+};
+
+static JSLinearString* ToString(JSContext* cx, JSString* string,
+ const StringName& name) {
+ MOZ_ASSERT(name.present());
+ return NewDependentString(cx, string, name.start, name.length);
+}
+
+template <typename CharT>
+bool EqualCharIgnoreCaseAscii(CharT c1, char c2) {
+ if constexpr (sizeof(CharT) > sizeof(char)) {
+ if (!mozilla::IsAscii(c1)) {
+ return false;
+ }
+ }
+
+ static constexpr auto toLower = 0x20;
+ static_assert('a' - 'A' == toLower);
+
+ // Convert both characters to lower case before the comparison.
+ char c = c1;
+ if (mozilla::IsAsciiUppercaseAlpha(c1)) {
+ c = c + toLower;
+ }
+ char d = c2;
+ if (mozilla::IsAsciiUppercaseAlpha(c2)) {
+ d = d + toLower;
+ }
+ return c == d;
+}
+
+using CalendarName = StringName;
+using AnnotationKey = StringName;
+using AnnotationValue = StringName;
+using TimeZoneName = StringName;
+
+struct Annotation final {
+ AnnotationKey key;
+ AnnotationValue value;
+ bool critical = false;
+};
+
+struct TimeSpec final {
+ PlainTime time;
+};
+
+struct TimeZoneUTCOffset final {
+ // ยฑ1 for time zones with an offset, otherwise 0.
+ int32_t sign = 0;
+
+ // An integer in the range [0, 23].
+ int32_t hour = 0;
+
+ // An integer in the range [0, 59].
+ int32_t minute = 0;
+};
+
+struct DateTimeUTCOffset final {
+ // ยฑ1 for time zones with an offset, otherwise 0.
+ int32_t sign = 0;
+
+ // An integer in the range [0, 23].
+ int32_t hour = 0;
+
+ // An integer in the range [0, 59].
+ int32_t minute = 0;
+
+ // An integer in the range [0, 59].
+ int32_t second = 0;
+
+ // An integer in the range [0, 999'999].
+ int32_t fractionalPart = 0;
+
+ // Time zone with sub-minute precision.
+ bool subMinutePrecision = false;
+
+ // Convert to a TimeZoneUTCOffset.
+ TimeZoneUTCOffset toTimeZoneUTCOffset() const {
+ MOZ_ASSERT(!subMinutePrecision, "unexpected sub-minute precision");
+ return {sign, hour, minute};
+ }
+};
+
+/**
+ * ParseDateTimeUTCOffset ( offsetString )
+ */
+static int64_t ParseDateTimeUTCOffset(const DateTimeUTCOffset& offset) {
+ constexpr int64_t nanoPerSec = 1'000'000'000;
+
+ MOZ_ASSERT(offset.sign == -1 || offset.sign == +1);
+ MOZ_ASSERT(0 <= offset.hour && offset.hour < 24);
+ MOZ_ASSERT(0 <= offset.minute && offset.minute < 60);
+ MOZ_ASSERT(0 <= offset.second && offset.second < 60);
+ MOZ_ASSERT(0 <= offset.fractionalPart && offset.fractionalPart < nanoPerSec);
+
+ // sign ร— (((hours ร— 60 + minutes) ร— 60 + seconds) ร— 10^9 + nanoseconds).
+ int64_t seconds = (offset.hour * 60 + offset.minute) * 60 + offset.second;
+ int64_t nanos = (seconds * nanoPerSec) + offset.fractionalPart;
+ int64_t result = offset.sign * nanos;
+
+ MOZ_ASSERT(std::abs(result) < ToNanoseconds(TemporalUnit::Day),
+ "time zone offset is less than 24:00 hours");
+
+ return result;
+}
+
+static int32_t ParseTimeZoneOffset(const TimeZoneUTCOffset& offset) {
+ MOZ_ASSERT(offset.sign == -1 || offset.sign == +1);
+ MOZ_ASSERT(0 <= offset.hour && offset.hour < 24);
+ MOZ_ASSERT(0 <= offset.minute && offset.minute < 60);
+
+ // sign ร— (hour ร— 60 + minute).
+ int32_t result = offset.sign * (offset.hour * 60 + offset.minute);
+
+ MOZ_ASSERT(std::abs(result) < UnitsPerDay(TemporalUnit::Minute),
+ "time zone offset is less than 24:00 hours");
+
+ return result;
+}
+
+/**
+ * Struct to hold time zone annotations.
+ */
+struct TimeZoneAnnotation final {
+ // Time zone offset.
+ TimeZoneUTCOffset offset;
+
+ // Time zone name.
+ TimeZoneName name;
+
+ /**
+ * Returns true iff the time zone has an offset part, e.g. "+01:00".
+ */
+ bool hasOffset() const { return offset.sign != 0; }
+
+ /**
+ * Returns true iff the time zone has an IANA name, e.g. "Asia/Tokyo".
+ */
+ bool hasName() const { return name.present(); }
+};
+
+/**
+ * Struct to hold any time zone parts of a parsed string.
+ */
+struct TimeZoneString final {
+ // Date-time UTC offset.
+ DateTimeUTCOffset offset;
+
+ // Time zone annotation;
+ TimeZoneAnnotation annotation;
+
+ // UTC time zone.
+ bool utc = false;
+
+ static auto from(DateTimeUTCOffset offset) {
+ TimeZoneString timeZone{};
+ timeZone.offset = offset;
+ return timeZone;
+ }
+
+ static auto from(TimeZoneUTCOffset offset) {
+ TimeZoneString timeZone{};
+ timeZone.annotation.offset = offset;
+ return timeZone;
+ }
+
+ static auto from(TimeZoneName name) {
+ TimeZoneString timeZone{};
+ timeZone.annotation.name = name;
+ return timeZone;
+ }
+
+ static auto UTC() {
+ TimeZoneString timeZone{};
+ timeZone.utc = true;
+ return timeZone;
+ }
+
+ /**
+ * Returns true iff the time zone has an offset part, e.g. "+01:00".
+ */
+ bool hasOffset() const { return offset.sign != 0; }
+
+ /**
+ * Returns true iff the time zone has an annotation.
+ */
+ bool hasAnnotation() const {
+ return annotation.hasName() || annotation.hasOffset();
+ }
+
+ /**
+ * Returns true iff the time zone uses the "Z" abbrevation to denote UTC time.
+ */
+ bool isUTC() const { return utc; }
+};
+
+/**
+ * Struct to hold the parsed date, time, time zone, and calendar components.
+ */
+struct ZonedDateTimeString final {
+ PlainDate date;
+ PlainTime time;
+ TimeZoneString timeZone;
+ CalendarName calendar;
+};
+
+template <typename CharT>
+static bool IsISO8601Calendar(mozilla::Span<const CharT> calendar) {
+ static constexpr std::string_view iso8601 = "iso8601";
+
+ if (calendar.size() != iso8601.length()) {
+ return false;
+ }
+
+ for (size_t i = 0; i < iso8601.length(); i++) {
+ if (!EqualCharIgnoreCaseAscii(calendar[i], iso8601[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static constexpr int32_t AbsentYear = INT32_MAX;
+
+/**
+ * ParseISODateTime ( isoString )
+ */
+static bool ParseISODateTime(JSContext* cx, const ZonedDateTimeString& parsed,
+ PlainDateTime* result) {
+ // Steps 1-6, 8, 10-13 (Not applicable here).
+
+ PlainDateTime dateTime = {parsed.date, parsed.time};
+
+ // NOTE: ToIntegerOrInfinity("") is 0.
+ if (dateTime.date.year == AbsentYear) {
+ dateTime.date.year = 0;
+ }
+
+ // Step 7.
+ if (dateTime.date.month == 0) {
+ dateTime.date.month = 1;
+ }
+
+ // Step 9.
+ if (dateTime.date.day == 0) {
+ dateTime.date.day = 1;
+ }
+
+ // Step 14.
+ if (dateTime.time.second == 60) {
+ dateTime.time.second = 59;
+ }
+
+ // ParseISODateTime, steps 15-16 (Not applicable in our implementation).
+
+ // Call ThrowIfInvalidISODate to report an error if |days| exceeds the number
+ // of days in the month. All other values are already in-bounds.
+ MOZ_ASSERT(std::abs(dateTime.date.year) <= 999'999);
+ MOZ_ASSERT(1 <= dateTime.date.month && dateTime.date.month <= 12);
+ MOZ_ASSERT(1 <= dateTime.date.day && dateTime.date.day <= 31);
+
+ // ParseISODateTime, step 17.
+ if (!ThrowIfInvalidISODate(cx, dateTime.date)) {
+ return false;
+ }
+
+ // ParseISODateTime, step 18.
+ MOZ_ASSERT(IsValidTime(dateTime.time));
+
+ // Steps 19-25. (Handled in caller.)
+
+ *result = dateTime;
+ return true;
+}
+
+static bool ParseTimeZoneAnnotation(JSContext* cx,
+ const TimeZoneAnnotation& annotation,
+ JSLinearString* linear,
+ MutableHandle<ParsedTimeZone> result) {
+ MOZ_ASSERT(annotation.hasOffset() || annotation.hasName());
+
+ if (annotation.hasOffset()) {
+ int32_t offset = ParseTimeZoneOffset(annotation.offset);
+ result.set(ParsedTimeZone::fromOffset(offset));
+ return true;
+ }
+
+ auto* str = ToString(cx, linear, annotation.name);
+ if (!str) {
+ return false;
+ }
+ result.set(ParsedTimeZone::fromName(str));
+ return true;
+}
+
+/**
+ * Struct for the parsed duration components.
+ */
+struct TemporalDurationString final {
+ // A non-negative integer or +Infinity.
+ double years = 0;
+
+ // A non-negative integer or +Infinity.
+ double months = 0;
+
+ // A non-negative integer or +Infinity.
+ double weeks = 0;
+
+ // A non-negative integer or +Infinity.
+ double days = 0;
+
+ // A non-negative integer or +Infinity.
+ double hours = 0;
+
+ // A non-negative integer or +Infinity.
+ double minutes = 0;
+
+ // A non-negative integer or +Infinity.
+ double seconds = 0;
+
+ // An integer in the range [0, 999'999].
+ int32_t hoursFraction = 0;
+
+ // An integer in the range [0, 999'999].
+ int32_t minutesFraction = 0;
+
+ // An integer in the range [0, 999'999].
+ int32_t secondsFraction = 0;
+
+ // ยฑ1 when an offset is present, otherwise 0.
+ int32_t sign = 0;
+};
+
+class ParserError final {
+ JSErrNum error_ = JSMSG_NOT_AN_ERROR;
+
+ public:
+ constexpr MOZ_IMPLICIT ParserError(JSErrNum error) : error_(error) {}
+
+ constexpr JSErrNum error() const { return error_; }
+
+ constexpr operator JSErrNum() const { return error(); }
+};
+
+namespace mozilla::detail {
+// Zero is used for tagging, so it mustn't be an error.
+static_assert(static_cast<JSErrNum>(0) == JSMSG_NOT_AN_ERROR);
+
+// Ensure efficient packing of the error type.
+template <>
+struct UnusedZero<::ParserError> {
+ private:
+ using Error = ::ParserError;
+ using ErrorKind = JSErrNum;
+
+ public:
+ using StorageType = std::underlying_type_t<ErrorKind>;
+
+ static constexpr bool value = true;
+ static constexpr StorageType nullValue = 0;
+
+ static constexpr Error Inspect(const StorageType& aValue) {
+ return Error(static_cast<ErrorKind>(aValue));
+ }
+ static constexpr Error Unwrap(StorageType aValue) {
+ return Error(static_cast<ErrorKind>(aValue));
+ }
+ static constexpr StorageType Store(Error aValue) {
+ return static_cast<StorageType>(aValue.error());
+ }
+};
+} // namespace mozilla::detail
+
+static_assert(mozilla::Result<ZonedDateTimeString, ParserError>::Strategy !=
+ mozilla::detail::PackingStrategy::Variant);
+
+template <typename CharT>
+class StringReader final {
+ mozilla::Span<const CharT> string_;
+
+ // Current position in the string.
+ size_t index_ = 0;
+
+ public:
+ explicit StringReader(mozilla::Span<const CharT> string) : string_(string) {}
+
+ /**
+ * Returns the input string.
+ */
+ mozilla::Span<const CharT> string() const { return string_; }
+
+ /**
+ * Returns a substring of the input string.
+ */
+ mozilla::Span<const CharT> substring(const StringName& name) const {
+ MOZ_ASSERT(name.present());
+ return string_.Subspan(name.start, name.length);
+ }
+
+ /**
+ * Returns the current parse position.
+ */
+ size_t index() const { return index_; }
+
+ /**
+ * Returns the length of the input string-
+ */
+ size_t length() const { return string_.size(); }
+
+ /**
+ * Returns true iff the whole string has been parsed.
+ */
+ bool atEnd() const { return index() == length(); }
+
+ /**
+ * Reset the parser to a previous parse position.
+ */
+ void reset(size_t index = 0) {
+ MOZ_ASSERT(index <= length());
+ index_ = index;
+ }
+
+ /**
+ * Returns true if at least `amount` characters can be read from the current
+ * parse position.
+ */
+ bool hasMore(size_t amount) const { return index() + amount <= length(); }
+
+ /**
+ * Advances the parse position by `amount` characters.
+ */
+ void advance(size_t amount) {
+ MOZ_ASSERT(hasMore(amount));
+ index_ += amount;
+ }
+
+ /**
+ * Returns the character at the current parse position.
+ */
+ CharT current() const { return string()[index()]; }
+
+ /**
+ * Returns the character at the next parse position.
+ */
+ CharT next() const { return string()[index() + 1]; }
+
+ /**
+ * Returns the character at position `index`.
+ */
+ CharT at(size_t index) const { return string()[index]; }
+};
+
+template <typename CharT>
+class TemporalParser final {
+ StringReader<CharT> reader_;
+
+ /**
+ * Read an unlimited amount of decimal digits, returning `Nothing` if no
+ * digits were read.
+ */
+ mozilla::Maybe<double> digits(JSContext* cx);
+
+ /**
+ * Read exactly `length` digits, returning `Nothing` on failure.
+ */
+ mozilla::Maybe<int32_t> digits(size_t length) {
+ MOZ_ASSERT(length > 0, "can't read zero digits");
+ MOZ_ASSERT(length <= std::numeric_limits<int32_t>::digits10,
+ "can't read more than digits10 digits without overflow");
+
+ if (!reader_.hasMore(length)) {
+ return mozilla::Nothing();
+ }
+ int32_t num = 0;
+ size_t index = reader_.index();
+ for (size_t i = 0; i < length; i++) {
+ auto ch = reader_.at(index + i);
+ if (!mozilla::IsAsciiDigit(ch)) {
+ return mozilla::Nothing();
+ }
+ num = num * 10 + AsciiDigitToNumber(ch);
+ }
+ reader_.advance(length);
+ return mozilla::Some(num);
+ }
+
+ // TimeFractionalPart :
+ // Digit{1, 9}
+ //
+ // Fraction :
+ // DecimalSeparator TimeFractionalPart
+ mozilla::Maybe<int32_t> fraction() {
+ if (!reader_.hasMore(2)) {
+ return mozilla::Nothing();
+ }
+ if (!hasDecimalSeparator() || !mozilla::IsAsciiDigit(reader_.next())) {
+ return mozilla::Nothing();
+ }
+
+ // Consume the decimal separator.
+ MOZ_ALWAYS_TRUE(decimalSeparator());
+
+ // Maximal nine fractional digits are supported.
+ constexpr size_t maxFractions = 9;
+
+ // Read up to |maxFractions| digits.
+ int32_t num = 0;
+ size_t index = reader_.index();
+ size_t i = 0;
+ for (; i < std::min(reader_.length() - index, maxFractions); i++) {
+ CharT ch = reader_.at(index + i);
+ if (!mozilla::IsAsciiDigit(ch)) {
+ break;
+ }
+ num = num * 10 + AsciiDigitToNumber(ch);
+ }
+
+ // Skip past the read digits.
+ reader_.advance(i);
+
+ // Normalize the fraction to |maxFractions| digits.
+ for (; i < maxFractions; i++) {
+ num *= 10;
+ }
+ return mozilla::Some(num);
+ }
+
+ /**
+ * Returns true iff the current character is `ch`.
+ */
+ bool hasCharacter(CharT ch) const {
+ return reader_.hasMore(1) && reader_.current() == ch;
+ }
+
+ /**
+ * Consumes the current character if it's equal to `ch` and then returns
+ * `true`. Otherwise returns `false`.
+ */
+ bool character(CharT ch) {
+ if (!hasCharacter(ch)) {
+ return false;
+ }
+ reader_.advance(1);
+ return true;
+ }
+
+ /**
+ * Consumes the next characters if they're equal to `str` and then returns
+ * `true`. Otherwise returns `false`.
+ */
+ template <size_t N>
+ bool string(const char (&str)[N]) {
+ static_assert(N > 2, "use character() for one element strings");
+
+ if (!reader_.hasMore(N - 1)) {
+ return false;
+ }
+ size_t index = reader_.index();
+ for (size_t i = 0; i < N - 1; i++) {
+ if (reader_.at(index + i) != str[i]) {
+ return false;
+ }
+ }
+ reader_.advance(N - 1);
+ return true;
+ }
+
+ /**
+ * Returns true if the next two characters are ASCII alphabetic characters.
+ */
+ bool hasTwoAsciiAlpha() {
+ if (!reader_.hasMore(2)) {
+ return false;
+ }
+ size_t index = reader_.index();
+ return mozilla::IsAsciiAlpha(reader_.at(index)) &&
+ mozilla::IsAsciiAlpha(reader_.at(index + 1));
+ }
+
+ /**
+ * Returns true iff the current character is one of `chars`.
+ */
+ bool hasOneOf(std::initializer_list<char16_t> chars) const {
+ if (!reader_.hasMore(1)) {
+ return false;
+ }
+ auto ch = reader_.current();
+ return std::find(chars.begin(), chars.end(), ch) != chars.end();
+ }
+
+ /**
+ * Consumes the current character if it's in `chars` and then returns `true`.
+ * Otherwise returns `false`.
+ */
+ bool oneOf(std::initializer_list<char16_t> chars) {
+ if (!hasOneOf(chars)) {
+ return false;
+ }
+ reader_.advance(1);
+ return true;
+ }
+
+ /**
+ * Consumes the current character if it matches the predicate and then returns
+ * `true`. Otherwise returns `false`.
+ */
+ template <typename Predicate>
+ bool matches(Predicate&& predicate) {
+ if (!reader_.hasMore(1)) {
+ return false;
+ }
+
+ CharT ch = reader_.current();
+ if (!predicate(ch)) {
+ return false;
+ }
+
+ reader_.advance(1);
+ return true;
+ }
+
+ // Sign :
+ // ASCIISign
+ // U+2212
+ //
+ // ASCIISign : one of
+ // + -
+ bool hasSign() const { return hasOneOf({'+', '-', 0x2212}); }
+
+ /**
+ * Consumes the current character, which must be a sign character, and returns
+ * its numeric value.
+ */
+ int32_t sign() {
+ MOZ_ASSERT(hasSign());
+ int32_t plus = hasCharacter('+');
+ reader_.advance(1);
+ return plus ? 1 : -1;
+ }
+
+ // DecimalSeparator : one of
+ // . ,
+ bool hasDecimalSeparator() const { return hasOneOf({'.', ','}); }
+
+ bool decimalSeparator() { return oneOf({'.', ','}); }
+
+ // DaysDesignator : one of
+ // D d
+ bool daysDesignator() { return oneOf({'D', 'd'}); }
+
+ // HoursDesignator : one of
+ // H h
+ bool hoursDesignator() { return oneOf({'H', 'h'}); }
+
+ // MinutesDesignator : one of
+ // M m
+ bool minutesDesignator() { return oneOf({'M', 'm'}); }
+
+ // MonthsDesignator : one of
+ // M m
+ bool monthsDesignator() { return oneOf({'M', 'm'}); }
+
+ // DurationDesignator : one of
+ // P p
+ bool durationDesignator() { return oneOf({'P', 'p'}); }
+
+ // SecondsDesignator : one of
+ // S s
+ bool secondsDesignator() { return oneOf({'S', 's'}); }
+
+ // DateTimeSeparator :
+ // <SP>
+ // T
+ // t
+ bool dateTimeSeparator() { return oneOf({' ', 'T', 't'}); }
+
+ // TimeDesignator : one of
+ // T t
+ bool hasTimeDesignator() const { return hasOneOf({'T', 't'}); }
+
+ bool timeDesignator() { return oneOf({'T', 't'}); }
+
+ // WeeksDesignator : one of
+ // W w
+ bool weeksDesignator() { return oneOf({'W', 'w'}); }
+
+ // YearsDesignator : one of
+ // Y y
+ bool yearsDesignator() { return oneOf({'Y', 'y'}); }
+
+ // UTCDesignator : one of
+ // Z z
+ bool utcDesignator() { return oneOf({'Z', 'z'}); }
+
+ // TZLeadingChar :
+ // Alpha
+ // .
+ // _
+ bool tzLeadingChar() {
+ return matches([](auto ch) {
+ return mozilla::IsAsciiAlpha(ch) || ch == '.' || ch == '_';
+ });
+ }
+
+ // TZChar :
+ // TZLeadingChar
+ // DecimalDigit
+ // -
+ // +
+ bool tzChar() {
+ return matches([](auto ch) {
+ return mozilla::IsAsciiAlphanumeric(ch) || ch == '.' || ch == '_' ||
+ ch == '-' || ch == '+';
+ });
+ }
+
+ // AnnotationCriticalFlag :
+ // !
+ bool annotationCriticalFlag() { return character('!'); }
+
+ // AKeyLeadingChar :
+ // LowercaseAlpha
+ // _
+ bool aKeyLeadingChar() {
+ return matches([](auto ch) {
+ return mozilla::IsAsciiLowercaseAlpha(ch) || ch == '_';
+ });
+ }
+
+ // AKeyChar :
+ // AKeyLeadingChar
+ // DecimalDigit
+ // -
+ bool aKeyChar() {
+ return matches([](auto ch) {
+ return mozilla::IsAsciiLowercaseAlpha(ch) || mozilla::IsAsciiDigit(ch) ||
+ ch == '-' || ch == '_';
+ });
+ }
+
+ // AnnotationValueComponent :
+ // Alpha AnnotationValueComponent?
+ // DecimalDigit AnnotationValueComponent?
+ bool annotationValueComponent() {
+ size_t index = reader_.index();
+ size_t i = 0;
+ for (; index + i < reader_.length(); i++) {
+ auto ch = reader_.at(index + i);
+ if (!mozilla::IsAsciiAlphanumeric(ch)) {
+ break;
+ }
+ }
+ if (i == 0) {
+ return false;
+ }
+ reader_.advance(i);
+ return true;
+ }
+
+ template <typename T>
+ static constexpr bool inBounds(const T& x, const T& min, const T& max) {
+ return min <= x && x <= max;
+ }
+
+ mozilla::Result<ZonedDateTimeString, ParserError> dateTime();
+
+ mozilla::Result<PlainDate, ParserError> date();
+
+ mozilla::Result<PlainDate, ParserError> dateSpecYearMonth();
+
+ mozilla::Result<PlainDate, ParserError> dateSpecMonthDay();
+
+ mozilla::Result<PlainDate, ParserError> validMonthDay();
+
+ mozilla::Result<PlainTime, ParserError> timeSpec();
+
+ // Return true when |Annotation| can start at the current position.
+ bool hasAnnotationStart() const { return hasCharacter('['); }
+
+ // Return true when |TimeZoneAnnotation| can start at the current position.
+ bool hasTimeZoneAnnotationStart() const {
+ if (!hasCharacter('[')) {
+ return false;
+ }
+
+ // Ensure no '=' is found before the closing ']', otherwise the opening '['
+ // may actually start an |Annotation| instead of a |TimeZoneAnnotation|.
+ for (size_t i = reader_.index() + 1; i < reader_.length(); i++) {
+ CharT ch = reader_.at(i);
+ if (ch == '=') {
+ return false;
+ }
+ if (ch == ']') {
+ break;
+ }
+ }
+ return true;
+ }
+
+ // Return true when |DateTimeUTCOffset| can start at the current position.
+ bool hasDateTimeUTCOffsetStart() {
+ return hasOneOf({'Z', 'z', '+', '-', 0x2212});
+ }
+
+ mozilla::Result<TimeZoneString, ParserError> dateTimeUTCOffset();
+
+ mozilla::Result<DateTimeUTCOffset, ParserError> utcOffsetSubMinutePrecision();
+
+ mozilla::Result<TimeZoneUTCOffset, ParserError> timeZoneUTCOffsetName();
+
+ mozilla::Result<TimeZoneAnnotation, ParserError> timeZoneIdentifier();
+
+ mozilla::Result<TimeZoneAnnotation, ParserError> timeZoneAnnotation();
+
+ mozilla::Result<TimeZoneName, ParserError> timeZoneIANAName();
+
+ mozilla::Result<AnnotationKey, ParserError> annotationKey();
+ mozilla::Result<AnnotationValue, ParserError> annotationValue();
+ mozilla::Result<Annotation, ParserError> annotation();
+ mozilla::Result<CalendarName, ParserError> annotations();
+
+ mozilla::Result<ZonedDateTimeString, ParserError> annotatedTime();
+
+ mozilla::Result<ZonedDateTimeString, ParserError> annotatedDateTime();
+
+ mozilla::Result<ZonedDateTimeString, ParserError>
+ annotatedDateTimeTimeRequired();
+
+ mozilla::Result<ZonedDateTimeString, ParserError> annotatedYearMonth();
+
+ mozilla::Result<ZonedDateTimeString, ParserError> annotatedMonthDay();
+
+ public:
+ explicit TemporalParser(mozilla::Span<const CharT> str) : reader_(str) {}
+
+ mozilla::Result<ZonedDateTimeString, ParserError>
+ parseTemporalInstantString();
+
+ mozilla::Result<ZonedDateTimeString, ParserError>
+ parseTemporalTimeZoneString();
+
+ mozilla::Result<TimeZoneAnnotation, ParserError> parseTimeZoneIdentifier();
+
+ mozilla::Result<TimeZoneUTCOffset, ParserError> parseTimeZoneOffsetString();
+
+ mozilla::Result<DateTimeUTCOffset, ParserError> parseDateTimeUTCOffset();
+
+ mozilla::Result<TemporalDurationString, ParserError>
+ parseTemporalDurationString(JSContext* cx);
+
+ mozilla::Result<ZonedDateTimeString, ParserError>
+ parseTemporalCalendarString();
+
+ mozilla::Result<ZonedDateTimeString, ParserError> parseTemporalTimeString();
+
+ mozilla::Result<ZonedDateTimeString, ParserError>
+ parseTemporalMonthDayString();
+
+ mozilla::Result<ZonedDateTimeString, ParserError>
+ parseTemporalYearMonthString();
+
+ mozilla::Result<ZonedDateTimeString, ParserError>
+ parseTemporalDateTimeString();
+
+ mozilla::Result<ZonedDateTimeString, ParserError>
+ parseTemporalZonedDateTimeString();
+};
+
+template <typename CharT>
+mozilla::Result<ZonedDateTimeString, ParserError>
+TemporalParser<CharT>::dateTime() {
+ // DateTime :
+ // Date
+ // Date DateTimeSeparator TimeSpec DateTimeUTCOffset?
+ ZonedDateTimeString result = {};
+
+ auto dt = date();
+ if (dt.isErr()) {
+ return dt.propagateErr();
+ }
+ result.date = dt.unwrap();
+
+ if (dateTimeSeparator()) {
+ auto time = timeSpec();
+ if (time.isErr()) {
+ return time.propagateErr();
+ }
+ result.time = time.unwrap();
+
+ if (hasDateTimeUTCOffsetStart()) {
+ auto tz = dateTimeUTCOffset();
+ if (tz.isErr()) {
+ return tz.propagateErr();
+ }
+ result.timeZone = tz.unwrap();
+ }
+ }
+
+ return result;
+}
+
+template <typename CharT>
+mozilla::Result<PlainDate, ParserError> TemporalParser<CharT>::date() {
+ // Date :
+ // DateYear - DateMonth - DateDay
+ // DateYear DateMonth DateDay
+ PlainDate result = {};
+
+ // DateYear :
+ // DecimalDigit{4}
+ // Sign DecimalDigit{6}
+ if (auto year = digits(4)) {
+ result.year = year.value();
+ } else if (hasSign()) {
+ int32_t yearSign = sign();
+ if (auto year = digits(6)) {
+ result.year = yearSign * year.value();
+ if (yearSign < 0 && result.year == 0) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_NEGATIVE_ZERO_YEAR);
+ }
+ } else {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_EXTENDED_YEAR);
+ }
+ } else {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_YEAR);
+ }
+
+ // Optional: -
+ character('-');
+
+ // DateMonth :
+ // 0 NonzeroDigit
+ // 10
+ // 11
+ // 12
+ if (auto month = digits(2)) {
+ result.month = month.value();
+ if (!inBounds(result.month, 1, 12)) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MONTH);
+ }
+ } else {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MONTH);
+ }
+
+ // Optional: -
+ character('-');
+
+ // DateDay :
+ // 0 NonzeroDigit
+ // 1 DecimalDigit
+ // 2 DecimalDigit
+ // 30
+ // 31
+ if (auto day = digits(2)) {
+ result.day = day.value();
+ if (!inBounds(result.day, 1, 31)) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DAY);
+ }
+ } else {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DAY);
+ }
+
+ return result;
+}
+
+template <typename CharT>
+mozilla::Result<PlainTime, ParserError> TemporalParser<CharT>::timeSpec() {
+ // TimeSpec :
+ // TimeHour
+ // TimeHour : TimeMinute
+ // TimeHour TimeMinute
+ // TimeHour : TimeMinute : TimeSecond TimeFraction?
+ // TimeHour TimeMinute TimeSecond TimeFraction?
+ PlainTime result = {};
+
+ // TimeHour :
+ // Hour[+Padded]
+ //
+ // Hour[Padded] :
+ // [~Padded] DecimalDigit
+ // [~Padded] 0 DecimalDigit
+ // 1 DecimalDigit
+ // 20
+ // 21
+ // 22
+ // 23
+ if (auto hour = digits(2)) {
+ result.hour = hour.value();
+ if (!inBounds(result.hour, 0, 23)) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_HOUR);
+ }
+ } else {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_HOUR);
+ }
+
+ // Optional: :
+ bool needsMinutes = character(':');
+
+ // TimeMinute :
+ // MinuteSecond
+ //
+ // MinuteSecond :
+ // 0 DecimalDigit
+ // 1 DecimalDigit
+ // 2 DecimalDigit
+ // 3 DecimalDigit
+ // 4 DecimalDigit
+ // 5 DecimalDigit
+ if (auto minute = digits(2)) {
+ result.minute = minute.value();
+ if (!inBounds(result.minute, 0, 59)) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MINUTE);
+ }
+
+ // Optional: :
+ bool needsSeconds = needsMinutes && character(':');
+
+ // TimeSecond :
+ // MinuteSecond
+ // 60
+ if (auto second = digits(2)) {
+ result.second = second.value();
+ if (!inBounds(result.second, 0, 60)) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_LEAPSECOND);
+ }
+
+ // TimeFraction :
+ // Fraction
+ if (auto f = fraction()) {
+ int32_t fractionalPart = f.value();
+ result.millisecond = fractionalPart / 1'000'000;
+ result.microsecond = (fractionalPart % 1'000'000) / 1'000;
+ result.nanosecond = fractionalPart % 1'000;
+ }
+ } else if (needsSeconds) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_SECOND);
+ }
+ } else if (needsMinutes) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MINUTE);
+ }
+
+ return result;
+}
+
+template <typename CharT>
+mozilla::Result<TimeZoneString, ParserError>
+TemporalParser<CharT>::dateTimeUTCOffset() {
+ // DateTimeUTCOffset :
+ // UTCDesignator
+ // UTCOffsetSubMinutePrecision
+
+ if (utcDesignator()) {
+ return TimeZoneString::UTC();
+ }
+
+ if (hasSign()) {
+ auto offset = utcOffsetSubMinutePrecision();
+ if (offset.isErr()) {
+ return offset.propagateErr();
+ }
+ return TimeZoneString::from(offset.unwrap());
+ }
+
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE);
+}
+
+template <typename CharT>
+mozilla::Result<TimeZoneUTCOffset, ParserError>
+TemporalParser<CharT>::timeZoneUTCOffsetName() {
+ // TimeZoneUTCOffsetName :
+ // UTCOffsetMinutePrecision
+ //
+ // UTCOffsetMinutePrecision :
+ // Sign Hour[+Padded]
+ // Sign Hour[+Padded] TimeSeparator[+Extended] MinuteSecond
+ // Sign Hour[+Padded] TimeSeparator[~Extended] MinuteSecond
+
+ TimeZoneUTCOffset result = {};
+
+ if (!hasSign()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE_SIGN);
+ }
+ result.sign = sign();
+
+ // Hour[Padded] :
+ // [~Padded] DecimalDigit
+ // [+Padded] 0 DecimalDigit
+ // 1 DecimalDigit
+ // 20
+ // 21
+ // 22
+ // 23
+ if (auto hour = digits(2)) {
+ result.hour = hour.value();
+ if (!inBounds(result.hour, 0, 23)) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_HOUR);
+ }
+ } else {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_HOUR);
+ }
+
+ // TimeSeparator[Extended] :
+ // [+Extended] :
+ // [~Extended] [empty]
+ bool needsMinutes = character(':');
+
+ // MinuteSecond :
+ // 0 DecimalDigit
+ // 1 DecimalDigit
+ // 2 DecimalDigit
+ // 3 DecimalDigit
+ // 4 DecimalDigit
+ // 5 DecimalDigit
+ if (auto minute = digits(2)) {
+ result.minute = minute.value();
+ if (!inBounds(result.minute, 0, 59)) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MINUTE);
+ }
+
+ if (hasCharacter(':')) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_SUBMINUTE_TIMEZONE);
+ }
+ } else if (needsMinutes) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MINUTE);
+ }
+
+ return result;
+}
+
+template <typename CharT>
+mozilla::Result<DateTimeUTCOffset, ParserError>
+TemporalParser<CharT>::utcOffsetSubMinutePrecision() {
+ // clang-format off
+ //
+ // UTCOffsetSubMinutePrecision :
+ // UTCOffsetMinutePrecision
+ // UTCOffsetWithSubMinuteComponents[+Extended]
+ // UTCOffsetWithSubMinuteComponents[~Extended]
+ //
+ // UTCOffsetMinutePrecision :
+ // Sign Hour[+Padded]
+ // Sign Hour[+Padded] TimeSeparator[+Extended] MinuteSecond
+ // Sign Hour[+Padded] TimeSeparator[~Extended] MinuteSecond
+ //
+ // UTCOffsetWithSubMinuteComponents[Extended] :
+ // Sign Hour[+Padded] TimeSeparator[?Extended] MinuteSecond TimeSeparator[?Extended] MinuteSecond Fraction?
+ //
+ // clang-format on
+
+ DateTimeUTCOffset result = {};
+
+ if (!hasSign()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE_SIGN);
+ }
+ result.sign = sign();
+
+ // Hour[Padded] :
+ // [~Padded] DecimalDigit
+ // [+Padded] 0 DecimalDigit
+ // 1 DecimalDigit
+ // 20
+ // 21
+ // 22
+ // 23
+ if (auto hour = digits(2)) {
+ result.hour = hour.value();
+ if (!inBounds(result.hour, 0, 23)) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_HOUR);
+ }
+ } else {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_HOUR);
+ }
+
+ // TimeSeparator[Extended] :
+ // [+Extended] :
+ // [~Extended] [empty]
+ bool needsMinutes = character(':');
+
+ // MinuteSecond :
+ // 0 DecimalDigit
+ // 1 DecimalDigit
+ // 2 DecimalDigit
+ // 3 DecimalDigit
+ // 4 DecimalDigit
+ // 5 DecimalDigit
+ if (auto minute = digits(2)) {
+ result.minute = minute.value();
+ if (!inBounds(result.minute, 0, 59)) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MINUTE);
+ }
+
+ // TimeSeparator[Extended] :
+ // [+Extended] :
+ // [~Extended] [empty]
+ bool needsSeconds = needsMinutes && character(':');
+
+ // MinuteSecond :
+ // 0 DecimalDigit
+ // 1 DecimalDigit
+ // 2 DecimalDigit
+ // 3 DecimalDigit
+ // 4 DecimalDigit
+ // 5 DecimalDigit
+ if (auto second = digits(2)) {
+ result.second = second.value();
+ if (!inBounds(result.second, 0, 59)) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_SECOND);
+ }
+
+ if (auto fractionalPart = fraction()) {
+ result.fractionalPart = fractionalPart.value();
+ }
+
+ result.subMinutePrecision = true;
+ } else if (needsSeconds) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_SECOND);
+ }
+ } else if (needsMinutes) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MINUTE);
+ }
+
+ return result;
+}
+
+template <typename CharT>
+mozilla::Result<TimeZoneAnnotation, ParserError>
+TemporalParser<CharT>::timeZoneIdentifier() {
+ // TimeZoneIdentifier :
+ // TimeZoneUTCOffsetName
+ // TimeZoneIANAName
+
+ TimeZoneAnnotation result = {};
+ if (hasSign()) {
+ auto offset = timeZoneUTCOffsetName();
+ if (offset.isErr()) {
+ return offset.propagateErr();
+ }
+ result.offset = offset.unwrap();
+ } else {
+ auto name = timeZoneIANAName();
+ if (name.isErr()) {
+ return name.propagateErr();
+ }
+ result.name = name.unwrap();
+ }
+
+ return result;
+}
+
+template <typename CharT>
+mozilla::Result<TimeZoneAnnotation, ParserError>
+TemporalParser<CharT>::timeZoneAnnotation() {
+ // TimeZoneAnnotation :
+ // [ AnnotationCriticalFlag? TimeZoneIdentifier ]
+
+ if (!character('[')) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_BEFORE_TIMEZONE);
+ }
+
+ // Skip over the optional critical flag.
+ annotationCriticalFlag();
+
+ auto result = timeZoneIdentifier();
+ if (result.isErr()) {
+ return result.propagateErr();
+ }
+
+ if (!character(']')) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_AFTER_TIMEZONE);
+ }
+
+ return result;
+}
+
+template <typename CharT>
+mozilla::Result<TimeZoneName, ParserError>
+TemporalParser<CharT>::timeZoneIANAName() {
+ // TimeZoneIANAName :
+ // TimeZoneIANANameComponent
+ // TimeZoneIANAName / TimeZoneIANANameComponent
+ //
+ // TimeZoneIANANameComponent :
+ // TZLeadingChar
+ // TimeZoneIANANameComponent TZChar
+
+ size_t start = reader_.index();
+
+ do {
+ if (!tzLeadingChar()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE_NAME);
+ }
+
+ // Optionally followed by a sequence of |TZChar|.
+ while (tzChar()) {
+ }
+ } while (character('/'));
+
+ return TimeZoneName{start, reader_.index() - start};
+}
+
+template <typename CharT>
+mozilla::Maybe<double> TemporalParser<CharT>::digits(JSContext* cx) {
+ auto span = reader_.string().Subspan(reader_.index());
+
+ // GetPrefixInteger can't fail when integer separator handling is disabled.
+ const CharT* endp = nullptr;
+ double num;
+ MOZ_ALWAYS_TRUE(GetPrefixInteger(span.data(), span.data() + span.size(), 10,
+ IntegerSeparatorHandling::None, &endp,
+ &num));
+
+ size_t len = endp - span.data();
+ if (len == 0) {
+ return mozilla::Nothing();
+ }
+ reader_.advance(len);
+ return mozilla::Some(num);
+}
+
+template <typename CharT>
+mozilla::Result<ZonedDateTimeString, ParserError>
+TemporalParser<CharT>::parseTemporalInstantString() {
+ // Initialize all fields to zero.
+ ZonedDateTimeString result = {};
+
+ // clang-format off
+ //
+ // TemporalInstantString :
+ // Date DateTimeSeparator TimeSpec DateTimeUTCOffset TimeZoneAnnotation? Annotations?
+ //
+ // clang-format on
+
+ auto dt = date();
+ if (dt.isErr()) {
+ return dt.propagateErr();
+ }
+ result.date = dt.unwrap();
+
+ if (!dateTimeSeparator()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DATE_TIME_SEPARATOR);
+ }
+
+ auto time = timeSpec();
+ if (time.isErr()) {
+ return time.propagateErr();
+ }
+ result.time = time.unwrap();
+
+ auto tz = dateTimeUTCOffset();
+ if (tz.isErr()) {
+ return tz.propagateErr();
+ }
+ result.timeZone = tz.unwrap();
+
+ if (hasTimeZoneAnnotationStart()) {
+ auto annotation = timeZoneAnnotation();
+ if (annotation.isErr()) {
+ return annotation.propagateErr();
+ }
+ result.timeZone.annotation = annotation.unwrap();
+ }
+
+ if (hasAnnotationStart()) {
+ if (auto cal = annotations(); cal.isErr()) {
+ return cal.propagateErr();
+ }
+ }
+
+ if (!reader_.atEnd()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
+ }
+
+ return result;
+}
+
+/**
+ * ParseTemporalInstantString ( isoString )
+ */
+template <typename CharT>
+static auto ParseTemporalInstantString(mozilla::Span<const CharT> str) {
+ TemporalParser<CharT> parser(str);
+ return parser.parseTemporalInstantString();
+}
+
+/**
+ * ParseTemporalInstantString ( isoString )
+ */
+static auto ParseTemporalInstantString(Handle<JSLinearString*> str) {
+ JS::AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars()) {
+ return ParseTemporalInstantString<Latin1Char>(str->latin1Range(nogc));
+ }
+ return ParseTemporalInstantString<char16_t>(str->twoByteRange(nogc));
+}
+
+/**
+ * ParseTemporalInstantString ( isoString )
+ */
+bool js::temporal::ParseTemporalInstantString(JSContext* cx,
+ Handle<JSString*> str,
+ PlainDateTime* result,
+ int64_t* offset) {
+ Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
+ if (!linear) {
+ return false;
+ }
+
+ // Step 1.
+ auto parseResult = ::ParseTemporalInstantString(linear);
+ if (parseResult.isErr()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ parseResult.unwrapErr());
+ return false;
+ }
+ ZonedDateTimeString parsed = parseResult.unwrap();
+
+ // Step 2.
+ if (!ParseISODateTime(cx, parsed, result)) {
+ return false;
+ }
+
+ // Steps 3-4.
+ if (parsed.timeZone.hasOffset()) {
+ *offset = ParseDateTimeUTCOffset(parsed.timeZone.offset);
+ } else {
+ MOZ_ASSERT(parsed.timeZone.isUTC());
+ *offset = 0;
+ }
+ return true;
+}
+
+template <typename CharT>
+mozilla::Result<ZonedDateTimeString, ParserError>
+TemporalParser<CharT>::parseTemporalTimeZoneString() {
+ // TimeZoneIdentifier :
+ // TimeZoneUTCOffsetName
+ // TimeZoneIANAName
+
+ if (hasSign()) {
+ if (auto offset = timeZoneUTCOffsetName();
+ offset.isOk() && reader_.atEnd()) {
+ ZonedDateTimeString result = {};
+ result.timeZone = TimeZoneString::from(offset.unwrap());
+ return result;
+ }
+ } else {
+ if (auto name = timeZoneIANAName(); name.isOk() && reader_.atEnd()) {
+ ZonedDateTimeString result = {};
+ result.timeZone = TimeZoneString::from(name.unwrap());
+ return result;
+ }
+ }
+
+ // Try all five parse goals from ParseISODateTime in order.
+ //
+ // TemporalDateTimeString
+ // TemporalInstantString
+ // TemporalTimeString
+ // TemporalMonthDayString
+ // TemporalYearMonthString
+
+ // Restart parsing from the start of the string.
+ reader_.reset();
+
+ if (auto dt = parseTemporalDateTimeString(); dt.isOk()) {
+ return dt.unwrap();
+ }
+
+ // Restart parsing from the start of the string.
+ reader_.reset();
+
+ if (auto dt = parseTemporalInstantString(); dt.isOk()) {
+ return dt.unwrap();
+ }
+
+ // Restart parsing from the start of the string.
+ reader_.reset();
+
+ if (auto dt = parseTemporalTimeString(); dt.isOk()) {
+ return dt.unwrap();
+ }
+
+ // Restart parsing from the start of the string.
+ reader_.reset();
+
+ if (auto dt = parseTemporalMonthDayString(); dt.isOk()) {
+ return dt.unwrap();
+ }
+
+ // Restart parsing from the start of the string.
+ reader_.reset();
+
+ if (auto dt = parseTemporalYearMonthString(); dt.isOk()) {
+ return dt.unwrap();
+ } else {
+ return dt.propagateErr();
+ }
+}
+
+/**
+ * ParseTemporalTimeZoneString ( timeZoneString )
+ */
+template <typename CharT>
+static auto ParseTemporalTimeZoneString(mozilla::Span<const CharT> str) {
+ TemporalParser<CharT> parser(str);
+ return parser.parseTemporalTimeZoneString();
+}
+
+/**
+ * ParseTemporalTimeZoneString ( timeZoneString )
+ */
+static auto ParseTemporalTimeZoneString(Handle<JSLinearString*> str) {
+ JS::AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars()) {
+ return ParseTemporalTimeZoneString<Latin1Char>(str->latin1Range(nogc));
+ }
+ return ParseTemporalTimeZoneString<char16_t>(str->twoByteRange(nogc));
+}
+
+/**
+ * ParseTemporalTimeZoneString ( timeZoneString )
+ */
+bool js::temporal::ParseTemporalTimeZoneString(
+ JSContext* cx, Handle<JSString*> str,
+ MutableHandle<ParsedTimeZone> result) {
+ Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
+ if (!linear) {
+ return false;
+ }
+
+ // Steps 1-4.
+ auto parseResult = ::ParseTemporalTimeZoneString(linear);
+ if (parseResult.isErr()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ parseResult.unwrapErr());
+ return false;
+ }
+ ZonedDateTimeString parsed = parseResult.unwrap();
+ const auto& timeZone = parsed.timeZone;
+
+ // Step 3.
+ PlainDateTime unused;
+ if (!ParseISODateTime(cx, parsed, &unused)) {
+ return false;
+ }
+
+ if (timeZone.hasAnnotation()) {
+ // Case 1: 19700101T00:00Z[+02:00]
+ // Case 2: 19700101T00:00+00:00[+02:00]
+ // Case 3: 19700101T00:00[+02:00]
+ // Case 4: 19700101T00:00Z[Europe/Berlin]
+ // Case 5: 19700101T00:00+00:00[Europe/Berlin]
+ // Case 6: 19700101T00:00[Europe/Berlin]
+
+ if (!ParseTimeZoneAnnotation(cx, timeZone.annotation, linear, result)) {
+ return false;
+ }
+ } else if (timeZone.isUTC()) {
+ result.set(ParsedTimeZone::fromName(cx->names().UTC));
+ } else if (timeZone.hasOffset()) {
+ // ToTemporalTimeZoneSlotValue, step 7.
+ //
+ // Error reporting for sub-minute precision moved here.
+ if (timeZone.offset.subMinutePrecision) {
+ JS_ReportErrorNumberASCII(
+ cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PARSER_INVALID_SUBMINUTE_TIMEZONE);
+ return false;
+ }
+
+ int32_t offset = ParseTimeZoneOffset(timeZone.offset.toTimeZoneUTCOffset());
+ result.set(ParsedTimeZone::fromOffset(offset));
+ } else {
+ // Step 5.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PARSER_MISSING_TIMEZONE);
+ return false;
+ }
+
+ // Step 6.
+ return true;
+}
+
+template <typename CharT>
+mozilla::Result<TimeZoneAnnotation, ParserError>
+TemporalParser<CharT>::parseTimeZoneIdentifier() {
+ auto result = timeZoneIdentifier();
+ if (result.isErr()) {
+ return result.propagateErr();
+ }
+ if (!reader_.atEnd()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
+ }
+ return result;
+}
+
+/**
+ * ParseTimeZoneIdentifier ( identifier )
+ */
+template <typename CharT>
+static auto ParseTimeZoneIdentifier(mozilla::Span<const CharT> str) {
+ TemporalParser<CharT> parser(str);
+ return parser.parseTimeZoneIdentifier();
+}
+
+/**
+ * ParseTimeZoneIdentifier ( identifier )
+ */
+static auto ParseTimeZoneIdentifier(Handle<JSLinearString*> str) {
+ JS::AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars()) {
+ return ParseTimeZoneIdentifier<Latin1Char>(str->latin1Range(nogc));
+ }
+ return ParseTimeZoneIdentifier<char16_t>(str->twoByteRange(nogc));
+}
+
+/**
+ * ParseTimeZoneIdentifier ( identifier )
+ */
+bool js::temporal::ParseTimeZoneIdentifier(
+ JSContext* cx, Handle<JSString*> str,
+ MutableHandle<ParsedTimeZone> result) {
+ Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
+ if (!linear) {
+ return false;
+ }
+
+ // Steps 1-2.
+ auto parseResult = ::ParseTimeZoneIdentifier(linear);
+ if (parseResult.isErr()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ parseResult.unwrapErr());
+ return false;
+ }
+ auto timeZone = parseResult.unwrap();
+
+ // Steps 3-4.
+ return ParseTimeZoneAnnotation(cx, timeZone, linear, result);
+}
+
+template <typename CharT>
+mozilla::Result<TimeZoneUTCOffset, ParserError>
+TemporalParser<CharT>::parseTimeZoneOffsetString() {
+ auto offset = timeZoneUTCOffsetName();
+ if (offset.isErr()) {
+ return offset.propagateErr();
+ }
+ if (!reader_.atEnd()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
+ }
+ return offset.unwrap();
+}
+
+/**
+ * ParseTimeZoneOffsetString ( isoString )
+ */
+template <typename CharT>
+static auto ParseTimeZoneOffsetString(mozilla::Span<const CharT> str) {
+ TemporalParser<CharT> parser(str);
+ return parser.parseTimeZoneOffsetString();
+}
+
+/**
+ * ParseTimeZoneOffsetString ( isoString )
+ */
+static auto ParseTimeZoneOffsetString(Handle<JSLinearString*> str) {
+ JS::AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars()) {
+ return ParseTimeZoneOffsetString<Latin1Char>(str->latin1Range(nogc));
+ }
+ return ParseTimeZoneOffsetString<char16_t>(str->twoByteRange(nogc));
+}
+
+/**
+ * ParseTimeZoneOffsetString ( isoString )
+ */
+bool js::temporal::ParseTimeZoneOffsetString(JSContext* cx,
+ Handle<JSString*> str,
+ int32_t* result) {
+ // Step 1. (Not applicable in our implementation.)
+
+ Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
+ if (!linear) {
+ return false;
+ }
+
+ // Step 2.
+ auto parseResult = ::ParseTimeZoneOffsetString(linear);
+ if (parseResult.isErr()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ parseResult.unwrapErr());
+ return false;
+ }
+
+ // Steps 3-13.
+ *result = ParseTimeZoneOffset(parseResult.unwrap());
+ return true;
+}
+
+template <typename CharT>
+mozilla::Result<DateTimeUTCOffset, ParserError>
+TemporalParser<CharT>::parseDateTimeUTCOffset() {
+ auto offset = utcOffsetSubMinutePrecision();
+ if (offset.isErr()) {
+ return offset.propagateErr();
+ }
+ if (!reader_.atEnd()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
+ }
+ return offset.unwrap();
+}
+
+/**
+ * ParseDateTimeUTCOffset ( offsetString )
+ */
+template <typename CharT>
+static auto ParseDateTimeUTCOffset(mozilla::Span<const CharT> str) {
+ TemporalParser<CharT> parser(str);
+ return parser.parseDateTimeUTCOffset();
+}
+
+/**
+ * ParseDateTimeUTCOffset ( offsetString )
+ */
+static auto ParseDateTimeUTCOffset(Handle<JSLinearString*> str) {
+ JS::AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars()) {
+ return ParseDateTimeUTCOffset<Latin1Char>(str->latin1Range(nogc));
+ }
+ return ParseDateTimeUTCOffset<char16_t>(str->twoByteRange(nogc));
+}
+
+/**
+ * ParseDateTimeUTCOffset ( offsetString )
+ */
+bool js::temporal::ParseDateTimeUTCOffset(JSContext* cx, Handle<JSString*> str,
+ int64_t* result) {
+ Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
+ if (!linear) {
+ return false;
+ }
+
+ // Steps 1-2.
+ auto parseResult = ::ParseDateTimeUTCOffset(linear);
+ if (parseResult.isErr()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ parseResult.unwrapErr());
+ return false;
+ }
+
+ // Steps 3-21.
+ *result = ParseDateTimeUTCOffset(parseResult.unwrap());
+ return true;
+}
+
+template <typename CharT>
+mozilla::Result<TemporalDurationString, ParserError>
+TemporalParser<CharT>::parseTemporalDurationString(JSContext* cx) {
+ // Initialize all fields to zero.
+ TemporalDurationString result = {};
+
+ // TemporalDurationString :
+ // Duration
+ //
+ // Duration :
+ // Sign? DurationDesignator DurationDate
+ // Sign? DurationDesignator DurationTime
+
+ if (hasSign()) {
+ result.sign = sign();
+ }
+
+ if (!durationDesignator()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DESIGNATOR);
+ }
+
+ // DurationDate :
+ // DurationYearsPart DurationTime?
+ // DurationMonthsPart DurationTime?
+ // DurationWeeksPart DurationTime?
+ // DurationDaysPart DurationTime?
+
+ do {
+ double num;
+ if (hasTimeDesignator()) {
+ break;
+ }
+ if (auto d = digits(cx); !d) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
+ } else {
+ num = *d;
+ }
+
+ // DurationYearsPart :
+ // DurationYears YearsDesignator DurationMonthsPart
+ // DurationYears YearsDesignator DurationWeeksPart
+ // DurationYears YearsDesignator DurationDaysPart?
+ //
+ // DurationYears :
+ // DecimalDigits[~Sep]
+ if (yearsDesignator()) {
+ result.years = num;
+ if (reader_.atEnd()) {
+ return result;
+ }
+ if (hasTimeDesignator()) {
+ break;
+ }
+ if (auto d = digits(cx); !d) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
+ } else {
+ num = *d;
+ }
+ }
+
+ // DurationMonthsPart :
+ // DurationMonths MonthsDesignator DurationWeeksPart
+ // DurationMonths MonthsDesignator DurationDaysPart?
+ //
+ // DurationMonths :
+ // DecimalDigits[~Sep]
+ if (monthsDesignator()) {
+ result.months = num;
+ if (reader_.atEnd()) {
+ return result;
+ }
+ if (hasTimeDesignator()) {
+ break;
+ }
+ if (auto d = digits(cx); !d) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
+ } else {
+ num = *d;
+ }
+ }
+
+ // DurationWeeksPart :
+ // DurationWeeks WeeksDesignator DurationDaysPart?
+ //
+ // DurationWeeks :
+ // DecimalDigits[~Sep]
+ if (weeksDesignator()) {
+ result.weeks = num;
+ if (reader_.atEnd()) {
+ return result;
+ }
+ if (hasTimeDesignator()) {
+ break;
+ }
+ if (auto d = digits(cx); !d) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
+ } else {
+ num = *d;
+ }
+ }
+
+ // DurationDaysPart :
+ // DurationDays DaysDesignator
+ //
+ // DurationDays :
+ // DecimalDigits[~Sep]
+ if (daysDesignator()) {
+ result.days = num;
+ if (reader_.atEnd()) {
+ return result;
+ }
+ if (hasTimeDesignator()) {
+ break;
+ }
+ }
+
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
+ } while (false);
+
+ // DurationTime :
+ // DurationTimeDesignator DurationHoursPart
+ // DurationTimeDesignator DurationMinutesPart
+ // DurationTimeDesignator DurationSecondsPart
+ if (!timeDesignator()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_TIME_DESIGNATOR);
+ }
+
+ double num;
+ mozilla::Maybe<int32_t> frac;
+ auto digitsAndFraction = [&]() {
+ auto d = digits(cx);
+ if (!d) {
+ return false;
+ }
+ num = *d;
+ frac = fraction();
+ return true;
+ };
+
+ if (!digitsAndFraction()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
+ }
+
+ // clang-format off
+ //
+ // DurationHoursPart :
+ // DurationWholeHours DurationHoursFraction HoursDesignator
+ // DurationWholeHours HoursDesignator DurationMinutesPart
+ // DurationWholeHours HoursDesignator DurationSecondsPart?
+ //
+ // DurationWholeHours :
+ // DecimalDigits[~Sep]
+ //
+ // DurationHoursFraction :
+ // TimeFraction
+ //
+ // TimeFraction :
+ // Fraction
+ //
+ // clang-format on
+ bool hasHoursFraction = false;
+ if (hoursDesignator()) {
+ hasHoursFraction = bool(frac);
+ result.hours = num;
+ result.hoursFraction = frac.valueOr(0);
+ if (reader_.atEnd()) {
+ return result;
+ }
+ if (!digitsAndFraction()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
+ }
+ }
+
+ // clang-format off
+ //
+ // DurationMinutesPart :
+ // DurationWholeMinutes DurationMinutesFraction MinutesDesignator
+ // DurationWholeMinutes MinutesDesignator DurationSecondsPart?
+ //
+ // DurationWholeMinutes :
+ // DecimalDigits[~Sep]
+ //
+ // DurationMinutesFraction :
+ // TimeFraction
+ //
+ // TimeFraction :
+ // Fraction
+ //
+ // clang-format on
+ bool hasMinutesFraction = false;
+ if (minutesDesignator()) {
+ if (hasHoursFraction) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DURATION_MINUTES);
+ }
+ hasMinutesFraction = bool(frac);
+ result.minutes = num;
+ result.minutesFraction = frac.valueOr(0);
+ if (reader_.atEnd()) {
+ return result;
+ }
+ if (!digitsAndFraction()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DURATION_DIGITS);
+ }
+ }
+
+ // DurationSecondsPart :
+ // DurationWholeSeconds DurationSecondsFraction? SecondsDesignator
+ //
+ // DurationWholeSeconds :
+ // DecimalDigits[~Sep]
+ //
+ // DurationSecondsFraction :
+ // TimeFraction
+ //
+ // TimeFraction :
+ // Fraction
+ if (secondsDesignator()) {
+ if (hasHoursFraction || hasMinutesFraction) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DURATION_SECONDS);
+ }
+ result.seconds = num;
+ result.secondsFraction = frac.valueOr(0);
+ if (reader_.atEnd()) {
+ return result;
+ }
+ }
+
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
+}
+
+/**
+ * ParseTemporalDurationString ( isoString )
+ */
+template <typename CharT>
+static auto ParseTemporalDurationString(JSContext* cx,
+ mozilla::Span<const CharT> str) {
+ TemporalParser<CharT> parser(str);
+ return parser.parseTemporalDurationString(cx);
+}
+
+/**
+ * ParseTemporalDurationString ( isoString )
+ */
+static auto ParseTemporalDurationString(JSContext* cx,
+ Handle<JSLinearString*> str) {
+ JS::AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars()) {
+ return ParseTemporalDurationString<Latin1Char>(cx, str->latin1Range(nogc));
+ }
+ return ParseTemporalDurationString<char16_t>(cx, str->twoByteRange(nogc));
+}
+
+/**
+ * ParseTemporalDurationString ( isoString )
+ */
+bool js::temporal::ParseTemporalDurationString(JSContext* cx,
+ Handle<JSString*> str,
+ Duration* result) {
+ Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
+ if (!linear) {
+ return false;
+ }
+
+ // Steps 1-3.
+ auto parseResult = ::ParseTemporalDurationString(cx, linear);
+ if (parseResult.isErr()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ parseResult.unwrapErr());
+ return false;
+ }
+ TemporalDurationString parsed = parseResult.unwrap();
+
+ // Steps 4-8.
+ double years = parsed.years;
+ double months = parsed.months;
+ double weeks = parsed.weeks;
+ double days = parsed.days;
+ double hours = parsed.hours;
+
+ // Steps 9-17.
+ double minutes, seconds, milliseconds, microseconds, nanoseconds;
+ if (parsed.hoursFraction) {
+ MOZ_ASSERT(parsed.hoursFraction > 0);
+ MOZ_ASSERT(parsed.hoursFraction < 1'000'000'000);
+
+ // Step 9.a.
+ MOZ_ASSERT(parsed.minutes == 0);
+ MOZ_ASSERT(parsed.minutesFraction == 0);
+ MOZ_ASSERT(parsed.seconds == 0);
+ MOZ_ASSERT(parsed.secondsFraction == 0);
+
+ // Steps 9.b-d.
+ int64_t h = int64_t(parsed.hoursFraction) * 60;
+ minutes = h / 1'000'000'000;
+
+ // Steps 13 and 15-17.
+ int64_t min = (h % 1'000'000'000) * 60;
+ seconds = min / 1'000'000'000;
+ milliseconds = (min % 1'000'000'000) / 1'000'000;
+ microseconds = (min % 1'000'000) / 1'000;
+ nanoseconds = (min % 1'000);
+ }
+
+ // Step 11.
+ else if (parsed.minutesFraction) {
+ MOZ_ASSERT(parsed.minutesFraction > 0);
+ MOZ_ASSERT(parsed.minutesFraction < 1'000'000'000);
+
+ // Step 11.a.
+ MOZ_ASSERT(parsed.seconds == 0);
+ MOZ_ASSERT(parsed.secondsFraction == 0);
+
+ // Step 10.
+ minutes = parsed.minutes;
+
+ // Steps 11.b-d and 15-17.
+ int64_t min = int64_t(parsed.minutesFraction) * 60;
+ seconds = min / 1'000'000'000;
+ milliseconds = (min % 1'000'000'000) / 1'000'000;
+ microseconds = (min % 1'000'000) / 1'000;
+ nanoseconds = (min % 1'000);
+ }
+
+ // Step 14.
+ else if (parsed.secondsFraction) {
+ MOZ_ASSERT(parsed.secondsFraction > 0);
+ MOZ_ASSERT(parsed.secondsFraction < 1'000'000'000);
+
+ // Step 10.
+ minutes = parsed.minutes;
+
+ // Step 12.
+ seconds = parsed.seconds;
+
+ // Steps 14, 16-17
+ milliseconds = (parsed.secondsFraction / 1'000'000);
+ microseconds = ((parsed.secondsFraction % 1'000'000) / 1'000);
+ nanoseconds = (parsed.secondsFraction % 1'000);
+ } else {
+ // Step 10.
+ minutes = parsed.minutes;
+
+ // Step 12.
+ seconds = parsed.seconds;
+
+ // Steps 15-17
+ milliseconds = 0;
+ microseconds = 0;
+ nanoseconds = 0;
+ }
+
+ // Steps 18-19.
+ int32_t factor = parsed.sign ? parsed.sign : 1;
+ MOZ_ASSERT(factor == -1 || factor == 1);
+
+ // Step 20.
+ *result = {
+ (years * factor) + (+0.0), (months * factor) + (+0.0),
+ (weeks * factor) + (+0.0), (days * factor) + (+0.0),
+ (hours * factor) + (+0.0), (minutes * factor) + (+0.0),
+ (seconds * factor) + (+0.0), (milliseconds * factor) + (+0.0),
+ (microseconds * factor) + (+0.0), (nanoseconds * factor) + (+0.0),
+ };
+ if (!ThrowIfInvalidDuration(cx, *result)) {
+ return false;
+ }
+ return true;
+}
+
+template <typename CharT>
+mozilla::Result<AnnotationKey, ParserError>
+TemporalParser<CharT>::annotationKey() {
+ // AnnotationKey :
+ // AKeyLeadingChar
+ // AnnotationKey AKeyChar
+
+ size_t start = reader_.index();
+
+ if (!aKeyLeadingChar()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_ANNOTATION_KEY);
+ }
+
+ // Optionally followed by a sequence of |AKeyChar|.
+ while (aKeyChar()) {
+ }
+
+ return AnnotationKey{start, reader_.index() - start};
+}
+
+template <typename CharT>
+mozilla::Result<AnnotationValue, ParserError>
+TemporalParser<CharT>::annotationValue() {
+ // AnnotationValue :
+ // AnnotationValueComponent
+ // AnnotationValueComponent - AnnotationValue
+
+ size_t start = reader_.index();
+
+ do {
+ if (!annotationValueComponent()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_ANNOTATION_VALUE);
+ }
+ } while (character('-'));
+
+ return AnnotationValue{start, reader_.index() - start};
+}
+
+template <typename CharT>
+mozilla::Result<Annotation, ParserError> TemporalParser<CharT>::annotation() {
+ // Annotation :
+ // [ AnnotationCriticalFlag? AnnotationKey = AnnotationValue ]
+
+ if (!character('[')) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_BEFORE_ANNOTATION);
+ }
+
+ bool critical = annotationCriticalFlag();
+
+ auto key = annotationKey();
+ if (key.isErr()) {
+ return key.propagateErr();
+ }
+
+ if (!character('=')) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_ASSIGNMENT_IN_ANNOTATION);
+ }
+
+ auto value = annotationValue();
+ if (value.isErr()) {
+ return value.propagateErr();
+ }
+
+ if (!character(']')) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_BRACKET_AFTER_ANNOTATION);
+ }
+
+ return Annotation{key.unwrap(), value.unwrap(), critical};
+}
+
+template <typename CharT>
+mozilla::Result<CalendarName, ParserError>
+TemporalParser<CharT>::annotations() {
+ // Annotations:
+ // Annotation Annotations?
+
+ MOZ_ASSERT(hasAnnotationStart());
+
+ CalendarName calendar;
+ bool calendarWasCritical = false;
+ while (hasAnnotationStart()) {
+ auto anno = annotation();
+ if (anno.isErr()) {
+ return anno.propagateErr();
+ }
+ auto [key, value, critical] = anno.unwrap();
+
+ // FIXME: spec issue - ignore case for "[u-ca=" to match BCP47?
+ // https://github.com/tc39/proposal-temporal/issues/2524
+
+ static constexpr std::string_view ca = "u-ca";
+
+ auto keySpan = reader_.substring(key);
+ if (keySpan.size() == ca.length() &&
+ std::equal(ca.begin(), ca.end(), keySpan.data())) {
+ if (!calendar.present()) {
+ calendar = value;
+ calendarWasCritical = critical;
+ } else if (critical || calendarWasCritical) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_CRITICAL_ANNOTATION);
+ }
+ } else if (critical) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_CRITICAL_ANNOTATION);
+ }
+ }
+ return calendar;
+}
+
+template <typename CharT>
+mozilla::Result<ZonedDateTimeString, ParserError>
+TemporalParser<CharT>::annotatedTime() {
+ // clang-format off
+ //
+ // AnnotatedTime :
+ // TimeDesignator TimeSpec DateTimeUTCOffset? TimeZoneAnnotation? Annotations?
+ // TimeSpecWithOptionalOffsetNotAmbiguous TimeZoneAnnotation? Annotations?
+ //
+ // clang-format on
+
+ if (timeDesignator()) {
+ ZonedDateTimeString result = {};
+
+ auto time = timeSpec();
+ if (time.isErr()) {
+ return time.propagateErr();
+ }
+ result.time = time.unwrap();
+
+ if (hasDateTimeUTCOffsetStart()) {
+ auto tz = dateTimeUTCOffset();
+ if (tz.isErr()) {
+ return tz.propagateErr();
+ }
+ result.timeZone = tz.unwrap();
+ }
+
+ if (hasTimeZoneAnnotationStart()) {
+ auto annotation = timeZoneAnnotation();
+ if (annotation.isErr()) {
+ return annotation.propagateErr();
+ }
+ result.timeZone.annotation = annotation.unwrap();
+ }
+
+ if (hasAnnotationStart()) {
+ auto cal = annotations();
+ if (cal.isErr()) {
+ return cal.propagateErr();
+ }
+ result.calendar = cal.unwrap();
+ }
+
+ return result;
+ }
+
+ // clang-format off
+ //
+ // TimeSpecWithOptionalOffsetNotAmbiguous :
+ // TimeSpec DateTimeUTCOffset? but not one of ValidMonthDay or DateSpecYearMonth
+ //
+ // clang-format on
+
+ size_t start = reader_.index();
+
+ ZonedDateTimeString result = {};
+
+ auto time = timeSpec();
+ if (time.isErr()) {
+ return time.propagateErr();
+ }
+ result.time = time.unwrap();
+
+ if (hasDateTimeUTCOffsetStart()) {
+ auto tz = dateTimeUTCOffset();
+ if (tz.isErr()) {
+ return tz.propagateErr();
+ }
+ result.timeZone = tz.unwrap();
+ }
+
+ size_t end = reader_.index();
+
+ // Reset and check if the input can also be parsed as ValidMonthDay.
+ reader_.reset(start);
+
+ if (validMonthDay().isOk()) {
+ if (reader_.index() == end) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_AMBIGUOUS_TIME_MONTH_DAY);
+ }
+ }
+
+ // Reset and check if the input can also be parsed as DateSpecYearMonth.
+ reader_.reset(start);
+
+ if (dateSpecYearMonth().isOk()) {
+ if (reader_.index() == end) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_AMBIGUOUS_TIME_YEAR_MONTH);
+ }
+ }
+
+ // Input can neither be parsed as ValidMonthDay nor DateSpecYearMonth.
+ reader_.reset(end);
+
+ if (hasTimeZoneAnnotationStart()) {
+ auto annotation = timeZoneAnnotation();
+ if (annotation.isErr()) {
+ return annotation.propagateErr();
+ }
+ result.timeZone.annotation = annotation.unwrap();
+ }
+
+ if (hasAnnotationStart()) {
+ auto cal = annotations();
+ if (cal.isErr()) {
+ return cal.propagateErr();
+ }
+ result.calendar = cal.unwrap();
+ }
+
+ return result;
+}
+
+template <typename CharT>
+mozilla::Result<ZonedDateTimeString, ParserError>
+TemporalParser<CharT>::annotatedDateTime() {
+ // AnnotatedDateTime[Zoned] :
+ // [~Zoned] DateTime TimeZoneAnnotation? Annotations?
+ // [+Zoned] DateTime TimeZoneAnnotation Annotations?
+
+ auto dt = dateTime();
+ if (dt.isErr()) {
+ return dt.propagateErr();
+ }
+ auto result = dt.unwrap();
+
+ if (hasTimeZoneAnnotationStart()) {
+ auto annotation = timeZoneAnnotation();
+ if (annotation.isErr()) {
+ return annotation.propagateErr();
+ }
+ result.timeZone.annotation = annotation.unwrap();
+ }
+
+ if (hasAnnotationStart()) {
+ auto cal = annotations();
+ if (cal.isErr()) {
+ return cal.propagateErr();
+ }
+ result.calendar = cal.unwrap();
+ }
+
+ return result;
+}
+
+template <typename CharT>
+mozilla::Result<ZonedDateTimeString, ParserError>
+TemporalParser<CharT>::annotatedDateTimeTimeRequired() {
+ // clang-format off
+ //
+ // AnnotatedDateTimeTimeRequired :
+ // Date DateTimeSeparator TimeSpec DateTimeUTCOffset? TimeZoneAnnotation? Annotations?
+ //
+ // clang-format on
+
+ ZonedDateTimeString result = {};
+
+ auto dt = date();
+ if (dt.isErr()) {
+ return dt.propagateErr();
+ }
+ result.date = dt.unwrap();
+
+ if (!dateTimeSeparator()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DATE_TIME_SEPARATOR);
+ }
+
+ auto time = timeSpec();
+ if (time.isErr()) {
+ return time.propagateErr();
+ }
+ result.time = time.unwrap();
+
+ if (hasDateTimeUTCOffsetStart()) {
+ auto tz = dateTimeUTCOffset();
+ if (tz.isErr()) {
+ return tz.propagateErr();
+ }
+ result.timeZone = tz.unwrap();
+ }
+
+ if (hasTimeZoneAnnotationStart()) {
+ auto annotation = timeZoneAnnotation();
+ if (annotation.isErr()) {
+ return annotation.propagateErr();
+ }
+ result.timeZone.annotation = annotation.unwrap();
+ }
+
+ if (hasAnnotationStart()) {
+ auto cal = annotations();
+ if (cal.isErr()) {
+ return cal.propagateErr();
+ }
+ result.calendar = cal.unwrap();
+ }
+
+ return result;
+}
+
+template <typename CharT>
+mozilla::Result<ZonedDateTimeString, ParserError>
+TemporalParser<CharT>::annotatedYearMonth() {
+ // AnnotatedYearMonth :
+ // DateSpecYearMonth TimeZoneAnnotation? Annotations?
+
+ ZonedDateTimeString result = {};
+
+ auto yearMonth = dateSpecYearMonth();
+ if (yearMonth.isErr()) {
+ return yearMonth.propagateErr();
+ }
+ result.date = yearMonth.unwrap();
+
+ if (hasTimeZoneAnnotationStart()) {
+ auto annotation = timeZoneAnnotation();
+ if (annotation.isErr()) {
+ return annotation.propagateErr();
+ }
+ result.timeZone.annotation = annotation.unwrap();
+ }
+
+ if (hasAnnotationStart()) {
+ auto cal = annotations();
+ if (cal.isErr()) {
+ return cal.propagateErr();
+ }
+ result.calendar = cal.unwrap();
+ }
+
+ return result;
+}
+
+template <typename CharT>
+mozilla::Result<ZonedDateTimeString, ParserError>
+TemporalParser<CharT>::annotatedMonthDay() {
+ // AnnotatedMonthDay :
+ // DateSpecMonthDay TimeZoneAnnotation? Annotations?
+
+ ZonedDateTimeString result = {};
+
+ auto monthDay = dateSpecMonthDay();
+ if (monthDay.isErr()) {
+ return monthDay.propagateErr();
+ }
+ result.date = monthDay.unwrap();
+
+ if (hasTimeZoneAnnotationStart()) {
+ auto annotation = timeZoneAnnotation();
+ if (annotation.isErr()) {
+ return annotation.propagateErr();
+ }
+ result.timeZone.annotation = annotation.unwrap();
+ }
+
+ if (hasAnnotationStart()) {
+ auto cal = annotations();
+ if (cal.isErr()) {
+ return cal.propagateErr();
+ }
+ result.calendar = cal.unwrap();
+ }
+
+ return result;
+}
+
+template <typename CharT>
+mozilla::Result<PlainDate, ParserError>
+TemporalParser<CharT>::dateSpecYearMonth() {
+ // DateSpecYearMonth :
+ // DateYear -? DateMonth
+ PlainDate result = {};
+
+ // DateYear :
+ // DecimalDigit{4}
+ // Sign DecimalDigit{6}
+ if (auto year = digits(4)) {
+ result.year = year.value();
+ } else if (hasSign()) {
+ int32_t yearSign = sign();
+ if (auto year = digits(6)) {
+ result.year = yearSign * year.value();
+ if (yearSign < 0 && result.year == 0) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_NEGATIVE_ZERO_YEAR);
+ }
+ } else {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_EXTENDED_YEAR);
+ }
+ } else {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_YEAR);
+ }
+
+ character('-');
+
+ // DateMonth :
+ // 0 NonzeroDigit
+ // 10
+ // 11
+ // 12
+ if (auto month = digits(2)) {
+ result.month = month.value();
+ if (!inBounds(result.month, 1, 12)) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MONTH);
+ }
+ } else {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MONTH);
+ }
+
+ // Absent days default to 1, cf. ParseISODateTime.
+ result.day = 1;
+
+ return result;
+}
+
+template <typename CharT>
+mozilla::Result<PlainDate, ParserError>
+TemporalParser<CharT>::dateSpecMonthDay() {
+ // DateSpecMonthDay :
+ // -- DateMonth -? DateDay
+ // DateMonth -? DateDay
+ PlainDate result = {};
+
+ string("--");
+
+ result.year = AbsentYear;
+
+ // DateMonth :
+ // 0 NonzeroDigit
+ // 10
+ // 11
+ // 12
+ if (auto month = digits(2)) {
+ result.month = month.value();
+ if (!inBounds(result.month, 1, 12)) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MONTH);
+ }
+ } else {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MONTH);
+ }
+
+ character('-');
+
+ // DateDay :
+ // 0 NonzeroDigit
+ // 1 DecimalDigit
+ // 2 DecimalDigit
+ // 30
+ // 31
+ if (auto day = digits(2)) {
+ result.day = day.value();
+ if (!inBounds(result.day, 1, 31)) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DAY);
+ }
+ } else {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DAY);
+ }
+
+ return result;
+}
+
+template <typename CharT>
+mozilla::Result<PlainDate, ParserError> TemporalParser<CharT>::validMonthDay() {
+ // ValidMonthDay :
+ // DateMonth -? 0 NonZeroDigit
+ // DateMonth -? 1 DecimalDigit
+ // DateMonth -? 2 DecimalDigit
+ // DateMonth -? 30 but not one of 0230 or 02-30
+ // DateMonthWithThirtyOneDays -? 31
+ //
+ // DateMonthWithThirtyOneDays : one of
+ // 01 03 05 07 08 10 12
+
+ PlainDate result = {};
+
+ // DateMonth :
+ // 0 NonzeroDigit
+ // 10
+ // 11
+ // 12
+ if (auto month = digits(2)) {
+ result.month = month.value();
+ if (!inBounds(result.month, 1, 12)) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_MONTH);
+ }
+ } else {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_MONTH);
+ }
+
+ character('-');
+
+ if (auto day = digits(2)) {
+ result.day = day.value();
+ if (!inBounds(result.day, 1, 31)) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DAY);
+ }
+ } else {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MISSING_DAY);
+ }
+
+ if (result.month == 2 && result.day > 29) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DAY);
+ }
+
+ if (result.day > 30) {
+ MOZ_ASSERT(result.day == 31);
+
+ static constexpr int32_t monthsWithThirtyOneDays[] = {
+ 1, 3, 5, 7, 8, 10, 12,
+ };
+
+ if (std::find(std::begin(monthsWithThirtyOneDays),
+ std::end(monthsWithThirtyOneDays),
+ result.month) == std::end(monthsWithThirtyOneDays)) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_INVALID_DAY);
+ }
+ }
+
+ return result;
+}
+
+template <typename CharT>
+mozilla::Result<ZonedDateTimeString, ParserError>
+TemporalParser<CharT>::parseTemporalCalendarString() {
+ // Handle the common case of a standalone calendar name first.
+ //
+ // All valid calendar names start with two alphabetic characters and none of
+ // the ParseISODateTime parse goals can start with two alphabetic characters.
+ // TemporalTimeString can start with 'T', so we can't only check the first
+ // character.
+ if (hasTwoAsciiAlpha()) {
+ auto cal = annotationValue();
+ if (cal.isErr()) {
+ return cal.propagateErr();
+ }
+ if (!reader_.atEnd()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
+ }
+
+ ZonedDateTimeString result = {};
+ result.calendar = cal.unwrap();
+ return result;
+ }
+
+ // Try all five parse goals from ParseISODateTime in order.
+ //
+ // TemporalDateTimeString
+ // TemporalInstantString
+ // TemporalTimeString
+ // TemporalZonedDateTimeString
+ // TemporalMonthDayString
+ // TemporalYearMonthString
+
+ if (auto dt = parseTemporalDateTimeString(); dt.isOk()) {
+ return dt.unwrap();
+ }
+
+ // Restart parsing from the start of the string.
+ reader_.reset();
+
+ if (auto dt = parseTemporalInstantString(); dt.isOk()) {
+ return dt.unwrap();
+ }
+
+ // Restart parsing from the start of the string.
+ reader_.reset();
+
+ if (auto dt = parseTemporalTimeString(); dt.isOk()) {
+ return dt.unwrap();
+ }
+
+ // Restart parsing from the start of the string.
+ reader_.reset();
+
+ if (auto dt = parseTemporalMonthDayString(); dt.isOk()) {
+ return dt.unwrap();
+ }
+
+ // Restart parsing from the start of the string.
+ reader_.reset();
+
+ if (auto dt = parseTemporalYearMonthString(); dt.isOk()) {
+ return dt.unwrap();
+ } else {
+ return dt.propagateErr();
+ }
+}
+
+/**
+ * ParseTemporalCalendarString ( isoString )
+ */
+template <typename CharT>
+static auto ParseTemporalCalendarString(mozilla::Span<const CharT> str) {
+ TemporalParser<CharT> parser(str);
+ return parser.parseTemporalCalendarString();
+}
+
+/**
+ * ParseTemporalCalendarString ( isoString )
+ */
+static auto ParseTemporalCalendarString(Handle<JSLinearString*> str) {
+ JS::AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars()) {
+ return ParseTemporalCalendarString<Latin1Char>(str->latin1Range(nogc));
+ }
+ return ParseTemporalCalendarString<char16_t>(str->twoByteRange(nogc));
+}
+
+/**
+ * ParseTemporalCalendarString ( isoString )
+ */
+JSLinearString* js::temporal::ParseTemporalCalendarString(
+ JSContext* cx, Handle<JSString*> str) {
+ Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
+ if (!linear) {
+ return nullptr;
+ }
+
+ // Steps 1-3.
+ auto parseResult = ::ParseTemporalCalendarString(linear);
+ if (parseResult.isErr()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ parseResult.unwrapErr());
+ return nullptr;
+ }
+ ZonedDateTimeString parsed = parseResult.unwrap();
+
+ PlainDateTime unused;
+ if (!ParseISODateTime(cx, parsed, &unused)) {
+ return nullptr;
+ }
+
+ // Step 2.b.
+ if (!parsed.calendar.present()) {
+ return cx->names().iso8601;
+ }
+
+ // Steps 2.c and 3.c
+ return ToString(cx, linear, parsed.calendar);
+}
+
+template <typename CharT>
+mozilla::Result<ZonedDateTimeString, ParserError>
+TemporalParser<CharT>::parseTemporalTimeString() {
+ // TemporalTimeString :
+ // AnnotatedTime
+ // AnnotatedDateTimeTimeRequired
+
+ if (auto time = annotatedTime(); time.isOk() && reader_.atEnd()) {
+ return time.unwrap();
+ }
+
+ // Reset and try the next option.
+ reader_.reset();
+
+ auto dt = annotatedDateTimeTimeRequired();
+ if (dt.isErr()) {
+ return dt.propagateErr();
+ }
+ if (!reader_.atEnd()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
+ }
+ return dt.unwrap();
+}
+
+/**
+ * ParseTemporalTimeString ( isoString )
+ */
+template <typename CharT>
+static auto ParseTemporalTimeString(mozilla::Span<const CharT> str) {
+ TemporalParser<CharT> parser(str);
+ return parser.parseTemporalTimeString();
+}
+
+/**
+ * ParseTemporalTimeString ( isoString )
+ */
+static auto ParseTemporalTimeString(Handle<JSLinearString*> str) {
+ JS::AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars()) {
+ return ParseTemporalTimeString<Latin1Char>(str->latin1Range(nogc));
+ }
+ return ParseTemporalTimeString<char16_t>(str->twoByteRange(nogc));
+}
+
+/**
+ * ParseTemporalTimeString ( isoString )
+ */
+bool js::temporal::ParseTemporalTimeString(JSContext* cx, Handle<JSString*> str,
+ PlainTime* result) {
+ Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
+ if (!linear) {
+ return false;
+ }
+
+ // Steps 1-2.
+ auto parseResult = ::ParseTemporalTimeString(linear);
+ if (parseResult.isErr()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ parseResult.unwrapErr());
+ return false;
+ }
+ ZonedDateTimeString parsed = parseResult.unwrap();
+
+ // Step 3.
+ if (parsed.timeZone.isUTC()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PARSER_INVALID_UTC_DESIGNATOR);
+ return false;
+ }
+
+ // Step 4.
+ PlainDateTime dateTime;
+ if (!ParseISODateTime(cx, parsed, &dateTime)) {
+ return false;
+ }
+ *result = dateTime.time;
+
+ // Step 5.
+ return true;
+}
+
+template <typename CharT>
+mozilla::Result<ZonedDateTimeString, ParserError>
+TemporalParser<CharT>::parseTemporalMonthDayString() {
+ // TemporalMonthDayString :
+ // AnnotatedMonthDay
+ // AnnotatedDateTime[~Zoned]
+
+ if (auto monthDay = annotatedMonthDay(); monthDay.isOk() && reader_.atEnd()) {
+ auto result = monthDay.unwrap();
+
+ // ParseISODateTime, step 3.
+ if (result.calendar.present() &&
+ !IsISO8601Calendar(reader_.substring(result.calendar))) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_MONTH_DAY_CALENDAR_NOT_ISO8601);
+ }
+ return result;
+ }
+
+ // Reset and try the next option.
+ reader_.reset();
+
+ auto dt = annotatedDateTime();
+ if (dt.isErr()) {
+ return dt.propagateErr();
+ }
+ if (!reader_.atEnd()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
+ }
+ return dt.unwrap();
+}
+
+/**
+ * ParseTemporalMonthDayString ( isoString )
+ */
+template <typename CharT>
+static auto ParseTemporalMonthDayString(mozilla::Span<const CharT> str) {
+ TemporalParser<CharT> parser(str);
+ return parser.parseTemporalMonthDayString();
+}
+
+/**
+ * ParseTemporalMonthDayString ( isoString )
+ */
+static auto ParseTemporalMonthDayString(Handle<JSLinearString*> str) {
+ JS::AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars()) {
+ return ParseTemporalMonthDayString<Latin1Char>(str->latin1Range(nogc));
+ }
+ return ParseTemporalMonthDayString<char16_t>(str->twoByteRange(nogc));
+}
+
+/**
+ * ParseTemporalMonthDayString ( isoString )
+ */
+bool js::temporal::ParseTemporalMonthDayString(
+ JSContext* cx, Handle<JSString*> str, PlainDate* result, bool* hasYear,
+ MutableHandle<JSString*> calendar) {
+ Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
+ if (!linear) {
+ return false;
+ }
+
+ // Steps 1-2 .
+ auto parseResult = ::ParseTemporalMonthDayString(linear);
+ if (parseResult.isErr()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ parseResult.unwrapErr());
+ return false;
+ }
+ ZonedDateTimeString parsed = parseResult.unwrap();
+
+ // Step 3.
+ if (parsed.timeZone.isUTC()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PARSER_INVALID_UTC_DESIGNATOR);
+ return false;
+ }
+
+ // Step 4.
+ PlainDateTime dateTime;
+ if (!ParseISODateTime(cx, parsed, &dateTime)) {
+ return false;
+ }
+ *result = dateTime.date;
+
+ // Steps 5-6.
+ *hasYear = parsed.date.year != AbsentYear;
+
+ if (parsed.calendar.present()) {
+ calendar.set(ToString(cx, linear, parsed.calendar));
+ if (!calendar) {
+ return false;
+ }
+ }
+
+ // Step 7.
+ return true;
+}
+
+template <typename CharT>
+mozilla::Result<ZonedDateTimeString, ParserError>
+TemporalParser<CharT>::parseTemporalYearMonthString() {
+ // TemporalYearMonthString :
+ // AnnotatedYearMonth
+ // AnnotatedDateTime[~Zoned]
+
+ if (auto yearMonth = annotatedYearMonth();
+ yearMonth.isOk() && reader_.atEnd()) {
+ auto result = yearMonth.unwrap();
+
+ // ParseISODateTime, step 3.
+ if (result.calendar.present() &&
+ !IsISO8601Calendar(reader_.substring(result.calendar))) {
+ return mozilla::Err(
+ JSMSG_TEMPORAL_PARSER_YEAR_MONTH_CALENDAR_NOT_ISO8601);
+ }
+ return result;
+ }
+
+ // Reset and try the next option.
+ reader_.reset();
+
+ auto dt = annotatedDateTime();
+ if (dt.isErr()) {
+ return dt.propagateErr();
+ }
+ if (!reader_.atEnd()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
+ }
+ return dt.unwrap();
+}
+
+/**
+ * ParseTemporalYearMonthString ( isoString )
+ */
+template <typename CharT>
+static auto ParseTemporalYearMonthString(mozilla::Span<const CharT> str) {
+ TemporalParser<CharT> parser(str);
+ return parser.parseTemporalYearMonthString();
+}
+
+/**
+ * ParseTemporalYearMonthString ( isoString )
+ */
+static auto ParseTemporalYearMonthString(Handle<JSLinearString*> str) {
+ JS::AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars()) {
+ return ParseTemporalYearMonthString<Latin1Char>(str->latin1Range(nogc));
+ }
+ return ParseTemporalYearMonthString<char16_t>(str->twoByteRange(nogc));
+}
+
+/**
+ * ParseTemporalYearMonthString ( isoString )
+ */
+bool js::temporal::ParseTemporalYearMonthString(
+ JSContext* cx, Handle<JSString*> str, PlainDate* result,
+ MutableHandle<JSString*> calendar) {
+ Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
+ if (!linear) {
+ return false;
+ }
+
+ // Steps 1-2.
+ auto parseResult = ::ParseTemporalYearMonthString(linear);
+ if (parseResult.isErr()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ parseResult.unwrapErr());
+ return false;
+ }
+ ZonedDateTimeString parsed = parseResult.unwrap();
+
+ // Step 3.
+ if (parsed.timeZone.isUTC()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PARSER_INVALID_UTC_DESIGNATOR);
+ return false;
+ }
+
+ // Step 4.
+ PlainDateTime dateTime;
+ if (!ParseISODateTime(cx, parsed, &dateTime)) {
+ return false;
+ }
+ *result = dateTime.date;
+
+ if (parsed.calendar.present()) {
+ calendar.set(ToString(cx, linear, parsed.calendar));
+ if (!calendar) {
+ return false;
+ }
+ }
+
+ // Step 5.
+ return true;
+}
+
+template <typename CharT>
+mozilla::Result<ZonedDateTimeString, ParserError>
+TemporalParser<CharT>::parseTemporalDateTimeString() {
+ // TemporalDateTimeString[Zoned] :
+ // AnnotatedDateTime[?Zoned]
+
+ auto dateTime = annotatedDateTime();
+ if (dateTime.isErr()) {
+ return dateTime.propagateErr();
+ }
+ if (!reader_.atEnd()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
+ }
+ return dateTime.unwrap();
+}
+
+/**
+ * ParseTemporalDateTimeString ( isoString )
+ */
+template <typename CharT>
+static auto ParseTemporalDateTimeString(mozilla::Span<const CharT> str) {
+ TemporalParser<CharT> parser(str);
+ return parser.parseTemporalDateTimeString();
+}
+
+/**
+ * ParseTemporalDateTimeString ( isoString )
+ */
+static auto ParseTemporalDateTimeString(Handle<JSLinearString*> str) {
+ JS::AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars()) {
+ return ParseTemporalDateTimeString<Latin1Char>(str->latin1Range(nogc));
+ }
+ return ParseTemporalDateTimeString<char16_t>(str->twoByteRange(nogc));
+}
+
+/**
+ * ParseTemporalDateTimeString ( isoString )
+ */
+bool js::temporal::ParseTemporalDateTimeString(
+ JSContext* cx, Handle<JSString*> str, PlainDateTime* result,
+ MutableHandle<JSString*> calendar) {
+ Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
+ if (!linear) {
+ return false;
+ }
+
+ // Steps 1-2.
+ auto parseResult = ::ParseTemporalDateTimeString(linear);
+ if (parseResult.isErr()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ parseResult.unwrapErr());
+ return false;
+ }
+ ZonedDateTimeString parsed = parseResult.unwrap();
+
+ // Step 3.
+ if (parsed.timeZone.isUTC()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PARSER_INVALID_UTC_DESIGNATOR);
+ return false;
+ }
+
+ // Step 4.
+ if (!ParseISODateTime(cx, parsed, result)) {
+ return false;
+ }
+
+ if (parsed.calendar.present()) {
+ calendar.set(ToString(cx, linear, parsed.calendar));
+ if (!calendar) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/**
+ * ParseTemporalDateString ( isoString )
+ */
+bool js::temporal::ParseTemporalDateString(JSContext* cx, Handle<JSString*> str,
+ PlainDate* result,
+ MutableHandle<JSString*> calendar) {
+ // Step 1.
+ PlainDateTime dateTime;
+ if (!ParseTemporalDateTimeString(cx, str, &dateTime, calendar)) {
+ return false;
+ }
+
+ // Step 2.
+ *result = dateTime.date;
+ return true;
+}
+
+template <typename CharT>
+mozilla::Result<ZonedDateTimeString, ParserError>
+TemporalParser<CharT>::parseTemporalZonedDateTimeString() {
+ // Parse goal: TemporalDateTimeString[+Zoned]
+ //
+ // TemporalDateTimeString[Zoned] :
+ // AnnotatedDateTime[?Zoned]
+ //
+ // AnnotatedDateTime[Zoned] :
+ // [~Zoned] DateTime TimeZoneAnnotation? Annotations?
+ // [+Zoned] DateTime TimeZoneAnnotation Annotations?
+
+ auto dt = dateTime();
+ if (dt.isErr()) {
+ return dt.propagateErr();
+ }
+ auto result = dt.unwrap();
+
+ auto annotation = timeZoneAnnotation();
+ if (annotation.isErr()) {
+ return annotation.propagateErr();
+ }
+ result.timeZone.annotation = annotation.unwrap();
+
+ if (hasAnnotationStart()) {
+ auto cal = annotations();
+ if (cal.isErr()) {
+ return cal.propagateErr();
+ }
+ result.calendar = cal.unwrap();
+ }
+
+ if (!reader_.atEnd()) {
+ return mozilla::Err(JSMSG_TEMPORAL_PARSER_GARBAGE_AFTER_INPUT);
+ }
+
+ return result;
+}
+
+/**
+ * ParseTemporalZonedDateTimeString ( isoString )
+ */
+template <typename CharT>
+static auto ParseTemporalZonedDateTimeString(mozilla::Span<const CharT> str) {
+ TemporalParser<CharT> parser(str);
+ return parser.parseTemporalZonedDateTimeString();
+}
+
+/**
+ * ParseTemporalZonedDateTimeString ( isoString )
+ */
+static auto ParseTemporalZonedDateTimeString(Handle<JSLinearString*> str) {
+ JS::AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars()) {
+ return ParseTemporalZonedDateTimeString<Latin1Char>(str->latin1Range(nogc));
+ }
+ return ParseTemporalZonedDateTimeString<char16_t>(str->twoByteRange(nogc));
+}
+
+/**
+ * ParseTemporalZonedDateTimeString ( isoString )
+ */
+bool js::temporal::ParseTemporalZonedDateTimeString(
+ JSContext* cx, Handle<JSString*> str, PlainDateTime* dateTime, bool* isUTC,
+ bool* hasOffset, int64_t* timeZoneOffset,
+ MutableHandle<ParsedTimeZone> timeZoneName,
+ MutableHandle<JSString*> calendar) {
+ Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
+ if (!linear) {
+ return false;
+ }
+
+ // Step 1.
+ auto parseResult = ::ParseTemporalZonedDateTimeString(linear);
+ if (parseResult.isErr()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ parseResult.unwrapErr());
+ return false;
+ }
+ ZonedDateTimeString parsed = parseResult.unwrap();
+
+ // Step 2. (ParseISODateTime, steps 1-18.)
+ if (!ParseISODateTime(cx, parsed, dateTime)) {
+ return false;
+ }
+
+ // Step 2. (ParseISODateTime, steps 19-21.)
+ {
+ MOZ_ASSERT(parsed.timeZone.hasAnnotation());
+
+ // Case 1: 19700101T00:00Z[+02:00]
+ // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "+02:00" }
+ //
+ // Case 2: 19700101T00:00+02:00[+02:00]
+ // { [[Z]]: false, [[OffsetString]]: "+02:00", [[Name]]: "+02:00" }
+ //
+ // Case 3: 19700101[+02:00]
+ // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "+02:00" }
+ //
+ // Case 4: 19700101T00:00Z[Europe/Berlin]
+ // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" }
+ //
+ // Case 5: 19700101T00:00+01:00[Europe/Berlin]
+ // { [[Z]]: false, [[OffsetString]]: "+01:00", [[Name]]: "Europe/Berlin" }
+ //
+ // Case 6: 19700101[Europe/Berlin]
+ // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" }
+
+ const auto& annotation = parsed.timeZone.annotation;
+ if (!ParseTimeZoneAnnotation(cx, annotation, linear, timeZoneName)) {
+ return false;
+ }
+
+ if (parsed.timeZone.isUTC()) {
+ *isUTC = true;
+ *hasOffset = false;
+ *timeZoneOffset = 0;
+ } else if (parsed.timeZone.hasOffset()) {
+ *isUTC = false;
+ *hasOffset = true;
+ *timeZoneOffset = ParseDateTimeUTCOffset(parsed.timeZone.offset);
+ } else {
+ *isUTC = false;
+ *hasOffset = false;
+ *timeZoneOffset = 0;
+ }
+ }
+
+ // Step 2. (ParseISODateTime, steps 23-24.)
+ if (parsed.calendar.present()) {
+ calendar.set(ToString(cx, linear, parsed.calendar));
+ if (!calendar) {
+ return false;
+ }
+ }
+
+ // Step 2. (ParseISODateTime, step 25.)
+ return true;
+}
+
+/**
+ * ParseTemporalRelativeToString ( isoString )
+ */
+template <typename CharT>
+static auto ParseTemporalRelativeToString(mozilla::Span<const CharT> str) {
+ TemporalParser<CharT> parser(str);
+ return parser.parseTemporalDateTimeString();
+}
+
+/**
+ * ParseTemporalRelativeToString ( isoString )
+ */
+static auto ParseTemporalRelativeToString(Handle<JSLinearString*> str) {
+ JS::AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars()) {
+ return ParseTemporalRelativeToString<Latin1Char>(str->latin1Range(nogc));
+ }
+ return ParseTemporalRelativeToString<char16_t>(str->twoByteRange(nogc));
+}
+
+/**
+ * ParseTemporalRelativeToString ( isoString )
+ */
+bool js::temporal::ParseTemporalRelativeToString(
+ JSContext* cx, Handle<JSString*> str, PlainDateTime* dateTime, bool* isUTC,
+ bool* hasOffset, int64_t* timeZoneOffset,
+ MutableHandle<ParsedTimeZone> timeZoneName,
+ MutableHandle<JSString*> calendar) {
+ Rooted<JSLinearString*> linear(cx, str->ensureLinear(cx));
+ if (!linear) {
+ return false;
+ }
+
+ // Steps 1-2.
+ auto parseResult = ::ParseTemporalRelativeToString(linear);
+ if (parseResult.isErr()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ parseResult.unwrapErr());
+ return false;
+ }
+ ZonedDateTimeString parsed = parseResult.unwrap();
+
+ // Step 3.
+ if (parsed.timeZone.isUTC() && !parsed.timeZone.hasAnnotation()) {
+ JS_ReportErrorNumberASCII(
+ cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_PARSER_INVALID_UTC_DESIGNATOR_WITHOUT_NAME);
+ return false;
+ }
+
+ // Step 4. (ParseISODateTime, steps 1-18.)
+ if (!ParseISODateTime(cx, parsed, dateTime)) {
+ return false;
+ }
+
+ // Step 4. (ParseISODateTime, steps 19-22.)
+ if (parsed.timeZone.hasAnnotation()) {
+ // Case 1: 19700101Z[+02:00]
+ // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "+02:00" }
+ //
+ // Case 2: 19700101+00:00[+02:00]
+ // { [[Z]]: false, [[OffsetString]]: "+00:00", [[Name]]: "+02:00" }
+ //
+ // Case 3: 19700101[+02:00]
+ // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "+02:00" }
+ //
+ // Case 4: 19700101Z[Europe/Berlin]
+ // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" }
+ //
+ // Case 5: 19700101+00:00[Europe/Berlin]
+ // { [[Z]]: false, [[OffsetString]]: "+00:00", [[Name]]: "Europe/Berlin" }
+ //
+ // Case 6: 19700101[Europe/Berlin]
+ // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" }
+
+ const auto& annotation = parsed.timeZone.annotation;
+ if (!ParseTimeZoneAnnotation(cx, annotation, linear, timeZoneName)) {
+ return false;
+ }
+
+ if (parsed.timeZone.isUTC()) {
+ *isUTC = true;
+ *hasOffset = false;
+ *timeZoneOffset = 0;
+ } else if (parsed.timeZone.hasOffset()) {
+ *isUTC = false;
+ *hasOffset = true;
+ *timeZoneOffset = ParseDateTimeUTCOffset(parsed.timeZone.offset);
+ } else {
+ *isUTC = false;
+ *hasOffset = false;
+ *timeZoneOffset = 0;
+ }
+ } else {
+ // ToRelativeTemporalObject ignores any other time zone information when no
+ // bracketed time zone annotation is present.
+
+ *isUTC = false;
+ *hasOffset = false;
+ *timeZoneOffset = 0;
+ timeZoneName.set(ParsedTimeZone{});
+ }
+
+ // Step 4. (ParseISODateTime, steps 23-24.)
+ if (parsed.calendar.present()) {
+ calendar.set(ToString(cx, linear, parsed.calendar));
+ if (!calendar) {
+ return false;
+ }
+ }
+
+ // Step 4. (Return)
+ return true;
+}
+
+void js::temporal::ParsedTimeZone::trace(JSTracer* trc) {
+ TraceNullableRoot(trc, &name, "ParsedTimeZone::name");
+}
diff --git a/js/src/builtin/temporal/TemporalParser.h b/js/src/builtin/temporal/TemporalParser.h
new file mode 100644
index 0000000000..677a90b58d
--- /dev/null
+++ b/js/src/builtin/temporal/TemporalParser.h
@@ -0,0 +1,166 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_TemporalParser_h
+#define builtin_temporal_TemporalParser_h
+
+#include "mozilla/Assertions.h"
+
+#include <cstdlib>
+#include <stdint.h>
+
+#include "builtin/temporal/TemporalUnit.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+
+class JSLinearString;
+class JS_PUBLIC_API JSTracer;
+
+namespace js::temporal {
+
+struct Duration;
+struct PlainDate;
+struct PlainDateTime;
+struct PlainTime;
+
+struct ParsedTimeZone {
+ JSLinearString* name = nullptr;
+ int32_t offset = INT32_MIN;
+
+ void trace(JSTracer* trc);
+
+ static ParsedTimeZone fromName(JSLinearString* name) {
+ MOZ_ASSERT(name);
+ return {name, 0};
+ }
+
+ static ParsedTimeZone fromOffset(int32_t offset) {
+ MOZ_ASSERT(std::abs(offset) < UnitsPerDay(TemporalUnit::Minute));
+ return {nullptr, offset};
+ }
+
+ explicit operator bool() const {
+ return name != nullptr || offset != INT32_MIN;
+ }
+};
+
+/**
+ * ParseTemporalInstantString ( isoString )
+ */
+bool ParseTemporalInstantString(JSContext* cx, JS::Handle<JSString*> str,
+ PlainDateTime* result, int64_t* offset);
+
+/**
+ * ParseTemporalTimeZoneString ( timeZoneString )
+ */
+bool ParseTemporalTimeZoneString(JSContext* cx, JS::Handle<JSString*> str,
+ JS::MutableHandle<ParsedTimeZone> result);
+
+/**
+ * ParseTimeZoneIdentifier ( identifier )
+ */
+bool ParseTimeZoneIdentifier(JSContext* cx, JS::Handle<JSString*> str,
+ JS::MutableHandle<ParsedTimeZone> result);
+
+/**
+ * ParseTimeZoneOffsetString ( isoString )
+ */
+bool ParseTimeZoneOffsetString(JSContext* cx, JS::Handle<JSString*> str,
+ int32_t* result);
+
+/**
+ * ParseDateTimeUTCOffset ( offsetString )
+ */
+bool ParseDateTimeUTCOffset(JSContext* cx, JS::Handle<JSString*> str,
+ int64_t* result);
+
+/**
+ * ParseTemporalDurationString ( isoString )
+ */
+bool ParseTemporalDurationString(JSContext* cx, JS::Handle<JSString*> str,
+ Duration* result);
+
+/**
+ * ParseTemporalCalendarString ( isoString )
+ */
+JSLinearString* ParseTemporalCalendarString(JSContext* cx,
+ JS::Handle<JSString*> str);
+
+/**
+ * ParseTemporalTimeString ( isoString )
+ */
+bool ParseTemporalTimeString(JSContext* cx, JS::Handle<JSString*> str,
+ PlainTime* result);
+
+/**
+ * ParseTemporalDateString ( isoString )
+ */
+bool ParseTemporalDateString(JSContext* cx, JS::Handle<JSString*> str,
+ PlainDate* result,
+ JS::MutableHandle<JSString*> calendar);
+
+/**
+ * ParseTemporalMonthDayString ( isoString )
+ */
+bool ParseTemporalMonthDayString(JSContext* cx, JS::Handle<JSString*> str,
+ PlainDate* result, bool* hasYear,
+ JS::MutableHandle<JSString*> calendar);
+
+/**
+ * ParseTemporalYearMonthString ( isoString )
+ */
+bool ParseTemporalYearMonthString(JSContext* cx, JS::Handle<JSString*> str,
+ PlainDate* result,
+ JS::MutableHandle<JSString*> calendar);
+
+/**
+ * ParseTemporalDateTimeString ( isoString )
+ */
+bool ParseTemporalDateTimeString(JSContext* cx, JS::Handle<JSString*> str,
+ PlainDateTime* result,
+ JS::MutableHandle<JSString*> calendar);
+
+/**
+ * ParseTemporalZonedDateTimeString ( isoString )
+ */
+bool ParseTemporalZonedDateTimeString(
+ JSContext* cx, JS::Handle<JSString*> str, PlainDateTime* dateTime,
+ bool* isUTC, bool* hasOffset, int64_t* timeZoneOffset,
+ JS::MutableHandle<ParsedTimeZone> timeZoneName,
+ JS::MutableHandle<JSString*> calendar);
+
+/**
+ * ParseTemporalRelativeToString ( isoString )
+ */
+bool ParseTemporalRelativeToString(
+ JSContext* cx, JS::Handle<JSString*> str, PlainDateTime* dateTime,
+ bool* isUTC, bool* hasOffset, int64_t* timeZoneOffset,
+ JS::MutableHandle<ParsedTimeZone> timeZoneName,
+ JS::MutableHandle<JSString*> calendar);
+
+} /* namespace js::temporal */
+
+namespace js {
+
+template <typename Wrapper>
+class WrappedPtrOperations<temporal::ParsedTimeZone, Wrapper> {
+ const auto& object() const {
+ return static_cast<const Wrapper*>(this)->get();
+ }
+
+ public:
+ JS::Handle<JSLinearString*> name() const {
+ return JS::Handle<JSLinearString*>::fromMarkedLocation(&object().name);
+ }
+
+ int32_t offset() const { return object().offset; }
+
+ explicit operator bool() const { return bool(object()); }
+};
+
+} /* namespace js */
+
+#endif /* builtin_temporal_TemporalParser_h */
diff --git a/js/src/builtin/temporal/TemporalRoundingMode.h b/js/src/builtin/temporal/TemporalRoundingMode.h
new file mode 100644
index 0000000000..91ef758fc6
--- /dev/null
+++ b/js/src/builtin/temporal/TemporalRoundingMode.h
@@ -0,0 +1,433 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_TemporalRoundingMode_h
+#define builtin_temporal_TemporalRoundingMode_h
+
+#include "mozilla/Assertions.h"
+
+#include <cmath>
+#include <stdint.h>
+
+namespace js::temporal {
+
+// Overview of integer rounding modes is available at
+// <https://en.wikipedia.org/wiki/Rounding#Rounding_to_integer>.
+enum class TemporalRoundingMode {
+ // 1. Directed rounding to an integer.
+
+ // Round toward positive infinity.
+ Ceil,
+
+ // Round toward negative infinity.
+ Floor,
+
+ // Round toward infinity or round away from zero.
+ Expand,
+
+ // Round toward zero or round away from infinity.
+ Trunc,
+
+ // 2. Rounding to the nearest integer.
+
+ // Round half toward positive infinity.
+ HalfCeil,
+
+ // Round half toward negative infinity.
+ HalfFloor,
+
+ // Round half toward infinity or round half away from zero.
+ HalfExpand,
+
+ // Round half toward zero or round half away from infinity.
+ HalfTrunc,
+
+ // Round half to even.
+ HalfEven,
+};
+
+/**
+ * NegateTemporalRoundingMode ( roundingMode )
+ */
+constexpr auto NegateTemporalRoundingMode(TemporalRoundingMode roundingMode) {
+ // Steps 1-5.
+ switch (roundingMode) {
+ case TemporalRoundingMode::Ceil:
+ return TemporalRoundingMode::Floor;
+
+ case TemporalRoundingMode::Floor:
+ return TemporalRoundingMode::Ceil;
+
+ case TemporalRoundingMode::HalfCeil:
+ return TemporalRoundingMode::HalfFloor;
+
+ case TemporalRoundingMode::HalfFloor:
+ return TemporalRoundingMode::HalfCeil;
+
+ case TemporalRoundingMode::Expand:
+ case TemporalRoundingMode::Trunc:
+ case TemporalRoundingMode::HalfExpand:
+ case TemporalRoundingMode::HalfTrunc:
+ case TemporalRoundingMode::HalfEven:
+ return roundingMode;
+ }
+ MOZ_CRASH("invalid rounding mode");
+}
+
+/**
+ * Adjust the rounding mode to round negative values in the same direction as
+ * positive values.
+ */
+constexpr auto ToPositiveRoundingMode(TemporalRoundingMode roundingMode) {
+ switch (roundingMode) {
+ case TemporalRoundingMode::Ceil:
+ case TemporalRoundingMode::Floor:
+ case TemporalRoundingMode::HalfCeil:
+ case TemporalRoundingMode::HalfFloor:
+ case TemporalRoundingMode::HalfEven:
+ // (Half-)Ceil/Floor round toward the same infinity for negative and
+ // positive values, so the rounding mode doesn't need to be adjusted. The
+ // same applies for half-even rounding.
+ return roundingMode;
+
+ case TemporalRoundingMode::Expand:
+ // Expand rounds positive values toward +infinity, but negative values
+ // toward -infinity. Adjust the rounding mode to Ceil to round negative
+ // values in the same direction as positive values.
+ return TemporalRoundingMode::Ceil;
+
+ case TemporalRoundingMode::Trunc:
+ // Truncation rounds positive values down toward zero, but negative values
+ // up toward zero. Adjust the rounding mode to Floor to round negative
+ // values in the same direction as positive values.
+ return TemporalRoundingMode::Floor;
+
+ case TemporalRoundingMode::HalfExpand:
+ // Adjust the rounding mode to Half-Ceil, similar to the Expand case.
+ return TemporalRoundingMode::HalfCeil;
+
+ case TemporalRoundingMode::HalfTrunc:
+ // Adjust the rounding mode to Half-Floor, similar to the Trunc case.
+ return TemporalRoundingMode::HalfFloor;
+ }
+ MOZ_CRASH("unexpected rounding mode");
+}
+
+// Temporal performs division on "mathematical values" [1] with implies using
+// infinite precision. This rules out using IEE-754 floating point types like
+// `double`. It also means we can't implement the algorithms from the
+// specification verbatim, but instead have to translate them into equivalent
+// operations.
+//
+// Throughout the following division functions, the divisor is required to be
+// positive. This allows to simplify the implementation, because it ensures
+// non-zero quotient and remainder values have the same sign as the dividend.
+//
+// [1] https://tc39.es/ecma262/#mathematical-value
+
+/**
+ * Compute ceiling division โŒˆdividend / divisorโŒ‰. The divisor must be a positive
+ * number.
+ */
+constexpr int64_t CeilDiv(int64_t dividend, int64_t divisor) {
+ MOZ_ASSERT(divisor > 0, "negative divisor not supported");
+
+ // NB: Division and modulo operation are fused into a single machine code
+ // instruction by the compiler.
+ int64_t quotient = dividend / divisor;
+ int64_t remainder = dividend % divisor;
+
+ // Ceiling division rounds the quotient toward positive infinity. When the
+ // quotient is negative, this is equivalent to rounding toward zero. See [1].
+ //
+ // int64_t division truncates, so rounding toward zero for negative quotients
+ // is already covered. When there is a non-zero positive remainder, the
+ // quotient is positive and we have to increment it by one to implement
+ // rounding toward positive infinity.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ if (remainder > 0) {
+ quotient += 1;
+ }
+ return quotient;
+}
+
+/**
+ * Compute floor division โŒŠdividend / divisorโŒ‹. The divisor must be a positive
+ * number.
+ */
+constexpr int64_t FloorDiv(int64_t dividend, int64_t divisor) {
+ MOZ_ASSERT(divisor > 0, "negative divisor not supported");
+
+ // NB: Division and modulo operation are fused into a single machine code
+ // instruction by the compiler.
+ int64_t quotient = dividend / divisor;
+ int64_t remainder = dividend % divisor;
+
+ // Floor division rounds the quotient toward negative infinity. When the
+ // quotient is positive, this is equivalent to rounding toward zero. See [1].
+ //
+ // int64_t division truncates, so rounding toward zero for positive quotients
+ // is already covered. When there is a non-zero negative remainder, the
+ // quotient is negative and we have to decrement it by one to implement
+ // rounding toward negative infinity.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ if (remainder < 0) {
+ quotient -= 1;
+ }
+ return quotient;
+}
+
+/**
+ * Compute "round toward infinity" division `dividend / divisor`. The divisor
+ * must be a positive number.
+ */
+constexpr int64_t ExpandDiv(int64_t dividend, int64_t divisor) {
+ MOZ_ASSERT(divisor > 0, "negative divisor not supported");
+
+ // NB: Division and modulo operation are fused into a single machine code
+ // instruction by the compiler.
+ int64_t quotient = dividend / divisor;
+ int64_t remainder = dividend % divisor;
+
+ // "Round toward infinity" division rounds positive quotients toward positive
+ // infinity and negative quotients toward negative infinity. See [1].
+ //
+ // When there is a non-zero positive remainder, the quotient is positive and
+ // we have to increment it by one to implement rounding toward positive
+ // infinity. When there is a non-zero negative remainder, the quotient is
+ // negative and we have to decrement it by one to implement rounding toward
+ // negative infinity.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ if (remainder > 0) {
+ quotient += 1;
+ }
+ if (remainder < 0) {
+ quotient -= 1;
+ }
+ return quotient;
+}
+
+/**
+ * Compute truncating division `dividend / divisor`. The divisor must be a
+ * positive number.
+ */
+constexpr int64_t TruncDiv(int64_t dividend, int64_t divisor) {
+ MOZ_ASSERT(divisor > 0, "negative divisor not supported");
+
+ // Truncating division rounds both positive and negative quotients toward
+ // zero, cf. [1].
+ //
+ // int64_t division truncates, so rounding toward zero implicitly happens.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ return dividend / divisor;
+}
+
+/**
+ * Compute "round half toward positive infinity" division `dividend / divisor`.
+ * The divisor must be a positive number.
+ */
+inline int64_t HalfCeilDiv(int64_t dividend, int64_t divisor) {
+ MOZ_ASSERT(divisor > 0, "negative divisor not supported");
+
+ // NB: Division and modulo operation are fused into a single machine code
+ // instruction by the compiler.
+ int64_t quotient = dividend / divisor;
+ int64_t remainder = dividend % divisor;
+
+ // "Round half toward positive infinity" division rounds the quotient toward
+ // positive infinity when the fractional part of the remainder is โ‰ฅ0.5. When
+ // the quotient is negative, this is equivalent to rounding toward zero
+ // instead of toward positive infinity. See [1].
+ //
+ // When the remainder is a non-zero positive value, the quotient is positive,
+ // too. When additionally the fractional part of the remainder is โ‰ฅ0.5, we
+ // have to increment the quotient by one to implement rounding toward positive
+ // infinity.
+ //
+ // int64_t division truncates, so we implicitly round toward zero for negative
+ // quotients. When the absolute value of the fractional part of the remainder
+ // is >0.5, we should instead have rounded toward negative infinity, so we
+ // need to decrement the quotient by one.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ if (remainder > 0 && uint64_t(std::abs(remainder)) * 2 >= uint64_t(divisor)) {
+ quotient += 1;
+ }
+ if (remainder < 0 && uint64_t(std::abs(remainder)) * 2 > uint64_t(divisor)) {
+ quotient -= 1;
+ }
+ return quotient;
+}
+
+/**
+ * Compute "round half toward negative infinity" division `dividend / divisor`.
+ * The divisor must be a positive number.
+ */
+inline int64_t HalfFloorDiv(int64_t dividend, int64_t divisor) {
+ MOZ_ASSERT(divisor > 0, "negative divisor not supported");
+
+ // NB: Division and modulo operation are fused into a single machine code
+ // instruction by the compiler.
+ int64_t quotient = dividend / divisor;
+ int64_t remainder = dividend % divisor;
+
+ // "Round half toward negative infinity" division rounds the quotient toward
+ // negative infinity when the fractional part of the remainder is โ‰ฅ0.5. When
+ // the quotient is positive, this is equivalent to rounding toward zero
+ // instead of toward negative infinity. See [1].
+ //
+ // When the remainder is a non-zero negative value, the quotient is negative,
+ // too. When additionally the fractional part of the remainder is โ‰ฅ0.5, we
+ // have to decrement the quotient by one to implement rounding toward negative
+ // infinity.
+ //
+ // int64_t division truncates, so we implicitly round toward zero for positive
+ // quotients. When the absolute value of the fractional part of the remainder
+ // is >0.5, we should instead have rounded toward positive infinity, so we
+ // need to increment the quotient by one.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ if (remainder < 0 && uint64_t(std::abs(remainder)) * 2 >= uint64_t(divisor)) {
+ quotient -= 1;
+ }
+ if (remainder > 0 && uint64_t(std::abs(remainder)) * 2 > uint64_t(divisor)) {
+ quotient += 1;
+ }
+ return quotient;
+}
+
+/**
+ * Compute "round half toward infinity" division `dividend / divisor`. The
+ * divisor must be a positive number.
+ */
+inline int64_t HalfExpandDiv(int64_t dividend, int64_t divisor) {
+ MOZ_ASSERT(divisor > 0, "negative divisor not supported");
+
+ // NB: Division and modulo operation are fused into a single machine code
+ // instruction by the compiler.
+ int64_t quotient = dividend / divisor;
+ int64_t remainder = dividend % divisor;
+
+ // "Round half toward infinity" division rounds positive quotients whose
+ // remainder has a fractional part โ‰ฅ0.5 toward positive infinity. And negative
+ // quotients whose remainder has a fractional part โ‰ฅ0.5 toward negative
+ // infinity. See [1].
+ //
+ // int64_t division truncates, which means it rounds toward zero, so we have
+ // to increment resp. decrement the quotient when the fractional part of the
+ // remainder is โ‰ฅ0.5 to round toward ยฑinfinity.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ if (uint64_t(std::abs(remainder)) * 2 >= uint64_t(divisor)) {
+ quotient += (dividend > 0) ? 1 : -1;
+ }
+ return quotient;
+}
+
+/**
+ * Compute "round half toward zero" division `dividend / divisor`. The divisor
+ * must be a positive number.
+ */
+inline int64_t HalfTruncDiv(int64_t dividend, int64_t divisor) {
+ MOZ_ASSERT(divisor > 0, "negative divisor not supported");
+
+ // NB: Division and modulo operation are fused into a single machine code
+ // instruction by the compiler.
+ int64_t quotient = dividend / divisor;
+ int64_t remainder = dividend % divisor;
+
+ // "Round half toward zero" division rounds both positive and negative
+ // quotients whose remainder has a fractional part โ‰ค0.5 toward zero. See [1].
+ //
+ // int64_t division truncates, so we implicitly round toward zero. When the
+ // fractional part of the remainder is >0.5, we should instead have rounded
+ // toward ยฑinfinity, so we need to increment resp. decrement the quotient by
+ // one.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ if (uint64_t(std::abs(remainder)) * 2 > uint64_t(divisor)) {
+ quotient += (dividend > 0) ? 1 : -1;
+ }
+ return quotient;
+}
+
+/**
+ * Compute "round half to even" division `dividend / divisor`. The divisor must
+ * be a positive number.
+ */
+inline int64_t HalfEvenDiv(int64_t dividend, int64_t divisor) {
+ MOZ_ASSERT(divisor > 0, "negative divisor not supported");
+
+ // NB: Division and modulo operation are fused into a single machine code
+ // instruction by the compiler.
+ int64_t quotient = dividend / divisor;
+ int64_t remainder = dividend % divisor;
+
+ // "Round half to even" division rounds both positive and negative quotients
+ // to the nearest even integer. See [1].
+ //
+ // int64_t division truncates, so we implicitly round toward zero. When the
+ // fractional part of the remainder is 0.5 and the quotient is odd or when the
+ // fractional part of the remainder is >0.5, we should instead have rounded
+ // toward ยฑinfinity, so we need to increment resp. decrement the quotient by
+ // one.
+ //
+ // [1]
+ // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
+ if ((quotient & 1) == 1 &&
+ uint64_t(std::abs(remainder)) * 2 == uint64_t(divisor)) {
+ quotient += (dividend > 0) ? 1 : -1;
+ }
+ if (uint64_t(std::abs(remainder)) * 2 > uint64_t(divisor)) {
+ quotient += (dividend > 0) ? 1 : -1;
+ }
+ return quotient;
+}
+
+/**
+ * Perform `dividend / divisor` and round the result according to the given
+ * rounding mode.
+ */
+inline int64_t Divide(int64_t dividend, int64_t divisor,
+ TemporalRoundingMode roundingMode) {
+ switch (roundingMode) {
+ case TemporalRoundingMode::Ceil:
+ return CeilDiv(dividend, divisor);
+ case TemporalRoundingMode::Floor:
+ return FloorDiv(dividend, divisor);
+ case TemporalRoundingMode::Expand:
+ return ExpandDiv(dividend, divisor);
+ case TemporalRoundingMode::Trunc:
+ return TruncDiv(dividend, divisor);
+ case TemporalRoundingMode::HalfCeil:
+ return HalfCeilDiv(dividend, divisor);
+ case TemporalRoundingMode::HalfFloor:
+ return HalfFloorDiv(dividend, divisor);
+ case TemporalRoundingMode::HalfExpand:
+ return HalfExpandDiv(dividend, divisor);
+ case TemporalRoundingMode::HalfTrunc:
+ return HalfTruncDiv(dividend, divisor);
+ case TemporalRoundingMode::HalfEven:
+ return HalfEvenDiv(dividend, divisor);
+ }
+ MOZ_CRASH("invalid rounding mode");
+}
+
+} /* namespace js::temporal */
+
+#endif /* builtin_temporal_TemporalRoundingMode_h */
diff --git a/js/src/builtin/temporal/TemporalTypes.h b/js/src/builtin/temporal/TemporalTypes.h
new file mode 100644
index 0000000000..654fd65a3b
--- /dev/null
+++ b/js/src/builtin/temporal/TemporalTypes.h
@@ -0,0 +1,546 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_TemporalTypes_h
+#define builtin_temporal_TemporalTypes_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+
+#include <stdint.h>
+
+#include "builtin/temporal/TemporalUnit.h"
+
+namespace js::temporal {
+
+// Use __builtin_assume when available, otherwise fall back to
+// __builtin_unreachable.
+#if defined __has_builtin
+# if __has_builtin(__builtin_assume)
+# define JS_ASSUME(x) __builtin_assume(x)
+# elif __has_builtin(__builtin_unreachable)
+# define JS_ASSUME(x) \
+ if (!(x)) __builtin_unreachable()
+# else
+# endif
+#endif
+
+// Fallback to no-op if neither built-in is available.
+#ifndef JS_ASSUME
+# define JS_ASSUME(x) \
+ do { \
+ } while (false)
+#endif
+
+/**
+ * Struct to represent a seconds and nanoseconds value. The nanoseconds value
+ * is normalized to the range [0, 999'999'999].
+ */
+template <typename Derived>
+struct SecondsAndNanoseconds {
+ // Seconds part in the range allowed by the derived class.
+ int64_t seconds = 0;
+
+ // Nanoseconds part in the range [0, 999'999'999].
+ int32_t nanoseconds = 0;
+
+ constexpr bool operator==(const SecondsAndNanoseconds& other) const {
+ return seconds == other.seconds && nanoseconds == other.nanoseconds;
+ }
+
+ constexpr bool operator<(const SecondsAndNanoseconds& other) const {
+ // The compiler can optimize expressions like |instant < Instant{}| to a
+ // single right-shift operation when we propagate the range of nanoseconds.
+ JS_ASSUME(nanoseconds >= 0);
+ JS_ASSUME(other.nanoseconds >= 0);
+ return (seconds < other.seconds) ||
+ (seconds == other.seconds && nanoseconds < other.nanoseconds);
+ }
+
+ // Other operators are implemented in terms of operator== and operator<.
+ constexpr bool operator!=(const SecondsAndNanoseconds& other) const {
+ return !(*this == other);
+ }
+ constexpr bool operator>(const SecondsAndNanoseconds& other) const {
+ return other < *this;
+ }
+ constexpr bool operator<=(const SecondsAndNanoseconds& other) const {
+ return !(other < *this);
+ }
+ constexpr bool operator>=(const SecondsAndNanoseconds& other) const {
+ return !(*this < other);
+ }
+
+ protected:
+ template <typename T, typename U, class R = Derived>
+ static constexpr R add(const SecondsAndNanoseconds<T>& self,
+ const SecondsAndNanoseconds<U>& other) {
+ // The caller needs to make sure integer overflow won't happen. CheckedInt
+ // will assert on overflow and we intentionally don't try to recover from
+ // overflow in this method.
+
+ mozilla::CheckedInt64 secs = self.seconds;
+ secs += other.seconds;
+
+ mozilla::CheckedInt32 nanos = self.nanoseconds;
+ nanos += other.nanoseconds;
+
+ if (nanos.value() >= 1'000'000'000) {
+ secs += 1;
+ nanos -= 1'000'000'000;
+ }
+ MOZ_ASSERT(0 <= nanos.value() && nanos.value() < 1'000'000'000);
+
+ return {secs.value(), nanos.value()};
+ }
+
+ template <class T, class U, class R = Derived>
+ static constexpr R subtract(const SecondsAndNanoseconds<T>& self,
+ const SecondsAndNanoseconds<U>& other) {
+ // The caller needs to make sure integer underflow won't happen. CheckedInt
+ // will assert on underflow and we intentionally don't try to recover from
+ // underflow in this method.
+
+ mozilla::CheckedInt64 secs = self.seconds;
+ secs -= other.seconds;
+
+ mozilla::CheckedInt32 nanos = self.nanoseconds;
+ nanos -= other.nanoseconds;
+
+ if (nanos.value() < 0) {
+ secs -= 1;
+ nanos += 1'000'000'000;
+ }
+ MOZ_ASSERT(0 <= nanos.value() && nanos.value() < 1'000'000'000);
+
+ return {secs.value(), nanos.value()};
+ }
+
+ static constexpr Derived negate(const Derived& self) {
+ return subtract(Derived{}, self);
+ }
+
+ public:
+ /**
+ * Return the absolute value.
+ */
+ constexpr Derived abs() const {
+ int64_t sec = seconds;
+ int32_t nanos = nanoseconds;
+ if (sec < 0) {
+ if (nanos > 0) {
+ sec += 1;
+ nanos -= 1'000'000'000;
+ }
+ sec = -sec;
+ nanos = -nanos;
+ }
+ return {sec, nanos};
+ }
+
+ /**
+ * Return the seconds value, rounded towards zero.
+ */
+ constexpr int64_t toSeconds() const {
+ int64_t sec = seconds;
+ int32_t nanos = nanoseconds;
+ if (sec < 0 && nanos > 0) {
+ sec += 1;
+ }
+ return sec;
+ }
+
+ /**
+ * Return the milliseconds value, rounded towards zero.
+ */
+ constexpr int64_t toMilliseconds() const {
+ int64_t sec = seconds;
+ int32_t nanos = nanoseconds;
+ if (sec < 0 && nanos > 0) {
+ sec += 1;
+ nanos -= 1'000'000'000;
+ }
+ return (sec * 1'000) + (nanos / 1'000'000);
+ }
+
+ /**
+ * Return the microseconds value, rounded towards zero.
+ */
+ constexpr int64_t toMicroseconds() const {
+ int64_t sec = seconds;
+ int32_t nanos = nanoseconds;
+ if (sec < 0 && nanos > 0) {
+ sec += 1;
+ nanos -= 1'000'000'000;
+ }
+ return (sec * 1'000'000) + (nanos / 1'000);
+ }
+
+ /**
+ * Return the nanoseconds value.
+ *
+ * The returned nanoseconds amount can be invalid on overflow. The caller is
+ * responsible for handling the overflow case.
+ */
+ constexpr mozilla::CheckedInt64 toNanoseconds() const {
+ mozilla::CheckedInt64 nanos = seconds;
+ nanos *= ToNanoseconds(TemporalUnit::Second);
+ nanos += nanoseconds;
+ return nanos;
+ }
+
+ /**
+ * Create from a minutes value.
+ */
+ static constexpr Derived fromMinutes(int64_t minutes) {
+ return {minutes * 60, 0};
+ }
+
+ /**
+ * Create from a seconds value.
+ */
+ static constexpr Derived fromSeconds(int64_t seconds) { return {seconds, 0}; }
+
+ /**
+ * Create from a milliseconds value.
+ */
+ static constexpr Derived fromMilliseconds(int64_t milliseconds) {
+ int64_t seconds = milliseconds / 1'000;
+ int32_t millis = milliseconds % 1'000;
+ if (millis < 0) {
+ seconds -= 1;
+ millis += 1'000;
+ }
+ return {seconds, millis * 1'000'000};
+ }
+
+ /**
+ * Create from a microseconds value.
+ */
+ static constexpr Derived fromMicroseconds(int64_t microseconds) {
+ int64_t seconds = microseconds / 1'000'000;
+ int32_t micros = microseconds % 1'000'000;
+ if (micros < 0) {
+ seconds -= 1;
+ micros += 1'000'000;
+ }
+ return {seconds, micros * 1'000};
+ }
+
+ /**
+ * Create from a nanoseconds value.
+ */
+ static constexpr Derived fromNanoseconds(int64_t nanoseconds) {
+ int64_t seconds = nanoseconds / 1'000'000'000;
+ int32_t nanos = nanoseconds % 1'000'000'000;
+ if (nanos < 0) {
+ seconds -= 1;
+ nanos += 1'000'000'000;
+ }
+ return {seconds, nanos};
+ }
+};
+
+#undef JS_ASSUME
+
+/**
+ * InstantSpan represents a span of time between two Instants, measured in
+ * nanoseconds.
+ */
+struct InstantSpan final : SecondsAndNanoseconds<InstantSpan> {
+ constexpr InstantSpan& operator+=(const InstantSpan& other) {
+ *this = add(*this, other);
+ return *this;
+ }
+
+ constexpr InstantSpan& operator-=(const InstantSpan& other) {
+ *this = subtract(*this, other);
+ return *this;
+ }
+
+ constexpr InstantSpan operator+(const InstantSpan& other) const {
+ return add(*this, other);
+ }
+
+ constexpr InstantSpan operator-(const InstantSpan& other) const {
+ return subtract(*this, other);
+ }
+
+ constexpr InstantSpan operator-() const { return negate(*this); }
+
+ /**
+ * Returns the maximum instant span value.
+ */
+ static constexpr InstantSpan max() {
+ // The limit is 2ร—8.64 ร— 10^21 nanoseconds, which is 2ร—8.64 ร— 10^12 seconds.
+ constexpr int64_t seconds = 2 * 8'640'000'000'000;
+ constexpr int64_t nanos = 0;
+ return {seconds, nanos};
+ }
+
+ /**
+ * Returns the minimum instant span value.
+ */
+ static constexpr InstantSpan min() { return -max(); }
+};
+
+/**
+ * Instant represents a time since the epoch value, measured in nanoseconds.
+ *
+ * Instant supports a range of ยฑ8.64 ร— 10^21 nanoseconds, covering ยฑ10^8 days
+ * in either direction relative to midnight at the beginning of 1 January 1970
+ * UTC. The range also exactly matches the supported range of JavaScript Date
+ * objects.
+ *
+ * C++ doesn't provide a built-in type capable of storing an integer in the
+ * range ยฑ8.64 ร— 10^21, therefore we need to create our own abstraction. This
+ * struct follows the design of `std::timespec` and splits the instant into a
+ * signed seconds part and an unsigned nanoseconds part.
+ */
+struct Instant final : SecondsAndNanoseconds<Instant> {
+ constexpr Instant& operator+=(const InstantSpan& other) {
+ *this = add(*this, other);
+ return *this;
+ }
+
+ constexpr Instant& operator-=(const InstantSpan& other) {
+ *this = subtract(*this, other);
+ return *this;
+ }
+
+ constexpr Instant operator+(const InstantSpan& other) const {
+ return add(*this, other);
+ }
+
+ constexpr Instant operator-(const InstantSpan& other) const {
+ return subtract(*this, other);
+ }
+
+ constexpr InstantSpan operator-(const Instant& other) const {
+ return subtract<Instant, Instant, InstantSpan>(*this, other);
+ }
+
+ constexpr Instant operator-() const { return negate(*this); }
+
+ /**
+ * Return this instant as microseconds from the start of the epoch. (Rounds
+ * towards negative infinity.)
+ */
+ constexpr int64_t floorToMicroseconds() const {
+ return (seconds * 1'000'000) + (nanoseconds / 1'000);
+ }
+
+ /**
+ * Return this instant as milliseconds from the start of the epoch. (Rounds
+ * towards negative infinity.)
+ */
+ constexpr int64_t floorToMilliseconds() const {
+ return (seconds * 1'000) + (nanoseconds / 1'000'000);
+ }
+
+ /**
+ * Return this instant as milliseconds from the start of the epoch. (Rounds
+ * towards positive infinity.)
+ */
+ constexpr int64_t ceilToMilliseconds() const {
+ return floorToMilliseconds() + int64_t(nanoseconds % 1'000'000 != 0);
+ }
+
+ /**
+ * Returns the maximum instant value.
+ */
+ static constexpr Instant max() {
+ // The limit is 8.64 ร— 10^21 nanoseconds, which is 8.64 ร— 10^12 seconds.
+ constexpr int64_t seconds = 8'640'000'000'000;
+ constexpr int64_t nanos = 0;
+ return {seconds, nanos};
+ }
+
+ /**
+ * Returns the minimum instant value.
+ */
+ static constexpr Instant min() { return -max(); }
+};
+
+/**
+ * Plain date represents a date in the ISO 8601 calendar.
+ */
+struct PlainDate final {
+ // [-271821, 275760]
+ //
+ // Dates are limited to the range of ยฑ100'000'000 days relative to midnight at
+ // the beginning of 1 January 1970 UTC. This limits valid years to [-271821,
+ // 275760].
+ int32_t year = 0;
+
+ // [1, 12]
+ int32_t month = 0;
+
+ // [1, 31]
+ int32_t day = 0;
+
+ bool operator==(const PlainDate& other) const {
+ return year == other.year && month == other.month && day == other.day;
+ }
+
+ bool operator!=(const PlainDate& other) const { return !(*this == other); }
+};
+
+/**
+ * Plain time represents a time value on a 24-hour clock. Leap seconds aren't
+ * supported.
+ */
+struct PlainTime final {
+ // [0, 23]
+ int32_t hour = 0;
+
+ // [0, 59]
+ int32_t minute = 0;
+
+ // [0, 59]
+ int32_t second = 0;
+
+ // [0, 999]
+ int32_t millisecond = 0;
+
+ // [0, 999]
+ int32_t microsecond = 0;
+
+ // [0, 999]
+ int32_t nanosecond = 0;
+
+ bool operator==(const PlainTime& other) const {
+ return hour == other.hour && minute == other.minute &&
+ second == other.second && millisecond == other.millisecond &&
+ microsecond == other.microsecond && nanosecond == other.nanosecond;
+ }
+
+ bool operator!=(const PlainTime& other) const { return !(*this == other); }
+};
+
+/**
+ * Plain date-time represents a date-time value in the ISO 8601 calendar.
+ */
+struct PlainDateTime final {
+ PlainDate date;
+ PlainTime time;
+
+ bool operator==(const PlainDateTime& other) const {
+ return date == other.date && time == other.time;
+ }
+
+ bool operator!=(const PlainDateTime& other) const {
+ return !(*this == other);
+ }
+};
+
+/**
+ * Duration represents the difference between dates or times. Each duration
+ * component is an integer and all components must have the same sign.
+ */
+struct Duration final {
+ double years = 0;
+ double months = 0;
+ double weeks = 0;
+ double days = 0;
+ double hours = 0;
+ double minutes = 0;
+ double seconds = 0;
+ double milliseconds = 0;
+ double microseconds = 0;
+ double nanoseconds = 0;
+
+ bool operator==(const Duration& other) const {
+ return years == other.years && months == other.months &&
+ weeks == other.weeks && days == other.days && hours == other.hours &&
+ minutes == other.minutes && seconds == other.seconds &&
+ milliseconds == other.milliseconds &&
+ microseconds == other.microseconds &&
+ nanoseconds == other.nanoseconds;
+ }
+
+ bool operator!=(const Duration& other) const { return !(*this == other); }
+
+ /**
+ * Return the date components of this duration.
+ */
+ Duration date() const { return {years, months, weeks, days}; }
+
+ /**
+ * Return the time components of this duration.
+ */
+ Duration time() const {
+ return {
+ 0,
+ 0,
+ 0,
+ 0,
+ hours,
+ minutes,
+ seconds,
+ milliseconds,
+ microseconds,
+ nanoseconds,
+ };
+ }
+
+ /**
+ * Return a new duration with every component negated.
+ */
+ Duration negate() const {
+ // Add zero to convert -0 to +0.
+ return {
+ -years + (+0.0), -months + (+0.0), -weeks + (+0.0),
+ -days + (+0.0), -hours + (+0.0), -minutes + (+0.0),
+ -seconds + (+0.0), -milliseconds + (+0.0), -microseconds + (+0.0),
+ -nanoseconds + (+0.0),
+ };
+ }
+};
+
+/**
+ * Date duration represents the difference between dates. Each duration
+ * component is an integer and all components must have the same sign.
+ */
+struct DateDuration final {
+ double years = 0;
+ double months = 0;
+ double weeks = 0;
+ double days = 0;
+
+ Duration toDuration() { return {years, months, weeks, days}; }
+};
+
+/**
+ * Time duration represents the difference between times. Each duration
+ * component is an integer and all components must have the same sign.
+ */
+struct TimeDuration final {
+ double days = 0;
+ double hours = 0;
+ double minutes = 0;
+ double seconds = 0;
+ double milliseconds = 0;
+ double microseconds = 0;
+ double nanoseconds = 0;
+
+ Duration toDuration() {
+ return {0,
+ 0,
+ 0,
+ days,
+ hours,
+ minutes,
+ seconds,
+ milliseconds,
+ microseconds,
+ nanoseconds};
+ }
+};
+
+} /* namespace js::temporal */
+
+#endif /* builtin_temporal_TemporalTypes_h */
diff --git a/js/src/builtin/temporal/TemporalUnit.h b/js/src/builtin/temporal/TemporalUnit.h
new file mode 100644
index 0000000000..3c8801cb85
--- /dev/null
+++ b/js/src/builtin/temporal/TemporalUnit.h
@@ -0,0 +1,135 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_TemporalUnit_h
+#define builtin_temporal_TemporalUnit_h
+
+#include "mozilla/Assertions.h"
+
+#include <stdint.h>
+
+namespace js::temporal {
+enum class TemporalUnit {
+ Auto,
+ Year,
+ Month,
+ Week,
+ Day,
+ Hour,
+ Minute,
+ Second,
+ Millisecond,
+ Microsecond,
+ Nanosecond
+};
+
+constexpr int64_t ToNanoseconds(TemporalUnit unit) {
+ switch (unit) {
+ case TemporalUnit::Day:
+ return 86'400'000'000'000;
+ case TemporalUnit::Hour:
+ return 3'600'000'000'000;
+ case TemporalUnit::Minute:
+ return 60'000'000'000;
+ case TemporalUnit::Second:
+ return 1'000'000'000;
+ case TemporalUnit::Millisecond:
+ return 1'000'000;
+ case TemporalUnit::Microsecond:
+ return 1'000;
+ case TemporalUnit::Nanosecond:
+ return 1;
+
+ case TemporalUnit::Auto:
+ case TemporalUnit::Year:
+ case TemporalUnit::Month:
+ case TemporalUnit::Week:
+ break;
+ }
+ MOZ_CRASH("Unexpected temporal unit");
+}
+
+constexpr int64_t ToMilliseconds(TemporalUnit unit) {
+ switch (unit) {
+ case TemporalUnit::Day:
+ return 86'400'000;
+ case TemporalUnit::Hour:
+ return 3'600'000;
+ case TemporalUnit::Minute:
+ return 60'000;
+ case TemporalUnit::Second:
+ return 1'000;
+ case TemporalUnit::Millisecond:
+ return 1;
+
+ case TemporalUnit::Auto:
+ case TemporalUnit::Year:
+ case TemporalUnit::Month:
+ case TemporalUnit::Week:
+ case TemporalUnit::Microsecond:
+ case TemporalUnit::Nanosecond:
+ break;
+ }
+ MOZ_CRASH("Unexpected temporal unit");
+}
+
+constexpr int64_t UnitsPerDay(TemporalUnit unit) {
+ switch (unit) {
+ case TemporalUnit::Day:
+ return 1;
+ case TemporalUnit::Hour:
+ return 24;
+ case TemporalUnit::Minute:
+ return 1440;
+ case TemporalUnit::Second:
+ return 86'400;
+ case TemporalUnit::Millisecond:
+ return 86'400'000;
+ case TemporalUnit::Microsecond:
+ return 86'400'000'000;
+ case TemporalUnit::Nanosecond:
+ return 86'400'000'000'000;
+
+ case TemporalUnit::Auto:
+ case TemporalUnit::Year:
+ case TemporalUnit::Month:
+ case TemporalUnit::Week:
+ break;
+ }
+ MOZ_CRASH("Unexpected temporal unit");
+}
+
+constexpr const char* TemporalUnitToString(TemporalUnit unit) {
+ switch (unit) {
+ case TemporalUnit::Auto:
+ return "auto";
+ case TemporalUnit::Year:
+ return "year";
+ case TemporalUnit::Month:
+ return "month";
+ case TemporalUnit::Week:
+ return "week";
+ case TemporalUnit::Day:
+ return "day";
+ case TemporalUnit::Hour:
+ return "hour";
+ case TemporalUnit::Minute:
+ return "minute";
+ case TemporalUnit::Second:
+ return "second";
+ case TemporalUnit::Millisecond:
+ return "millisecond";
+ case TemporalUnit::Microsecond:
+ return "microsecond";
+ case TemporalUnit::Nanosecond:
+ return "nanosecond";
+ }
+ MOZ_CRASH("Unexpected temporal unit");
+}
+
+} /* namespace js::temporal */
+
+#endif /* builtin_temporal_TemporalUnit_h */
diff --git a/js/src/builtin/temporal/TimeZone.cpp b/js/src/builtin/temporal/TimeZone.cpp
new file mode 100644
index 0000000000..ca7e1b9f11
--- /dev/null
+++ b/js/src/builtin/temporal/TimeZone.cpp
@@ -0,0 +1,2729 @@
+/* -*- 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/TimeZone.h"
+
+#include "mozilla/Array.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/intl/TimeZone.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Range.h"
+#include "mozilla/Result.h"
+#include "mozilla/Span.h"
+#include "mozilla/UniquePtr.h"
+
+#include <cmath>
+#include <cstdlib>
+#include <initializer_list>
+#include <iterator>
+#include <utility>
+
+#include "jsnum.h"
+#include "jspubtd.h"
+#include "jstypes.h"
+#include "NamespaceImports.h"
+
+#include "builtin/Array.h"
+#include "builtin/intl/CommonFunctions.h"
+#include "builtin/intl/FormatBuffer.h"
+#include "builtin/intl/SharedIntlData.h"
+#include "builtin/temporal/Calendar.h"
+#include "builtin/temporal/Instant.h"
+#include "builtin/temporal/PlainDate.h"
+#include "builtin/temporal/PlainDateTime.h"
+#include "builtin/temporal/PlainTime.h"
+#include "builtin/temporal/Temporal.h"
+#include "builtin/temporal/TemporalParser.h"
+#include "builtin/temporal/TemporalTypes.h"
+#include "builtin/temporal/TemporalUnit.h"
+#include "builtin/temporal/Wrapped.h"
+#include "builtin/temporal/ZonedDateTime.h"
+#include "gc/AllocKind.h"
+#include "gc/Barrier.h"
+#include "gc/GCContext.h"
+#include "gc/GCEnum.h"
+#include "gc/Tracer.h"
+#include "js/AllocPolicy.h"
+#include "js/CallArgs.h"
+#include "js/CallNonGenericMethod.h"
+#include "js/Class.h"
+#include "js/ComparisonOperators.h"
+#include "js/Date.h"
+#include "js/ErrorReport.h"
+#include "js/ForOfIterator.h"
+#include "js/friend/ErrorMessages.h"
+#include "js/Printer.h"
+#include "js/PropertyDescriptor.h"
+#include "js/PropertySpec.h"
+#include "js/RootingAPI.h"
+#include "js/StableStringChars.h"
+#include "threading/ProtectedData.h"
+#include "vm/ArrayObject.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/Compartment.h"
+#include "vm/DateTime.h"
+#include "vm/GlobalObject.h"
+#include "vm/Interpreter.h"
+#include "vm/JSAtomState.h"
+#include "vm/JSContext.h"
+#include "vm/JSObject.h"
+#include "vm/PlainObject.h"
+#include "vm/Runtime.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 IsTimeZone(Handle<Value> v) {
+ return v.isObject() && v.toObject().is<TimeZoneObject>();
+}
+
+void js::temporal::TimeZoneValue::trace(JSTracer* trc) {
+ TraceNullableRoot(trc, &object_, "TimeZoneValue::object");
+}
+
+void js::temporal::TimeZoneRecord::trace(JSTracer* trc) {
+ receiver_.trace(trc);
+ TraceNullableRoot(trc, &getOffsetNanosecondsFor_,
+ "TimeZoneMethods::getOffsetNanosecondsFor");
+ TraceNullableRoot(trc, &getPossibleInstantsFor_,
+ "TimeZoneMethods::getPossibleInstantsFor");
+}
+
+static mozilla::UniquePtr<mozilla::intl::TimeZone> CreateIntlTimeZone(
+ JSContext* cx, JSString* identifier) {
+ JS::AutoStableStringChars stableChars(cx);
+ if (!stableChars.initTwoByte(cx, identifier)) {
+ return nullptr;
+ }
+
+ auto result = mozilla::intl::TimeZone::TryCreate(
+ mozilla::Some(stableChars.twoByteRange()));
+ if (result.isErr()) {
+ intl::ReportInternalError(cx, result.unwrapErr());
+ return nullptr;
+ }
+ return result.unwrap();
+}
+
+static mozilla::intl::TimeZone* GetOrCreateIntlTimeZone(
+ JSContext* cx, Handle<TimeZoneObjectMaybeBuiltin*> timeZone) {
+ // Obtain a cached mozilla::intl::TimeZone object.
+ if (auto* tz = timeZone->getTimeZone()) {
+ return tz;
+ }
+
+ auto* tz = CreateIntlTimeZone(cx, timeZone->identifier()).release();
+ if (!tz) {
+ return nullptr;
+ }
+ timeZone->setTimeZone(tz);
+
+ intl::AddICUCellMemory(timeZone,
+ TimeZoneObjectMaybeBuiltin::EstimatedMemoryUse);
+ return tz;
+}
+
+/**
+ * IsValidTimeZoneName ( timeZone )
+ * IsAvailableTimeZoneName ( timeZone )
+ */
+bool js::temporal::IsValidTimeZoneName(
+ JSContext* cx, Handle<JSString*> timeZone,
+ MutableHandle<JSAtom*> validatedTimeZone) {
+ intl::SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
+
+ if (!sharedIntlData.validateTimeZoneName(cx, timeZone, validatedTimeZone)) {
+ return false;
+ }
+
+ if (validatedTimeZone) {
+ cx->markAtom(validatedTimeZone);
+ }
+ return true;
+}
+
+/**
+ * 6.5.2 CanonicalizeTimeZoneName ( timeZone )
+ *
+ * Canonicalizes the given IANA time zone name.
+ *
+ * ES2024 Intl draft rev 74ca7099f103d143431b2ea422ae640c6f43e3e6
+ */
+JSString* js::temporal::CanonicalizeTimeZoneName(
+ JSContext* cx, Handle<JSLinearString*> timeZone) {
+ // Step 1. (Not applicable, the input is already a valid IANA time zone.)
+#ifdef DEBUG
+ MOZ_ASSERT(!StringEqualsLiteral(timeZone, "Etc/Unknown"),
+ "Invalid time zone");
+
+ Rooted<JSAtom*> checkTimeZone(cx);
+ if (!IsValidTimeZoneName(cx, timeZone, &checkTimeZone)) {
+ return nullptr;
+ }
+ MOZ_ASSERT(EqualStrings(timeZone, checkTimeZone),
+ "Time zone name not normalized");
+#endif
+
+ // Step 2.
+ Rooted<JSLinearString*> ianaTimeZone(cx);
+ do {
+ intl::SharedIntlData& sharedIntlData = cx->runtime()->sharedIntlData.ref();
+
+ // Some time zone names are canonicalized differently by ICU -- handle
+ // those first:
+ Rooted<JSAtom*> canonicalTimeZone(cx);
+ if (!sharedIntlData.tryCanonicalizeTimeZoneConsistentWithIANA(
+ cx, timeZone, &canonicalTimeZone)) {
+ return nullptr;
+ }
+
+ if (canonicalTimeZone) {
+ cx->markAtom(canonicalTimeZone);
+ ianaTimeZone = canonicalTimeZone;
+ break;
+ }
+
+ JS::AutoStableStringChars stableChars(cx);
+ if (!stableChars.initTwoByte(cx, timeZone)) {
+ return nullptr;
+ }
+
+ intl::FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> buffer(cx);
+ auto result = mozilla::intl::TimeZone::GetCanonicalTimeZoneID(
+ stableChars.twoByteRange(), buffer);
+ if (result.isErr()) {
+ intl::ReportInternalError(cx, result.unwrapErr());
+ return nullptr;
+ }
+
+ ianaTimeZone = buffer.toString(cx);
+ if (!ianaTimeZone) {
+ return nullptr;
+ }
+ } while (false);
+
+#ifdef DEBUG
+ MOZ_ASSERT(!StringEqualsLiteral(ianaTimeZone, "Etc/Unknown"),
+ "Invalid canonical time zone");
+
+ if (!IsValidTimeZoneName(cx, ianaTimeZone, &checkTimeZone)) {
+ return nullptr;
+ }
+ MOZ_ASSERT(EqualStrings(ianaTimeZone, checkTimeZone),
+ "Unsupported canonical time zone");
+#endif
+
+ // Step 3.
+ if (StringEqualsLiteral(ianaTimeZone, "Etc/UTC") ||
+ StringEqualsLiteral(ianaTimeZone, "Etc/GMT")) {
+ return cx->names().UTC;
+ }
+
+ // We don't need to check against "GMT", because ICU uses the tzdata rearguard
+ // format, where "GMT" is a link to "Etc/GMT".
+ MOZ_ASSERT(!StringEqualsLiteral(ianaTimeZone, "GMT"));
+
+ // Step 4.
+ return ianaTimeZone;
+}
+
+/**
+ * IsValidTimeZoneName ( timeZone )
+ * IsAvailableTimeZoneName ( timeZone )
+ * CanonicalizeTimeZoneName ( timeZone )
+ */
+JSString* js::temporal::ValidateAndCanonicalizeTimeZoneName(
+ JSContext* cx, Handle<JSString*> timeZone) {
+ Rooted<JSAtom*> validatedTimeZone(cx);
+ if (!IsValidTimeZoneName(cx, timeZone, &validatedTimeZone)) {
+ return nullptr;
+ }
+
+ if (!validatedTimeZone) {
+ if (auto chars = QuoteString(cx, timeZone)) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_TIMEZONE_INVALID_IDENTIFIER,
+ chars.get());
+ }
+ return nullptr;
+ }
+
+ return CanonicalizeTimeZoneName(cx, validatedTimeZone);
+}
+
+class EpochInstantList final {
+ // GetNamedTimeZoneEpochNanoseconds can return up-to two elements.
+ static constexpr size_t MaxLength = 2;
+
+ mozilla::Array<Instant, MaxLength> array_ = {};
+ size_t length_ = 0;
+
+ public:
+ EpochInstantList() = default;
+
+ size_t length() const { return length_; }
+
+ void append(const Instant& instant) { array_[length_++] = instant; }
+
+ auto& operator[](size_t i) { return array_[i]; }
+ const auto& operator[](size_t i) const { return array_[i]; }
+
+ auto begin() const { return array_.begin(); }
+ auto end() const { return array_.begin() + length_; }
+};
+
+/**
+ * GetNamedTimeZoneEpochNanoseconds ( timeZoneIdentifier, year, month, day,
+ * hour, minute, second, millisecond, microsecond, nanosecond )
+ */
+static bool GetNamedTimeZoneEpochNanoseconds(
+ JSContext* cx, Handle<TimeZoneObjectMaybeBuiltin*> timeZone,
+ const PlainDateTime& dateTime, EpochInstantList& instants) {
+ MOZ_ASSERT(timeZone->offsetMinutes().isUndefined());
+ MOZ_ASSERT(IsValidISODateTime(dateTime));
+ MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
+ MOZ_ASSERT(instants.length() == 0);
+
+ // FIXME: spec issue - assert ISODateTimeWithinLimits instead of
+ // IsValidISODate
+
+ int64_t ms = MakeDate(dateTime);
+
+ auto* tz = GetOrCreateIntlTimeZone(cx, timeZone);
+ if (!tz) {
+ return false;
+ }
+
+ auto getOffset = [&](mozilla::intl::TimeZone::LocalOption skippedTime,
+ mozilla::intl::TimeZone::LocalOption repeatedTime,
+ int32_t* offset) {
+ auto result = tz->GetUTCOffsetMs(ms, skippedTime, repeatedTime);
+ if (result.isErr()) {
+ intl::ReportInternalError(cx, result.unwrapErr());
+ return false;
+ }
+
+ *offset = result.unwrap();
+ MOZ_ASSERT(std::abs(*offset) < UnitsPerDay(TemporalUnit::Millisecond));
+
+ return true;
+ };
+
+ constexpr auto formerTime = mozilla::intl::TimeZone::LocalOption::Former;
+ constexpr auto latterTime = mozilla::intl::TimeZone::LocalOption::Latter;
+
+ int32_t formerOffset;
+ if (!getOffset(formerTime, formerTime, &formerOffset)) {
+ return false;
+ }
+
+ int32_t latterOffset;
+ if (!getOffset(latterTime, latterTime, &latterOffset)) {
+ return false;
+ }
+
+ if (formerOffset == latterOffset) {
+ auto instant = GetUTCEpochNanoseconds(
+ dateTime, InstantSpan::fromMilliseconds(formerOffset));
+ instants.append(instant);
+ return true;
+ }
+
+ int32_t disambiguationOffset;
+ if (!getOffset(formerTime, latterTime, &disambiguationOffset)) {
+ return false;
+ }
+
+ // Skipped time.
+ if (disambiguationOffset == formerOffset) {
+ return true;
+ }
+
+ // Repeated time.
+ for (auto offset : {formerOffset, latterOffset}) {
+ auto instant =
+ GetUTCEpochNanoseconds(dateTime, InstantSpan::fromMilliseconds(offset));
+ instants.append(instant);
+ }
+
+ MOZ_ASSERT(instants.length() == 2);
+
+ // Ensure the returned instants are sorted in numerical order.
+ if (instants[0] > instants[1]) {
+ std::swap(instants[0], instants[1]);
+ }
+
+ return true;
+}
+
+/**
+ * GetNamedTimeZoneOffsetNanoseconds ( timeZoneIdentifier, epochNanoseconds )
+ */
+static bool GetNamedTimeZoneOffsetNanoseconds(
+ JSContext* cx, Handle<TimeZoneObjectMaybeBuiltin*> timeZone,
+ const Instant& epochInstant, int64_t* offset) {
+ MOZ_ASSERT(timeZone->offsetMinutes().isUndefined());
+
+ // Round down (floor) to the previous full milliseconds.
+ int64_t millis = epochInstant.floorToMilliseconds();
+
+ auto* tz = GetOrCreateIntlTimeZone(cx, timeZone);
+ if (!tz) {
+ return false;
+ }
+
+ auto result = tz->GetOffsetMs(millis);
+ if (result.isErr()) {
+ intl::ReportInternalError(cx, result.unwrapErr());
+ return false;
+ }
+
+ // FIXME: spec issue - should constrain the range to not exceed 24-hours.
+ // https://github.com/tc39/ecma262/issues/3101
+
+ int64_t nanoPerMs = 1'000'000;
+ *offset = result.unwrap() * nanoPerMs;
+ return true;
+}
+
+/**
+ * GetNamedTimeZoneNextTransition ( timeZoneIdentifier, epochNanoseconds )
+ */
+static bool GetNamedTimeZoneNextTransition(JSContext* cx,
+ Handle<TimeZoneObject*> timeZone,
+ const Instant& epochInstant,
+ mozilla::Maybe<Instant>* result) {
+ MOZ_ASSERT(timeZone->offsetMinutes().isUndefined());
+
+ // Round down (floor) to the previous full millisecond.
+ //
+ // IANA has experimental support for transitions at sub-second precision, but
+ // the default configuration doesn't enable it, therefore it's safe to round
+ // to milliseconds here. In addition to that, ICU also only supports
+ // transitions at millisecond precision.
+ int64_t millis = epochInstant.floorToMilliseconds();
+
+ auto* tz = GetOrCreateIntlTimeZone(cx, timeZone);
+ if (!tz) {
+ return false;
+ }
+
+ auto next = tz->GetNextTransition(millis);
+ if (next.isErr()) {
+ intl::ReportInternalError(cx, next.unwrapErr());
+ return false;
+ }
+
+ auto transition = next.unwrap();
+ if (!transition) {
+ *result = mozilla::Nothing();
+ return true;
+ }
+
+ auto transitionInstant = Instant::fromMilliseconds(*transition);
+ if (!IsValidEpochInstant(transitionInstant)) {
+ *result = mozilla::Nothing();
+ return true;
+ }
+
+ *result = mozilla::Some(transitionInstant);
+ return true;
+}
+
+/**
+ * GetNamedTimeZonePreviousTransition ( timeZoneIdentifier, epochNanoseconds )
+ */
+static bool GetNamedTimeZonePreviousTransition(
+ JSContext* cx, Handle<TimeZoneObject*> timeZone,
+ const Instant& epochInstant, mozilla::Maybe<Instant>* result) {
+ MOZ_ASSERT(timeZone->offsetMinutes().isUndefined());
+
+ // Round up (ceil) to the next full millisecond.
+ //
+ // IANA has experimental support for transitions at sub-second precision, but
+ // the default configuration doesn't enable it, therefore it's safe to round
+ // to milliseconds here. In addition to that, ICU also only supports
+ // transitions at millisecond precision.
+ int64_t millis = epochInstant.ceilToMilliseconds();
+
+ auto* tz = GetOrCreateIntlTimeZone(cx, timeZone);
+ if (!tz) {
+ return false;
+ }
+
+ auto previous = tz->GetPreviousTransition(millis);
+ if (previous.isErr()) {
+ intl::ReportInternalError(cx, previous.unwrapErr());
+ return false;
+ }
+
+ auto transition = previous.unwrap();
+ if (!transition) {
+ *result = mozilla::Nothing();
+ return true;
+ }
+
+ auto transitionInstant = Instant::fromMilliseconds(*transition);
+ if (!IsValidEpochInstant(transitionInstant)) {
+ *result = mozilla::Nothing();
+ return true;
+ }
+
+ *result = mozilla::Some(transitionInstant);
+ return true;
+}
+
+/**
+ * FormatOffsetTimeZoneIdentifier ( offsetMinutes [ , style ] )
+ */
+static JSString* FormatOffsetTimeZoneIdentifier(JSContext* cx,
+ int32_t offsetMinutes) {
+ MOZ_ASSERT(std::abs(offsetMinutes) < UnitsPerDay(TemporalUnit::Minute));
+
+ // Step 1.
+ char sign = offsetMinutes >= 0 ? '+' : '-';
+
+ // Step 2.
+ int32_t absoluteMinutes = std::abs(offsetMinutes);
+
+ // Step 3.
+ int32_t hour = absoluteMinutes / 60;
+
+ // Step 4.
+ int32_t minute = absoluteMinutes % 60;
+
+ // Step 5. (Inlined FormatTimeString).
+ //
+ // Format: "sign hour{2} : minute{2}"
+ char result[] = {
+ sign, char('0' + (hour / 10)), char('0' + (hour % 10)),
+ ':', char('0' + (minute / 10)), char('0' + (minute % 10)),
+ };
+
+ // Step 6.
+ return NewStringCopyN<CanGC>(cx, result, std::size(result));
+}
+
+/**
+ * CreateTemporalTimeZone ( identifier [ , newTarget ] )
+ */
+static TimeZoneObject* CreateTemporalTimeZone(JSContext* cx,
+ const CallArgs& args,
+ Handle<JSString*> identifier,
+ Handle<Value> offsetMinutes) {
+ MOZ_ASSERT(offsetMinutes.isUndefined() || offsetMinutes.isInt32());
+ MOZ_ASSERT_IF(offsetMinutes.isInt32(), std::abs(offsetMinutes.toInt32()) <
+ UnitsPerDay(TemporalUnit::Minute));
+
+ // Steps 1-2.
+ Rooted<JSObject*> proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_TimeZone, &proto)) {
+ return nullptr;
+ }
+
+ auto* timeZone = NewObjectWithClassProto<TimeZoneObject>(cx, proto);
+ if (!timeZone) {
+ return nullptr;
+ }
+
+ // Step 4.a. (Not applicable in our implementation.)
+
+ // Steps 3.a or 4.b.
+ timeZone->setFixedSlot(TimeZoneObject::IDENTIFIER_SLOT,
+ StringValue(identifier));
+
+ // Step 3.b or 4.c.
+ timeZone->setFixedSlot(TimeZoneObject::OFFSET_MINUTES_SLOT, offsetMinutes);
+
+ // Step 5.
+ return timeZone;
+}
+
+static BuiltinTimeZoneObject* CreateBuiltinTimeZone(
+ JSContext* cx, Handle<JSString*> identifier) {
+ // TODO: Implement a built-in time zone object cache.
+
+ auto* object = NewObjectWithGivenProto<BuiltinTimeZoneObject>(cx, nullptr);
+ if (!object) {
+ return nullptr;
+ }
+
+ object->setFixedSlot(BuiltinTimeZoneObject::IDENTIFIER_SLOT,
+ StringValue(identifier));
+
+ object->setFixedSlot(BuiltinTimeZoneObject::OFFSET_MINUTES_SLOT,
+ UndefinedValue());
+
+ return object;
+}
+
+static BuiltinTimeZoneObject* CreateBuiltinTimeZone(JSContext* cx,
+ int32_t offsetMinutes) {
+ // TODO: It's unclear if offset time zones should also be cached. Real world
+ // experience will tell if a cache should be added.
+
+ MOZ_ASSERT(std::abs(offsetMinutes) < UnitsPerDay(TemporalUnit::Minute));
+
+ Rooted<JSString*> identifier(
+ cx, FormatOffsetTimeZoneIdentifier(cx, offsetMinutes));
+ if (!identifier) {
+ return nullptr;
+ }
+
+ auto* object = NewObjectWithGivenProto<BuiltinTimeZoneObject>(cx, nullptr);
+ if (!object) {
+ return nullptr;
+ }
+
+ object->setFixedSlot(BuiltinTimeZoneObject::IDENTIFIER_SLOT,
+ StringValue(identifier));
+
+ object->setFixedSlot(BuiltinTimeZoneObject::OFFSET_MINUTES_SLOT,
+ Int32Value(offsetMinutes));
+
+ return object;
+}
+
+/**
+ * CreateTemporalTimeZone ( identifier [ , newTarget ] )
+ */
+static TimeZoneObject* CreateTemporalTimeZone(
+ JSContext* cx, Handle<BuiltinTimeZoneObject*> timeZone) {
+ // Steps 1-2.
+ auto* object = NewBuiltinClassInstance<TimeZoneObject>(cx);
+ if (!object) {
+ return nullptr;
+ }
+
+ // Step 4.a. (Not applicable in our implementation.)
+
+ // Steps 3.a or 4.b.
+ object->setFixedSlot(
+ TimeZoneObject::IDENTIFIER_SLOT,
+ timeZone->getFixedSlot(BuiltinTimeZoneObject::IDENTIFIER_SLOT));
+
+ // Step 3.b or 4.c.
+ object->setFixedSlot(
+ TimeZoneObject::OFFSET_MINUTES_SLOT,
+ timeZone->getFixedSlot(BuiltinTimeZoneObject::OFFSET_MINUTES_SLOT));
+
+ // Step 5.
+ return object;
+}
+
+/**
+ * CreateTemporalTimeZone ( identifier [ , newTarget ] )
+ */
+BuiltinTimeZoneObject* js::temporal::CreateTemporalTimeZone(
+ JSContext* cx, Handle<JSString*> identifier) {
+ return ::CreateBuiltinTimeZone(cx, identifier);
+}
+
+/**
+ * ToTemporalTimeZoneSlotValue ( temporalTimeZoneLike )
+ */
+bool js::temporal::ToTemporalTimeZone(JSContext* cx,
+ Handle<ParsedTimeZone> string,
+ MutableHandle<TimeZoneValue> result) {
+ // Steps 1-3. (Not applicable)
+
+ // Steps 4-5.
+ if (string.name()) {
+ // Steps 4.a-c. (Not applicable in our implementation.)
+
+ // Steps 4.d-e.
+ Rooted<JSString*> timeZoneName(
+ cx, ValidateAndCanonicalizeTimeZoneName(cx, string.name()));
+ if (!timeZoneName) {
+ return false;
+ }
+
+ // Steps 4.f and 5.
+ auto* obj = ::CreateBuiltinTimeZone(cx, timeZoneName);
+ if (!obj) {
+ return false;
+ }
+
+ result.set(TimeZoneValue(obj));
+ return true;
+ }
+
+ // Steps 4.b-c and 8.
+ auto* obj = ::CreateBuiltinTimeZone(cx, string.offset());
+ if (!obj) {
+ return false;
+ }
+
+ result.set(TimeZoneValue(obj));
+ return true;
+}
+
+/**
+ * ObjectImplementsTemporalTimeZoneProtocol ( object )
+ */
+static bool ObjectImplementsTemporalTimeZoneProtocol(JSContext* cx,
+ Handle<JSObject*> object,
+ bool* result) {
+ // Step 1. (Not applicable in our implementation.)
+ MOZ_ASSERT(!object->canUnwrapAs<TimeZoneObject>(),
+ "TimeZone objects handled in the caller");
+
+ // Step 2.
+ for (auto key : {
+ &JSAtomState::getOffsetNanosecondsFor,
+ &JSAtomState::getPossibleInstantsFor,
+ &JSAtomState::id,
+ }) {
+ // Step 2.a.
+ bool has;
+ if (!HasProperty(cx, object, cx->names().*key, &has)) {
+ return false;
+ }
+ if (!has) {
+ *result = false;
+ return true;
+ }
+ }
+
+ // Step 3.
+ *result = true;
+ return true;
+}
+
+/**
+ * ToTemporalTimeZoneSlotValue ( temporalTimeZoneLike )
+ */
+bool js::temporal::ToTemporalTimeZone(JSContext* cx,
+ Handle<Value> temporalTimeZoneLike,
+ MutableHandle<TimeZoneValue> result) {
+ // Step 1.
+ Rooted<Value> timeZoneLike(cx, temporalTimeZoneLike);
+ if (timeZoneLike.isObject()) {
+ Rooted<JSObject*> obj(cx, &timeZoneLike.toObject());
+
+ // Step 1.b. (Partial)
+ if (obj->canUnwrapAs<TimeZoneObject>()) {
+ result.set(TimeZoneValue(obj));
+ return true;
+ }
+
+ // Step 1.a.
+ if (auto* zonedDateTime = obj->maybeUnwrapIf<ZonedDateTimeObject>()) {
+ result.set(zonedDateTime->timeZone());
+ return result.wrap(cx);
+ }
+
+ // Step 1.b.
+ bool implementsTimeZoneProtocol;
+ if (!ObjectImplementsTemporalTimeZoneProtocol(
+ cx, obj, &implementsTimeZoneProtocol)) {
+ return false;
+ }
+ if (!implementsTimeZoneProtocol) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_OBJECT,
+ "Temporal.TimeZone", obj->getClass()->name);
+ return false;
+ }
+
+ // Step 1.c.
+ result.set(TimeZoneValue(obj));
+ return true;
+ }
+
+ // Step 2.
+ if (!timeZoneLike.isString()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK,
+ timeZoneLike, nullptr, "not a string");
+ return false;
+ }
+ Rooted<JSString*> identifier(cx, timeZoneLike.toString());
+
+ // Step 3.
+ Rooted<ParsedTimeZone> timeZoneName(cx);
+ if (!ParseTemporalTimeZoneString(cx, identifier, &timeZoneName)) {
+ return false;
+ }
+
+ // Steps 4-8.
+ return ToTemporalTimeZone(cx, timeZoneName, result);
+}
+
+/**
+ * ToTemporalTimeZoneObject ( timeZoneSlotValue )
+ */
+JSObject* js::temporal::ToTemporalTimeZoneObject(
+ JSContext* cx, Handle<TimeZoneValue> timeZone) {
+ // Step 1.
+ if (timeZone.isObject()) {
+ return timeZone.toObject();
+ }
+
+ // Step 2.
+ return CreateTemporalTimeZone(cx, timeZone.toString());
+}
+
+/**
+ * ToTemporalTimeZoneIdentifier ( timeZoneSlotValue )
+ */
+JSString* js::temporal::ToTemporalTimeZoneIdentifier(
+ JSContext* cx, Handle<TimeZoneValue> timeZone) {
+ // Step 1.
+ if (timeZone.isString()) {
+ // Step 1.a. (Not applicable in our implementation.)
+
+ // Step 1.b.
+ return timeZone.toString()->identifier();
+ }
+
+ // Step 2.
+ Rooted<JSObject*> timeZoneObj(cx, timeZone.toObject());
+ Rooted<Value> identifier(cx);
+ if (!GetProperty(cx, timeZoneObj, timeZoneObj, cx->names().id, &identifier)) {
+ return nullptr;
+ }
+
+ // Step 3.
+ if (!identifier.isString()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, identifier,
+ nullptr, "not a string");
+ return nullptr;
+ }
+
+ // Step 4.
+ return identifier.toString();
+}
+
+static bool TimeZone_getOffsetNanosecondsFor(JSContext* cx, unsigned argc,
+ Value* vp);
+
+static bool TimeZone_getPossibleInstantsFor(JSContext* cx, unsigned argc,
+ Value* vp);
+
+/**
+ * TimeZoneMethodsRecordLookup ( timeZoneRec, methodName )
+ */
+static bool TimeZoneMethodsRecordLookup(JSContext* cx,
+ MutableHandle<TimeZoneRecord> timeZone,
+ TimeZoneMethod methodName) {
+ // Step 1. (Not applicable in our implementation.)
+
+ // Steps 2-4.
+ auto object = timeZone.receiver().toObject();
+
+ auto lookup = [&](Handle<PropertyName*> name, JSNative native,
+ MutableHandle<JSObject*> result) {
+ auto* method = GetMethod(cx, object, name);
+ if (!method) {
+ return false;
+ }
+
+ // As an optimization we only store the method if the receiver is either
+ // a custom time zone object or if the method isn't the default, built-in
+ // time zone method.
+ if (!object->is<TimeZoneObject>() || !IsNativeFunction(method, native)) {
+ result.set(method);
+ }
+ return true;
+ };
+
+ switch (methodName) {
+ // Steps 2 and 4.
+ case TimeZoneMethod::GetOffsetNanosecondsFor:
+ return lookup(cx->names().getOffsetNanosecondsFor,
+ TimeZone_getOffsetNanosecondsFor,
+ timeZone.getOffsetNanosecondsFor());
+
+ // Steps 3 and 4.
+ case TimeZoneMethod::GetPossibleInstantsFor:
+ return lookup(cx->names().getPossibleInstantsFor,
+ TimeZone_getPossibleInstantsFor,
+ timeZone.getPossibleInstantsFor());
+ }
+
+ MOZ_CRASH("invalid time zone method");
+}
+
+/**
+ * CreateTimeZoneMethodsRecord ( timeZone, methods )
+ */
+bool js::temporal::CreateTimeZoneMethodsRecord(
+ JSContext* cx, Handle<TimeZoneValue> timeZone,
+ mozilla::EnumSet<TimeZoneMethod> methods,
+ MutableHandle<TimeZoneRecord> result) {
+ MOZ_ASSERT(!methods.isEmpty());
+
+ // Step 1.
+ result.set(TimeZoneRecord{timeZone});
+
+#ifdef DEBUG
+ // Remember the set of looked-up methods for assertions.
+ result.get().lookedUp() += methods;
+#endif
+
+ // Built-in time zones don't perform observable lookups.
+ if (timeZone.isString()) {
+ return true;
+ }
+
+ // Step 2.
+ for (auto method : methods) {
+ if (!TimeZoneMethodsRecordLookup(cx, result, method)) {
+ return false;
+ }
+ }
+
+ // Step 3.
+ return true;
+}
+
+bool js::temporal::WrapTimeZoneValueObject(JSContext* cx,
+ MutableHandle<JSObject*> timeZone) {
+ // First handle the common case when |timeZone| is TimeZoneObjectMaybeBuiltin
+ // from the current compartment.
+ if (MOZ_LIKELY(timeZone->is<TimeZoneObjectMaybeBuiltin>() &&
+ timeZone->compartment() == cx->compartment())) {
+ return true;
+ }
+
+ // If it's not a built-in time zone, simply wrap the object into the current
+ // compartment.
+ auto* unwrappedTimeZone = timeZone->maybeUnwrapIf<BuiltinTimeZoneObject>();
+ if (!unwrappedTimeZone) {
+ return cx->compartment()->wrap(cx, timeZone);
+ }
+
+ // If this is a built-in time zone from a different compartment, create a
+ // fresh copy using the current compartment.
+ //
+ // We create a fresh copy, so we don't have to support the cross-compartment
+ // case, which makes detection of "string" time zones easier.
+
+ const auto& offsetMinutes = unwrappedTimeZone->offsetMinutes();
+ if (offsetMinutes.isInt32()) {
+ auto* obj = CreateBuiltinTimeZone(cx, offsetMinutes.toInt32());
+ if (!obj) {
+ return false;
+ }
+
+ timeZone.set(obj);
+ return true;
+ }
+ MOZ_ASSERT(offsetMinutes.isUndefined());
+
+ Rooted<JSString*> identifier(cx, unwrappedTimeZone->identifier());
+ if (!cx->compartment()->wrap(cx, &identifier)) {
+ return false;
+ }
+
+ auto* obj = ::CreateBuiltinTimeZone(cx, identifier);
+ if (!obj) {
+ return false;
+ }
+
+ timeZone.set(obj);
+ return true;
+}
+
+/**
+ * Temporal.TimeZone.prototype.getOffsetNanosecondsFor ( instant )
+ */
+static bool BuiltinGetOffsetNanosecondsFor(
+ JSContext* cx, Handle<TimeZoneObjectMaybeBuiltin*> timeZone,
+ const Instant& instant, int64_t* offsetNanoseconds) {
+ // Steps 1-3. (Not applicable.)
+
+ // Step 4.
+ if (timeZone->offsetMinutes().isInt32()) {
+ int32_t offset = timeZone->offsetMinutes().toInt32();
+ MOZ_ASSERT(std::abs(offset) < UnitsPerDay(TemporalUnit::Minute));
+
+ *offsetNanoseconds = int64_t(offset) * ToNanoseconds(TemporalUnit::Minute);
+ return true;
+ }
+ MOZ_ASSERT(timeZone->offsetMinutes().isUndefined());
+
+ // Step 5.
+ int64_t offset;
+ if (!GetNamedTimeZoneOffsetNanoseconds(cx, timeZone, instant, &offset)) {
+ return false;
+ }
+ MOZ_ASSERT(std::abs(offset) < ToNanoseconds(TemporalUnit::Day));
+
+ *offsetNanoseconds = offset;
+ return true;
+}
+
+/**
+ * GetOffsetNanosecondsFor ( timeZoneRec, instant )
+ */
+static bool GetOffsetNanosecondsForSlow(JSContext* cx,
+ Handle<TimeZoneRecord> timeZone,
+ Handle<Wrapped<InstantObject*>> instant,
+ int64_t* offsetNanoseconds) {
+ // Step 1. (Inlined call to TimeZoneMethodsRecordCall)
+ Rooted<Value> fval(cx, ObjectValue(*timeZone.getOffsetNanosecondsFor()));
+ auto thisv = timeZone.receiver().toObject();
+ Rooted<Value> instantVal(cx, ObjectValue(*instant));
+ Rooted<Value> rval(cx);
+ if (!Call(cx, fval, thisv, instantVal, &rval)) {
+ return false;
+ }
+
+ // Step 2. (Not applicable)
+
+ // Step 3.
+ if (!rval.isNumber()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, rval,
+ nullptr, "not a number");
+ return false;
+ }
+
+ // Steps 4-6.
+ double num = rval.toNumber();
+ if (!IsInteger(num) || std::abs(num) >= ToNanoseconds(TemporalUnit::Day)) {
+ ToCStringBuf cbuf;
+ const char* numStr = NumberToCString(&cbuf, num);
+
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_TIMEZONE_NANOS_RANGE, numStr);
+ return false;
+ }
+
+ // Step 7.
+ *offsetNanoseconds = int64_t(num);
+ return true;
+}
+
+/**
+ * GetOffsetNanosecondsFor ( timeZoneRec, instant )
+ */
+bool js::temporal::GetOffsetNanosecondsFor(
+ JSContext* cx, Handle<TimeZoneRecord> timeZone,
+ Handle<Wrapped<InstantObject*>> instant, int64_t* offsetNanoseconds) {
+ MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
+ timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
+
+ // Step 2. (Reordered)
+ auto getOffsetNanosecondsFor = timeZone.getOffsetNanosecondsFor();
+ if (!getOffsetNanosecondsFor) {
+ auto* unwrapped = instant.unwrap(cx);
+ if (!unwrapped) {
+ return false;
+ }
+ auto instant = ToInstant(unwrapped);
+ auto builtin = timeZone.receiver().toTimeZoneObjectMaybeBuiltin();
+
+ return BuiltinGetOffsetNanosecondsFor(cx, builtin, instant,
+ offsetNanoseconds);
+ }
+
+ // Steps 1 and 3-7.
+ return ::GetOffsetNanosecondsForSlow(cx, timeZone, instant,
+ offsetNanoseconds);
+}
+
+/**
+ * GetOffsetNanosecondsFor ( timeZoneRec, instant )
+ */
+bool js::temporal::GetOffsetNanosecondsFor(
+ JSContext* cx, Handle<TimeZoneValue> timeZone,
+ Handle<Wrapped<InstantObject*>> instant, int64_t* offsetNanoseconds) {
+ Rooted<TimeZoneRecord> timeZoneRec(cx);
+ if (!CreateTimeZoneMethodsRecord(cx, timeZone,
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ },
+ &timeZoneRec)) {
+ return false;
+ }
+
+ return GetOffsetNanosecondsFor(cx, timeZoneRec, instant, offsetNanoseconds);
+}
+
+/**
+ * GetOffsetNanosecondsFor ( timeZoneRec, instant )
+ */
+bool js::temporal::GetOffsetNanosecondsFor(JSContext* cx,
+ Handle<TimeZoneRecord> timeZone,
+ const Instant& instant,
+ int64_t* offsetNanoseconds) {
+ MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
+ timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
+
+ // Step 2. (Reordered)
+ auto getOffsetNanosecondsFor = timeZone.getOffsetNanosecondsFor();
+ if (!getOffsetNanosecondsFor) {
+ auto builtin = timeZone.receiver().toTimeZoneObjectMaybeBuiltin();
+ return BuiltinGetOffsetNanosecondsFor(cx, builtin, instant,
+ offsetNanoseconds);
+ }
+
+ // Steps 1 and 3-7.
+ Rooted<InstantObject*> obj(cx, CreateTemporalInstant(cx, instant));
+ if (!obj) {
+ return false;
+ }
+ return ::GetOffsetNanosecondsForSlow(cx, timeZone, obj, offsetNanoseconds);
+}
+
+/**
+ * GetOffsetNanosecondsFor ( timeZoneRec, instant )
+ */
+bool js::temporal::GetOffsetNanosecondsFor(JSContext* cx,
+ Handle<TimeZoneValue> timeZone,
+ const Instant& instant,
+ int64_t* offsetNanoseconds) {
+ Rooted<TimeZoneRecord> timeZoneRec(cx);
+ if (!CreateTimeZoneMethodsRecord(cx, timeZone,
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ },
+ &timeZoneRec)) {
+ return false;
+ }
+
+ return GetOffsetNanosecondsFor(cx, timeZoneRec, instant, offsetNanoseconds);
+}
+
+/**
+ * FormatUTCOffsetNanoseconds ( offsetNanoseconds )
+ */
+JSString* js::temporal::FormatUTCOffsetNanoseconds(JSContext* cx,
+ int64_t offsetNanoseconds) {
+ MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
+
+ // Step 1.
+ char sign = offsetNanoseconds >= 0 ? '+' : '-';
+
+ // Step 2.
+ int64_t absoluteNanoseconds = std::abs(offsetNanoseconds);
+
+ // Step 6. (Reordered)
+ int32_t subSecondNanoseconds = int32_t(absoluteNanoseconds % 1'000'000'000);
+
+ // Step 5. (Reordered)
+ int32_t quotient = int32_t(absoluteNanoseconds / 1'000'000'000);
+ int32_t second = quotient % 60;
+
+ // Step 4. (Reordered)
+ quotient /= 60;
+ int32_t minute = quotient % 60;
+
+ // Step 3.
+ int32_t hour = quotient / 60;
+ MOZ_ASSERT(hour < 24, "time zone offset mustn't exceed 24-hours");
+
+ // Format: "sign hour{2} : minute{2} : second{2} . fractional{9}"
+ constexpr size_t maxLength = 1 + 2 + 1 + 2 + 1 + 2 + 1 + 9;
+ char result[maxLength];
+
+ size_t n = 0;
+
+ // Steps 7-8. (Inlined FormatTimeString).
+ result[n++] = sign;
+ result[n++] = '0' + (hour / 10);
+ result[n++] = '0' + (hour % 10);
+ result[n++] = ':';
+ result[n++] = '0' + (minute / 10);
+ result[n++] = '0' + (minute % 10);
+
+ if (second != 0 || subSecondNanoseconds != 0) {
+ result[n++] = ':';
+ result[n++] = '0' + (second / 10);
+ result[n++] = '0' + (second % 10);
+
+ if (uint32_t fractional = subSecondNanoseconds) {
+ result[n++] = '.';
+
+ uint32_t k = 100'000'000;
+ do {
+ result[n++] = '0' + (fractional / k);
+ fractional %= k;
+ k /= 10;
+ } while (fractional);
+ }
+ }
+
+ MOZ_ASSERT(n <= maxLength);
+
+ // Step 9.
+ return NewStringCopyN<CanGC>(cx, result, n);
+}
+
+/**
+ * GetOffsetStringFor ( timeZoneRec, instant )
+ */
+JSString* js::temporal::GetOffsetStringFor(JSContext* cx,
+ Handle<TimeZoneValue> timeZone,
+ const Instant& instant) {
+ // Step 1.
+ int64_t offsetNanoseconds;
+ if (!GetOffsetNanosecondsFor(cx, timeZone, instant, &offsetNanoseconds)) {
+ return nullptr;
+ }
+ MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
+
+ // Step 2.
+ return FormatUTCOffsetNanoseconds(cx, offsetNanoseconds);
+}
+
+/**
+ * GetOffsetStringFor ( timeZoneRec, instant )
+ */
+JSString* js::temporal::GetOffsetStringFor(
+ JSContext* cx, Handle<TimeZoneRecord> timeZone,
+ Handle<Wrapped<InstantObject*>> instant) {
+ // Step 1.
+ int64_t offsetNanoseconds;
+ if (!GetOffsetNanosecondsFor(cx, timeZone, instant, &offsetNanoseconds)) {
+ return nullptr;
+ }
+ MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
+
+ // Step 2.
+ return FormatUTCOffsetNanoseconds(cx, offsetNanoseconds);
+}
+
+/**
+ * TimeZoneEquals ( one, two )
+ */
+bool js::temporal::TimeZoneEquals(JSContext* cx, Handle<JSString*> one,
+ Handle<JSString*> two, bool* equals) {
+ // Steps 1-3. (Not applicable)
+
+ // Step 4.
+ if (!EqualStrings(cx, one, two, equals)) {
+ return false;
+ }
+ if (*equals) {
+ return true;
+ }
+
+ // Step 5.
+ Rooted<ParsedTimeZone> timeZoneOne(cx);
+ if (!ParseTimeZoneIdentifier(cx, one, &timeZoneOne)) {
+ return false;
+ }
+
+ // Step 6.
+ Rooted<ParsedTimeZone> timeZoneTwo(cx);
+ if (!ParseTimeZoneIdentifier(cx, two, &timeZoneTwo)) {
+ return false;
+ }
+
+ // Step 7.
+ if (timeZoneOne.name() && timeZoneTwo.name()) {
+ // Step 7.a.
+ Rooted<JSAtom*> validTimeZoneOne(cx);
+ if (!IsValidTimeZoneName(cx, timeZoneOne.name(), &validTimeZoneOne)) {
+ return false;
+ }
+ if (!validTimeZoneOne) {
+ *equals = false;
+ return true;
+ }
+
+ // Step 7.b.
+ Rooted<JSAtom*> validTimeZoneTwo(cx);
+ if (!IsValidTimeZoneName(cx, timeZoneTwo.name(), &validTimeZoneTwo)) {
+ return false;
+ }
+ if (!validTimeZoneTwo) {
+ *equals = false;
+ return true;
+ }
+
+ // Step 7.c and 9.
+ Rooted<JSString*> canonicalOne(
+ cx, CanonicalizeTimeZoneName(cx, validTimeZoneOne));
+ if (!canonicalOne) {
+ return false;
+ }
+
+ JSString* canonicalTwo = CanonicalizeTimeZoneName(cx, validTimeZoneTwo);
+ if (!canonicalTwo) {
+ return false;
+ }
+
+ return EqualStrings(cx, canonicalOne, canonicalTwo, equals);
+ }
+
+ // Step 8.a.
+ if (!timeZoneOne.name() && !timeZoneTwo.name()) {
+ *equals = (timeZoneOne.offset() == timeZoneTwo.offset());
+ return true;
+ }
+
+ // Step 9.
+ *equals = false;
+ return true;
+}
+
+/**
+ * TimeZoneEquals ( one, two )
+ */
+bool js::temporal::TimeZoneEquals(JSContext* cx, Handle<TimeZoneValue> one,
+ Handle<TimeZoneValue> two, bool* equals) {
+ // Step 1.
+ if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) {
+ *equals = true;
+ return true;
+ }
+
+ // Step 2.
+ Rooted<JSString*> timeZoneOne(cx, ToTemporalTimeZoneIdentifier(cx, one));
+ if (!timeZoneOne) {
+ return false;
+ }
+
+ // Step 3.
+ Rooted<JSString*> timeZoneTwo(cx, ToTemporalTimeZoneIdentifier(cx, two));
+ if (!timeZoneTwo) {
+ return false;
+ }
+
+ // Steps 4-9.
+ return TimeZoneEquals(cx, timeZoneOne, timeZoneTwo, equals);
+}
+
+// ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
+// 5.2.5 Mathematical Operations
+static inline double PositiveModulo(double dividend, double divisor) {
+ MOZ_ASSERT(divisor > 0);
+ MOZ_ASSERT(std::isfinite(divisor));
+
+ double result = std::fmod(dividend, divisor);
+ if (result < 0) {
+ result += divisor;
+ }
+ return result + (+0.0);
+}
+
+/* ES5 15.9.1.10. */
+static double HourFromTime(double t) {
+ return PositiveModulo(std::floor(t / msPerHour), HoursPerDay);
+}
+
+static double MinFromTime(double t) {
+ return PositiveModulo(std::floor(t / msPerMinute), MinutesPerHour);
+}
+
+static double SecFromTime(double t) {
+ return PositiveModulo(std::floor(t / msPerSecond), SecondsPerMinute);
+}
+
+static double msFromTime(double t) { return PositiveModulo(t, msPerSecond); }
+
+/**
+ * GetISOPartsFromEpoch ( epochNanoseconds )
+ */
+static PlainDateTime GetISOPartsFromEpoch(const Instant& instant) {
+ // TODO: YearFromTime/MonthFromTime/DayFromTime recompute the same values
+ // multiple times. Consider adding a new function avoids this.
+
+ // Step 1.
+ MOZ_ASSERT(IsValidEpochInstant(instant));
+
+ // Step 2.
+ int32_t remainderNs = instant.nanoseconds % 1'000'000;
+
+ // Step 3.
+ int64_t epochMilliseconds = instant.floorToMilliseconds();
+
+ // Step 4.
+ int32_t year = JS::YearFromTime(epochMilliseconds);
+
+ // Step 5.
+ int32_t month = JS::MonthFromTime(epochMilliseconds) + 1;
+
+ // Step 6.
+ int32_t day = JS::DayFromTime(epochMilliseconds);
+
+ // Step 7.
+ int32_t hour = HourFromTime(epochMilliseconds);
+
+ // Step 8.
+ int32_t minute = MinFromTime(epochMilliseconds);
+
+ // Step 9.
+ int32_t second = SecFromTime(epochMilliseconds);
+
+ // Step 10.
+ int32_t millisecond = msFromTime(epochMilliseconds);
+
+ // Step 11.
+ int32_t microsecond = remainderNs / 1000;
+
+ // Step 12.
+ int32_t nanosecond = remainderNs % 1000;
+
+ // Step 13.
+ PlainDateTime result = {
+ {year, month, day},
+ {hour, minute, second, millisecond, microsecond, nanosecond}};
+
+ // Always valid when the epoch nanoseconds are within the representable limit.
+ MOZ_ASSERT(IsValidISODateTime(result));
+ MOZ_ASSERT(ISODateTimeWithinLimits(result));
+
+ return result;
+}
+
+/**
+ * BalanceISODateTime ( year, month, day, hour, minute, second, millisecond,
+ * microsecond, nanosecond )
+ */
+static PlainDateTime BalanceISODateTime(const PlainDateTime& dateTime,
+ int64_t nanoseconds) {
+ MOZ_ASSERT(IsValidISODateTime(dateTime));
+ MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
+ MOZ_ASSERT(std::abs(nanoseconds) < ToNanoseconds(TemporalUnit::Day));
+
+ auto& [date, time] = dateTime;
+
+ // Step 1.
+ auto balancedTime = BalanceTime(time, nanoseconds);
+ MOZ_ASSERT(-1 <= balancedTime.days && balancedTime.days <= 1);
+
+ // Step 2.
+ auto balancedDate =
+ BalanceISODate(date.year, date.month, date.day + balancedTime.days);
+
+ // Step 3.
+ return {balancedDate, balancedTime.time};
+}
+
+/**
+ * GetPlainDateTimeFor ( timeZoneRec, instant, calendar [ ,
+ * precalculatedOffsetNanoseconds ] )
+ */
+static PlainDateTimeObject* GetPlainDateTimeFor(
+ JSContext* cx, Handle<TimeZoneValue> timeZone,
+ Handle<Wrapped<InstantObject*>> instant, Handle<CalendarValue> calendar) {
+ // Step 1. (Not applicable in our implementation.)
+
+ // Steps 2-3.
+ int64_t offsetNanoseconds;
+ if (!GetOffsetNanosecondsFor(cx, timeZone, instant, &offsetNanoseconds)) {
+ return nullptr;
+ }
+
+ // Step 4.
+ MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
+
+ auto* unwrappedInstant = instant.unwrap(cx);
+ if (!unwrappedInstant) {
+ return nullptr;
+ }
+
+ // Steps 5-7.
+ auto dateTime =
+ GetPlainDateTimeFor(ToInstant(unwrappedInstant), offsetNanoseconds);
+
+ // FIXME: spec issue - CreateTemporalDateTime is infallible
+ // https://github.com/tc39/proposal-temporal/issues/2523
+ MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
+
+ return CreateTemporalDateTime(cx, dateTime, calendar);
+}
+
+/**
+ * GetPlainDateTimeFor ( timeZoneRec, instant, calendar [ ,
+ * precalculatedOffsetNanoseconds ] )
+ */
+PlainDateTime js::temporal::GetPlainDateTimeFor(const Instant& instant,
+ int64_t offsetNanoseconds) {
+ // Steps 1-3. (Not applicable)
+
+ // Step 4.
+ MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
+
+ // TODO: Steps 5-6 can be combined into a single operation to improve perf.
+
+ // Step 5.
+ PlainDateTime dateTime = GetISOPartsFromEpoch(instant);
+
+ // Step 6.
+ auto balanced = BalanceISODateTime(dateTime, offsetNanoseconds);
+
+ // FIXME: spec issue - CreateTemporalDateTime is infallible
+ // https://github.com/tc39/proposal-temporal/issues/2523
+ MOZ_ASSERT(ISODateTimeWithinLimits(balanced));
+
+ // Step 7.
+ return balanced;
+}
+
+/**
+ * GetPlainDateTimeFor ( timeZone, instant, calendar [ ,
+ * precalculatedOffsetNanoseconds ] )
+ */
+bool js::temporal::GetPlainDateTimeFor(JSContext* cx,
+ Handle<TimeZoneRecord> timeZone,
+ const Instant& instant,
+ PlainDateTime* result) {
+ MOZ_ASSERT(IsValidEpochInstant(instant));
+
+ // Step 1.
+ MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
+ timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
+
+ // Steps 2-3.
+ int64_t offsetNanoseconds;
+ if (!GetOffsetNanosecondsFor(cx, timeZone, instant, &offsetNanoseconds)) {
+ return false;
+ }
+
+ // Step 4.
+ MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
+
+ // Steps 5-7.
+ *result = GetPlainDateTimeFor(instant, offsetNanoseconds);
+ return true;
+}
+
+/**
+ * GetPlainDateTimeFor ( timeZone, instant, calendar [ ,
+ * precalculatedOffsetNanoseconds ] )
+ */
+bool js::temporal::GetPlainDateTimeFor(JSContext* cx,
+ Handle<TimeZoneValue> timeZone,
+ const Instant& instant,
+ PlainDateTime* result) {
+ Rooted<TimeZoneRecord> timeZoneRec(cx);
+ if (!CreateTimeZoneMethodsRecord(cx, timeZone,
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ },
+ &timeZoneRec)) {
+ return false;
+ }
+
+ return GetPlainDateTimeFor(cx, timeZoneRec, instant, result);
+}
+
+/**
+ * GetPlainDateTimeFor ( timeZone, instant, calendar [ ,
+ * precalculatedOffsetNanoseconds ] )
+ */
+PlainDateTimeObject* js::temporal::GetPlainDateTimeFor(
+ JSContext* cx, Handle<TimeZoneValue> timeZone, const Instant& instant,
+ Handle<CalendarValue> calendar) {
+ // Steps 1-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, timeZone, instant, &dateTime)) {
+ return nullptr;
+ }
+
+ // FIXME: spec issue - CreateTemporalDateTime is infallible
+ // https://github.com/tc39/proposal-temporal/issues/2523
+ MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
+
+ // Step 7.
+ return CreateTemporalDateTime(cx, dateTime, calendar);
+}
+
+/**
+ * GetPlainDateTimeFor ( timeZone, instant, calendar [ ,
+ * precalculatedOffsetNanoseconds ] )
+ */
+PlainDateTimeObject* js::temporal::GetPlainDateTimeFor(
+ JSContext* cx, const Instant& instant, Handle<CalendarValue> calendar,
+ int64_t offsetNanoseconds) {
+ MOZ_ASSERT(IsValidEpochInstant(instant));
+
+ // Steps 1-6.
+ auto dateTime = GetPlainDateTimeFor(instant, offsetNanoseconds);
+
+ // FIXME: spec issue - CreateTemporalDateTime is infallible
+ // https://github.com/tc39/proposal-temporal/issues/2523
+ MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
+
+ // Step 7.
+ return CreateTemporalDateTime(cx, dateTime, calendar);
+}
+
+/**
+ * Temporal.TimeZone.prototype.getPossibleInstantsFor ( dateTime )
+ */
+static bool BuiltinGetPossibleInstantsFor(
+ JSContext* cx, Handle<TimeZoneObjectMaybeBuiltin*> timeZone,
+ const PlainDateTime& dateTime, EpochInstantList& possibleInstants) {
+ MOZ_ASSERT(possibleInstants.length() == 0);
+
+ // Steps 1-3. (Not applicable)
+
+ // Step 4.
+ if (timeZone->offsetMinutes().isInt32()) {
+ int32_t offsetMin = timeZone->offsetMinutes().toInt32();
+ MOZ_ASSERT(std::abs(offsetMin) < UnitsPerDay(TemporalUnit::Minute));
+
+ // Step 4.a.
+ auto epochInstant =
+ GetUTCEpochNanoseconds(dateTime, InstantSpan::fromMinutes(offsetMin));
+
+ // Step 4.b.
+ possibleInstants.append(epochInstant);
+ } else {
+ // Step 5.
+ if (!GetNamedTimeZoneEpochNanoseconds(cx, timeZone, dateTime,
+ possibleInstants)) {
+ return false;
+ }
+ }
+
+ MOZ_ASSERT(possibleInstants.length() <= 2);
+
+ // Step 7.b.
+ for (const auto& epochInstant : possibleInstants) {
+ if (!IsValidEpochInstant(epochInstant)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_INVALID);
+ return false;
+ }
+ }
+
+ // Steps 6-8. (Handled in the caller).
+ return true;
+}
+
+static bool BuiltinGetPossibleInstantsFor(
+ JSContext* cx, Handle<TimeZoneObjectMaybeBuiltin*> timeZone,
+ const PlainDateTime& dateTime, MutableHandle<InstantVector> list) {
+ // Temporal.TimeZone.prototype.getInstantFor, step 4.
+ EpochInstantList possibleInstants;
+ if (!BuiltinGetPossibleInstantsFor(cx, timeZone, dateTime,
+ possibleInstants)) {
+ return false;
+ }
+
+ // Temporal.TimeZone.prototype.getInstantFor, step 7.
+ for (const auto& possibleInstant : possibleInstants) {
+ auto* instant = CreateTemporalInstant(cx, possibleInstant);
+ if (!instant) {
+ return false;
+ }
+
+ if (!list.append(instant)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * GetPossibleInstantsFor ( timeZoneRec, dateTime )
+ */
+static bool GetPossibleInstantsForSlow(
+ JSContext* cx, Handle<TimeZoneRecord> timeZone,
+ Handle<Wrapped<PlainDateTimeObject*>> dateTime,
+ MutableHandle<InstantVector> list) {
+ // Step 1. (Inlined call to TimeZoneMethodsRecordCall)
+ Rooted<Value> fval(cx, ObjectValue(*timeZone.getPossibleInstantsFor()));
+ auto thisv = timeZone.receiver().toObject();
+ Rooted<Value> arg(cx, ObjectValue(*dateTime));
+ Rooted<Value> rval(cx);
+ if (!Call(cx, fval, thisv, arg, &rval)) {
+ return false;
+ }
+
+ // Step 2. (Not applicable)
+
+ // Step 3.
+ JS::ForOfIterator iterator(cx);
+ if (!iterator.init(rval)) {
+ return false;
+ }
+
+ // Step 4. (Not applicable in our implementation.)
+
+ // Steps 5-6.
+ Rooted<Value> nextValue(cx);
+ while (true) {
+ // Steps 6.a and 6.b.i.
+ bool done;
+ if (!iterator.next(&nextValue, &done)) {
+ return false;
+ }
+ if (done) {
+ break;
+ }
+
+ // Steps 6.b.ii.
+ if (nextValue.isObject()) {
+ JSObject* obj = &nextValue.toObject();
+ if (obj->canUnwrapAs<InstantObject>()) {
+ // Step 6.b.iii.
+ if (!list.append(obj)) {
+ return false;
+ }
+ continue;
+ }
+ }
+
+ // Step 6.b.ii.1.
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, nextValue,
+ nullptr, "not an instant");
+
+ // Step 6.b.ii.2.
+ iterator.closeThrow();
+ return false;
+ }
+
+ // Step 7.
+ return true;
+}
+
+/**
+ * GetPossibleInstantsFor ( timeZoneRec, dateTime )
+ */
+static bool GetPossibleInstantsFor(
+ JSContext* cx, Handle<TimeZoneRecord> timeZone,
+ Handle<Wrapped<PlainDateTimeObject*>> dateTimeObj,
+ const PlainDateTime& dateTime, MutableHandle<InstantVector> list) {
+ MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
+ timeZone, TimeZoneMethod::GetPossibleInstantsFor));
+
+ // Step 2. (Reordered)
+ auto getPossibleInstantsFor = timeZone.getPossibleInstantsFor();
+ if (!getPossibleInstantsFor) {
+ bool arrayIterationSane;
+ if (timeZone.receiver().isString()) {
+ // "String" time zones don't perform observable array iteration.
+ arrayIterationSane = true;
+ } else {
+ // "Object" time zones need to ensure array iteration is still sane.
+ if (!IsArrayIterationSane(cx, &arrayIterationSane)) {
+ return false;
+ }
+ }
+
+ if (arrayIterationSane) {
+ auto builtin = timeZone.receiver().toTimeZoneObjectMaybeBuiltin();
+ return BuiltinGetPossibleInstantsFor(cx, builtin, dateTime, list);
+ }
+ }
+
+ // Steps 1 and 3-7.
+ return GetPossibleInstantsForSlow(cx, timeZone, dateTimeObj, list);
+}
+
+/**
+ * GetPossibleInstantsFor ( timeZoneRec, dateTime )
+ */
+bool js::temporal::GetPossibleInstantsFor(
+ JSContext* cx, Handle<TimeZoneRecord> timeZone,
+ Handle<PlainDateTimeWithCalendar> dateTime,
+ MutableHandle<InstantVector> list) {
+ // Step 2. (Reordered)
+ auto getPossibleInstantsFor = timeZone.getPossibleInstantsFor();
+ if (!getPossibleInstantsFor) {
+ bool arrayIterationSane;
+ if (timeZone.receiver().isString()) {
+ // "String" time zones don't perform observable array iteration.
+ arrayIterationSane = true;
+ } else {
+ // "Object" time zones need to ensure array iteration is still sane.
+ if (!IsArrayIterationSane(cx, &arrayIterationSane)) {
+ return false;
+ }
+ }
+
+ if (arrayIterationSane) {
+ auto builtin = timeZone.receiver().toTimeZoneObjectMaybeBuiltin();
+ return BuiltinGetPossibleInstantsFor(cx, builtin,
+ ToPlainDateTime(dateTime), list);
+ }
+ }
+
+ Rooted<PlainDateTimeObject*> dateTimeObj(
+ cx, CreateTemporalDateTime(cx, ToPlainDateTime(dateTime),
+ dateTime.calendar()));
+ if (!dateTimeObj) {
+ return false;
+ }
+
+ // Steps 1 and 3-7.
+ return GetPossibleInstantsForSlow(cx, timeZone, dateTimeObj, list);
+}
+
+/**
+ * AddTime ( hour, minute, second, millisecond, microsecond, nanosecond, hours,
+ * minutes, seconds, milliseconds, microseconds, nanoseconds )
+ */
+static auto AddTime(const PlainTime& time, int64_t nanoseconds) {
+ MOZ_ASSERT(IsValidTime(time));
+ MOZ_ASSERT(std::abs(nanoseconds) <= 2 * ToNanoseconds(TemporalUnit::Day));
+
+ // Steps 1-7.
+ return BalanceTime(time, nanoseconds);
+}
+
+/**
+ * DisambiguatePossibleInstants ( possibleInstants, timeZoneRec, dateTime,
+ * disambiguation )
+ */
+bool js::temporal::DisambiguatePossibleInstants(
+ JSContext* cx, Handle<InstantVector> possibleInstants,
+ Handle<TimeZoneRecord> timeZone, const PlainDateTime& dateTime,
+ TemporalDisambiguation disambiguation,
+ MutableHandle<Wrapped<InstantObject*>> result) {
+ // Step 1.
+ MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
+ timeZone, TimeZoneMethod::GetPossibleInstantsFor));
+
+ // Step 2.
+ MOZ_ASSERT_IF(possibleInstants.empty() &&
+ disambiguation != TemporalDisambiguation::Reject,
+ TimeZoneMethodsRecordHasLookedUp(
+ timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
+
+ // Steps 3-4.
+ if (possibleInstants.length() == 1) {
+ result.set(possibleInstants[0]);
+ return true;
+ }
+
+ // Steps 5-6.
+ if (!possibleInstants.empty()) {
+ // Step 5.a.
+ if (disambiguation == TemporalDisambiguation::Earlier ||
+ disambiguation == TemporalDisambiguation::Compatible) {
+ result.set(possibleInstants[0]);
+ return true;
+ }
+
+ // Step 5.b.
+ if (disambiguation == TemporalDisambiguation::Later) {
+ size_t last = possibleInstants.length() - 1;
+ result.set(possibleInstants[last]);
+ return true;
+ }
+
+ // Step 5.c.
+ MOZ_ASSERT(disambiguation == TemporalDisambiguation::Reject);
+
+ // Step 5.d.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS);
+ return false;
+ }
+
+ // Step 7.
+ if (disambiguation == TemporalDisambiguation::Reject) {
+ // TODO: Improve error message to say the date was skipped.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS);
+ return false;
+ }
+
+ constexpr auto oneDay =
+ InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day));
+
+ // Step 8.
+ auto epochNanoseconds = GetUTCEpochNanoseconds(dateTime);
+
+ // Steps 9 and 11.
+ auto dayBefore = epochNanoseconds - oneDay;
+
+ // Step 10.
+ if (!IsValidEpochInstant(dayBefore)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_INVALID);
+ return false;
+ }
+
+ // Step 12 and 14.
+ auto dayAfter = epochNanoseconds + oneDay;
+
+ // Step 13.
+ if (!IsValidEpochInstant(dayAfter)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_INVALID);
+ return false;
+ }
+
+ // Step 15.
+ int64_t offsetBefore;
+ if (!GetOffsetNanosecondsFor(cx, timeZone, dayBefore, &offsetBefore)) {
+ return false;
+ }
+ MOZ_ASSERT(std::abs(offsetBefore) < ToNanoseconds(TemporalUnit::Day));
+
+ // Step 16.
+ int64_t offsetAfter;
+ if (!GetOffsetNanosecondsFor(cx, timeZone, dayAfter, &offsetAfter)) {
+ return false;
+ }
+ MOZ_ASSERT(std::abs(offsetAfter) < ToNanoseconds(TemporalUnit::Day));
+
+ // Step 17.
+ int64_t nanoseconds = offsetAfter - offsetBefore;
+
+ // Step 18.
+ if (disambiguation == TemporalDisambiguation::Earlier) {
+ // Step 18.a.
+ auto earlierTime = ::AddTime(dateTime.time, -nanoseconds);
+ MOZ_ASSERT(std::abs(earlierTime.days) <= 2,
+ "subtracting nanoseconds is at most two days");
+
+ // Step 18.b.
+ PlainDate earlierDate;
+ if (!AddISODate(cx, dateTime.date, {0, 0, 0, double(earlierTime.days)},
+ TemporalOverflow::Constrain, &earlierDate)) {
+ return false;
+ }
+
+ // Step 18.c.
+ Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
+ Rooted<PlainDateTimeWithCalendar> earlierDateTime(
+ cx,
+ PlainDateTimeWithCalendar{{earlierDate, earlierTime.time}, calendar});
+
+ // Step 18.d.
+ Rooted<InstantVector> earlierInstants(cx, InstantVector(cx));
+ if (!GetPossibleInstantsFor(cx, timeZone, earlierDateTime,
+ &earlierInstants)) {
+ return false;
+ }
+
+ // Step 18.e.
+ if (earlierInstants.empty()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS);
+ return false;
+ }
+
+ // Step 18.f.
+ result.set(earlierInstants[0]);
+ return true;
+ }
+
+ // Step 19.
+ MOZ_ASSERT(disambiguation == TemporalDisambiguation::Compatible ||
+ disambiguation == TemporalDisambiguation::Later);
+
+ // Step 20.
+ auto laterTime = ::AddTime(dateTime.time, nanoseconds);
+ MOZ_ASSERT(std::abs(laterTime.days) <= 2,
+ "adding nanoseconds is at most two days");
+
+ // Step 21.
+ PlainDate laterDate;
+ if (!AddISODate(cx, dateTime.date, {0, 0, 0, double(laterTime.days)},
+ TemporalOverflow::Constrain, &laterDate)) {
+ return false;
+ }
+
+ // Step 22.
+ Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
+ Rooted<PlainDateTimeWithCalendar> laterDateTime(
+ cx, PlainDateTimeWithCalendar{{laterDate, laterTime.time}, calendar});
+
+ // Step 23.
+ Rooted<InstantVector> laterInstants(cx, InstantVector(cx));
+ if (!GetPossibleInstantsFor(cx, timeZone, laterDateTime, &laterInstants)) {
+ return false;
+ }
+
+ // Steps 24-25.
+ if (laterInstants.empty()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_TIMEZONE_INSTANT_AMBIGUOUS);
+ return false;
+ }
+
+ // Step 26.
+ size_t last = laterInstants.length() - 1;
+ result.set(laterInstants[last]);
+ return true;
+}
+
+/**
+ * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
+ */
+static bool GetInstantFor(JSContext* cx, Handle<TimeZoneRecord> timeZone,
+ Handle<Wrapped<PlainDateTimeObject*>> dateTime,
+ TemporalDisambiguation disambiguation,
+ MutableHandle<Wrapped<InstantObject*>> result) {
+ // Step 1.
+ MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
+ timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
+
+ // Step 2.
+ MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
+ timeZone, TimeZoneMethod::GetPossibleInstantsFor));
+
+ auto* unwrappedDateTime = dateTime.unwrap(cx);
+ if (!unwrappedDateTime) {
+ return false;
+ }
+ auto plainDateTime = ToPlainDateTime(unwrappedDateTime);
+
+ // Step 3.
+ Rooted<InstantVector> possibleInstants(cx, InstantVector(cx));
+ if (!GetPossibleInstantsFor(cx, timeZone, dateTime, plainDateTime,
+ &possibleInstants)) {
+ return false;
+ }
+
+ // Step 4.
+ return DisambiguatePossibleInstants(cx, possibleInstants, timeZone,
+ plainDateTime, disambiguation, result);
+}
+
+/**
+ * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
+ */
+static bool GetInstantFor(JSContext* cx, Handle<TimeZoneValue> timeZone,
+ Handle<Wrapped<PlainDateTimeObject*>> dateTime,
+ TemporalDisambiguation disambiguation,
+ MutableHandle<Wrapped<InstantObject*>> result) {
+ Rooted<TimeZoneRecord> timeZoneRec(cx);
+ if (!CreateTimeZoneMethodsRecord(cx, timeZone,
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ TimeZoneMethod::GetPossibleInstantsFor,
+ },
+ &timeZoneRec)) {
+ return false;
+ }
+
+ return GetInstantFor(cx, timeZoneRec, dateTime, disambiguation, result);
+}
+
+/**
+ * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
+ */
+bool js::temporal::GetInstantFor(JSContext* cx, Handle<TimeZoneValue> timeZone,
+ Handle<PlainDateTimeObject*> dateTime,
+ TemporalDisambiguation disambiguation,
+ Instant* result) {
+ Rooted<TimeZoneRecord> timeZoneRec(cx);
+ if (!CreateTimeZoneMethodsRecord(cx, timeZone,
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ TimeZoneMethod::GetPossibleInstantsFor,
+ },
+ &timeZoneRec)) {
+ return false;
+ }
+
+ Rooted<Wrapped<InstantObject*>> instant(cx);
+ if (!::GetInstantFor(cx, timeZoneRec, dateTime, disambiguation, &instant)) {
+ return false;
+ }
+
+ auto* unwrappedInstant = instant.unwrap(cx);
+ if (!unwrappedInstant) {
+ return false;
+ }
+
+ *result = ToInstant(unwrappedInstant);
+ return true;
+}
+
+/**
+ * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
+ */
+bool js::temporal::GetInstantFor(JSContext* cx, Handle<TimeZoneRecord> timeZone,
+ Handle<PlainDateTimeWithCalendar> dateTime,
+ TemporalDisambiguation disambiguation,
+ Instant* result) {
+ // Step 1.
+ MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
+ timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
+
+ // Step 2.
+ MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
+ timeZone, TimeZoneMethod::GetPossibleInstantsFor));
+
+ // Step 3.
+ Rooted<InstantVector> possibleInstants(cx, InstantVector(cx));
+ if (!GetPossibleInstantsFor(cx, timeZone, dateTime, &possibleInstants)) {
+ return false;
+ }
+
+ // Step 4.
+ Rooted<Wrapped<InstantObject*>> instant(cx);
+ if (!DisambiguatePossibleInstants(cx, possibleInstants, timeZone,
+ ToPlainDateTime(dateTime), disambiguation,
+ &instant)) {
+ return false;
+ }
+
+ auto* unwrappedInstant = instant.unwrap(cx);
+ if (!unwrappedInstant) {
+ return false;
+ }
+
+ *result = ToInstant(unwrappedInstant);
+ return true;
+}
+
+/**
+ * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
+ */
+bool js::temporal::GetInstantFor(JSContext* cx, Handle<TimeZoneValue> timeZone,
+ Handle<PlainDateTimeWithCalendar> dateTime,
+ TemporalDisambiguation disambiguation,
+ Instant* result) {
+ Rooted<TimeZoneRecord> timeZoneRec(cx);
+ if (!CreateTimeZoneMethodsRecord(cx, timeZone,
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ TimeZoneMethod::GetPossibleInstantsFor,
+ },
+ &timeZoneRec)) {
+ return false;
+ }
+
+ return GetInstantFor(cx, timeZoneRec, dateTime, disambiguation, result);
+}
+
+/**
+ * IsOffsetTimeZoneIdentifier ( offsetString )
+ *
+ * Return true if |offsetString| is the prefix of a time zone offset string.
+ * Time zone offset strings are be parsed through the |TimeZoneUTCOffsetName|
+ * production.
+ *
+ * TimeZoneUTCOffsetName :
+ * UTCOffsetMinutePrecision
+ *
+ * UTCOffsetMinutePrecision :
+ * Sign Hour[+Padded]
+ * Sign Hour[+Padded] TimeSeparator[+Extended] MinuteSecond
+ * Sign Hour[+Padded] TimeSeparator[~Extended] MinuteSecond
+ *
+ * Sign :
+ * ASCIISign
+ * U+2212
+ *
+ * ASCIISign : one of + -
+ *
+ * NOTE: IANA time zone identifiers can't start with |Sign|.
+ */
+static bool IsOffsetTimeZoneIdentifierPrefix(JSLinearString* offsetString) {
+ // Empty string can't be the prefix of |TimeZoneUTCOffsetName|.
+ if (offsetString->empty()) {
+ return false;
+ }
+
+ // Return true iff |offsetString| starts with |Sign|.
+ char16_t ch = offsetString->latin1OrTwoByteChar(0);
+ return ch == '+' || ch == '-' || ch == 0x2212;
+}
+
+/**
+ * Temporal.TimeZone ( identifier )
+ */
+static bool TimeZoneConstructor(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ if (!ThrowIfNotConstructing(cx, args, "Temporal.TimeZone")) {
+ return false;
+ }
+
+ // Step 2.
+ if (!args.requireAtLeast(cx, "Temporal.TimeZone", 1)) {
+ return false;
+ }
+
+ if (!args[0].isString()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_SEARCH_STACK, args[0],
+ nullptr, "not a string");
+ return false;
+ }
+
+ Rooted<JSLinearString*> identifier(cx, args[0].toString()->ensureLinear(cx));
+ if (!identifier) {
+ return false;
+ }
+
+ Rooted<JSString*> canonical(cx);
+ Rooted<Value> offsetMinutes(cx);
+ if (IsOffsetTimeZoneIdentifierPrefix(identifier)) {
+ // Step 3.
+ int32_t minutes;
+ if (!ParseTimeZoneOffsetString(cx, identifier, &minutes)) {
+ return false;
+ }
+ MOZ_ASSERT(std::abs(minutes) < UnitsPerDay(TemporalUnit::Minute));
+
+ canonical = FormatOffsetTimeZoneIdentifier(cx, minutes);
+ if (!canonical) {
+ return false;
+ }
+
+ offsetMinutes.setInt32(minutes);
+ } else {
+ // Step 4.
+ canonical = ValidateAndCanonicalizeTimeZoneName(cx, identifier);
+ if (!canonical) {
+ return false;
+ }
+
+ offsetMinutes.setUndefined();
+ }
+
+ // Step 5.
+ auto* timeZone = CreateTemporalTimeZone(cx, args, canonical, offsetMinutes);
+ if (!timeZone) {
+ return false;
+ }
+
+ args.rval().setObject(*timeZone);
+ return true;
+}
+
+/**
+ * Temporal.TimeZone.from ( item )
+ */
+static bool TimeZone_from(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ Rooted<TimeZoneValue> timeZone(cx);
+ if (!ToTemporalTimeZone(cx, args.get(0), &timeZone)) {
+ return false;
+ }
+
+ // Step 2.
+ auto* obj = ToTemporalTimeZoneObject(cx, timeZone);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.TimeZone.prototype.equals ( timeZoneLike )
+ */
+static bool TimeZone_equals(JSContext* cx, const CallArgs& args) {
+ Rooted<TimeZoneValue> timeZone(cx, &args.thisv().toObject());
+
+ // Step 3.
+ Rooted<TimeZoneValue> other(cx);
+ if (!ToTemporalTimeZone(cx, args.get(0), &other)) {
+ return false;
+ }
+
+ // Step 4.
+ bool equals;
+ if (!TimeZoneEquals(cx, timeZone, other, &equals)) {
+ return false;
+ }
+
+ args.rval().setBoolean(equals);
+ return true;
+}
+
+/**
+ * Temporal.TimeZone.prototype.equals ( timeZoneLike )
+ */
+static bool TimeZone_equals(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsTimeZone, TimeZone_equals>(cx, args);
+}
+
+/**
+ * Temporal.TimeZone.prototype.getOffsetNanosecondsFor ( instant )
+ */
+static bool TimeZone_getOffsetNanosecondsFor(JSContext* cx,
+ const CallArgs& args) {
+ Rooted<TimeZoneObject*> timeZone(
+ cx, &args.thisv().toObject().as<TimeZoneObject>());
+
+ // Step 3.
+ Instant instant;
+ if (!ToTemporalInstant(cx, args.get(0), &instant)) {
+ return false;
+ }
+
+ // Steps 4-5.
+ int64_t offset;
+ if (!BuiltinGetOffsetNanosecondsFor(cx, timeZone, instant, &offset)) {
+ return false;
+ }
+
+ args.rval().setNumber(offset);
+ return true;
+}
+
+/**
+ * Temporal.TimeZone.prototype.getOffsetNanosecondsFor ( instant )
+ */
+static bool TimeZone_getOffsetNanosecondsFor(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsTimeZone, TimeZone_getOffsetNanosecondsFor>(
+ cx, args);
+}
+
+/**
+ * Temporal.TimeZone.prototype.getOffsetStringFor ( instant )
+ */
+static bool TimeZone_getOffsetStringFor(JSContext* cx, const CallArgs& args) {
+ Rooted<TimeZoneValue> timeZone(cx, &args.thisv().toObject());
+
+ // FIXME: spec issue - CreateTimeZoneMethodsRecord called before
+ // ToTemporalInstant whereas TimeZone.p.{getPlainDateTimeFor,getInstantFor}
+ // first convert the input arguments.
+
+ // Step 3.
+ Rooted<TimeZoneRecord> timeZoneRec(cx);
+ if (!CreateTimeZoneMethodsRecord(cx, timeZone,
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ },
+ &timeZoneRec)) {
+ return false;
+ }
+
+ // Step 4.
+ Rooted<Wrapped<InstantObject*>> instant(cx,
+ ToTemporalInstant(cx, args.get(0)));
+ if (!instant) {
+ return false;
+ }
+
+ // Step 5.
+ JSString* str = GetOffsetStringFor(cx, timeZoneRec, instant);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.TimeZone.prototype.getOffsetStringFor ( instant )
+ */
+static bool TimeZone_getOffsetStringFor(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsTimeZone, TimeZone_getOffsetStringFor>(cx,
+ args);
+}
+
+/**
+ * Temporal.TimeZone.prototype.getPlainDateTimeFor ( instant [, calendarLike ] )
+ */
+static bool TimeZone_getPlainDateTimeFor(JSContext* cx, const CallArgs& args) {
+ Rooted<TimeZoneValue> timeZone(cx, &args.thisv().toObject());
+
+ // Step 3.
+ Rooted<Wrapped<InstantObject*>> instant(cx,
+ ToTemporalInstant(cx, args.get(0)));
+ if (!instant) {
+ return false;
+ }
+
+ // Step 4.
+ Rooted<CalendarValue> calendar(cx);
+ if (!ToTemporalCalendarWithISODefault(cx, args.get(1), &calendar)) {
+ return false;
+ }
+
+ // Steps 5-6.
+ auto* result = GetPlainDateTimeFor(cx, timeZone, instant, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.TimeZone.prototype.getPlainDateTimeFor ( instant [, calendarLike ] )
+ */
+static bool TimeZone_getPlainDateTimeFor(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsTimeZone, TimeZone_getPlainDateTimeFor>(cx,
+ args);
+}
+
+/**
+ * Temporal.TimeZone.prototype.getInstantFor ( dateTime [ , options ] )
+ */
+static bool TimeZone_getInstantFor(JSContext* cx, const CallArgs& args) {
+ Rooted<TimeZoneValue> timeZone(cx, &args.thisv().toObject());
+
+ // Step 3.
+ Rooted<Wrapped<PlainDateTimeObject*>> dateTime(
+ cx, ToTemporalDateTime(cx, args.get(0)));
+ if (!dateTime) {
+ return false;
+ }
+
+ // Steps 4-5.
+ auto disambiguation = TemporalDisambiguation::Compatible;
+ if (args.hasDefined(1)) {
+ // Step 4.
+ Rooted<JSObject*> options(
+ cx, RequireObjectArg(cx, "options", "getInstantFor", args[1]));
+ if (!options) {
+ return false;
+ }
+
+ // Step 5.
+ if (!ToTemporalDisambiguation(cx, options, &disambiguation)) {
+ return false;
+ }
+ }
+
+ // Steps 6-7.
+ Rooted<Wrapped<InstantObject*>> result(cx);
+ if (!::GetInstantFor(cx, timeZone, dateTime, disambiguation, &result)) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.TimeZone.prototype.getInstantFor ( dateTime [ , options ] )
+ */
+static bool TimeZone_getInstantFor(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsTimeZone, TimeZone_getInstantFor>(cx, args);
+}
+
+/**
+ * Temporal.TimeZone.prototype.getPossibleInstantsFor ( dateTime )
+ */
+static bool TimeZone_getPossibleInstantsFor(JSContext* cx,
+ const CallArgs& args) {
+ Rooted<TimeZoneObject*> timeZone(
+ cx, &args.thisv().toObject().as<TimeZoneObject>());
+
+ // Step 3.
+ PlainDateTime dateTime;
+ if (!ToTemporalDateTime(cx, args.get(0), &dateTime)) {
+ return false;
+ }
+
+ // Steps 4-5.
+ EpochInstantList possibleInstants;
+ if (!BuiltinGetPossibleInstantsFor(cx, timeZone, dateTime,
+ possibleInstants)) {
+ return false;
+ }
+
+ // Step 6.
+ size_t length = possibleInstants.length();
+ Rooted<ArrayObject*> result(cx, NewDenseFullyAllocatedArray(cx, length));
+ if (!result) {
+ return false;
+ }
+ result->ensureDenseInitializedLength(0, length);
+
+ // Step 7.
+ for (size_t i = 0; i < length; i++) {
+ // Step 7.a. (Already performed in step 4 in our implementation.)
+ MOZ_ASSERT(IsValidEpochInstant(possibleInstants[i]));
+
+ // Step 7.b.
+ auto* instant = CreateTemporalInstant(cx, possibleInstants[i]);
+ if (!instant) {
+ return false;
+ }
+
+ // Step 7.c.
+ result->initDenseElement(i, ObjectValue(*instant));
+ }
+
+ // Step 8.
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.TimeZone.prototype.getPossibleInstantsFor ( dateTime )
+ */
+static bool TimeZone_getPossibleInstantsFor(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsTimeZone, TimeZone_getPossibleInstantsFor>(
+ cx, args);
+}
+
+/**
+ * Temporal.TimeZone.prototype.getNextTransition ( startingPoint )
+ */
+static bool TimeZone_getNextTransition(JSContext* cx, const CallArgs& args) {
+ Rooted<TimeZoneObject*> timeZone(
+ cx, &args.thisv().toObject().as<TimeZoneObject>());
+
+ // Step 3.
+ Instant startingPoint;
+ if (!ToTemporalInstant(cx, args.get(0), &startingPoint)) {
+ return false;
+ }
+
+ // Step 4.
+ if (!timeZone->offsetMinutes().isUndefined()) {
+ args.rval().setNull();
+ return true;
+ }
+
+ // Step 5.
+ mozilla::Maybe<Instant> transition;
+ if (!GetNamedTimeZoneNextTransition(cx, timeZone, startingPoint,
+ &transition)) {
+ return false;
+ }
+
+ // Step 6.
+ if (!transition) {
+ args.rval().setNull();
+ return true;
+ }
+
+ // Step 7.
+ auto* instant = CreateTemporalInstant(cx, *transition);
+ if (!instant) {
+ return false;
+ }
+
+ args.rval().setObject(*instant);
+ return true;
+}
+
+/**
+ * Temporal.TimeZone.prototype.getNextTransition ( startingPoint )
+ */
+static bool TimeZone_getNextTransition(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsTimeZone, TimeZone_getNextTransition>(cx, args);
+}
+
+/**
+ * Temporal.TimeZone.prototype.getPreviousTransition ( startingPoint )
+ */
+static bool TimeZone_getPreviousTransition(JSContext* cx,
+ const CallArgs& args) {
+ Rooted<TimeZoneObject*> timeZone(
+ cx, &args.thisv().toObject().as<TimeZoneObject>());
+
+ // Step 3.
+ Instant startingPoint;
+ if (!ToTemporalInstant(cx, args.get(0), &startingPoint)) {
+ return false;
+ }
+
+ // Step 4.
+ if (!timeZone->offsetMinutes().isUndefined()) {
+ args.rval().setNull();
+ return true;
+ }
+
+ // Step 5.
+ mozilla::Maybe<Instant> transition;
+ if (!GetNamedTimeZonePreviousTransition(cx, timeZone, startingPoint,
+ &transition)) {
+ return false;
+ }
+
+ // Step 6.
+ if (!transition) {
+ args.rval().setNull();
+ return true;
+ }
+
+ // Step 7.
+ auto* instant = CreateTemporalInstant(cx, *transition);
+ if (!instant) {
+ return false;
+ }
+
+ args.rval().setObject(*instant);
+ return true;
+}
+
+/**
+ * Temporal.TimeZone.prototype.getPreviousTransition ( startingPoint )
+ */
+static bool TimeZone_getPreviousTransition(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsTimeZone, TimeZone_getPreviousTransition>(cx,
+ args);
+}
+
+/**
+ * Temporal.TimeZone.prototype.toString ( )
+ */
+static bool TimeZone_toString(JSContext* cx, const CallArgs& args) {
+ auto* timeZone = &args.thisv().toObject().as<TimeZoneObject>();
+
+ // Steps 3-4.
+ args.rval().setString(timeZone->identifier());
+ return true;
+}
+
+/**
+ * Temporal.TimeZone.prototype.toString ( )
+ */
+static bool TimeZone_toString(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsTimeZone, TimeZone_toString>(cx, args);
+}
+
+/**
+ * Temporal.TimeZone.prototype.toJSON ( )
+ */
+static bool TimeZone_toJSON(JSContext* cx, const CallArgs& args) {
+ auto* timeZone = &args.thisv().toObject().as<TimeZoneObject>();
+
+ // Steps 3-4.
+ args.rval().setString(timeZone->identifier());
+ return true;
+}
+
+/**
+ * Temporal.TimeZone.prototype.toJSON ( )
+ */
+static bool TimeZone_toJSON(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsTimeZone, TimeZone_toJSON>(cx, args);
+}
+
+/**
+ * get Temporal.TimeZone.prototype.id
+ */
+static bool TimeZone_id(JSContext* cx, const CallArgs& args) {
+ auto* timeZone = &args.thisv().toObject().as<TimeZoneObject>();
+
+ // Steps 3-4.
+ args.rval().setString(timeZone->identifier());
+ return true;
+}
+
+/**
+ * get Temporal.TimeZone.prototype.id
+ */
+static bool TimeZone_id(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsTimeZone, TimeZone_id>(cx, args);
+}
+
+void js::temporal::TimeZoneObjectMaybeBuiltin::finalize(JS::GCContext* gcx,
+ JSObject* obj) {
+ MOZ_ASSERT(gcx->onMainThread());
+
+ if (auto* timeZone = obj->as<TimeZoneObjectMaybeBuiltin>().getTimeZone()) {
+ intl::RemoveICUCellMemory(gcx, obj, TimeZoneObject::EstimatedMemoryUse);
+ delete timeZone;
+ }
+}
+
+const JSClassOps TimeZoneObject::classOps_ = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ TimeZoneObject::finalize, // finalize
+ nullptr, // call
+ nullptr, // construct
+ nullptr, // trace
+};
+
+const JSClass TimeZoneObject::class_ = {
+ "Temporal.TimeZone",
+ JSCLASS_HAS_RESERVED_SLOTS(TimeZoneObject::SLOT_COUNT) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_TimeZone) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &TimeZoneObject::classOps_,
+ &TimeZoneObject::classSpec_,
+};
+
+const JSClass& TimeZoneObject::protoClass_ = PlainObject::class_;
+
+static const JSFunctionSpec TimeZone_methods[] = {
+ JS_FN("from", TimeZone_from, 1, 0),
+ JS_FS_END,
+};
+
+static const JSFunctionSpec TimeZone_prototype_methods[] = {
+ JS_FN("equals", TimeZone_equals, 1, 0),
+ JS_FN("getOffsetNanosecondsFor", TimeZone_getOffsetNanosecondsFor, 1, 0),
+ JS_FN("getOffsetStringFor", TimeZone_getOffsetStringFor, 1, 0),
+ JS_FN("getPlainDateTimeFor", TimeZone_getPlainDateTimeFor, 1, 0),
+ JS_FN("getInstantFor", TimeZone_getInstantFor, 1, 0),
+ JS_FN("getPossibleInstantsFor", TimeZone_getPossibleInstantsFor, 1, 0),
+ JS_FN("getNextTransition", TimeZone_getNextTransition, 1, 0),
+ JS_FN("getPreviousTransition", TimeZone_getPreviousTransition, 1, 0),
+ JS_FN("toString", TimeZone_toString, 0, 0),
+ JS_FN("toJSON", TimeZone_toJSON, 0, 0),
+ JS_FS_END,
+};
+
+static const JSPropertySpec TimeZone_prototype_properties[] = {
+ JS_PSG("id", TimeZone_id, 0),
+ JS_STRING_SYM_PS(toStringTag, "Temporal.TimeZone", JSPROP_READONLY),
+ JS_PS_END,
+};
+
+const ClassSpec TimeZoneObject::classSpec_ = {
+ GenericCreateConstructor<TimeZoneConstructor, 1, gc::AllocKind::FUNCTION>,
+ GenericCreatePrototype<TimeZoneObject>,
+ TimeZone_methods,
+ nullptr,
+ TimeZone_prototype_methods,
+ TimeZone_prototype_properties,
+ nullptr,
+ ClassSpec::DontDefineConstructor,
+};
+
+const JSClassOps BuiltinTimeZoneObject::classOps_ = {
+ nullptr, // addProperty
+ nullptr, // delProperty
+ nullptr, // enumerate
+ nullptr, // newEnumerate
+ nullptr, // resolve
+ nullptr, // mayResolve
+ BuiltinTimeZoneObject::finalize, // finalize
+ nullptr, // call
+ nullptr, // construct
+ nullptr, // trace
+};
+
+const JSClass BuiltinTimeZoneObject::class_ = {
+ "Temporal.BuiltinTimeZone",
+ JSCLASS_HAS_RESERVED_SLOTS(BuiltinTimeZoneObject::SLOT_COUNT) |
+ JSCLASS_FOREGROUND_FINALIZE,
+ &BuiltinTimeZoneObject::classOps_,
+};
diff --git a/js/src/builtin/temporal/TimeZone.h b/js/src/builtin/temporal/TimeZone.h
new file mode 100644
index 0000000000..f1d0bf3f1f
--- /dev/null
+++ b/js/src/builtin/temporal/TimeZone.h
@@ -0,0 +1,625 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_TimeZone_h
+#define builtin_temporal_TimeZone_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/EnumSet.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "builtin/temporal/Wrapped.h"
+#include "js/GCVector.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "vm/JSObject.h"
+#include "vm/NativeObject.h"
+
+class JSLinearString;
+class JS_PUBLIC_API JSTracer;
+struct JSClassOps;
+
+namespace js {
+struct ClassSpec;
+}
+
+namespace mozilla::intl {
+class TimeZone;
+}
+
+namespace js::temporal {
+
+class TimeZoneObjectMaybeBuiltin : public NativeObject {
+ public:
+ static constexpr uint32_t IDENTIFIER_SLOT = 0;
+ static constexpr uint32_t OFFSET_MINUTES_SLOT = 1;
+ static constexpr uint32_t INTL_TIMEZONE_SLOT = 2;
+ static constexpr uint32_t SLOT_COUNT = 3;
+
+ // Estimated memory use for intl::TimeZone (see IcuMemoryUsage).
+ static constexpr size_t EstimatedMemoryUse = 6840;
+
+ JSString* identifier() const {
+ return getFixedSlot(IDENTIFIER_SLOT).toString();
+ }
+
+ const auto& offsetMinutes() const {
+ return getFixedSlot(OFFSET_MINUTES_SLOT);
+ }
+
+ mozilla::intl::TimeZone* getTimeZone() const {
+ const auto& slot = getFixedSlot(INTL_TIMEZONE_SLOT);
+ if (slot.isUndefined()) {
+ return nullptr;
+ }
+ return static_cast<mozilla::intl::TimeZone*>(slot.toPrivate());
+ }
+
+ void setTimeZone(mozilla::intl::TimeZone* timeZone) {
+ setFixedSlot(INTL_TIMEZONE_SLOT, JS::PrivateValue(timeZone));
+ }
+
+ protected:
+ static void finalize(JS::GCContext* gcx, JSObject* obj);
+};
+
+class TimeZoneObject : public TimeZoneObjectMaybeBuiltin {
+ public:
+ static const JSClass class_;
+ static const JSClass& protoClass_;
+
+ private:
+ static const JSClassOps classOps_;
+ static const ClassSpec classSpec_;
+};
+
+class BuiltinTimeZoneObject : public TimeZoneObjectMaybeBuiltin {
+ public:
+ static const JSClass class_;
+
+ private:
+ static const JSClassOps classOps_;
+};
+
+} /* namespace js::temporal */
+
+template <>
+inline bool JSObject::is<js::temporal::TimeZoneObjectMaybeBuiltin>() const {
+ return is<js::temporal::TimeZoneObject>() ||
+ is<js::temporal::BuiltinTimeZoneObject>();
+}
+
+namespace js::temporal {
+
+/**
+ * Temporal time zones can be either objects or strings. Objects are either
+ * instances of `Temporal.TimeZone` or user-defined time zones. Strings are
+ * either canonical time zone identifiers or time zone offset strings.
+ *
+ * Examples of valid Temporal time zones:
+ * - Any object
+ * - "UTC"
+ * - "America/New_York"
+ * - "+00:00"
+ *
+ * Examples of invalid Temporal time zones:
+ * - Number values
+ * - "utc" (wrong case)
+ * - "Etc/UTC" (canonical name is "UTC")
+ * - "+00" (missing minutes part)
+ * - "+00:00:00" (sub-minute precision)
+ * - "+00:00:01" (sub-minute precision)
+ * - "-00:00" (wrong sign for zero offset)
+ *
+ * String-valued Temporal time zones are an optimization to avoid allocating
+ * `Temporal.TimeZone` objects when creating `Temporal.ZonedDateTime` objects.
+ * For example `Temporal.ZonedDateTime.from("1970-01-01[UTC]")` doesn't require
+ * to allocate a fresh `Temporal.TimeZone` object for the "UTC" time zone.
+ *
+ * The specification creates new `Temporal.TimeZone` objects whenever any
+ * operation is performed on a string-valued Temporal time zone. This newly
+ * created object can't be accessed by the user and implementations are expected
+ * to optimize away the allocation.
+ *
+ * The following two implementation approaches are possible:
+ *
+ * 1. Represent string-valued time zones as JSStrings. Additionally keep a
+ * mapping from JSString to `mozilla::intl::TimeZone` to avoid repeatedly
+ * creating new `mozilla::intl::TimeZone` for time zone operations. Offset
+ * string time zones have to be special cased, because they don't use
+ * `mozilla::intl::TimeZone`. Either detect offset strings by checking the
+ * time zone identifier or store offset strings as the offset in minutes
+ * value to avoid reparsing the offset string again and again.
+ * 2. Represent string-valued time zones as `Temporal.TimeZone`-like objects.
+ * These internal `Temporal.TimeZone`-like objects must not be exposed to
+ * user-code.
+ *
+ * Option 2 is a bit easier to implement, so we use this approach for now.
+ */
+class TimeZoneValue final {
+ JSObject* object_ = nullptr;
+
+ public:
+ /**
+ * Default initialize this TimeZoneValue.
+ */
+ TimeZoneValue() = default;
+
+ /**
+ * Initialize this TimeZoneValue with a "string" time zone object.
+ */
+ explicit TimeZoneValue(BuiltinTimeZoneObject* timeZone) : object_(timeZone) {
+ MOZ_ASSERT(isString());
+ }
+
+ /**
+ * Initialize this TimeZoneValue with an "object" time zone object.
+ */
+ explicit TimeZoneValue(JSObject* timeZone) : object_(timeZone) {
+ MOZ_ASSERT(isObject());
+ }
+
+ /**
+ * Initialize this TimeZoneValue from a slot Value, which must be either a
+ * "string" or "object" time zone object.
+ */
+ explicit TimeZoneValue(const JS::Value& value) : object_(&value.toObject()) {}
+
+ /**
+ * Return true if this TimeZoneValue is not null.
+ */
+ explicit operator bool() const { return !!object_; }
+
+ /**
+ * Return true if this TimeZoneValue is a "string" time zone.
+ */
+ bool isString() const {
+ return object_ && object_->is<BuiltinTimeZoneObject>();
+ }
+
+ /**
+ * Return true if this TimeZoneValue is an "object" time zone.
+ */
+ bool isObject() const { return object_ && !isString(); }
+
+ /**
+ * Return true if this TimeZoneValue holds a TimeZoneObjectMaybeBuiltin.
+ */
+ bool isTimeZoneObjectMaybeBuiltin() const {
+ return object_ && object_->is<TimeZoneObjectMaybeBuiltin>();
+ }
+
+ /**
+ * Return this "string" time zone.
+ */
+ auto* toString() const {
+ MOZ_ASSERT(isString());
+ return &object_->as<BuiltinTimeZoneObject>();
+ }
+
+ /**
+ * Return this "object" time zone.
+ */
+ JSObject* toObject() const {
+ MOZ_ASSERT(isObject());
+ return object_;
+ }
+
+ /**
+ * Return the underlying object as a TimeZoneObjectMaybeBuiltin.
+ */
+ auto* toTimeZoneObjectMaybeBuiltin() const {
+ MOZ_ASSERT(isTimeZoneObjectMaybeBuiltin());
+ return &object_->as<TimeZoneObjectMaybeBuiltin>();
+ }
+
+ /**
+ * Return the Value representation of this TimeZoneValue.
+ */
+ JS::Value toValue() const {
+ if (isString()) {
+ return JS::StringValue(toString()->identifier());
+ }
+
+ MOZ_ASSERT(object_);
+ return JS::ObjectValue(*object_);
+ }
+
+ /**
+ * Return the slot Value representation of this TimeZoneValue.
+ */
+ JS::Value toSlotValue() const {
+ MOZ_ASSERT(object_);
+ return JS::ObjectValue(*object_);
+ }
+
+ // Helper methods for (Mutable)WrappedPtrOperations.
+ auto address() { return &object_; }
+ auto address() const { return &object_; }
+
+ // Trace implementation.
+ void trace(JSTracer* trc);
+};
+
+enum class TimeZoneMethod {
+ GetOffsetNanosecondsFor,
+ GetPossibleInstantsFor,
+};
+
+class TimeZoneRecord {
+ TimeZoneValue receiver_;
+
+ // Null unless non-builtin time zone methods are used.
+ JSObject* getOffsetNanosecondsFor_ = nullptr;
+ JSObject* getPossibleInstantsFor_ = nullptr;
+
+#ifdef DEBUG
+ mozilla::EnumSet<TimeZoneMethod> lookedUp_{};
+#endif
+
+ public:
+ /**
+ * Default initialize this TimeZoneRecord.
+ */
+ TimeZoneRecord() = default;
+
+ explicit TimeZoneRecord(const TimeZoneValue& receiver)
+ : receiver_(receiver) {}
+
+ const auto& receiver() const { return receiver_; }
+ auto* getOffsetNanosecondsFor() const { return getOffsetNanosecondsFor_; }
+ auto* getPossibleInstantsFor() const { return getPossibleInstantsFor_; }
+
+#ifdef DEBUG
+ auto& lookedUp() const { return lookedUp_; }
+ auto& lookedUp() { return lookedUp_; }
+#endif
+
+ // Helper methods for (Mutable)WrappedPtrOperations.
+ auto* receiverDoNotUse() const { return &receiver_; }
+ auto* getOffsetNanosecondsForDoNotUse() const {
+ return &getOffsetNanosecondsFor_;
+ }
+ auto* getOffsetNanosecondsForDoNotUse() { return &getOffsetNanosecondsFor_; }
+ auto* getPossibleInstantsForDoNotUse() const {
+ return &getPossibleInstantsFor_;
+ }
+ auto* getPossibleInstantsForDoNotUse() { return &getPossibleInstantsFor_; }
+
+ // Trace implementation.
+ void trace(JSTracer* trc);
+};
+
+struct Instant;
+struct ParsedTimeZone;
+struct PlainDateTime;
+class CalendarValue;
+class InstantObject;
+class PlainDateTimeObject;
+class PlainDateTimeWithCalendar;
+enum class TemporalDisambiguation;
+
+/**
+ * IsValidTimeZoneName ( timeZone )
+ * IsAvailableTimeZoneName ( timeZone )
+ */
+bool IsValidTimeZoneName(JSContext* cx, JS::Handle<JSString*> timeZone,
+ JS::MutableHandle<JSAtom*> validatedTimeZone);
+
+/**
+ * CanonicalizeTimeZoneName ( timeZone )
+ */
+JSString* CanonicalizeTimeZoneName(JSContext* cx,
+ JS::Handle<JSLinearString*> timeZone);
+
+/**
+ * IsValidTimeZoneName ( timeZone )
+ * IsAvailableTimeZoneName ( timeZone )
+ * CanonicalizeTimeZoneName ( timeZone )
+ */
+JSString* ValidateAndCanonicalizeTimeZoneName(JSContext* cx,
+ JS::Handle<JSString*> timeZone);
+
+/**
+ * CreateTemporalTimeZone ( identifier [ , newTarget ] )
+ */
+BuiltinTimeZoneObject* CreateTemporalTimeZone(JSContext* cx,
+ JS::Handle<JSString*> identifier);
+
+/**
+ * ToTemporalTimeZoneSlotValue ( temporalTimeZoneLike )
+ */
+bool ToTemporalTimeZone(JSContext* cx,
+ JS::Handle<JS::Value> temporalTimeZoneLike,
+ JS::MutableHandle<TimeZoneValue> result);
+
+/**
+ * ToTemporalTimeZoneSlotValue ( temporalTimeZoneLike )
+ */
+bool ToTemporalTimeZone(JSContext* cx, JS::Handle<ParsedTimeZone> string,
+ JS::MutableHandle<TimeZoneValue> result);
+
+/**
+ * ToTemporalTimeZoneObject ( timeZoneSlotValue )
+ */
+JSObject* ToTemporalTimeZoneObject(JSContext* cx,
+ JS::Handle<TimeZoneValue> timeZone);
+
+/**
+ * ToTemporalTimeZoneIdentifier ( timeZoneSlotValue )
+ */
+JSString* ToTemporalTimeZoneIdentifier(JSContext* cx,
+ JS::Handle<TimeZoneValue> timeZone);
+
+/**
+ * TimeZoneEquals ( one, two )
+ */
+bool TimeZoneEquals(JSContext* cx, JS::Handle<JSString*> one,
+ JS::Handle<JSString*> two, bool* equals);
+
+/**
+ * TimeZoneEquals ( one, two )
+ */
+bool TimeZoneEquals(JSContext* cx, JS::Handle<TimeZoneValue> one,
+ JS::Handle<TimeZoneValue> two, bool* equals);
+
+/**
+ * GetPlainDateTimeFor ( timeZoneRec, instant, calendar [ ,
+ * precalculatedOffsetNanoseconds ] )
+ */
+PlainDateTimeObject* GetPlainDateTimeFor(JSContext* cx,
+ JS::Handle<TimeZoneValue> timeZone,
+ const Instant& instant,
+ JS::Handle<CalendarValue> calendar);
+
+/**
+ * GetPlainDateTimeFor ( timeZoneRec, instant, calendar [ ,
+ * precalculatedOffsetNanoseconds ] )
+ */
+PlainDateTimeObject* GetPlainDateTimeFor(JSContext* cx, const Instant& instant,
+ JS::Handle<CalendarValue> calendar,
+ int64_t offsetNanoseconds);
+
+/**
+ * GetPlainDateTimeFor ( timeZoneRec, instant, calendar [ ,
+ * precalculatedOffsetNanoseconds ] )
+ */
+PlainDateTime GetPlainDateTimeFor(const Instant& instant,
+ int64_t offsetNanoseconds);
+
+/**
+ * GetPlainDateTimeFor ( timeZoneRec, instant, calendar [ ,
+ * precalculatedOffsetNanoseconds ] )
+ */
+bool GetPlainDateTimeFor(JSContext* cx, JS::Handle<TimeZoneRecord> timeZone,
+ const Instant& instant, PlainDateTime* result);
+
+/**
+ * GetPlainDateTimeFor ( timeZoneRec, instant, calendar [ ,
+ * precalculatedOffsetNanoseconds ] )
+ */
+bool GetPlainDateTimeFor(JSContext* cx, JS::Handle<TimeZoneValue> timeZone,
+ const Instant& instant, PlainDateTime* result);
+
+/**
+ * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
+ */
+bool GetInstantFor(JSContext* cx, JS::Handle<TimeZoneValue> timeZone,
+ JS::Handle<PlainDateTimeObject*> dateTime,
+ TemporalDisambiguation disambiguation, Instant* result);
+
+/**
+ * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
+ */
+bool GetInstantFor(JSContext* cx, JS::Handle<TimeZoneRecord> timeZone,
+ JS::Handle<PlainDateTimeWithCalendar> dateTime,
+ TemporalDisambiguation disambiguation, Instant* result);
+
+/**
+ * GetInstantFor ( timeZoneRec, dateTime, disambiguation )
+ */
+bool GetInstantFor(JSContext* cx, JS::Handle<TimeZoneValue> timeZone,
+ JS::Handle<PlainDateTimeWithCalendar> dateTime,
+ TemporalDisambiguation disambiguation, Instant* result);
+
+/**
+ * FormatUTCOffsetNanoseconds ( offsetNanoseconds )
+ */
+JSString* FormatUTCOffsetNanoseconds(JSContext* cx, int64_t offsetNanoseconds);
+
+/**
+ * GetOffsetStringFor ( timeZoneRec, instant )
+ */
+JSString* GetOffsetStringFor(JSContext* cx, JS::Handle<TimeZoneValue> timeZone,
+ const Instant& instant);
+
+/**
+ * GetOffsetStringFor ( timeZoneRec, instant )
+ */
+JSString* GetOffsetStringFor(JSContext* cx, JS::Handle<TimeZoneRecord> timeZone,
+ JS::Handle<Wrapped<InstantObject*>> instant);
+
+/**
+ * GetOffsetNanosecondsFor ( timeZoneRec, instant )
+ */
+bool GetOffsetNanosecondsFor(JSContext* cx, JS::Handle<TimeZoneRecord> timeZone,
+ JS::Handle<Wrapped<InstantObject*>> instant,
+ int64_t* offsetNanoseconds);
+
+/**
+ * GetOffsetNanosecondsFor ( timeZoneRec, instant )
+ */
+bool GetOffsetNanosecondsFor(JSContext* cx, JS::Handle<TimeZoneValue> timeZone,
+ JS::Handle<Wrapped<InstantObject*>> instant,
+ int64_t* offsetNanoseconds);
+
+/**
+ * GetOffsetNanosecondsFor ( timeZoneRec, instant )
+ */
+bool GetOffsetNanosecondsFor(JSContext* cx, JS::Handle<TimeZoneRecord> timeZone,
+ const Instant& instant,
+ int64_t* offsetNanoseconds);
+
+/**
+ * GetOffsetNanosecondsFor ( timeZoneRec, instant )
+ */
+bool GetOffsetNanosecondsFor(JSContext* cx, JS::Handle<TimeZoneValue> timeZone,
+ const Instant& instant,
+ int64_t* offsetNanoseconds);
+
+using InstantVector = JS::StackGCVector<Wrapped<InstantObject*>>;
+
+/**
+ * GetPossibleInstantsFor ( timeZoneRec, dateTime )
+ */
+bool GetPossibleInstantsFor(JSContext* cx, JS::Handle<TimeZoneRecord> timeZone,
+ JS::Handle<PlainDateTimeWithCalendar> dateTime,
+ JS::MutableHandle<InstantVector> list);
+
+/**
+ * DisambiguatePossibleInstants ( possibleInstants, timeZoneRec, dateTime,
+ * disambiguation )
+ */
+bool DisambiguatePossibleInstants(
+ JSContext* cx, JS::Handle<InstantVector> possibleInstants,
+ JS::Handle<TimeZoneRecord> timeZone, const PlainDateTime& dateTime,
+ TemporalDisambiguation disambiguation,
+ JS::MutableHandle<Wrapped<InstantObject*>> result);
+
+/**
+ * CreateTimeZoneMethodsRecord ( timeZone, methods )
+ */
+bool CreateTimeZoneMethodsRecord(JSContext* cx,
+ JS::Handle<TimeZoneValue> timeZone,
+ mozilla::EnumSet<TimeZoneMethod> methods,
+ JS::MutableHandle<TimeZoneRecord> result);
+
+#ifdef DEBUG
+/**
+ * TimeZoneMethodsRecordHasLookedUp ( timeZoneRec, methodName )
+ */
+inline bool TimeZoneMethodsRecordHasLookedUp(const TimeZoneRecord& timeZone,
+ TimeZoneMethod methodName) {
+ // Steps 1-4.
+ return timeZone.lookedUp().contains(methodName);
+}
+#endif
+
+/**
+ * TimeZoneMethodsRecordIsBuiltin ( timeZoneRec )
+ */
+inline bool TimeZoneMethodsRecordIsBuiltin(const TimeZoneRecord& timeZone) {
+ // Steps 1-2.
+ return timeZone.receiver().isString();
+}
+
+// Helper for MutableWrappedPtrOperations.
+bool WrapTimeZoneValueObject(JSContext* cx,
+ JS::MutableHandle<JSObject*> timeZone);
+
+} /* namespace js::temporal */
+
+namespace js {
+
+template <typename Wrapper>
+class WrappedPtrOperations<temporal::TimeZoneValue, Wrapper> {
+ const auto& container() const {
+ return static_cast<const Wrapper*>(this)->get();
+ }
+
+ public:
+ explicit operator bool() const { return !!container(); }
+
+ bool isString() const { return container().isString(); }
+
+ bool isObject() const { return container().isObject(); }
+
+ JS::Handle<temporal::BuiltinTimeZoneObject*> toString() const {
+ MOZ_ASSERT(container().isString());
+ auto h = JS::Handle<JSObject*>::fromMarkedLocation(container().address());
+ return h.template as<temporal::BuiltinTimeZoneObject>();
+ }
+
+ JS::Handle<JSObject*> toObject() const {
+ MOZ_ASSERT(container().isObject());
+ return JS::Handle<JSObject*>::fromMarkedLocation(container().address());
+ }
+
+ JS::Handle<temporal::TimeZoneObjectMaybeBuiltin*>
+ toTimeZoneObjectMaybeBuiltin() const {
+ MOZ_ASSERT(container().isTimeZoneObjectMaybeBuiltin());
+ auto h = JS::Handle<JSObject*>::fromMarkedLocation(container().address());
+ return h.template as<temporal::TimeZoneObjectMaybeBuiltin>();
+ }
+
+ JS::Value toValue() const { return container().toValue(); }
+
+ JS::Value toSlotValue() const { return container().toSlotValue(); }
+};
+
+template <typename Wrapper>
+class MutableWrappedPtrOperations<temporal::TimeZoneValue, Wrapper>
+ : public WrappedPtrOperations<temporal::TimeZoneValue, Wrapper> {
+ auto& container() { return static_cast<Wrapper*>(this)->get(); }
+
+ public:
+ /**
+ * Wrap the time zone value into the current compartment.
+ */
+ bool wrap(JSContext* cx) {
+ MOZ_ASSERT(container().isString() || container().isObject());
+ auto mh =
+ JS::MutableHandle<JSObject*>::fromMarkedLocation(container().address());
+ return temporal::WrapTimeZoneValueObject(cx, mh);
+ }
+};
+
+template <typename Wrapper>
+class WrappedPtrOperations<temporal::TimeZoneRecord, Wrapper> {
+ const auto& container() const {
+ return static_cast<const Wrapper*>(this)->get();
+ }
+
+ public:
+ JS::Handle<temporal::TimeZoneValue> receiver() const {
+ return JS::Handle<temporal::TimeZoneValue>::fromMarkedLocation(
+ container().receiverDoNotUse());
+ }
+
+ JS::Handle<JSObject*> getOffsetNanosecondsFor() const {
+ return JS::Handle<JSObject*>::fromMarkedLocation(
+ container().getOffsetNanosecondsForDoNotUse());
+ }
+
+ JS::Handle<JSObject*> getPossibleInstantsFor() const {
+ return JS::Handle<JSObject*>::fromMarkedLocation(
+ container().getPossibleInstantsForDoNotUse());
+ }
+};
+
+template <typename Wrapper>
+class MutableWrappedPtrOperations<temporal::TimeZoneRecord, Wrapper>
+ : public WrappedPtrOperations<temporal::TimeZoneRecord, Wrapper> {
+ auto& container() { return static_cast<Wrapper*>(this)->get(); }
+
+ public:
+ JS::MutableHandle<JSObject*> getOffsetNanosecondsFor() {
+ return JS::MutableHandle<JSObject*>::fromMarkedLocation(
+ container().getOffsetNanosecondsForDoNotUse());
+ }
+
+ JS::MutableHandle<JSObject*> getPossibleInstantsFor() {
+ return JS::MutableHandle<JSObject*>::fromMarkedLocation(
+ container().getPossibleInstantsForDoNotUse());
+ }
+};
+
+} /* namespace js */
+
+#endif /* builtin_temporal_TimeZone_h */
diff --git a/js/src/builtin/temporal/ToString.cpp b/js/src/builtin/temporal/ToString.cpp
new file mode 100644
index 0000000000..c789c5e95c
--- /dev/null
+++ b/js/src/builtin/temporal/ToString.cpp
@@ -0,0 +1,679 @@
+/* -*- 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/ToString.h"
+
+#include "mozilla/Assertions.h"
+
+#include <cstdlib>
+#include <stddef.h>
+#include <stdint.h>
+#include <type_traits>
+#include <utility>
+
+#include "builtin/temporal/Calendar.h"
+#include "builtin/temporal/Instant.h"
+#include "builtin/temporal/PlainDate.h"
+#include "builtin/temporal/PlainMonthDay.h"
+#include "builtin/temporal/PlainYearMonth.h"
+#include "builtin/temporal/Temporal.h"
+#include "builtin/temporal/TemporalRoundingMode.h"
+#include "builtin/temporal/TemporalTypes.h"
+#include "builtin/temporal/TemporalUnit.h"
+#include "builtin/temporal/TimeZone.h"
+#include "builtin/temporal/ZonedDateTime.h"
+#include "gc/Policy.h"
+#include "js/RootingAPI.h"
+#include "util/StringBuffer.h"
+#include "vm/StringType.h"
+
+using namespace js;
+using namespace js::temporal;
+
+enum class TemporalStringFormat {
+ None,
+ Date,
+ Time,
+ DateTime,
+ YearMonth,
+ MonthDay,
+ ZonedDateTime,
+ Instant,
+};
+
+class TemporalStringBuilder {
+ JSStringBuilder sb_;
+
+ TemporalStringFormat kind_ = TemporalStringFormat::None;
+
+#ifdef DEBUG
+ bool reserved_ = false;
+#endif
+
+ static constexpr size_t reserveAmount(TemporalStringFormat format) {
+ // Note: This doesn't reserve too much space, because the string builder
+ // already internally reserves space for 64 characters.
+
+ constexpr size_t datePart = 1 + 6 + 1 + 2 + 1 + 2; // 13
+ constexpr size_t timePart = 2 + 1 + 2 + 1 + 2 + 1 + 9; // 18
+ constexpr size_t dateTimePart = datePart + 1 + timePart; // including 'T'
+ constexpr size_t timeZoneOffsetPart = 1 + 2 + 1 + 2; // 6
+
+ switch (format) {
+ case TemporalStringFormat::Date:
+ case TemporalStringFormat::YearMonth:
+ case TemporalStringFormat::MonthDay:
+ return datePart;
+ case TemporalStringFormat::Time:
+ return timePart;
+ case TemporalStringFormat::DateTime:
+ return dateTimePart;
+ case TemporalStringFormat::ZonedDateTime:
+ return dateTimePart + timeZoneOffsetPart;
+ case TemporalStringFormat::Instant:
+ return dateTimePart + timeZoneOffsetPart;
+ case TemporalStringFormat::None:
+ break;
+ }
+ MOZ_CRASH("invalid reserve amount");
+ }
+
+ public:
+ TemporalStringBuilder(JSContext* cx, TemporalStringFormat kind)
+ : sb_(cx), kind_(kind) {
+ MOZ_ASSERT(kind != TemporalStringFormat::None);
+ }
+
+ bool reserve() {
+ MOZ_ASSERT(!reserved_);
+
+ if (!sb_.reserve(reserveAmount(kind_))) {
+ return false;
+ }
+
+#ifdef DEBUG
+ reserved_ = true;
+#endif
+ return true;
+ }
+
+ void append(char value) {
+ MOZ_ASSERT(reserved_);
+ sb_.infallibleAppend(value);
+ }
+
+ void appendTwoDigit(int32_t value) {
+ MOZ_ASSERT(0 <= value && value <= 99);
+ MOZ_ASSERT(reserved_);
+
+ sb_.infallibleAppend(char('0' + (value / 10)));
+ sb_.infallibleAppend(char('0' + (value % 10)));
+ }
+
+ void appendFourDigit(int32_t value) {
+ MOZ_ASSERT(0 <= value && value <= 9999);
+ MOZ_ASSERT(reserved_);
+
+ sb_.infallibleAppend(char('0' + (value / 1000)));
+ sb_.infallibleAppend(char('0' + (value % 1000) / 100));
+ sb_.infallibleAppend(char('0' + (value % 100) / 10));
+ sb_.infallibleAppend(char('0' + (value % 10)));
+ }
+
+ void appendSixDigit(int32_t value) {
+ MOZ_ASSERT(0 <= value && value <= 999999);
+ MOZ_ASSERT(reserved_);
+
+ sb_.infallibleAppend(char('0' + (value / 100000)));
+ sb_.infallibleAppend(char('0' + (value % 100000) / 10000));
+ sb_.infallibleAppend(char('0' + (value % 10000) / 1000));
+ sb_.infallibleAppend(char('0' + (value % 1000) / 100));
+ sb_.infallibleAppend(char('0' + (value % 100) / 10));
+ sb_.infallibleAppend(char('0' + (value % 10)));
+ }
+
+ void appendYear(int32_t year) {
+ if (0 <= year && year <= 9999) {
+ appendFourDigit(year);
+ } else {
+ append(year < 0 ? '-' : '+');
+ appendSixDigit(std::abs(year));
+ }
+ }
+
+ auto* finishString() { return sb_.finishString(); }
+
+ auto& builder() { return sb_; }
+};
+
+/**
+ * FormatFractionalSeconds ( subSecondNanoseconds, precision )
+ */
+static void FormatFractionalSeconds(TemporalStringBuilder& result,
+ int32_t subSecondNanoseconds,
+ Precision precision) {
+ MOZ_ASSERT(0 <= subSecondNanoseconds && subSecondNanoseconds < 1'000'000'000);
+ MOZ_ASSERT(precision != Precision::Minute());
+
+ // Steps 1-2.
+ if (precision == Precision::Auto()) {
+ // Step 1.a.
+ if (subSecondNanoseconds == 0) {
+ return;
+ }
+
+ // Step 3. (Reordered)
+ result.append('.');
+
+ // Steps 1.b-c.
+ uint32_t k = 100'000'000;
+ do {
+ result.append(char('0' + (subSecondNanoseconds / k)));
+ subSecondNanoseconds %= k;
+ k /= 10;
+ } while (subSecondNanoseconds);
+ } else {
+ // Step 2.a.
+ uint8_t p = precision.value();
+ if (p == 0) {
+ return;
+ }
+
+ // Step 3. (Reordered)
+ result.append('.');
+
+ // Steps 2.b-c.
+ uint32_t k = 100'000'000;
+ for (uint8_t i = 0; i < p; i++) {
+ result.append(char('0' + (subSecondNanoseconds / k)));
+ subSecondNanoseconds %= k;
+ k /= 10;
+ }
+ }
+}
+
+/**
+ * FormatTimeString ( hour, minute, second, subSecondNanoseconds, precision )
+ */
+static void FormatTimeString(TemporalStringBuilder& result,
+ const PlainTime& time, Precision precision) {
+ // Step 1.
+ result.appendTwoDigit(time.hour);
+
+ // Step 2.
+ result.append(':');
+ result.appendTwoDigit(time.minute);
+
+ // Steps 4-7.
+ if (precision != Precision::Minute()) {
+ result.append(':');
+ result.appendTwoDigit(time.second);
+
+ int32_t subSecondNanoseconds = time.millisecond * 1'000'000 +
+ time.microsecond * 1'000 + time.nanosecond;
+ FormatFractionalSeconds(result, subSecondNanoseconds, precision);
+ }
+}
+
+static void FormatDateString(TemporalStringBuilder& result,
+ const PlainDate& date) {
+ result.appendYear(date.year);
+ result.append('-');
+ result.appendTwoDigit(date.month);
+ result.append('-');
+ result.appendTwoDigit(date.day);
+}
+
+static void FormatDateTimeString(TemporalStringBuilder& result,
+ const PlainDateTime& dateTime,
+ Precision precision) {
+ FormatDateString(result, dateTime.date);
+ result.append('T');
+ FormatTimeString(result, dateTime.time, precision);
+}
+
+/**
+ * FormatOffsetTimeZoneIdentifier ( offsetMinutes [ , style ] )
+ */
+static void FormatOffsetTimeZoneIdentifier(TemporalStringBuilder& result,
+ int32_t offsetMinutes) {
+ MOZ_ASSERT(std::abs(offsetMinutes) < UnitsPerDay(TemporalUnit::Minute),
+ "time zone offset mustn't exceed 24-hours");
+
+ // Step 1.
+ char sign = offsetMinutes >= 0 ? '+' : '-';
+
+ // Step 2.
+ int32_t absoluteMinutes = std::abs(offsetMinutes);
+
+ // Step 3.
+ int32_t hours = absoluteMinutes / 60;
+
+ // Step 4.
+ int32_t minutes = absoluteMinutes % 60;
+
+ // Steps 5-6. (Inlined FormatTimeString)
+ result.append(sign);
+ result.appendTwoDigit(hours);
+ result.append(':');
+ result.appendTwoDigit(minutes);
+}
+
+// Returns |RoundNumberToIncrement(offsetNanoseconds, 60 ร— 10^9, "halfExpand")|
+// divided by |60 ร— 10^9|.
+static int32_t RoundNanosecondsToMinutes(int64_t offsetNanoseconds) {
+ MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
+
+ constexpr int64_t increment = ToNanoseconds(TemporalUnit::Minute);
+
+ int64_t quotient = offsetNanoseconds / increment;
+ int64_t remainder = offsetNanoseconds % increment;
+ if (std::abs(remainder * 2) >= increment) {
+ quotient += (offsetNanoseconds > 0 ? 1 : -1);
+ }
+ return quotient;
+}
+
+/**
+ * FormatDateTimeUTCOffsetRounded ( offsetNanoseconds )
+ */
+static void FormatDateTimeUTCOffsetRounded(TemporalStringBuilder& result,
+ int64_t offsetNanoseconds) {
+ MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
+
+ // Steps 1-3.
+ int32_t offsetMinutes = RoundNanosecondsToMinutes(offsetNanoseconds);
+
+ // Step 4.
+ FormatOffsetTimeZoneIdentifier(result, offsetMinutes);
+}
+
+/**
+ * FormatCalendarAnnotation ( id, showCalendar )
+ */
+static bool FormatCalendarAnnotation(TemporalStringBuilder& result,
+ JSLinearString* id,
+ CalendarOption showCalendar) {
+ switch (showCalendar) {
+ case CalendarOption::Never:
+ return true;
+
+ case CalendarOption::Auto: {
+ if (StringEqualsLiteral(id, "iso8601")) {
+ return true;
+ }
+ [[fallthrough]];
+ }
+
+ case CalendarOption::Always: {
+ auto& sb = result.builder();
+ return sb.append("[u-ca=") && sb.append(id) && sb.append(']');
+ }
+
+ case CalendarOption::Critical: {
+ auto& sb = result.builder();
+ return sb.append("[!u-ca=") && sb.append(id) && sb.append(']');
+ }
+ }
+ MOZ_CRASH("bad calendar option");
+}
+
+/**
+ * MaybeFormatCalendarAnnotation ( calendar, showCalendar )
+ */
+static bool MaybeFormatCalendarAnnotation(JSContext* cx,
+ TemporalStringBuilder& result,
+ Handle<CalendarValue> calendar,
+ CalendarOption showCalendar) {
+ // Step 1.
+ if (showCalendar == CalendarOption::Never) {
+ return true;
+ }
+
+ // Step 2.
+ JSString* calendarIdentifier = ToTemporalCalendarIdentifier(cx, calendar);
+ if (!calendarIdentifier) {
+ return false;
+ }
+
+ JSLinearString* linearCalendarId = calendarIdentifier->ensureLinear(cx);
+ if (!linearCalendarId) {
+ return false;
+ }
+
+ // Step 3.
+ return FormatCalendarAnnotation(result, linearCalendarId, showCalendar);
+}
+
+static bool FormatTimeZoneAnnotation(TemporalStringBuilder& result,
+ JSLinearString* id,
+ TimeZoneNameOption showTimeZone) {
+ switch (showTimeZone) {
+ case TimeZoneNameOption::Never:
+ return true;
+
+ case TimeZoneNameOption::Auto: {
+ auto& sb = result.builder();
+ return sb.append("[") && sb.append(id) && sb.append(']');
+ }
+
+ case TimeZoneNameOption::Critical: {
+ auto& sb = result.builder();
+ return sb.append("[!") && sb.append(id) && sb.append(']');
+ }
+ }
+ MOZ_CRASH("bad time zone option");
+}
+
+static bool MaybeFormatTimeZoneAnnotation(JSContext* cx,
+ TemporalStringBuilder& result,
+ Handle<TimeZoneValue> timeZone,
+ TimeZoneNameOption showTimeZone) {
+ if (showTimeZone == TimeZoneNameOption::Never) {
+ return true;
+ }
+
+ JSString* timeZoneIdentifier = ToTemporalTimeZoneIdentifier(cx, timeZone);
+ if (!timeZoneIdentifier) {
+ return false;
+ }
+
+ JSLinearString* linearTimeZoneId = timeZoneIdentifier->ensureLinear(cx);
+ if (!linearTimeZoneId) {
+ return false;
+ }
+
+ return FormatTimeZoneAnnotation(result, linearTimeZoneId, showTimeZone);
+}
+
+/**
+ * TemporalInstantToString ( instant, timeZone, precision )
+ */
+JSString* js::temporal::TemporalInstantToString(JSContext* cx,
+ Handle<InstantObject*> instant,
+ Handle<TimeZoneValue> timeZone,
+ Precision precision) {
+ TemporalStringBuilder result(cx, TemporalStringFormat::Instant);
+ if (!result.reserve()) {
+ return nullptr;
+ }
+
+ // Steps 1-2. (Not applicable in our implementation.)
+
+ // Steps 3-6.
+ int64_t offsetNanoseconds = 0;
+ if (timeZone) {
+ // Steps 3-4. (Not applicable)
+
+ // Steps 5-6.
+ if (!GetOffsetNanosecondsFor(cx, timeZone, instant, &offsetNanoseconds)) {
+ return nullptr;
+ }
+ MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
+ }
+
+ // Step 7.
+ auto dateTime = GetPlainDateTimeFor(ToInstant(instant), offsetNanoseconds);
+
+ // Step 8. (Inlined TemporalDateTimeToString)
+ FormatDateTimeString(result, dateTime, precision);
+
+ // Steps 9-10.
+ Rooted<JSString*> timeZoneString(cx);
+ if (!timeZone) {
+ // Step 9.a.
+ result.append('Z');
+ } else {
+ // Step 10.a.
+ FormatDateTimeUTCOffsetRounded(result, offsetNanoseconds);
+ }
+
+ // Step 11.
+ return result.finishString();
+}
+
+/**
+ * TemporalDateToString ( temporalDate, showCalendar )
+ */
+JSString* js::temporal::TemporalDateToString(
+ JSContext* cx, Handle<PlainDateObject*> temporalDate,
+ CalendarOption showCalendar) {
+ auto date = ToPlainDate(temporalDate);
+
+ // Steps 1-2. (Not applicable in our implementation.)
+
+ TemporalStringBuilder result(cx, TemporalStringFormat::Date);
+ if (!result.reserve()) {
+ return nullptr;
+ }
+
+ // Steps 3-5.
+ FormatDateString(result, date);
+
+ // Step 6.
+ Rooted<CalendarValue> calendar(cx, temporalDate->calendar());
+ if (!MaybeFormatCalendarAnnotation(cx, result, calendar, showCalendar)) {
+ return nullptr;
+ }
+
+ // Step 7.
+ return result.finishString();
+}
+
+/**
+ * TemporalDateTimeToString ( isoYear, isoMonth, isoDay, hour, minute, second,
+ * millisecond, microsecond, nanosecond, calendar, precision, showCalendar )
+ */
+JSString* js::temporal::TemporalDateTimeToString(JSContext* cx,
+ const PlainDateTime& dateTime,
+ Handle<CalendarValue> calendar,
+ Precision precision,
+ CalendarOption showCalendar) {
+ TemporalStringBuilder result(cx, TemporalStringFormat::DateTime);
+ if (!result.reserve()) {
+ return nullptr;
+ }
+
+ // Step 1. (Not applicable in our implementation.)
+
+ // Steps 2-6.
+ FormatDateTimeString(result, dateTime, precision);
+
+ // Step 7.
+ if (!MaybeFormatCalendarAnnotation(cx, result, calendar, showCalendar)) {
+ return nullptr;
+ }
+
+ // Step 8.
+ return result.finishString();
+}
+
+/**
+ * TemporalTimeToString ( hour, minute, second, millisecond, microsecond,
+ * nanosecond, precision )
+ */
+JSString* js::temporal::TemporalTimeToString(JSContext* cx,
+ const PlainTime& time,
+ Precision precision) {
+ // Step 1. (Not applicable in our implementation.)
+
+ TemporalStringBuilder result(cx, TemporalStringFormat::Time);
+ if (!result.reserve()) {
+ return nullptr;
+ }
+
+ // Steps 2-3.
+ FormatTimeString(result, time, precision);
+
+ return result.finishString();
+}
+
+/**
+ * TemporalMonthDayToString ( monthDay, showCalendar )
+ */
+JSString* js::temporal::TemporalMonthDayToString(
+ JSContext* cx, Handle<PlainMonthDayObject*> monthDay,
+ CalendarOption showCalendar) {
+ // Steps 1-2. (Not applicable in our implementation.)
+
+ TemporalStringBuilder result(cx, TemporalStringFormat::MonthDay);
+ if (!result.reserve()) {
+ return nullptr;
+ }
+
+ // Step 6. (Reordered)
+ Rooted<CalendarValue> calendar(cx, monthDay->calendar());
+ JSString* str = ToTemporalCalendarIdentifier(cx, calendar);
+ if (!str) {
+ return nullptr;
+ }
+
+ Rooted<JSLinearString*> calendarIdentifier(cx, str->ensureLinear(cx));
+ if (!calendarIdentifier) {
+ return nullptr;
+ }
+
+ // Steps 3-5 and 7.
+ auto date = ToPlainDate(monthDay);
+ if (showCalendar == CalendarOption::Always ||
+ showCalendar == CalendarOption::Critical ||
+ !StringEqualsLiteral(calendarIdentifier, "iso8601")) {
+ // FIXME: spec issue - don't print "year" part when showCalendar is "never".
+ //
+ // ```js
+ // let cal = new Proxy({id: "cal"}, {has(t, pk) { return true; }});
+ // let pmd = new Temporal.PlainMonthDay(8, 1, cal);
+ // pmd.toString({calendarName: "never"})
+ // ```
+
+ FormatDateString(result, date);
+ } else {
+ result.appendTwoDigit(date.month);
+ result.append('-');
+ result.appendTwoDigit(date.day);
+ }
+
+ // Steps 8-9.
+ if (!FormatCalendarAnnotation(result, calendarIdentifier, showCalendar)) {
+ return nullptr;
+ }
+
+ // Step 10.
+ return result.finishString();
+}
+
+/**
+ * TemporalYearMonthToString ( yearMonth, showCalendar )
+ */
+JSString* js::temporal::TemporalYearMonthToString(
+ JSContext* cx, Handle<PlainYearMonthObject*> yearMonth,
+ CalendarOption showCalendar) {
+ // Steps 1-2. (Not applicable in our implementation.)
+
+ TemporalStringBuilder result(cx, TemporalStringFormat::YearMonth);
+ if (!result.reserve()) {
+ return nullptr;
+ }
+
+ // Step 6. (Reordered)
+ Rooted<CalendarValue> calendar(cx, yearMonth->calendar());
+ JSString* str = ToTemporalCalendarIdentifier(cx, calendar);
+ if (!str) {
+ return nullptr;
+ }
+
+ Rooted<JSLinearString*> calendarIdentifier(cx, str->ensureLinear(cx));
+ if (!calendarIdentifier) {
+ return nullptr;
+ }
+
+ // Steps 3-5 and 7.
+ auto date = ToPlainDate(yearMonth);
+ if (showCalendar == CalendarOption::Always ||
+ showCalendar == CalendarOption::Critical ||
+ !StringEqualsLiteral(calendarIdentifier, "iso8601")) {
+ // FIXME: spec issue - don't print "day" part when showCalendar is "never".
+ //
+ // ```js
+ // let cal = new Proxy({id: "cal"}, {has(t, pk) { return true; }});
+ // let pym = new Temporal.PlainYearMonth(2023, 8, cal);
+ // pym.toString({calendarName: "never"})
+ // ```
+
+ FormatDateString(result, date);
+ } else {
+ result.appendYear(date.year);
+ result.append('-');
+ result.appendTwoDigit(date.month);
+ }
+
+ // Steps 8-9.
+ if (!FormatCalendarAnnotation(result, calendarIdentifier, showCalendar)) {
+ return nullptr;
+ }
+
+ // Step 10.
+ return result.finishString();
+}
+
+/**
+ * TemporalZonedDateTimeToString ( zonedDateTime, precision, showCalendar,
+ * showTimeZone, showOffset [ , increment, unit, roundingMode ] )
+ */
+JSString* js::temporal::TemporalZonedDateTimeToString(
+ JSContext* cx, Handle<ZonedDateTime> zonedDateTime, Precision precision,
+ CalendarOption showCalendar, TimeZoneNameOption showTimeZone,
+ ShowOffsetOption showOffset, Increment increment, TemporalUnit unit,
+ TemporalRoundingMode roundingMode) {
+ TemporalStringBuilder result(cx, TemporalStringFormat::ZonedDateTime);
+ if (!result.reserve()) {
+ return nullptr;
+ }
+
+ // Steps 1-3. (Not applicable in our implementation.)
+
+ // Step 4.
+ Instant ns;
+ if (!RoundTemporalInstant(cx, zonedDateTime.instant(), increment, unit,
+ roundingMode, &ns)) {
+ return nullptr;
+ }
+
+ // Step 5.
+ auto timeZone = zonedDateTime.timeZone();
+
+ // Steps 6-8.
+ int64_t offsetNanoseconds;
+ if (!GetOffsetNanosecondsFor(cx, timeZone, ns, &offsetNanoseconds)) {
+ return nullptr;
+ }
+ MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
+
+ // Step 9.
+ auto temporalDateTime = GetPlainDateTimeFor(ns, offsetNanoseconds);
+
+ // Step 10. (Inlined TemporalDateTimeToString)
+ FormatDateTimeString(result, temporalDateTime, precision);
+
+ // Steps 11-12.
+ if (showOffset != ShowOffsetOption::Never) {
+ FormatDateTimeUTCOffsetRounded(result, offsetNanoseconds);
+ }
+
+ // Steps 13-14.
+ if (!MaybeFormatTimeZoneAnnotation(cx, result, timeZone, showTimeZone)) {
+ return nullptr;
+ }
+
+ // Step 15.
+ if (!MaybeFormatCalendarAnnotation(cx, result, zonedDateTime.calendar(),
+ showCalendar)) {
+ return nullptr;
+ }
+
+ // Step 16.
+ return result.finishString();
+}
diff --git a/js/src/builtin/temporal/ToString.h b/js/src/builtin/temporal/ToString.h
new file mode 100644
index 0000000000..7560ea8ca2
--- /dev/null
+++ b/js/src/builtin/temporal/ToString.h
@@ -0,0 +1,86 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_ToString_h
+#define builtin_temporal_ToString_h
+
+#include "builtin/temporal/Temporal.h"
+#include "builtin/temporal/TemporalRoundingMode.h"
+#include "builtin/temporal/TemporalUnit.h"
+#include "js/TypeDecls.h"
+
+namespace js::temporal {
+
+class CalendarValue;
+class InstantObject;
+class PlainDateObject;
+class PlainMonthDayObject;
+class PlainYearMonthObject;
+class TimeZoneValue;
+class ZonedDateTime;
+
+struct PlainDateTime;
+struct PlainTime;
+
+/**
+ * TemporalInstantToString ( instant, timeZone, precision )
+ */
+JSString* TemporalInstantToString(JSContext* cx,
+ JS::Handle<InstantObject*> instant,
+ JS::Handle<TimeZoneValue> timeZone,
+ Precision precision);
+
+/**
+ * TemporalDateToString ( temporalDate, showCalendar )
+ */
+JSString* TemporalDateToString(JSContext* cx,
+ JS::Handle<PlainDateObject*> temporalDate,
+ CalendarOption showCalendar);
+
+/**
+ * TemporalDateTimeToString ( isoYear, isoMonth, isoDay, hour, minute, second,
+ * millisecond, microsecond, nanosecond, calendar, precision, showCalendar )
+ */
+JSString* TemporalDateTimeToString(JSContext* cx, const PlainDateTime& dateTime,
+ JS::Handle<CalendarValue> calendar,
+ Precision precision,
+ CalendarOption showCalendar);
+
+/**
+ * TemporalTimeToString ( hour, minute, second, millisecond, microsecond,
+ * nanosecond, precision )
+ */
+JSString* TemporalTimeToString(JSContext* cx, const PlainTime& time,
+ Precision precision);
+
+/**
+ * TemporalMonthDayToString ( monthDay, showCalendar )
+ */
+JSString* TemporalMonthDayToString(JSContext* cx,
+ JS::Handle<PlainMonthDayObject*> monthDay,
+ CalendarOption showCalendar);
+
+/**
+ * TemporalYearMonthToString ( yearMonth, showCalendar )
+ */
+JSString* TemporalYearMonthToString(JSContext* cx,
+ JS::Handle<PlainYearMonthObject*> yearMonth,
+ CalendarOption showCalendar);
+
+/**
+ * TemporalZonedDateTimeToString ( zonedDateTime, precision, showCalendar,
+ * showTimeZone, showOffset [ , increment, unit, roundingMode ] )
+ */
+JSString* TemporalZonedDateTimeToString(
+ JSContext* cx, JS::Handle<ZonedDateTime> zonedDateTime, Precision precision,
+ CalendarOption showCalendar, TimeZoneNameOption showTimeZone,
+ ShowOffsetOption showOffset, Increment increment = Increment{1},
+ TemporalUnit unit = TemporalUnit::Nanosecond,
+ TemporalRoundingMode roundingMode = TemporalRoundingMode::Trunc);
+
+} /* namespace js::temporal */
+
+#endif /* builtin_temporal_ToString_h */
diff --git a/js/src/builtin/temporal/Wrapped.cpp b/js/src/builtin/temporal/Wrapped.cpp
new file mode 100644
index 0000000000..ca0ec9f8b1
--- /dev/null
+++ b/js/src/builtin/temporal/Wrapped.cpp
@@ -0,0 +1,22 @@
+/* -*- 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/Wrapped.h"
+
+#include "jsfriendapi.h"
+
+#include "js/ErrorReport.h"
+#include "js/friend/ErrorMessages.h"
+#include "js/Wrapper.h"
+
+void js::temporal::ReportDeadWrapperOrAccessDenied(JSContext* cx,
+ JSObject* obj) {
+ if (JS_IsDeadWrapper(obj)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
+ } else {
+ ReportAccessDenied(cx);
+ }
+}
diff --git a/js/src/builtin/temporal/Wrapped.h b/js/src/builtin/temporal/Wrapped.h
new file mode 100644
index 0000000000..904a9b3ac9
--- /dev/null
+++ b/js/src/builtin/temporal/Wrapped.h
@@ -0,0 +1,169 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_Wrapped_h
+#define builtin_temporal_Wrapped_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+
+#include <type_traits>
+
+#include "gc/Tracer.h"
+#include "js/RootingAPI.h"
+#include "vm/JSObject.h"
+#include "vm/NativeObject.h"
+
+namespace js::temporal {
+
+/**
+ * Type to represent possibly wrapped objects from a different compartment.
+ *
+ * This can be used to represent specific JSObject sub-classes in return types
+ * without having to pass unwrapped objects around.
+ */
+template <class T>
+class MOZ_STACK_CLASS Wrapped final {
+ static_assert(std::is_pointer_v<T>);
+ static_assert(std::is_convertible_v<T, NativeObject*>);
+
+ using U = std::remove_pointer_t<T>;
+
+ JSObject* ptr_ = nullptr;
+
+ public:
+ Wrapped() = default;
+
+ MOZ_IMPLICIT Wrapped(decltype(nullptr)) : ptr_(nullptr) {}
+
+ MOZ_IMPLICIT Wrapped(T ptr) : ptr_(ptr) {
+ // No assertion needed when the object already has the correct type.
+ }
+
+ MOZ_IMPLICIT Wrapped(JSObject* ptr) : ptr_(ptr) {
+ // Ensure the caller passed a valid pointer.
+ MOZ_ASSERT_IF(ptr_, ptr_->canUnwrapAs<U>());
+ }
+
+ template <typename S>
+ MOZ_IMPLICIT Wrapped(
+ const JS::Rooted<S>& root,
+ std::enable_if_t<std::is_convertible_v<S, T>, int> dummy = 0)
+ : Wrapped(root.get()) {}
+
+ MOZ_IMPLICIT Wrapped(const JS::Rooted<JSObject*>& root)
+ : Wrapped(root.get()) {}
+
+ template <typename S>
+ MOZ_IMPLICIT Wrapped(
+ const JS::Handle<S>& root,
+ std::enable_if_t<std::is_convertible_v<S, T>, int> dummy = 0)
+ : Wrapped(root.get()) {}
+
+ MOZ_IMPLICIT Wrapped(const JS::Handle<JSObject*>& root)
+ : Wrapped(root.get()) {}
+
+ template <typename S>
+ MOZ_IMPLICIT Wrapped(
+ const JS::MutableHandle<S>& root,
+ std::enable_if_t<std::is_convertible_v<S, T>, int> dummy = 0)
+ : Wrapped(root.get()) {}
+
+ MOZ_IMPLICIT Wrapped(const JS::MutableHandle<JSObject*>& root)
+ : Wrapped(root.get()) {}
+
+ Wrapped& operator=(decltype(nullptr)) {
+ ptr_ = nullptr;
+ return *this;
+ }
+
+ Wrapped& operator=(T ptr) {
+ ptr_ = ptr;
+ return *this;
+ }
+
+ explicit operator bool() const { return !!ptr_; }
+
+ JSObject* operator->() const { return ptr_; }
+
+ JSObject& operator*() const { return *ptr_; }
+
+ JSObject* get() const { return ptr_; }
+
+ operator JSObject*() const { return get(); }
+
+ auto address() const { return &ptr_; }
+
+ U& unwrap() const {
+ MOZ_ASSERT(ptr_);
+
+ // Direct unwrap because the constructor already verified the object can be
+ // unwrapped.
+ //
+ // We use JSObject::unwrapAs() instead of JSObject::maybeUnwrapIf(), because
+ // this is an unrooted Wrapped, so hazard analysis will ensure that no
+ // wrappers have been invalidated, because wrapper invalidation generally
+ // only happens in the same case as GC.
+ //
+ // Rooted Wrapped are accessed through their WrappedPtrOperations
+ // specialization, which uses JSObject::maybeUnwrapIf() to handle the
+ // wrapper invalidation case correctly.
+ return ptr_->unwrapAs<U>();
+ }
+
+ U* unwrapOrNull() const {
+ // Direct unwrap because the constructor already verified the object can be
+ // unwrapped.
+ //
+ // See Wrapped::unwrap() for why we don't call maybeUnwrapIf() here.
+ return ptr_ ? &ptr_->unwrapAs<U>() : nullptr;
+ }
+
+ void trace(JSTracer* trc) { TraceNullableRoot(trc, &ptr_, "Wrapped::ptr_"); }
+};
+
+void ReportDeadWrapperOrAccessDenied(JSContext* cx, JSObject* obj);
+
+} /* namespace js::temporal */
+
+namespace js {
+template <typename T, typename Container>
+class WrappedPtrOperations<temporal::Wrapped<T>, Container> {
+ using U = std::remove_pointer_t<T>;
+
+ const auto& wrapped() const {
+ return static_cast<const Container*>(this)->get();
+ }
+
+ public:
+ explicit operator bool() const { return !!wrapped(); }
+
+ JSObject* operator->() const { return wrapped().get(); }
+
+ JSObject& operator*() const { return *wrapped().get(); }
+
+ JS::Handle<JSObject*> object() const {
+ return JS::Handle<JSObject*>::fromMarkedLocation(wrapped().address());
+ }
+
+ operator JS::Handle<JSObject*>() const { return object(); }
+
+ [[nodiscard]] U* unwrap(JSContext* cx) const {
+ JSObject* obj = wrapped().get();
+
+ // Call JSObject::maybeUnwrapIf() instead of JSObject::unwrapAs() in case
+ // |obj| is an invalidated wrapper.
+ if (auto* unwrapped = obj->maybeUnwrapIf<U>()) {
+ return unwrapped;
+ }
+
+ temporal::ReportDeadWrapperOrAccessDenied(cx, obj);
+ return nullptr;
+ }
+};
+} // namespace js
+
+#endif /* builtin_temporal_Wrapped_h */
diff --git a/js/src/builtin/temporal/ZonedDateTime.cpp b/js/src/builtin/temporal/ZonedDateTime.cpp
new file mode 100644
index 0000000000..690ff223b1
--- /dev/null
+++ b/js/src/builtin/temporal/ZonedDateTime.cpp
@@ -0,0 +1,4124 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "builtin/temporal/ZonedDateTime.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h"
+
+#include <cstdlib>
+#include <utility>
+
+#include "jspubtd.h"
+#include "NamespaceImports.h"
+
+#include "builtin/temporal/Calendar.h"
+#include "builtin/temporal/Duration.h"
+#include "builtin/temporal/Instant.h"
+#include "builtin/temporal/PlainDate.h"
+#include "builtin/temporal/PlainDateTime.h"
+#include "builtin/temporal/PlainMonthDay.h"
+#include "builtin/temporal/PlainTime.h"
+#include "builtin/temporal/PlainYearMonth.h"
+#include "builtin/temporal/Temporal.h"
+#include "builtin/temporal/TemporalFields.h"
+#include "builtin/temporal/TemporalParser.h"
+#include "builtin/temporal/TemporalRoundingMode.h"
+#include "builtin/temporal/TemporalTypes.h"
+#include "builtin/temporal/TemporalUnit.h"
+#include "builtin/temporal/TimeZone.h"
+#include "builtin/temporal/ToString.h"
+#include "builtin/temporal/Wrapped.h"
+#include "ds/IdValuePair.h"
+#include "gc/AllocKind.h"
+#include "gc/Barrier.h"
+#include "js/AllocPolicy.h"
+#include "js/CallArgs.h"
+#include "js/CallNonGenericMethod.h"
+#include "js/Class.h"
+#include "js/ComparisonOperators.h"
+#include "js/ErrorReport.h"
+#include "js/friend/ErrorMessages.h"
+#include "js/GCVector.h"
+#include "js/Id.h"
+#include "js/Printer.h"
+#include "js/PropertyDescriptor.h"
+#include "js/PropertySpec.h"
+#include "js/RootingAPI.h"
+#include "js/TracingAPI.h"
+#include "js/Value.h"
+#include "vm/BigIntType.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/GlobalObject.h"
+#include "vm/JSAtomState.h"
+#include "vm/JSContext.h"
+#include "vm/JSObject.h"
+#include "vm/ObjectOperations.h"
+#include "vm/PlainObject.h"
+#include "vm/StringType.h"
+
+#include "vm/JSContext-inl.h"
+#include "vm/JSObject-inl.h"
+#include "vm/NativeObject-inl.h"
+#include "vm/ObjectOperations-inl.h"
+
+using namespace js;
+using namespace js::temporal;
+
+static inline bool IsZonedDateTime(Handle<Value> v) {
+ return v.isObject() && v.toObject().is<ZonedDateTimeObject>();
+}
+
+// Returns |RoundNumberToIncrement(offsetNanoseconds, 60 ร— 10^9, "halfExpand")|.
+static int64_t RoundNanosecondsToMinutesIncrement(int64_t offsetNanoseconds) {
+ MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
+
+ constexpr int64_t increment = ToNanoseconds(TemporalUnit::Minute);
+
+ int64_t quotient = offsetNanoseconds / increment;
+ int64_t remainder = offsetNanoseconds % increment;
+ if (std::abs(remainder * 2) >= increment) {
+ quotient += (offsetNanoseconds > 0 ? 1 : -1);
+ }
+ return quotient * increment;
+}
+
+/**
+ * InterpretISODateTimeOffset ( year, month, day, hour, minute, second,
+ * millisecond, microsecond, nanosecond, offsetBehaviour, offsetNanoseconds,
+ * timeZoneRec, disambiguation, offsetOption, matchBehaviour )
+ */
+bool js::temporal::InterpretISODateTimeOffset(
+ JSContext* cx, const PlainDateTime& dateTime,
+ OffsetBehaviour offsetBehaviour, int64_t offsetNanoseconds,
+ Handle<TimeZoneRecord> timeZone, TemporalDisambiguation disambiguation,
+ TemporalOffset offsetOption, MatchBehaviour matchBehaviour,
+ Instant* result) {
+ MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
+
+ // Step 1.
+ MOZ_ASSERT(IsValidISODate(dateTime.date));
+
+ // Step 2.
+ MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
+ timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
+
+ // Step 3.
+ MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
+ timeZone, TimeZoneMethod::GetPossibleInstantsFor));
+
+ // Step 4.
+ Rooted<CalendarValue> calendar(cx, CalendarValue(cx->names().iso8601));
+ Rooted<PlainDateTimeWithCalendar> temporalDateTime(cx);
+ if (!CreateTemporalDateTime(cx, dateTime, calendar, &temporalDateTime)) {
+ return false;
+ }
+
+ // Step 5.
+ if (offsetBehaviour == OffsetBehaviour::Wall ||
+ offsetOption == TemporalOffset::Ignore) {
+ // Steps 5.a-b.
+ return GetInstantFor(cx, timeZone, temporalDateTime, disambiguation,
+ result);
+ }
+
+ // Step 6.
+ if (offsetBehaviour == OffsetBehaviour::Exact ||
+ offsetOption == TemporalOffset::Use) {
+ // Step 6.a.
+ auto epochNanoseconds = GetUTCEpochNanoseconds(
+ dateTime, InstantSpan::fromNanoseconds(offsetNanoseconds));
+
+ // Step 6.b.
+ if (!IsValidEpochInstant(epochNanoseconds)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_INVALID);
+ return false;
+ }
+
+ // Step 6.c.
+ *result = epochNanoseconds;
+ return true;
+ }
+
+ // Step 7.
+ MOZ_ASSERT(offsetBehaviour == OffsetBehaviour::Option);
+
+ // Step 8.
+ MOZ_ASSERT(offsetOption == TemporalOffset::Prefer ||
+ offsetOption == TemporalOffset::Reject);
+
+ // FIXME: spec issue - duplicate assertion
+
+ // Step 9.
+ MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
+ timeZone, TimeZoneMethod::GetPossibleInstantsFor));
+
+ // Step 10.
+ Rooted<InstantVector> possibleInstants(cx, InstantVector(cx));
+ if (!GetPossibleInstantsFor(cx, timeZone, temporalDateTime,
+ &possibleInstants)) {
+ return false;
+ }
+
+ // Step 11.
+ if (!possibleInstants.empty()) {
+ // Step 11.a.
+ Rooted<Wrapped<InstantObject*>> candidate(cx);
+ for (size_t i = 0; i < possibleInstants.length(); i++) {
+ candidate = possibleInstants[i];
+
+ // Step 11.a.i.
+ int64_t candidateNanoseconds;
+ if (!GetOffsetNanosecondsFor(cx, timeZone, candidate,
+ &candidateNanoseconds)) {
+ return false;
+ }
+ MOZ_ASSERT(std::abs(candidateNanoseconds) <
+ ToNanoseconds(TemporalUnit::Day));
+
+ // Step 11.a.ii.
+ if (candidateNanoseconds == offsetNanoseconds) {
+ auto* unwrapped = candidate.unwrap(cx);
+ if (!unwrapped) {
+ return false;
+ }
+
+ *result = ToInstant(unwrapped);
+ return true;
+ }
+
+ // Step 11.a.iii.
+ if (matchBehaviour == MatchBehaviour::MatchMinutes) {
+ // Step 11.a.iii.1.
+ int64_t roundedCandidateNanoseconds =
+ RoundNanosecondsToMinutesIncrement(candidateNanoseconds);
+
+ // Step 11.a.iii.2.
+ if (roundedCandidateNanoseconds == offsetNanoseconds) {
+ auto* unwrapped = candidate.unwrap(cx);
+ if (!unwrapped) {
+ return false;
+ }
+
+ // Step 11.a.iii.2.a.
+ *result = ToInstant(unwrapped);
+ return true;
+ }
+ }
+ }
+ }
+
+ // Step 12.
+ if (offsetOption == TemporalOffset::Reject) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_ZONED_DATE_TIME_NO_TIME_FOUND);
+ return false;
+ }
+
+ // Step 13.
+ Rooted<Wrapped<InstantObject*>> instant(cx);
+ if (!DisambiguatePossibleInstants(cx, possibleInstants, timeZone,
+ ToPlainDateTime(temporalDateTime),
+ disambiguation, &instant)) {
+ return false;
+ }
+
+ auto* unwrappedInstant = instant.unwrap(cx);
+ if (!unwrappedInstant) {
+ return false;
+ }
+
+ // Step 14.
+ *result = ToInstant(unwrappedInstant);
+ return true;
+}
+
+/**
+ * ToTemporalZonedDateTime ( item [ , options ] )
+ */
+static bool ToTemporalZonedDateTime(JSContext* cx, Handle<Value> item,
+ Handle<JSObject*> maybeOptions,
+ MutableHandle<ZonedDateTime> result) {
+ // Step 1. (Not applicable in our implementation)
+
+ // Step 2.
+ Rooted<PlainObject*> maybeResolvedOptions(cx);
+ if (maybeOptions) {
+ maybeResolvedOptions = SnapshotOwnProperties(cx, maybeOptions);
+ if (!maybeResolvedOptions) {
+ return false;
+ }
+ }
+
+ // Step 3.
+ auto offsetBehaviour = OffsetBehaviour::Option;
+
+ // Step 4.
+ auto matchBehaviour = MatchBehaviour::MatchExactly;
+
+ // Step 7. (Reordered)
+ int64_t offsetNanoseconds = 0;
+
+ // Step 5.
+ Rooted<CalendarValue> calendar(cx);
+ Rooted<TimeZoneValue> timeZone(cx);
+ PlainDateTime dateTime;
+ auto disambiguation = TemporalDisambiguation::Compatible;
+ auto offsetOption = TemporalOffset::Reject;
+ if (item.isObject()) {
+ Rooted<JSObject*> itemObj(cx, &item.toObject());
+
+ // Step 5.a.
+ if (auto* zonedDateTime = itemObj->maybeUnwrapIf<ZonedDateTimeObject>()) {
+ auto instant = ToInstant(zonedDateTime);
+ Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
+ Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
+
+ if (!timeZone.wrap(cx)) {
+ return false;
+ }
+ if (!calendar.wrap(cx)) {
+ return false;
+ }
+
+ result.set(ZonedDateTime{instant, timeZone, calendar});
+ return true;
+ }
+
+ // Step 5.b.
+ if (!GetTemporalCalendarWithISODefault(cx, itemObj, &calendar)) {
+ return false;
+ }
+
+ // Step 5.c.
+ Rooted<CalendarRecord> calendarRec(cx);
+ if (!CreateCalendarMethodsRecord(cx, calendar,
+ {
+ CalendarMethod::DateFromFields,
+ CalendarMethod::Fields,
+ },
+ &calendarRec)) {
+ return false;
+ }
+
+ // Step 5.d.
+ JS::RootedVector<PropertyKey> fieldNames(cx);
+ if (!CalendarFields(cx, calendarRec,
+ {CalendarField::Day, CalendarField::Month,
+ CalendarField::MonthCode, CalendarField::Year},
+ &fieldNames)) {
+ return false;
+ }
+
+ // Step 5.e.
+ if (!AppendSorted(cx, fieldNames.get(),
+ {
+ TemporalField::Hour,
+ TemporalField::Microsecond,
+ TemporalField::Millisecond,
+ TemporalField::Minute,
+ TemporalField::Nanosecond,
+ TemporalField::Offset,
+ TemporalField::Second,
+ TemporalField::TimeZone,
+ })) {
+ return false;
+ }
+
+ // Step 5.f.
+ Rooted<PlainObject*> fields(
+ cx, PrepareTemporalFields(cx, itemObj, fieldNames,
+ {TemporalField::TimeZone}));
+ if (!fields) {
+ return false;
+ }
+
+ // Step 5.g.
+ Rooted<Value> timeZoneValue(cx);
+ if (!GetProperty(cx, fields, fields, cx->names().timeZone,
+ &timeZoneValue)) {
+ return false;
+ }
+
+ // Step 5.h.
+ if (!ToTemporalTimeZone(cx, timeZoneValue, &timeZone)) {
+ return false;
+ }
+
+ // Step 5.i.
+ Rooted<Value> offsetValue(cx);
+ if (!GetProperty(cx, fields, fields, cx->names().offset, &offsetValue)) {
+ return false;
+ }
+
+ // Step 5.j.
+ MOZ_ASSERT(offsetValue.isString() || offsetValue.isUndefined());
+
+ // Step 5.k.
+ Rooted<JSString*> offsetString(cx);
+ if (offsetValue.isString()) {
+ offsetString = offsetValue.toString();
+ } else {
+ offsetBehaviour = OffsetBehaviour::Wall;
+ }
+
+ if (maybeResolvedOptions) {
+ // Steps 5.l-m.
+ if (!ToTemporalDisambiguation(cx, maybeResolvedOptions,
+ &disambiguation)) {
+ return false;
+ }
+
+ // Step 5.n.
+ if (!ToTemporalOffset(cx, maybeResolvedOptions, &offsetOption)) {
+ return false;
+ }
+
+ // Step 5.o.
+ if (!InterpretTemporalDateTimeFields(cx, calendarRec, fields,
+ maybeResolvedOptions, &dateTime)) {
+ return false;
+ }
+ } else {
+ // Steps 5.l-n. (Not applicable)
+
+ // Step 5.o.
+ if (!InterpretTemporalDateTimeFields(cx, calendarRec, fields,
+ &dateTime)) {
+ return false;
+ }
+ }
+
+ // Step 8.
+ if (offsetBehaviour == OffsetBehaviour::Option) {
+ if (!ParseDateTimeUTCOffset(cx, offsetString, &offsetNanoseconds)) {
+ return false;
+ }
+ }
+ } else {
+ // Step 6.a.
+ if (!item.isString()) {
+ ReportValueError(cx, JSMSG_UNEXPECTED_TYPE, JSDVG_IGNORE_STACK, item,
+ nullptr, "not a string");
+ return false;
+ }
+ Rooted<JSString*> string(cx, item.toString());
+
+ // Case 1: 19700101Z[+02:00]
+ // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "+02:00" }
+ //
+ // Case 2: 19700101+00:00[+02:00]
+ // { [[Z]]: false, [[OffsetString]]: "+00:00", [[Name]]: "+02:00" }
+ //
+ // Case 3: 19700101[+02:00]
+ // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "+02:00" }
+ //
+ // Case 4: 19700101Z[Europe/Berlin]
+ // { [[Z]]: true, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" }
+ //
+ // Case 5: 19700101+00:00[Europe/Berlin]
+ // { [[Z]]: false, [[OffsetString]]: "+00:00", [[Name]]: "Europe/Berlin" }
+ //
+ // Case 6: 19700101[Europe/Berlin]
+ // { [[Z]]: false, [[OffsetString]]: undefined, [[Name]]: "Europe/Berlin" }
+
+ // Steps 6.b-c.
+ bool isUTC;
+ bool hasOffset;
+ int64_t timeZoneOffset;
+ Rooted<ParsedTimeZone> timeZoneString(cx);
+ Rooted<JSString*> calendarString(cx);
+ if (!ParseTemporalZonedDateTimeString(cx, string, &dateTime, &isUTC,
+ &hasOffset, &timeZoneOffset,
+ &timeZoneString, &calendarString)) {
+ return false;
+ }
+
+ // Step 6.d.
+ MOZ_ASSERT(timeZoneString);
+
+ // Step 6.e.
+ if (!ToTemporalTimeZone(cx, timeZoneString, &timeZone)) {
+ return false;
+ }
+
+ // Step 6.f. (Not applicable in our implementation.)
+
+ // Step 6.g.
+ if (isUTC) {
+ offsetBehaviour = OffsetBehaviour::Exact;
+ }
+
+ // Step 6.h.
+ else if (!hasOffset) {
+ offsetBehaviour = OffsetBehaviour::Wall;
+ }
+
+ // Steps 6.i-l.
+ if (calendarString) {
+ if (!ToBuiltinCalendar(cx, calendarString, &calendar)) {
+ return false;
+ }
+ } else {
+ calendar.set(CalendarValue(cx->names().iso8601));
+ }
+
+ // Step 6.m.
+ matchBehaviour = MatchBehaviour::MatchMinutes;
+
+ if (maybeResolvedOptions) {
+ // Step 6.n.
+ if (!ToTemporalDisambiguation(cx, maybeResolvedOptions,
+ &disambiguation)) {
+ return false;
+ }
+
+ // Step 6.o.
+ if (!ToTemporalOffset(cx, maybeResolvedOptions, &offsetOption)) {
+ return false;
+ }
+
+ // Step 6.p.
+ TemporalOverflow ignored;
+ if (!ToTemporalOverflow(cx, maybeResolvedOptions, &ignored)) {
+ return false;
+ }
+ }
+
+ // Step 8.
+ if (offsetBehaviour == OffsetBehaviour::Option) {
+ MOZ_ASSERT(hasOffset);
+ offsetNanoseconds = timeZoneOffset;
+ }
+ }
+
+ // Step 9.
+ Rooted<TimeZoneRecord> timeZoneRec(cx);
+ if (!CreateTimeZoneMethodsRecord(cx, timeZone,
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ TimeZoneMethod::GetPossibleInstantsFor,
+ },
+ &timeZoneRec)) {
+ return false;
+ }
+
+ // Step 10.
+ Instant epochNanoseconds;
+ if (!InterpretISODateTimeOffset(
+ cx, dateTime, offsetBehaviour, offsetNanoseconds, timeZoneRec,
+ disambiguation, offsetOption, matchBehaviour, &epochNanoseconds)) {
+ return false;
+ }
+
+ // Step 11.
+ result.set(ZonedDateTime{epochNanoseconds, timeZone, calendar});
+ return true;
+}
+
+/**
+ * ToTemporalZonedDateTime ( item [ , options ] )
+ */
+static bool ToTemporalZonedDateTime(JSContext* cx, Handle<Value> item,
+ MutableHandle<ZonedDateTime> result) {
+ return ToTemporalZonedDateTime(cx, item, nullptr, result);
+}
+
+/**
+ * ToTemporalZonedDateTime ( item [ , options ] )
+ */
+static ZonedDateTimeObject* ToTemporalZonedDateTime(
+ JSContext* cx, Handle<Value> item, Handle<JSObject*> maybeOptions) {
+ Rooted<ZonedDateTime> result(cx);
+ if (!ToTemporalZonedDateTime(cx, item, maybeOptions, &result)) {
+ return nullptr;
+ }
+ return CreateTemporalZonedDateTime(cx, result.instant(), result.timeZone(),
+ result.calendar());
+}
+
+/**
+ * CreateTemporalZonedDateTime ( epochNanoseconds, timeZone, calendar [ ,
+ * newTarget ] )
+ */
+static ZonedDateTimeObject* CreateTemporalZonedDateTime(
+ JSContext* cx, const CallArgs& args, Handle<BigInt*> epochNanoseconds,
+ Handle<TimeZoneValue> timeZone, Handle<CalendarValue> calendar) {
+ // Step 1.
+ MOZ_ASSERT(IsValidEpochNanoseconds(epochNanoseconds));
+
+ // Steps 3-4.
+ Rooted<JSObject*> proto(cx);
+ if (!GetPrototypeFromBuiltinConstructor(cx, args, JSProto_ZonedDateTime,
+ &proto)) {
+ return nullptr;
+ }
+
+ auto* obj = NewObjectWithClassProto<ZonedDateTimeObject>(cx, proto);
+ if (!obj) {
+ return nullptr;
+ }
+
+ // Step 4.
+ auto instant = ToInstant(epochNanoseconds);
+ obj->setFixedSlot(ZonedDateTimeObject::SECONDS_SLOT,
+ NumberValue(instant.seconds));
+ obj->setFixedSlot(ZonedDateTimeObject::NANOSECONDS_SLOT,
+ Int32Value(instant.nanoseconds));
+
+ // Step 5.
+ obj->setFixedSlot(ZonedDateTimeObject::TIMEZONE_SLOT, timeZone.toSlotValue());
+
+ // Step 6.
+ obj->setFixedSlot(ZonedDateTimeObject::CALENDAR_SLOT, calendar.toValue());
+
+ // Step 7.
+ return obj;
+}
+
+/**
+ * CreateTemporalZonedDateTime ( epochNanoseconds, timeZone, calendar [ ,
+ * newTarget ] )
+ */
+ZonedDateTimeObject* js::temporal::CreateTemporalZonedDateTime(
+ JSContext* cx, const Instant& instant, Handle<TimeZoneValue> timeZone,
+ Handle<CalendarValue> calendar) {
+ // Step 1.
+ MOZ_ASSERT(IsValidEpochInstant(instant));
+
+ // Steps 2-3.
+ auto* obj = NewBuiltinClassInstance<ZonedDateTimeObject>(cx);
+ if (!obj) {
+ return nullptr;
+ }
+
+ // Step 4.
+ obj->setFixedSlot(ZonedDateTimeObject::SECONDS_SLOT,
+ NumberValue(instant.seconds));
+ obj->setFixedSlot(ZonedDateTimeObject::NANOSECONDS_SLOT,
+ Int32Value(instant.nanoseconds));
+
+ // Step 5.
+ obj->setFixedSlot(ZonedDateTimeObject::TIMEZONE_SLOT, timeZone.toSlotValue());
+
+ // Step 6.
+ obj->setFixedSlot(ZonedDateTimeObject::CALENDAR_SLOT, calendar.toValue());
+
+ // Step 7.
+ return obj;
+}
+
+struct PlainDateTimeAndInstant {
+ PlainDateTime dateTime;
+ Instant instant;
+};
+
+/**
+ * AddDaysToZonedDateTime ( instant, dateTime, timeZoneRec, calendar, days [ ,
+ * overflow ] )
+ */
+static bool AddDaysToZonedDateTime(JSContext* cx, const Instant& instant,
+ const PlainDateTime& dateTime,
+ Handle<TimeZoneRecord> timeZone,
+ Handle<CalendarValue> calendar, double days,
+ TemporalOverflow overflow,
+ PlainDateTimeAndInstant* result) {
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2. (Not applicable)
+
+ // Step 3.
+ if (days == 0) {
+ *result = {dateTime, instant};
+ return true;
+ }
+
+ // Step 4.
+ PlainDate addedDate;
+ if (!AddISODate(cx, dateTime.date, {0, 0, 0, days}, overflow, &addedDate)) {
+ return false;
+ }
+
+ // Step 5.
+ Rooted<PlainDateTimeWithCalendar> dateTimeResult(cx);
+ if (!CreateTemporalDateTime(cx, {addedDate, dateTime.time}, calendar,
+ &dateTimeResult)) {
+ return false;
+ }
+
+ // Step 6.
+ Instant instantResult;
+ if (!GetInstantFor(cx, timeZone, dateTimeResult,
+ TemporalDisambiguation::Compatible, &instantResult)) {
+ return false;
+ }
+
+ // Step 7.
+ *result = {ToPlainDateTime(dateTimeResult), instantResult};
+ return true;
+}
+
+/**
+ * AddDaysToZonedDateTime ( instant, dateTime, timeZoneRec, calendar, days [ ,
+ * overflow ] )
+ */
+bool js::temporal::AddDaysToZonedDateTime(
+ JSContext* cx, const Instant& instant, const PlainDateTime& dateTime,
+ Handle<TimeZoneRecord> timeZone, Handle<CalendarValue> calendar,
+ double days, TemporalOverflow overflow, Instant* result) {
+ // Steps 1-7.
+ PlainDateTimeAndInstant dateTimeAndInstant;
+ if (!::AddDaysToZonedDateTime(cx, instant, dateTime, timeZone, calendar, days,
+ overflow, &dateTimeAndInstant)) {
+ return false;
+ }
+
+ *result = dateTimeAndInstant.instant;
+ return true;
+}
+
+/**
+ * AddDaysToZonedDateTime ( instant, dateTime, timeZoneRec, calendar, days [ ,
+ * overflow ] )
+ */
+bool js::temporal::AddDaysToZonedDateTime(JSContext* cx, const Instant& instant,
+ const PlainDateTime& dateTime,
+ Handle<TimeZoneRecord> timeZone,
+ Handle<CalendarValue> calendar,
+ double days, Instant* result) {
+ // Step 2.
+ auto overflow = TemporalOverflow::Constrain;
+
+ // Steps 1 and 3-7.
+ return AddDaysToZonedDateTime(cx, instant, dateTime, timeZone, calendar, days,
+ overflow, result);
+}
+
+/**
+ * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
+ * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds
+ * [ , precalculatedPlainDateTime [ , options ] ] )
+ */
+static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
+ Handle<TimeZoneRecord> timeZone,
+ Handle<CalendarRecord> calendar,
+ const Duration& duration,
+ mozilla::Maybe<const PlainDateTime&> dateTime,
+ Handle<JSObject*> maybeOptions, Instant* result) {
+ MOZ_ASSERT(IsValidEpochInstant(epochNanoseconds));
+ MOZ_ASSERT(IsValidDuration(duration.date()));
+ MOZ_ASSERT(IsValidDuration(duration.time()));
+
+ // Step 1.
+ MOZ_ASSERT(TimeZoneMethodsRecordHasLookedUp(
+ timeZone, TimeZoneMethod::GetPossibleInstantsFor));
+
+ // Steps 2-3.
+ MOZ_ASSERT_IF(!dateTime,
+ TimeZoneMethodsRecordHasLookedUp(
+ timeZone, TimeZoneMethod::GetOffsetNanosecondsFor));
+
+ // Steps 4-5. (Not applicable in our implementation)
+
+ // Step 6.
+ if (duration.years == 0 && duration.months == 0 && duration.weeks == 0 &&
+ duration.days == 0) {
+ // Step 6.a.
+ return AddInstant(cx, epochNanoseconds, duration, result);
+ }
+
+ // Step 7. (Not applicable in our implementation)
+
+ // Steps 8-9.
+ PlainDateTime temporalDateTime;
+ if (dateTime) {
+ // Step 8.a.
+ temporalDateTime = *dateTime;
+ } else {
+ // Step 9.a.
+ if (!GetPlainDateTimeFor(cx, timeZone, epochNanoseconds,
+ &temporalDateTime)) {
+ return false;
+ }
+ }
+ auto& [date, time] = temporalDateTime;
+
+ // Step 10.
+ if (duration.years == 0 && duration.months == 0 && duration.weeks == 0) {
+ // Step 10.a.
+ auto overflow = TemporalOverflow::Constrain;
+ if (maybeOptions) {
+ if (!ToTemporalOverflow(cx, maybeOptions, &overflow)) {
+ return false;
+ }
+ }
+
+ // Step 10.b.
+ Instant intermediate;
+ if (!AddDaysToZonedDateTime(cx, epochNanoseconds, temporalDateTime,
+ timeZone, calendar.receiver(), duration.days,
+ overflow, &intermediate)) {
+ return false;
+ }
+
+ // Step 10.c.
+ return AddInstant(cx, intermediate, duration.time(), result);
+ }
+
+ // Step 11.
+ MOZ_ASSERT(
+ CalendarMethodsRecordHasLookedUp(calendar, CalendarMethod::DateAdd));
+
+ // Step 12.
+ const auto& datePart = date;
+
+ // Step 13.
+ auto dateDuration = duration.date();
+
+ // Step 14.
+ PlainDate addedDate;
+ if (maybeOptions) {
+ if (!CalendarDateAdd(cx, calendar, datePart, dateDuration, maybeOptions,
+ &addedDate)) {
+ return false;
+ }
+ } else {
+ if (!CalendarDateAdd(cx, calendar, datePart, dateDuration, &addedDate)) {
+ return false;
+ }
+ }
+
+ // Step 15.
+ Rooted<PlainDateTimeWithCalendar> intermediateDateTime(cx);
+ if (!CreateTemporalDateTime(cx, {addedDate, time}, calendar.receiver(),
+ &intermediateDateTime)) {
+ return false;
+ }
+
+ // Step 16.
+ Instant intermediateInstant;
+ if (!GetInstantFor(cx, timeZone, intermediateDateTime,
+ TemporalDisambiguation::Compatible,
+ &intermediateInstant)) {
+ return false;
+ }
+
+ // Step 17.
+ return AddInstant(cx, intermediateInstant, duration.time(), result);
+}
+
+/**
+ * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
+ * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds
+ * [ , precalculatedPlainDateTime [ , options ] ] )
+ */
+static bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
+ Handle<TimeZoneRecord> timeZone,
+ Handle<CalendarRecord> calendar,
+ const Duration& duration,
+ Handle<JSObject*> maybeOptions, Instant* result) {
+ return ::AddZonedDateTime(cx, epochNanoseconds, timeZone, calendar, duration,
+ mozilla::Nothing(), maybeOptions, result);
+}
+
+/**
+ * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
+ * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds
+ * [ , precalculatedPlainDateTime [ , options ] ] )
+ */
+bool js::temporal::AddZonedDateTime(JSContext* cx,
+ const Instant& epochNanoseconds,
+ Handle<TimeZoneRecord> timeZone,
+ Handle<CalendarRecord> calendar,
+ const Duration& duration, Instant* result) {
+ return ::AddZonedDateTime(cx, epochNanoseconds, timeZone, calendar, duration,
+ mozilla::Nothing(), nullptr, result);
+}
+
+/**
+ * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
+ * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds
+ * [ , precalculatedPlainDateTime [ , options ] ] )
+ */
+bool js::temporal::AddZonedDateTime(
+ JSContext* cx, const Instant& epochNanoseconds,
+ Handle<TimeZoneRecord> timeZone, Handle<CalendarRecord> calendar,
+ const Duration& duration, const PlainDateTime& dateTime, Instant* result) {
+ return ::AddZonedDateTime(cx, epochNanoseconds, timeZone, calendar, duration,
+ mozilla::SomeRef(dateTime), nullptr, result);
+}
+
+double js::temporal::NanosecondsAndDays::daysNumber() const {
+ if (days) {
+ return BigInt::numberValue(days);
+ }
+ return double(daysInt);
+}
+
+void js::temporal::NanosecondsAndDays::trace(JSTracer* trc) {
+ if (days) {
+ TraceRoot(trc, &days, "NanosecondsAndDays::days");
+ }
+}
+
+/**
+ * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ ,
+ * precalculatedPlainDateTime ] )
+ */
+static bool NanosecondsToDays(
+ JSContext* cx, const InstantSpan& nanoseconds,
+ Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
+ mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
+ MutableHandle<NanosecondsAndDays> result) {
+ MOZ_ASSERT(IsValidInstantSpan(nanoseconds));
+
+ // Step 1.
+ if (nanoseconds == InstantSpan{}) {
+ result.set(NanosecondsAndDays::from(
+ int64_t(0), InstantSpan{},
+ InstantSpan::fromNanoseconds(ToNanoseconds(TemporalUnit::Day))));
+ return true;
+ }
+
+ // Step 2.
+ int32_t sign = nanoseconds < InstantSpan{} ? -1 : 1;
+
+ // Step 3.
+ auto startNs = zonedRelativeTo.instant();
+ auto calendar = zonedRelativeTo.calendar();
+
+ // Step 5.
+ //
+ // NB: This addition can't overflow, because we've checked that |nanoseconds|
+ // can be represented as an InstantSpan value.
+ auto endNs = startNs + nanoseconds;
+
+ // Step 6.
+ if (!IsValidEpochInstant(endNs)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_INVALID);
+ return false;
+ }
+
+ // Steps 4 and 8.
+ PlainDateTime startDateTime;
+ if (!precalculatedPlainDateTime) {
+ if (!GetPlainDateTimeFor(cx, timeZone, startNs, &startDateTime)) {
+ return false;
+ }
+ } else {
+ startDateTime = *precalculatedPlainDateTime;
+ }
+
+ // Steps 7 and 9.
+ PlainDateTime endDateTime;
+ if (!GetPlainDateTimeFor(cx, timeZone, endNs, &endDateTime)) {
+ return false;
+ }
+
+ // Steps 10-11. (Not applicable in our implementation.)
+
+ // Step 12.
+ //
+ // Overflows in step 21 can be safely ignored, because they take too long to
+ // happen for int64.
+ int64_t days = DaysUntil(startDateTime.date, endDateTime.date);
+
+ // Step 13.
+ int32_t timeSign = CompareTemporalTime(startDateTime.time, endDateTime.time);
+
+ // Steps 14-15.
+ if (days > 0 && timeSign > 0) {
+ days -= 1;
+ } else if (days < 0 && timeSign < 0) {
+ days += 1;
+ }
+
+ // Step 16.
+ PlainDateTimeAndInstant relativeResult;
+ if (!::AddDaysToZonedDateTime(cx, startNs, startDateTime, timeZone, calendar,
+ days, TemporalOverflow::Constrain,
+ &relativeResult)) {
+ return false;
+ }
+ MOZ_ASSERT(IsValidISODateTime(relativeResult.dateTime));
+ MOZ_ASSERT(IsValidEpochInstant(relativeResult.instant));
+
+ // Step 17.
+ if (sign > 0) {
+ // Step 17.a.
+ while (days > 0 && relativeResult.instant > endNs) {
+ // This loop can iterate indefinitely when given a specially crafted
+ // time zone object, so we need to check for interrupts.
+ if (!CheckForInterrupt(cx)) {
+ return false;
+ }
+
+ // Step 17.a.i.
+ days -= 1;
+
+ // Step 17.a.ii.
+ if (!::AddDaysToZonedDateTime(cx, startNs, startDateTime, timeZone,
+ calendar, days, TemporalOverflow::Constrain,
+ &relativeResult)) {
+ return false;
+ }
+ MOZ_ASSERT(IsValidISODateTime(relativeResult.dateTime));
+ MOZ_ASSERT(IsValidEpochInstant(relativeResult.instant));
+ }
+
+ MOZ_ASSERT_IF(days > 0, relativeResult.instant <= endNs);
+ }
+
+ MOZ_ASSERT_IF(days == 0, relativeResult.instant == startNs);
+
+ // Step 18.
+ auto ns = endNs - relativeResult.instant;
+ MOZ_ASSERT(IsValidInstantSpan(ns));
+
+ // Steps 19-21.
+ InstantSpan dayLengthNs{};
+ while (true) {
+ // This loop can iterate indefinitely when given a specially crafted time
+ // zone object, so we need to check for interrupts.
+ if (!CheckForInterrupt(cx)) {
+ return false;
+ }
+
+ // Step 21.a.
+ PlainDateTimeAndInstant oneDayFarther;
+ if (!::AddDaysToZonedDateTime(
+ cx, relativeResult.instant, relativeResult.dateTime, timeZone,
+ calendar, sign, TemporalOverflow::Constrain, &oneDayFarther)) {
+ return false;
+ }
+ MOZ_ASSERT(IsValidISODateTime(oneDayFarther.dateTime));
+ MOZ_ASSERT(IsValidEpochInstant(oneDayFarther.instant));
+
+ // Step 21.b.
+ dayLengthNs = oneDayFarther.instant - relativeResult.instant;
+ MOZ_ASSERT(IsValidInstantSpan(dayLengthNs));
+
+ // clang-format off
+ //
+ // First iteration:
+ //
+ // ns = endNs - relativeResult.instant
+ // dayLengthNs = oneDayFarther.instant - relativeResult.instant
+ // diff = ns - dayLengthNs
+ // = (endNs - relativeResult.instant) - (oneDayFarther.instant - relativeResult.instant)
+ // = endNs - relativeResult.instant - oneDayFarther.instant + relativeResult.instant
+ // = endNs - oneDayFarther.instant
+ //
+ // Second iteration:
+ //
+ // ns = diff'
+ // = endNs - oneDayFarther.instant'
+ // relativeResult.instant = oneDayFarther.instant'
+ // dayLengthNs = oneDayFarther.instant - relativeResult.instant
+ // = oneDayFarther.instant - oneDayFarther.instant'
+ // diff = ns - dayLengthNs
+ // = (endNs - oneDayFarther.instant') - (oneDayFarther.instant - oneDayFarther.instant')
+ // = endNs - oneDayFarther.instant' - oneDayFarther.instant + oneDayFarther.instant'
+ // = endNs - oneDayFarther.instant
+ //
+ // Where |diff'| and |oneDayFarther.instant'| denote the variables from the
+ // previous iteration.
+ //
+ // This repeats for all following iterations.
+ //
+ // |endNs| and |oneDayFarther.instant| are both valid epoch instant values,
+ // so the difference is a valid epoch instant difference value, too.
+ //
+ // clang-format on
+
+ // Step 21.c.
+ auto diff = ns - dayLengthNs;
+ MOZ_ASSERT(IsValidInstantSpan(diff));
+ MOZ_ASSERT(diff == (endNs - oneDayFarther.instant));
+
+ if (diff == InstantSpan{} || ((diff < InstantSpan{}) == (sign < 0))) {
+ // Step 21.c.i.
+ ns = diff;
+
+ // Step 21.c.ii.
+ relativeResult = oneDayFarther;
+
+ // Step 21.c.iii.
+ days += sign;
+ } else {
+ // Step 21.d.
+ break;
+ }
+ }
+
+ // Step 22.
+ if (days < 0 && sign > 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_ZONED_DATE_TIME_INCORRECT_SIGN,
+ "days");
+ return false;
+ }
+
+ // Step 23.
+ if (days > 0 && sign < 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_ZONED_DATE_TIME_INCORRECT_SIGN,
+ "days");
+ return false;
+ }
+
+ MOZ_ASSERT(IsValidInstantSpan(dayLengthNs));
+ MOZ_ASSERT(IsValidInstantSpan(ns));
+
+ // FIXME: spec issue - rewrite steps 24-25 as:
+ //
+ // If sign = -1, then
+ // If nanoseconds > 0, throw a RangeError.
+ // Else,
+ // Assert: nanoseconds โ‰ฅ 0.
+ //
+ // https://github.com/tc39/proposal-temporal/issues/2530
+
+ // Steps 24-25.
+ if (sign < 0) {
+ if (ns > InstantSpan{}) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_ZONED_DATE_TIME_INCORRECT_SIGN,
+ "nanoseconds");
+ return false;
+ }
+ } else {
+ MOZ_ASSERT(ns >= InstantSpan{});
+ }
+
+ // Step 26.
+ MOZ_ASSERT(ns.abs() < dayLengthNs.abs());
+
+ // Step 27.
+ result.set(NanosecondsAndDays::from(days, ns, dayLengthNs.abs()));
+ return true;
+}
+
+/**
+ * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ ,
+ * precalculatedPlainDateTime ] )
+ */
+bool js::temporal::NanosecondsToDays(JSContext* cx,
+ const InstantSpan& nanoseconds,
+ Handle<ZonedDateTime> zonedRelativeTo,
+ Handle<TimeZoneRecord> timeZone,
+ MutableHandle<NanosecondsAndDays> result) {
+ return ::NanosecondsToDays(cx, nanoseconds, zonedRelativeTo, timeZone,
+ mozilla::Nothing(), result);
+}
+
+/**
+ * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ ,
+ * precalculatedPlainDateTime ] )
+ */
+bool js::temporal::NanosecondsToDays(
+ JSContext* cx, const InstantSpan& nanoseconds,
+ Handle<ZonedDateTime> zonedRelativeTo, Handle<TimeZoneRecord> timeZone,
+ const PlainDateTime& precalculatedPlainDateTime,
+ MutableHandle<NanosecondsAndDays> result) {
+ return ::NanosecondsToDays(cx, nanoseconds, zonedRelativeTo, timeZone,
+ mozilla::SomeRef(precalculatedPlainDateTime),
+ result);
+}
+
+/**
+ * DifferenceZonedDateTime ( ns1, ns2, timeZoneRec, calendarRec, largestUnit,
+ * options, precalculatedPlainDateTime )
+ */
+static bool DifferenceZonedDateTime(
+ JSContext* cx, const Instant& ns1, const Instant& ns2,
+ Handle<TimeZoneRecord> timeZone, Handle<CalendarRecord> calendar,
+ TemporalUnit largestUnit, Handle<PlainObject*> maybeOptions,
+ mozilla::Maybe<const PlainDateTime&> precalculatedPlainDateTime,
+ Duration* result) {
+ MOZ_ASSERT(IsValidEpochInstant(ns1));
+ MOZ_ASSERT(IsValidEpochInstant(ns2));
+
+ // Steps 1.
+ if (ns1 == ns2) {
+ *result = {};
+ return true;
+ }
+
+ // Steps 2-3.
+ PlainDateTime startDateTime;
+ if (!precalculatedPlainDateTime) {
+ // Steps 2.a-b.
+ if (!GetPlainDateTimeFor(cx, timeZone, ns1, &startDateTime)) {
+ return false;
+ }
+ } else {
+ startDateTime = *precalculatedPlainDateTime;
+ }
+
+ // Steps 4-5.
+ PlainDateTime endDateTime;
+ if (!GetPlainDateTimeFor(cx, timeZone, ns2, &endDateTime)) {
+ return false;
+ }
+
+ // Step 6.
+ Duration dateDifference;
+ if (maybeOptions) {
+ if (!DifferenceISODateTime(cx, startDateTime, endDateTime, calendar,
+ largestUnit, maybeOptions, &dateDifference)) {
+ return false;
+ }
+ } else {
+ if (!DifferenceISODateTime(cx, startDateTime, endDateTime, calendar,
+ largestUnit, &dateDifference)) {
+ return false;
+ }
+ }
+
+ // Step 7.
+ Instant intermediateNs;
+ if (!AddZonedDateTime(cx, ns1, timeZone, calendar,
+ {
+ dateDifference.years,
+ dateDifference.months,
+ dateDifference.weeks,
+ },
+ startDateTime, &intermediateNs)) {
+ return false;
+ }
+ MOZ_ASSERT(IsValidEpochInstant(intermediateNs));
+
+ // Step 8.
+ auto timeRemainder = ns2 - intermediateNs;
+ MOZ_ASSERT(IsValidInstantSpan(timeRemainder));
+
+ // Step 9.
+ Rooted<ZonedDateTime> intermediate(
+ cx,
+ ZonedDateTime{intermediateNs, timeZone.receiver(), calendar.receiver()});
+
+ // Step 10.
+ Rooted<NanosecondsAndDays> nanosAndDays(cx);
+ if (!NanosecondsToDays(cx, timeRemainder, intermediate, timeZone,
+ &nanosAndDays)) {
+ return false;
+ }
+
+ // Step 11.
+ TimeDuration timeDifference;
+ if (!BalanceTimeDuration(cx, nanosAndDays.nanoseconds(), TemporalUnit::Hour,
+ &timeDifference)) {
+ return false;
+ }
+
+ // Step 12.
+ *result = {
+ dateDifference.years, dateDifference.months,
+ dateDifference.weeks, nanosAndDays.daysNumber(),
+ timeDifference.hours, timeDifference.minutes,
+ timeDifference.seconds, timeDifference.milliseconds,
+ timeDifference.microseconds, timeDifference.nanoseconds,
+ };
+ MOZ_ASSERT(IsValidDuration(*result));
+ return true;
+}
+
+/**
+ * DifferenceZonedDateTime ( ns1, ns2, timeZoneRec, calendarRec, largestUnit,
+ * options, precalculatedPlainDateTime )
+ */
+bool js::temporal::DifferenceZonedDateTime(
+ JSContext* cx, const Instant& ns1, const Instant& ns2,
+ Handle<TimeZoneRecord> timeZone, Handle<CalendarRecord> calendar,
+ TemporalUnit largestUnit, const PlainDateTime& precalculatedPlainDateTime,
+ Duration* result) {
+ return ::DifferenceZonedDateTime(
+ cx, ns1, ns2, timeZone, calendar, largestUnit, nullptr,
+ mozilla::SomeRef(precalculatedPlainDateTime), result);
+}
+
+/**
+ * TimeZoneEquals ( one, two )
+ */
+static bool TimeZoneEqualsOrThrow(JSContext* cx, Handle<TimeZoneValue> one,
+ Handle<TimeZoneValue> two) {
+ // Step 1.
+ if (one.isObject() && two.isObject() && one.toObject() == two.toObject()) {
+ return true;
+ }
+
+ // Step 2.
+ Rooted<JSString*> timeZoneOne(cx, ToTemporalTimeZoneIdentifier(cx, one));
+ if (!timeZoneOne) {
+ return false;
+ }
+
+ // Step 3.
+ Rooted<JSString*> timeZoneTwo(cx, ToTemporalTimeZoneIdentifier(cx, two));
+ if (!timeZoneTwo) {
+ return false;
+ }
+
+ // Steps 4-9.
+ bool equals;
+ if (!TimeZoneEquals(cx, timeZoneOne, timeZoneTwo, &equals)) {
+ return false;
+ }
+ if (equals) {
+ return true;
+ }
+
+ // Throw an error when the time zone identifiers don't match. Used when
+ // unequal time zones throw a RangeError.
+ if (auto charsOne = QuoteString(cx, timeZoneOne)) {
+ if (auto charsTwo = QuoteString(cx, timeZoneTwo)) {
+ JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_TIMEZONE_INCOMPATIBLE,
+ charsOne.get(), charsTwo.get());
+ }
+ }
+ return false;
+}
+
+/**
+ * RoundISODateTime ( year, month, day, hour, minute, second, millisecond,
+ * microsecond, nanosecond, increment, unit, roundingMode [ , dayLength ] )
+ */
+static bool RoundISODateTime(JSContext* cx, const PlainDateTime& dateTime,
+ Increment increment, TemporalUnit unit,
+ TemporalRoundingMode roundingMode,
+ const InstantSpan& dayLength,
+ PlainDateTime* result) {
+ MOZ_ASSERT(IsValidInstantSpan(dayLength));
+ MOZ_ASSERT(dayLength > (InstantSpan{}));
+
+ const auto& [date, time] = dateTime;
+
+ // Step 1.
+ MOZ_ASSERT(IsValidISODateTime(dateTime));
+ MOZ_ASSERT(ISODateTimeWithinLimits(dateTime));
+
+ // Step 2. (Not applicable in our implementation.)
+
+ // Step 3.
+ auto roundedTime = RoundTime(time, increment, unit, roundingMode, dayLength);
+
+ // |dayLength| can be as small as 1, so the number of rounded days can be as
+ // large as the number of nanoseconds in |time|.
+ MOZ_ASSERT(0 <= roundedTime.days &&
+ roundedTime.days < ToNanoseconds(TemporalUnit::Day));
+
+ // Step 4.
+ PlainDate balanceResult;
+ if (!BalanceISODate(cx, date.year, date.month,
+ int64_t(date.day) + roundedTime.days, &balanceResult)) {
+ return false;
+ }
+
+ // Step 5.
+ *result = {balanceResult, roundedTime.time};
+ return true;
+}
+
+/**
+ * DifferenceTemporalZonedDateTime ( operation, zonedDateTime, other, options )
+ */
+static bool DifferenceTemporalZonedDateTime(JSContext* cx,
+ TemporalDifference operation,
+ const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ Rooted<ZonedDateTime> other(cx);
+ if (!ToTemporalZonedDateTime(cx, args.get(0), &other)) {
+ return false;
+ }
+
+ // Step 3.
+ if (!CalendarEqualsOrThrow(cx, zonedDateTime.calendar(), other.calendar())) {
+ return false;
+ }
+
+ // Steps 4-5.
+ Rooted<PlainObject*> resolvedOptions(cx);
+ DifferenceSettings settings;
+ if (args.hasDefined(1)) {
+ Rooted<JSObject*> options(
+ cx, RequireObjectArg(cx, "options", ToName(operation), args[1]));
+ if (!options) {
+ return false;
+ }
+
+ // Step 4.
+ resolvedOptions = SnapshotOwnProperties(cx, options);
+ if (!resolvedOptions) {
+ return false;
+ }
+
+ // Step 5.
+ if (!GetDifferenceSettings(
+ cx, operation, resolvedOptions, TemporalUnitGroup::DateTime,
+ TemporalUnit::Nanosecond, TemporalUnit::Hour, &settings)) {
+ return false;
+ }
+ } else {
+ // Steps 4-5.
+ settings = {
+ TemporalUnit::Nanosecond,
+ TemporalUnit::Hour,
+ TemporalRoundingMode::Trunc,
+ Increment{1},
+ };
+ }
+
+ // Step 6.
+ if (settings.largestUnit > TemporalUnit::Day) {
+ MOZ_ASSERT(settings.smallestUnit >= settings.largestUnit);
+
+ // Step 6.a.
+ Duration difference;
+ if (!DifferenceInstant(cx, zonedDateTime.instant(), other.instant(),
+ settings.roundingIncrement, settings.smallestUnit,
+ settings.largestUnit, settings.roundingMode,
+ &difference)) {
+ return false;
+ }
+
+ // Step 6.b.
+ if (operation == TemporalDifference::Since) {
+ difference = difference.negate();
+ }
+
+ auto* result = CreateTemporalDuration(cx, difference);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+ }
+
+ // FIXME: spec issue - move this step next to the calendar validation?
+ // https://github.com/tc39/proposal-temporal/issues/2533
+
+ // Step 7.
+ if (!TimeZoneEqualsOrThrow(cx, zonedDateTime.timeZone(), other.timeZone())) {
+ return false;
+ }
+
+ // Step 8.
+ if (zonedDateTime.instant() == other.instant()) {
+ auto* obj = CreateTemporalDuration(cx, {});
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+ }
+
+ // Step 9.
+ Rooted<TimeZoneRecord> timeZone(cx);
+ if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(),
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ TimeZoneMethod::GetPossibleInstantsFor,
+ },
+ &timeZone)) {
+ return false;
+ }
+
+ // Step 10.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(),
+ {
+ CalendarMethod::DateAdd,
+ CalendarMethod::DateUntil,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Steps 11-12.
+ PlainDateTime precalculatedPlainDateTime;
+ if (!GetPlainDateTimeFor(cx, timeZone, zonedDateTime.instant(),
+ &precalculatedPlainDateTime)) {
+ return false;
+ }
+
+ // Step 13.
+ Rooted<PlainDateObject*> plainRelativeTo(
+ cx, CreateTemporalDate(cx, precalculatedPlainDateTime.date,
+ calendar.receiver()));
+ if (!plainRelativeTo) {
+ return false;
+ }
+
+ // Step 14.
+ if (resolvedOptions) {
+ Rooted<Value> largestUnitValue(
+ cx, StringValue(TemporalUnitToString(cx, settings.largestUnit)));
+ if (!DefineDataProperty(cx, resolvedOptions, cx->names().largestUnit,
+ largestUnitValue)) {
+ return false;
+ }
+ }
+
+ // Step 15.
+ Duration difference;
+ if (!::DifferenceZonedDateTime(
+ cx, zonedDateTime.instant(), other.instant(), timeZone, calendar,
+ settings.largestUnit, resolvedOptions,
+ mozilla::SomeRef<const PlainDateTime>(precalculatedPlainDateTime),
+ &difference)) {
+ return false;
+ }
+
+ // Step 16.
+ bool roundingGranularityIsNoop =
+ settings.smallestUnit == TemporalUnit::Nanosecond &&
+ settings.roundingIncrement == Increment{1};
+
+ // Step 17.
+ if (roundingGranularityIsNoop) {
+ if (operation == TemporalDifference::Since) {
+ difference = difference.negate();
+ }
+
+ auto* obj = CreateTemporalDuration(cx, difference);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+ }
+
+ // Steps 18-19.
+ Duration roundResult;
+ if (!RoundDuration(cx, difference, settings.roundingIncrement,
+ settings.smallestUnit, settings.roundingMode,
+ plainRelativeTo, calendar, zonedDateTime, timeZone,
+ precalculatedPlainDateTime, &roundResult)) {
+ return false;
+ }
+
+ // Step 20.
+ Duration adjustResult;
+ if (!AdjustRoundedDurationDays(cx, roundResult, settings.roundingIncrement,
+ settings.smallestUnit, settings.roundingMode,
+ zonedDateTime, calendar, timeZone,
+ precalculatedPlainDateTime, &adjustResult)) {
+ return false;
+ }
+
+ // Step 21.
+ DateDuration balanceResult;
+ if (!temporal::BalanceDateDurationRelative(
+ cx, adjustResult.date(), settings.largestUnit, settings.smallestUnit,
+ plainRelativeTo, calendar, &balanceResult)) {
+ return false;
+ }
+
+ // Step 22.
+ auto result = Duration{
+ balanceResult.years, balanceResult.months,
+ balanceResult.weeks, balanceResult.days,
+ adjustResult.hours, adjustResult.minutes,
+ adjustResult.seconds, adjustResult.milliseconds,
+ adjustResult.microseconds, adjustResult.nanoseconds,
+ };
+ if (operation == TemporalDifference::Since) {
+ result = result.negate();
+ }
+
+ auto* obj = CreateTemporalDuration(cx, result);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+enum class ZonedDateTimeDuration { Add, Subtract };
+
+/**
+ * AddDurationToOrSubtractDurationFromZonedDateTime ( operation, zonedDateTime,
+ * temporalDurationLike, options )
+ */
+static bool AddDurationToOrSubtractDurationFromZonedDateTime(
+ JSContext* cx, ZonedDateTimeDuration operation, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, &args.thisv().toObject().as<ZonedDateTimeObject>());
+
+ // Step 1. (Not applicable in our implementation.)
+
+ // Step 2.
+ Duration duration;
+ if (!ToTemporalDurationRecord(cx, args.get(0), &duration)) {
+ return false;
+ }
+
+ // Step 3.
+ Rooted<JSObject*> options(cx);
+ if (args.hasDefined(1)) {
+ const char* name =
+ operation == ZonedDateTimeDuration::Add ? "add" : "subtract";
+ options = RequireObjectArg(cx, "options", name, args[1]);
+ } else {
+ options = NewPlainObjectWithProto(cx, nullptr);
+ }
+ if (!options) {
+ return false;
+ }
+
+ // Step 4.
+ Rooted<TimeZoneRecord> timeZone(cx);
+ if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(),
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ TimeZoneMethod::GetPossibleInstantsFor,
+ },
+ &timeZone)) {
+ return false;
+ }
+
+ // Step 5.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(),
+ {
+ CalendarMethod::DateAdd,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Step 6.
+ if (operation == ZonedDateTimeDuration::Subtract) {
+ duration = duration.negate();
+ }
+
+ Instant resultInstant;
+ if (!::AddZonedDateTime(cx, zonedDateTime.instant(), timeZone, calendar,
+ duration, options, &resultInstant)) {
+ return false;
+ }
+ MOZ_ASSERT(IsValidEpochInstant(resultInstant));
+
+ // Step 7.
+ auto* result = CreateTemporalZonedDateTime(
+ cx, resultInstant, timeZone.receiver(), calendar.receiver());
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime ( epochNanoseconds, timeZoneLike [ , calendarLike ] )
+ */
+static bool ZonedDateTimeConstructor(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ if (!ThrowIfNotConstructing(cx, args, "Temporal.ZonedDateTime")) {
+ return false;
+ }
+
+ // Step 2.
+ Rooted<BigInt*> epochNanoseconds(cx, js::ToBigInt(cx, args.get(0)));
+ if (!epochNanoseconds) {
+ return false;
+ }
+
+ // Step 3.
+ if (!IsValidEpochNanoseconds(epochNanoseconds)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INSTANT_INVALID);
+ return false;
+ }
+
+ // Step 4.
+ Rooted<TimeZoneValue> timeZone(cx);
+ if (!ToTemporalTimeZone(cx, args.get(1), &timeZone)) {
+ return false;
+ }
+
+ // Step 5.
+ Rooted<CalendarValue> calendar(cx);
+ if (!ToTemporalCalendarWithISODefault(cx, args.get(2), &calendar)) {
+ return false;
+ }
+
+ // Step 6.
+ auto* obj = CreateTemporalZonedDateTime(cx, args, epochNanoseconds, timeZone,
+ calendar);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.from ( item [ , options ] )
+ */
+static bool ZonedDateTime_from(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ Rooted<JSObject*> options(cx);
+ if (args.hasDefined(1)) {
+ options = RequireObjectArg(cx, "options", "from", args[1]);
+ if (!options) {
+ return false;
+ }
+ }
+
+ // Step 2.
+ if (args.get(0).isObject()) {
+ JSObject* item = &args[0].toObject();
+ if (auto* zonedDateTime = item->maybeUnwrapIf<ZonedDateTimeObject>()) {
+ auto epochInstant = ToInstant(zonedDateTime);
+ Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
+ Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
+
+ if (!timeZone.wrap(cx)) {
+ return false;
+ }
+ if (!calendar.wrap(cx)) {
+ return false;
+ }
+
+ if (options) {
+ // Steps 2.a-b.
+ TemporalDisambiguation ignoredDisambiguation;
+ if (!ToTemporalDisambiguation(cx, options, &ignoredDisambiguation)) {
+ return false;
+ }
+
+ // Step 2.c.
+ TemporalOffset ignoredOffset;
+ if (!ToTemporalOffset(cx, options, &ignoredOffset)) {
+ return false;
+ }
+
+ // Step 2.d.
+ TemporalOverflow ignoredOverflow;
+ if (!ToTemporalOverflow(cx, options, &ignoredOverflow)) {
+ return false;
+ }
+ }
+
+ // Step 2.e.
+ auto* result =
+ CreateTemporalZonedDateTime(cx, epochInstant, timeZone, calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+ }
+ }
+
+ // Step 3.
+ auto* result = ToTemporalZonedDateTime(cx, args.get(0), options);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.compare ( one, two )
+ */
+static bool ZonedDateTime_compare(JSContext* cx, unsigned argc, Value* vp) {
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1.
+ Rooted<ZonedDateTime> one(cx);
+ if (!ToTemporalZonedDateTime(cx, args.get(0), &one)) {
+ return false;
+ }
+
+ // Step 2.
+ Rooted<ZonedDateTime> two(cx);
+ if (!ToTemporalZonedDateTime(cx, args.get(1), &two)) {
+ return false;
+ }
+
+ // Step 3.
+ auto oneNs = one.instant();
+ auto twoNs = two.instant();
+ args.rval().setInt32(oneNs > twoNs ? 1 : oneNs < twoNs ? -1 : 0);
+ return true;
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.calendarId
+ */
+static bool ZonedDateTime_calendarId(JSContext* cx, const CallArgs& args) {
+ auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>();
+
+ // Step 3.
+ Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
+ auto* calendarId = ToTemporalCalendarIdentifier(cx, calendar);
+ if (!calendarId) {
+ return false;
+ }
+
+ args.rval().setString(calendarId);
+ return true;
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.calendarId
+ */
+static bool ZonedDateTime_calendarId(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_calendarId>(cx,
+ args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.timeZoneId
+ */
+static bool ZonedDateTime_timeZoneId(JSContext* cx, const CallArgs& args) {
+ auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>();
+
+ // Step 3.
+ Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
+ auto* timeZoneId = ToTemporalTimeZoneIdentifier(cx, timeZone);
+ if (!timeZoneId) {
+ return false;
+ }
+
+ args.rval().setString(timeZoneId);
+ return true;
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.timeZoneId
+ */
+static bool ZonedDateTime_timeZoneId(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_timeZoneId>(cx,
+ args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.year
+ */
+static bool ZonedDateTime_year(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ return CalendarYear(cx, zonedDateTime.calendar(), dateTime, args.rval());
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.year
+ */
+static bool ZonedDateTime_year(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_year>(cx, args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.month
+ */
+static bool ZonedDateTime_month(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ return CalendarMonth(cx, zonedDateTime.calendar(), dateTime, args.rval());
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.month
+ */
+static bool ZonedDateTime_month(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_month>(cx, args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.monthCode
+ */
+static bool ZonedDateTime_monthCode(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ return CalendarMonthCode(cx, zonedDateTime.calendar(), dateTime, args.rval());
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.monthCode
+ */
+static bool ZonedDateTime_monthCode(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_monthCode>(cx,
+ args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.day
+ */
+static bool ZonedDateTime_day(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Step 4. (Reordered)
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(),
+ {
+ CalendarMethod::Day,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Steps 3 and 5-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ return CalendarDay(cx, calendar, dateTime, args.rval());
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.day
+ */
+static bool ZonedDateTime_day(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_day>(cx, args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.hour
+ */
+static bool ZonedDateTime_hour(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ args.rval().setInt32(dateTime.time.hour);
+ return true;
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.hour
+ */
+static bool ZonedDateTime_hour(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_hour>(cx, args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.minute
+ */
+static bool ZonedDateTime_minute(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ args.rval().setInt32(dateTime.time.minute);
+ return true;
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.minute
+ */
+static bool ZonedDateTime_minute(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_minute>(cx, args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.second
+ */
+static bool ZonedDateTime_second(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ args.rval().setInt32(dateTime.time.second);
+ return true;
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.second
+ */
+static bool ZonedDateTime_second(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_second>(cx, args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.millisecond
+ */
+static bool ZonedDateTime_millisecond(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ args.rval().setInt32(dateTime.time.millisecond);
+ return true;
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.millisecond
+ */
+static bool ZonedDateTime_millisecond(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_millisecond>(cx,
+ args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.microsecond
+ */
+static bool ZonedDateTime_microsecond(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ args.rval().setInt32(dateTime.time.microsecond);
+ return true;
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.microsecond
+ */
+static bool ZonedDateTime_microsecond(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_microsecond>(cx,
+ args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.nanosecond
+ */
+static bool ZonedDateTime_nanosecond(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ args.rval().setInt32(dateTime.time.nanosecond);
+ return true;
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.nanosecond
+ */
+static bool ZonedDateTime_nanosecond(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_nanosecond>(cx,
+ args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.epochSeconds
+ */
+static bool ZonedDateTime_epochSeconds(JSContext* cx, const CallArgs& args) {
+ auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>();
+
+ // Step 3.
+ auto instant = ToInstant(zonedDateTime);
+
+ // Steps 4-5.
+ args.rval().setNumber(instant.seconds);
+ return true;
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.epochSeconds
+ */
+static bool ZonedDateTime_epochSeconds(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_epochSeconds>(
+ cx, args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.epochMilliseconds
+ */
+static bool ZonedDateTime_epochMilliseconds(JSContext* cx,
+ const CallArgs& args) {
+ auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>();
+
+ // Step 3.
+ auto instant = ToInstant(zonedDateTime);
+
+ // Steps 4-5.
+ args.rval().setNumber(instant.floorToMilliseconds());
+ return true;
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.epochMilliseconds
+ */
+static bool ZonedDateTime_epochMilliseconds(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_epochMilliseconds>(
+ cx, args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.epochMicroseconds
+ */
+static bool ZonedDateTime_epochMicroseconds(JSContext* cx,
+ const CallArgs& args) {
+ auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>();
+
+ // Step 3.
+ auto instant = ToInstant(zonedDateTime);
+
+ // Step 4.
+ auto* microseconds =
+ BigInt::createFromInt64(cx, instant.floorToMicroseconds());
+ if (!microseconds) {
+ return false;
+ }
+
+ // Step 5.
+ args.rval().setBigInt(microseconds);
+ return true;
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.epochMicroseconds
+ */
+static bool ZonedDateTime_epochMicroseconds(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_epochMicroseconds>(
+ cx, args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.epochNanoseconds
+ */
+static bool ZonedDateTime_epochNanoseconds(JSContext* cx,
+ const CallArgs& args) {
+ auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>();
+
+ // Step 3.
+ auto* nanoseconds = ToEpochNanoseconds(cx, ToInstant(zonedDateTime));
+ if (!nanoseconds) {
+ return false;
+ }
+
+ args.rval().setBigInt(nanoseconds);
+ return true;
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.epochNanoseconds
+ */
+static bool ZonedDateTime_epochNanoseconds(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_epochNanoseconds>(
+ cx, args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.dayOfWeek
+ */
+static bool ZonedDateTime_dayOfWeek(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ return CalendarDayOfWeek(cx, zonedDateTime.calendar(), dateTime, args.rval());
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.dayOfWeek
+ */
+static bool ZonedDateTime_dayOfWeek(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_dayOfWeek>(cx,
+ args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.dayOfYear
+ */
+static bool ZonedDateTime_dayOfYear(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ return CalendarDayOfYear(cx, zonedDateTime.calendar(), dateTime, args.rval());
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.dayOfYear
+ */
+static bool ZonedDateTime_dayOfYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_dayOfYear>(cx,
+ args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.weekOfYear
+ */
+static bool ZonedDateTime_weekOfYear(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ return CalendarWeekOfYear(cx, zonedDateTime.calendar(), dateTime,
+ args.rval());
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.weekOfYear
+ */
+static bool ZonedDateTime_weekOfYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_weekOfYear>(cx,
+ args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.yearOfWeek
+ */
+static bool ZonedDateTime_yearOfWeek(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ return CalendarYearOfWeek(cx, zonedDateTime.calendar(), dateTime,
+ args.rval());
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.yearOfWeek
+ */
+static bool ZonedDateTime_yearOfWeek(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_yearOfWeek>(cx,
+ args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.hoursInDay
+ */
+static bool ZonedDateTime_hoursInDay(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Step 3.
+ Rooted<TimeZoneRecord> timeZone(cx);
+ if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(),
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ TimeZoneMethod::GetPossibleInstantsFor,
+ },
+ &timeZone)) {
+ return false;
+ }
+
+ // Step 4.
+ auto instant = zonedDateTime.instant();
+
+ // Step 5.
+ PlainDateTime temporalDateTime;
+ if (!GetPlainDateTimeFor(cx, timeZone, instant, &temporalDateTime)) {
+ return false;
+ }
+
+ // Steps 6-8.
+ const auto& date = temporalDateTime.date;
+ Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
+
+ // Step 9.
+ Rooted<PlainDateTimeWithCalendar> today(cx);
+ if (!CreateTemporalDateTime(cx, {date, {}}, isoCalendar, &today)) {
+ return false;
+ }
+
+ // Step 10.
+ auto tomorrowFields = BalanceISODate(date.year, date.month, date.day + 1);
+
+ // Step 11.
+ Rooted<PlainDateTimeWithCalendar> tomorrow(cx);
+ if (!CreateTemporalDateTime(cx, {tomorrowFields, {}}, isoCalendar,
+ &tomorrow)) {
+ return false;
+ }
+
+ // Step 12.
+ Instant todayInstant;
+ if (!GetInstantFor(cx, timeZone, today, TemporalDisambiguation::Compatible,
+ &todayInstant)) {
+ return false;
+ }
+
+ // Step 13.
+ Instant tomorrowInstant;
+ if (!GetInstantFor(cx, timeZone, tomorrow, TemporalDisambiguation::Compatible,
+ &tomorrowInstant)) {
+ return false;
+ }
+
+ // Step 14.
+ auto diffNs = tomorrowInstant - todayInstant;
+ MOZ_ASSERT(IsValidInstantSpan(diffNs));
+
+ // Step 15.
+ constexpr int32_t secPerHour = 60 * 60;
+ constexpr int64_t nsPerSec = ToNanoseconds(TemporalUnit::Second);
+ constexpr double nsPerHour = ToNanoseconds(TemporalUnit::Hour);
+
+ int64_t hours = diffNs.seconds / secPerHour;
+ int64_t seconds = diffNs.seconds % secPerHour;
+ int64_t nanoseconds = seconds * nsPerSec + diffNs.nanoseconds;
+
+ double result = double(hours) + double(nanoseconds) / nsPerHour;
+ args.rval().setNumber(result);
+ return true;
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.hoursInDay
+ */
+static bool ZonedDateTime_hoursInDay(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_hoursInDay>(cx,
+ args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.daysInWeek
+ */
+static bool ZonedDateTime_daysInWeek(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ return CalendarDaysInWeek(cx, zonedDateTime.calendar(), dateTime,
+ args.rval());
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.daysInWeek
+ */
+static bool ZonedDateTime_daysInWeek(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_daysInWeek>(cx,
+ args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.daysInMonth
+ */
+static bool ZonedDateTime_daysInMonth(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ return CalendarDaysInMonth(cx, zonedDateTime.calendar(), dateTime,
+ args.rval());
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.daysInMonth
+ */
+static bool ZonedDateTime_daysInMonth(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_daysInMonth>(cx,
+ args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.daysInYear
+ */
+static bool ZonedDateTime_daysInYear(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ return CalendarDaysInYear(cx, zonedDateTime.calendar(), dateTime,
+ args.rval());
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.daysInYear
+ */
+static bool ZonedDateTime_daysInYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_daysInYear>(cx,
+ args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.monthsInYear
+ */
+static bool ZonedDateTime_monthsInYear(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ return CalendarMonthsInYear(cx, zonedDateTime.calendar(), dateTime,
+ args.rval());
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.monthsInYear
+ */
+static bool ZonedDateTime_monthsInYear(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_monthsInYear>(
+ cx, args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.inLeapYear
+ */
+static bool ZonedDateTime_inLeapYear(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime dateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &dateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ return CalendarInLeapYear(cx, zonedDateTime.calendar(), dateTime,
+ args.rval());
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.inLeapYear
+ */
+static bool ZonedDateTime_inLeapYear(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_inLeapYear>(cx,
+ args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.offsetNanoseconds
+ */
+static bool ZonedDateTime_offsetNanoseconds(JSContext* cx,
+ const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Step 3.
+ auto timeZone = zonedDateTime.timeZone();
+
+ // Step 4.
+ auto instant = zonedDateTime.instant();
+
+ // Step 5.
+ int64_t offsetNanoseconds;
+ if (!GetOffsetNanosecondsFor(cx, timeZone, instant, &offsetNanoseconds)) {
+ return false;
+ }
+ MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
+
+ args.rval().setNumber(offsetNanoseconds);
+ return true;
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.offsetNanoseconds
+ */
+static bool ZonedDateTime_offsetNanoseconds(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_offsetNanoseconds>(
+ cx, args);
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.offset
+ */
+static bool ZonedDateTime_offset(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Step 3.
+ auto timeZone = zonedDateTime.timeZone();
+
+ // Step 4.
+ auto instant = zonedDateTime.instant();
+
+ // Step 5.
+ JSString* str = GetOffsetStringFor(cx, timeZone, instant);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * get Temporal.ZonedDateTime.prototype.offset
+ */
+static bool ZonedDateTime_offset(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_offset>(cx, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.with ( temporalZonedDateTimeLike [ , options
+ * ] )
+ */
+static bool ZonedDateTime_with(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Step 3.
+ Rooted<JSObject*> temporalZonedDateTimeLike(
+ cx,
+ RequireObjectArg(cx, "temporalZonedDateTimeLike", "with", args.get(0)));
+ if (!temporalZonedDateTimeLike) {
+ return false;
+ }
+
+ // Step 4.
+ if (!RejectTemporalLikeObject(cx, temporalZonedDateTimeLike)) {
+ return false;
+ }
+
+ // Step 5.
+ Rooted<PlainObject*> resolvedOptions(cx);
+ if (args.hasDefined(1)) {
+ Rooted<JSObject*> options(cx,
+ RequireObjectArg(cx, "options", "with", args[1]));
+ if (!options) {
+ return false;
+ }
+ resolvedOptions = SnapshotOwnProperties(cx, options);
+ } else {
+ resolvedOptions = NewPlainObjectWithProto(cx, nullptr);
+ }
+ if (!resolvedOptions) {
+ return false;
+ }
+
+ // Step 6.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(),
+ {
+ CalendarMethod::DateFromFields,
+ CalendarMethod::Fields,
+ CalendarMethod::MergeFields,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Step 7.
+ Rooted<TimeZoneRecord> timeZone(cx);
+ if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(),
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ TimeZoneMethod::GetPossibleInstantsFor,
+ },
+ &timeZone)) {
+ return false;
+ }
+
+ // Step 8.
+ auto instant = zonedDateTime.instant();
+
+ // Step 9.
+ int64_t offsetNanoseconds;
+ if (!GetOffsetNanosecondsFor(cx, timeZone, instant, &offsetNanoseconds)) {
+ return false;
+ }
+
+ // Step 10.
+ Rooted<PlainDateTimeObject*> dateTime(
+ cx,
+ GetPlainDateTimeFor(cx, instant, calendar.receiver(), offsetNanoseconds));
+ if (!dateTime) {
+ return false;
+ }
+
+ // Step 11.
+ JS::RootedVector<PropertyKey> fieldNames(cx);
+ if (!CalendarFields(cx, calendar,
+ {CalendarField::Day, CalendarField::Month,
+ CalendarField::MonthCode, CalendarField::Year},
+ &fieldNames)) {
+ return false;
+ }
+
+ // Step 12.
+ Rooted<PlainObject*> fields(cx,
+ PrepareTemporalFields(cx, dateTime, fieldNames));
+ if (!fields) {
+ return false;
+ }
+
+ // Steps 13-18.
+ struct TimeField {
+ using FieldName = ImmutableTenuredPtr<PropertyName*> JSAtomState::*;
+
+ FieldName name;
+ int32_t value;
+ } timeFields[] = {
+ {&JSAtomState::hour, dateTime->isoHour()},
+ {&JSAtomState::minute, dateTime->isoMinute()},
+ {&JSAtomState::second, dateTime->isoSecond()},
+ {&JSAtomState::millisecond, dateTime->isoMillisecond()},
+ {&JSAtomState::microsecond, dateTime->isoMicrosecond()},
+ {&JSAtomState::nanosecond, dateTime->isoNanosecond()},
+ };
+
+ Rooted<Value> timeFieldValue(cx);
+ for (const auto& timeField : timeFields) {
+ Handle<PropertyName*> name = cx->names().*(timeField.name);
+ timeFieldValue.setInt32(timeField.value);
+
+ if (!DefineDataProperty(cx, fields, name, timeFieldValue)) {
+ return false;
+ }
+ }
+
+ // Step 19.
+ JSString* fieldsOffset = FormatUTCOffsetNanoseconds(cx, offsetNanoseconds);
+ if (!fieldsOffset) {
+ return false;
+ }
+
+ timeFieldValue.setString(fieldsOffset);
+ if (!DefineDataProperty(cx, fields, cx->names().offset, timeFieldValue)) {
+ return false;
+ }
+
+ // Step 20.
+ if (!AppendSorted(cx, fieldNames.get(),
+ {
+ TemporalField::Hour,
+ TemporalField::Microsecond,
+ TemporalField::Millisecond,
+ TemporalField::Minute,
+ TemporalField::Nanosecond,
+ TemporalField::Offset,
+ TemporalField::Second,
+ })) {
+ return false;
+ }
+
+ // Step 21.
+ Rooted<PlainObject*> partialZonedDateTime(
+ cx,
+ PreparePartialTemporalFields(cx, temporalZonedDateTimeLike, fieldNames));
+ if (!partialZonedDateTime) {
+ return false;
+ }
+
+ // Step 22.
+ Rooted<JSObject*> mergedFields(
+ cx, CalendarMergeFields(cx, calendar, fields, partialZonedDateTime));
+ if (!mergedFields) {
+ return false;
+ }
+
+ // Step 23.
+ fields = PrepareTemporalFields(cx, mergedFields, fieldNames,
+ {TemporalField::Offset});
+ if (!fields) {
+ return false;
+ }
+
+ // Step 24-25.
+ auto disambiguation = TemporalDisambiguation::Compatible;
+ if (!ToTemporalDisambiguation(cx, resolvedOptions, &disambiguation)) {
+ return false;
+ }
+
+ // Step 26.
+ auto offset = TemporalOffset::Prefer;
+ if (!ToTemporalOffset(cx, resolvedOptions, &offset)) {
+ return false;
+ }
+
+ // Step 27.
+ PlainDateTime dateTimeResult;
+ if (!InterpretTemporalDateTimeFields(cx, calendar, fields, resolvedOptions,
+ &dateTimeResult)) {
+ return false;
+ }
+
+ // Step 28.
+ Rooted<Value> offsetString(cx);
+ if (!GetProperty(cx, fields, fields, cx->names().offset, &offsetString)) {
+ return false;
+ }
+
+ // Step 29.
+ MOZ_ASSERT(offsetString.isString());
+
+ // Step 30.
+ Rooted<JSString*> offsetStr(cx, offsetString.toString());
+ int64_t newOffsetNanoseconds;
+ if (!ParseDateTimeUTCOffset(cx, offsetStr, &newOffsetNanoseconds)) {
+ return false;
+ }
+
+ // Step 31.
+ Instant epochNanoseconds;
+ if (!InterpretISODateTimeOffset(
+ cx, dateTimeResult, OffsetBehaviour::Option, newOffsetNanoseconds,
+ timeZone, disambiguation, offset, MatchBehaviour::MatchExactly,
+ &epochNanoseconds)) {
+ return false;
+ }
+
+ // Step 32.
+ auto* result = CreateTemporalZonedDateTime(
+ cx, epochNanoseconds, timeZone.receiver(), calendar.receiver());
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.with ( temporalZonedDateTimeLike [ , options
+ * ] )
+ */
+static bool ZonedDateTime_with(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_with>(cx, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.withPlainTime ( [ plainTimeLike ] )
+ */
+static bool ZonedDateTime_withPlainTime(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-4.
+ PlainTime time = {};
+ if (args.hasDefined(0)) {
+ if (!ToTemporalTime(cx, args[0], &time)) {
+ return false;
+ }
+ }
+
+ // Step 5.
+ Rooted<TimeZoneRecord> timeZone(cx);
+ if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(),
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ TimeZoneMethod::GetPossibleInstantsFor,
+ },
+ &timeZone)) {
+ return false;
+ }
+
+ // Steps 6 and 8.
+ PlainDateTime plainDateTime;
+ if (!GetPlainDateTimeFor(cx, timeZone, zonedDateTime.instant(),
+ &plainDateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ auto calendar = zonedDateTime.calendar();
+
+ // Step 9.
+ Rooted<PlainDateTimeWithCalendar> resultPlainDateTime(cx);
+ if (!CreateTemporalDateTime(cx, {plainDateTime.date, time}, calendar,
+ &resultPlainDateTime)) {
+ return false;
+ }
+
+ // Step 10.
+ Instant instant;
+ if (!GetInstantFor(cx, timeZone, resultPlainDateTime,
+ TemporalDisambiguation::Compatible, &instant)) {
+ return false;
+ }
+
+ // Step 11.
+ auto* result =
+ CreateTemporalZonedDateTime(cx, instant, timeZone.receiver(), calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.withPlainTime ( [ plainTimeLike ] )
+ */
+static bool ZonedDateTime_withPlainTime(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_withPlainTime>(
+ cx, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.withPlainDate ( plainDateLike )
+ */
+static bool ZonedDateTime_withPlainDate(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Step 3.
+ Rooted<PlainDateWithCalendar> plainDate(cx);
+ if (!ToTemporalDate(cx, args.get(0), &plainDate)) {
+ return false;
+ }
+ auto date = plainDate.date();
+
+ // Step 4.
+ Rooted<TimeZoneRecord> timeZone(cx);
+ if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(),
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ TimeZoneMethod::GetPossibleInstantsFor,
+ },
+ &timeZone)) {
+ return false;
+ }
+
+ // Steps 5-6.
+ PlainDateTime plainDateTime;
+ if (!GetPlainDateTimeFor(cx, timeZone, zonedDateTime.instant(),
+ &plainDateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ Rooted<CalendarValue> calendar(cx);
+ if (!ConsolidateCalendars(cx, zonedDateTime.calendar(), plainDate.calendar(),
+ &calendar)) {
+ return false;
+ }
+
+ // Step 8.
+ Rooted<PlainDateTimeWithCalendar> resultPlainDateTime(cx);
+ if (!CreateTemporalDateTime(cx, {date, plainDateTime.time}, calendar,
+ &resultPlainDateTime)) {
+ return false;
+ }
+
+ // Step 9.
+ Instant instant;
+ if (!GetInstantFor(cx, timeZone, resultPlainDateTime,
+ TemporalDisambiguation::Compatible, &instant)) {
+ return false;
+ }
+
+ // Step 10.
+ auto* result =
+ CreateTemporalZonedDateTime(cx, instant, timeZone.receiver(), calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.withPlainDate ( plainDateLike )
+ */
+static bool ZonedDateTime_withPlainDate(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_withPlainDate>(
+ cx, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.withTimeZone ( timeZoneLike )
+ */
+static bool ZonedDateTime_withTimeZone(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Step 3.
+ Rooted<TimeZoneValue> timeZone(cx);
+ if (!ToTemporalTimeZone(cx, args.get(0), &timeZone)) {
+ return false;
+ }
+
+ // Step 4.
+ auto* result = CreateTemporalZonedDateTime(
+ cx, zonedDateTime.instant(), timeZone, zonedDateTime.calendar());
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.withTimeZone ( timeZoneLike )
+ */
+static bool ZonedDateTime_withTimeZone(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_withTimeZone>(
+ cx, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.withCalendar ( calendarLike )
+ */
+static bool ZonedDateTime_withCalendar(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Step 3.
+ Rooted<CalendarValue> calendar(cx);
+ if (!ToTemporalCalendar(cx, args.get(0), &calendar)) {
+ return false;
+ }
+
+ // Step 4.
+ auto* result = CreateTemporalZonedDateTime(
+ cx, zonedDateTime.instant(), zonedDateTime.timeZone(), calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.withCalendar ( calendarLike )
+ */
+static bool ZonedDateTime_withCalendar(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_withCalendar>(
+ cx, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.add ( temporalDurationLike [ , options ] )
+ */
+static bool ZonedDateTime_add(JSContext* cx, const CallArgs& args) {
+ return AddDurationToOrSubtractDurationFromZonedDateTime(
+ cx, ZonedDateTimeDuration::Add, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.add ( temporalDurationLike [ , options ] )
+ */
+static bool ZonedDateTime_add(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_add>(cx, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.subtract ( temporalDurationLike [ , options
+ * ] )
+ */
+static bool ZonedDateTime_subtract(JSContext* cx, const CallArgs& args) {
+ return AddDurationToOrSubtractDurationFromZonedDateTime(
+ cx, ZonedDateTimeDuration::Subtract, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.subtract ( temporalDurationLike [ , options
+ * ] )
+ */
+static bool ZonedDateTime_subtract(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_subtract>(cx,
+ args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.until ( other [ , options ] )
+ */
+static bool ZonedDateTime_until(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ return DifferenceTemporalZonedDateTime(cx, TemporalDifference::Until, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.until ( other [ , options ] )
+ */
+static bool ZonedDateTime_until(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_until>(cx, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.since ( other [ , options ] )
+ */
+static bool ZonedDateTime_since(JSContext* cx, const CallArgs& args) {
+ // Step 3.
+ return DifferenceTemporalZonedDateTime(cx, TemporalDifference::Since, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.since ( other [ , options ] )
+ */
+static bool ZonedDateTime_since(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_since>(cx, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.round ( roundTo )
+ */
+static bool ZonedDateTime_round(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-12.
+ auto smallestUnit = TemporalUnit::Auto;
+ auto roundingMode = TemporalRoundingMode::HalfExpand;
+ auto roundingIncrement = Increment{1};
+ if (args.get(0).isString()) {
+ // Step 4. (Not applicable in our implementation.)
+
+ // Step 9.
+ Rooted<JSString*> paramString(cx, args[0].toString());
+ if (!GetTemporalUnit(cx, paramString, TemporalUnitKey::SmallestUnit,
+ TemporalUnitGroup::DayTime, &smallestUnit)) {
+ return false;
+ }
+
+ // Steps 6-8 and 10-12. (Implicit)
+ } else {
+ // Steps 3 and 5.a
+ Rooted<JSObject*> roundTo(
+ cx, RequireObjectArg(cx, "roundTo", "round", args.get(0)));
+ if (!roundTo) {
+ return false;
+ }
+
+ // Steps 6-7.
+ if (!ToTemporalRoundingIncrement(cx, roundTo, &roundingIncrement)) {
+ return false;
+ }
+
+ // Step 8.
+ if (!ToTemporalRoundingMode(cx, roundTo, &roundingMode)) {
+ return false;
+ }
+
+ // Step 9.
+ if (!GetTemporalUnit(cx, roundTo, TemporalUnitKey::SmallestUnit,
+ TemporalUnitGroup::DayTime, &smallestUnit)) {
+ return false;
+ }
+
+ if (smallestUnit == TemporalUnit::Auto) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_MISSING_OPTION, "smallestUnit");
+ return false;
+ }
+
+ MOZ_ASSERT(TemporalUnit::Day <= smallestUnit &&
+ smallestUnit <= TemporalUnit::Nanosecond);
+
+ // Steps 10-11.
+ auto maximum = Increment{1};
+ bool inclusive = true;
+ if (smallestUnit > TemporalUnit::Day) {
+ maximum = MaximumTemporalDurationRoundingIncrement(smallestUnit);
+ inclusive = false;
+ }
+
+ // Step 12.
+ if (!ValidateTemporalRoundingIncrement(cx, roundingIncrement, maximum,
+ inclusive)) {
+ return false;
+ }
+ }
+
+ // Step 13.
+ if (smallestUnit == TemporalUnit::Nanosecond &&
+ roundingIncrement == Increment{1}) {
+ // Step 13.a.
+ auto* result = CreateTemporalZonedDateTime(cx, zonedDateTime.instant(),
+ zonedDateTime.timeZone(),
+ zonedDateTime.calendar());
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+ }
+
+ // Step 14.
+ Rooted<TimeZoneRecord> timeZone(cx);
+ if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(),
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ TimeZoneMethod::GetPossibleInstantsFor,
+ },
+ &timeZone)) {
+ return false;
+ }
+
+ // Step 16. (Reordered)
+ auto calendar = zonedDateTime.calendar();
+
+ // Steps 15 and 17.
+ int64_t offsetNanoseconds;
+ if (!GetOffsetNanosecondsFor(cx, timeZone, zonedDateTime.instant(),
+ &offsetNanoseconds)) {
+ return false;
+ }
+ MOZ_ASSERT(std::abs(offsetNanoseconds) < ToNanoseconds(TemporalUnit::Day));
+
+ // Step 18.
+ auto temporalDateTime =
+ GetPlainDateTimeFor(zonedDateTime.instant(), offsetNanoseconds);
+
+ // Step 19.
+ Rooted<CalendarValue> isoCalendar(cx, CalendarValue(cx->names().iso8601));
+ Rooted<PlainDateTimeWithCalendar> dtStart(cx);
+ if (!CreateTemporalDateTime(cx, {temporalDateTime.date, {}}, isoCalendar,
+ &dtStart)) {
+ return false;
+ }
+
+ // Steps 20-21.
+ Instant startNs;
+ if (!GetInstantFor(cx, timeZone, dtStart, TemporalDisambiguation::Compatible,
+ &startNs)) {
+ return false;
+ }
+
+ // Step 22.
+ Instant endNs;
+ if (!AddDaysToZonedDateTime(cx, startNs, ToPlainDateTime(dtStart), timeZone,
+ calendar, 1, &endNs)) {
+ return false;
+ }
+ MOZ_ASSERT(IsValidEpochInstant(endNs));
+
+ // Step 23.
+ auto dayLengthNs = endNs - startNs;
+ MOZ_ASSERT(IsValidInstantSpan(dayLengthNs));
+
+ // Step 24.
+ if (dayLengthNs <= InstantSpan{}) {
+ JS_ReportErrorNumberASCII(
+ cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_ZONED_DATE_TIME_NON_POSITIVE_DAY_LENGTH);
+ return false;
+ }
+
+ // Step 25.
+ PlainDateTime roundResult;
+ if (!RoundISODateTime(cx, temporalDateTime, roundingIncrement, smallestUnit,
+ roundingMode, dayLengthNs, &roundResult)) {
+ return false;
+ }
+
+ // Step 26.
+ Instant epochNanoseconds;
+ if (!InterpretISODateTimeOffset(
+ cx, roundResult, OffsetBehaviour::Option, offsetNanoseconds, timeZone,
+ TemporalDisambiguation::Compatible, TemporalOffset::Prefer,
+ MatchBehaviour::MatchExactly, &epochNanoseconds)) {
+ return false;
+ }
+
+ // Step 27.
+ auto* result = CreateTemporalZonedDateTime(cx, epochNanoseconds,
+ timeZone.receiver(), calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.round ( roundTo )
+ */
+static bool ZonedDateTime_round(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_round>(cx, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.equals ( other )
+ */
+static bool ZonedDateTime_equals(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Step 3.
+ Rooted<ZonedDateTime> other(cx);
+ if (!ToTemporalZonedDateTime(cx, args.get(0), &other)) {
+ return false;
+ }
+
+ // Steps 4-6.
+ bool equals = zonedDateTime.instant() == other.instant();
+ if (equals && !TimeZoneEquals(cx, zonedDateTime.timeZone(), other.timeZone(),
+ &equals)) {
+ return false;
+ }
+ if (equals && !CalendarEquals(cx, zonedDateTime.calendar(), other.calendar(),
+ &equals)) {
+ return false;
+ }
+
+ args.rval().setBoolean(equals);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.equals ( other )
+ */
+static bool ZonedDateTime_equals(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_equals>(cx, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toString ( [ options ] )
+ */
+static bool ZonedDateTime_toString(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ SecondsStringPrecision precision = {Precision::Auto(),
+ TemporalUnit::Nanosecond, Increment{1}};
+ auto roundingMode = TemporalRoundingMode::Trunc;
+ auto showCalendar = CalendarOption::Auto;
+ auto showTimeZone = TimeZoneNameOption::Auto;
+ auto showOffset = ShowOffsetOption::Auto;
+ if (args.hasDefined(0)) {
+ // Step 3.
+ Rooted<JSObject*> options(
+ cx, RequireObjectArg(cx, "options", "toString", args[0]));
+ if (!options) {
+ return false;
+ }
+
+ // Steps 4-5.
+ if (!ToCalendarNameOption(cx, options, &showCalendar)) {
+ return false;
+ }
+
+ // Step 6.
+ auto digits = Precision::Auto();
+ if (!ToFractionalSecondDigits(cx, options, &digits)) {
+ return false;
+ }
+
+ // Step 7.
+ if (!ToShowOffsetOption(cx, options, &showOffset)) {
+ return false;
+ }
+
+ // Step 8.
+ if (!ToTemporalRoundingMode(cx, options, &roundingMode)) {
+ return false;
+ }
+
+ // Step 9.
+ auto smallestUnit = TemporalUnit::Auto;
+ if (!GetTemporalUnit(cx, options, TemporalUnitKey::SmallestUnit,
+ TemporalUnitGroup::Time, &smallestUnit)) {
+ return false;
+ }
+
+ // Step 10.
+ if (smallestUnit == TemporalUnit::Hour) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_TEMPORAL_INVALID_UNIT_OPTION, "hour",
+ "smallestUnit");
+ return false;
+ }
+
+ // Step 11.
+ if (!ToTimeZoneNameOption(cx, options, &showTimeZone)) {
+ return false;
+ }
+
+ // Step 12.
+ precision = ToSecondsStringPrecision(smallestUnit, digits);
+ }
+
+ // Step 13.
+ JSString* str = TemporalZonedDateTimeToString(
+ cx, zonedDateTime, precision.precision, showCalendar, showTimeZone,
+ showOffset, precision.increment, precision.unit, roundingMode);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toString ( [ options ] )
+ */
+static bool ZonedDateTime_toString(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toString>(cx,
+ args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toLocaleString ( [ locales [ , options ] ] )
+ */
+static bool ZonedDateTime_toLocaleString(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Step 3.
+ JSString* str = TemporalZonedDateTimeToString(
+ cx, zonedDateTime, Precision::Auto(), CalendarOption::Auto,
+ TimeZoneNameOption::Auto, ShowOffsetOption::Auto);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toLocaleString ( [ locales [ , options ] ] )
+ */
+static bool ZonedDateTime_toLocaleString(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toLocaleString>(
+ cx, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toJSON ( )
+ */
+static bool ZonedDateTime_toJSON(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Step 3.
+ JSString* str = TemporalZonedDateTimeToString(
+ cx, zonedDateTime, Precision::Auto(), CalendarOption::Auto,
+ TimeZoneNameOption::Auto, ShowOffsetOption::Auto);
+ if (!str) {
+ return false;
+ }
+
+ args.rval().setString(str);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toJSON ( )
+ */
+static bool ZonedDateTime_toJSON(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toJSON>(cx, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.valueOf ( )
+ */
+static bool ZonedDateTime_valueOf(JSContext* cx, unsigned argc, Value* vp) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
+ "ZonedDateTime", "primitive type");
+ return false;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.startOfDay ( )
+ */
+static bool ZonedDateTime_startOfDay(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Step 3.
+ Rooted<TimeZoneRecord> timeZone(cx);
+ if (!CreateTimeZoneMethodsRecord(cx, zonedDateTime.timeZone(),
+ {
+ TimeZoneMethod::GetOffsetNanosecondsFor,
+ TimeZoneMethod::GetPossibleInstantsFor,
+ },
+ &timeZone)) {
+ return false;
+ }
+
+ // Step 4.
+ auto calendar = zonedDateTime.calendar();
+
+ // Step 5.
+ auto instant = zonedDateTime.instant();
+
+ // Steps 5-6.
+ PlainDateTime temporalDateTime;
+ if (!GetPlainDateTimeFor(cx, timeZone, instant, &temporalDateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ Rooted<PlainDateTimeWithCalendar> startDateTime(cx);
+ if (!CreateTemporalDateTime(cx, {temporalDateTime.date, {}}, calendar,
+ &startDateTime)) {
+ return false;
+ }
+
+ // Step 8.
+ Instant startInstant;
+ if (!GetInstantFor(cx, timeZone, startDateTime,
+ TemporalDisambiguation::Compatible, &startInstant)) {
+ return false;
+ }
+
+ // Step 9.
+ auto* result = CreateTemporalZonedDateTime(cx, startInstant,
+ timeZone.receiver(), calendar);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.startOfDay ( )
+ */
+static bool ZonedDateTime_startOfDay(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_startOfDay>(cx,
+ args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toInstant ( )
+ */
+static bool ZonedDateTime_toInstant(JSContext* cx, const CallArgs& args) {
+ auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>();
+ auto instant = ToInstant(zonedDateTime);
+
+ // Step 3.
+ auto* result = CreateTemporalInstant(cx, instant);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toInstant ( )
+ */
+static bool ZonedDateTime_toInstant(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toInstant>(cx,
+ args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toPlainDate ( )
+ */
+static bool ZonedDateTime_toPlainDate(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime temporalDateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &temporalDateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ auto* result =
+ CreateTemporalDate(cx, temporalDateTime.date, zonedDateTime.calendar());
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toPlainDate ( )
+ */
+static bool ZonedDateTime_toPlainDate(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toPlainDate>(cx,
+ args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toPlainTime ( )
+ */
+static bool ZonedDateTime_toPlainTime(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-6.
+ PlainDateTime temporalDateTime;
+ if (!GetPlainDateTimeFor(cx, zonedDateTime.timeZone(),
+ zonedDateTime.instant(), &temporalDateTime)) {
+ return false;
+ }
+
+ // Step 7.
+ auto* result = CreateTemporalTime(cx, temporalDateTime.time);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toPlainTime ( )
+ */
+static bool ZonedDateTime_toPlainTime(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toPlainTime>(cx,
+ args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toPlainDateTime ( )
+ */
+static bool ZonedDateTime_toPlainDateTime(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Steps 3-5.
+ auto* result =
+ GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), zonedDateTime.instant(),
+ zonedDateTime.calendar());
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toPlainDateTime ( )
+ */
+static bool ZonedDateTime_toPlainDateTime(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toPlainDateTime>(
+ cx, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toPlainYearMonth ( )
+ */
+static bool ZonedDateTime_toPlainYearMonth(JSContext* cx,
+ const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Step 3.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(),
+ {
+ CalendarMethod::Fields,
+ CalendarMethod::YearMonthFromFields,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Steps 4-6.
+ Rooted<PlainDateTimeObject*> temporalDateTime(
+ cx,
+ GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), zonedDateTime.instant(),
+ zonedDateTime.calendar()));
+ if (!temporalDateTime) {
+ return false;
+ }
+
+ // Step 7.
+ JS::RootedVector<PropertyKey> fieldNames(cx);
+ if (!CalendarFields(cx, calendar,
+ {CalendarField::MonthCode, CalendarField::Year},
+ &fieldNames)) {
+ return false;
+ }
+
+ // Step 8.
+ Rooted<PlainObject*> fields(
+ cx, PrepareTemporalFields(cx, temporalDateTime, fieldNames));
+ if (!fields) {
+ return false;
+ }
+
+ // Steps 9-10.
+ auto result = CalendarYearMonthFromFields(cx, calendar, fields);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toPlainYearMonth ( )
+ */
+static bool ZonedDateTime_toPlainYearMonth(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toPlainYearMonth>(
+ cx, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toPlainMonthDay ( )
+ */
+static bool ZonedDateTime_toPlainMonthDay(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Step 3.
+ Rooted<CalendarRecord> calendar(cx);
+ if (!CreateCalendarMethodsRecord(cx, zonedDateTime.calendar(),
+ {
+ CalendarMethod::Fields,
+ CalendarMethod::MonthDayFromFields,
+ },
+ &calendar)) {
+ return false;
+ }
+
+ // Steps 4-6.
+ Rooted<PlainDateTimeObject*> temporalDateTime(
+ cx,
+ GetPlainDateTimeFor(cx, zonedDateTime.timeZone(), zonedDateTime.instant(),
+ zonedDateTime.calendar()));
+ if (!temporalDateTime) {
+ return false;
+ }
+
+ // Step 7.
+ JS::RootedVector<PropertyKey> fieldNames(cx);
+ if (!CalendarFields(cx, calendar,
+ {CalendarField::Day, CalendarField::MonthCode},
+ &fieldNames)) {
+ return false;
+ }
+
+ // Step 8.
+ Rooted<PlainObject*> fields(
+ cx, PrepareTemporalFields(cx, temporalDateTime, fieldNames));
+ if (!fields) {
+ return false;
+ }
+
+ // Steps 9-10.
+ auto result = CalendarMonthDayFromFields(cx, calendar, fields);
+ if (!result) {
+ return false;
+ }
+
+ args.rval().setObject(*result);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.toPlainMonthDay ( )
+ */
+static bool ZonedDateTime_toPlainMonthDay(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_toPlainMonthDay>(
+ cx, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.getISOFields ( )
+ */
+static bool ZonedDateTime_getISOFields(JSContext* cx, const CallArgs& args) {
+ Rooted<ZonedDateTime> zonedDateTime(
+ cx, ZonedDateTime{&args.thisv().toObject().as<ZonedDateTimeObject>()});
+
+ // Step 3.
+ Rooted<IdValueVector> fields(cx, IdValueVector(cx));
+
+ // Step 4.
+ auto instant = zonedDateTime.instant();
+
+ // Step 5.
+ auto calendar = zonedDateTime.calendar();
+
+ // Step 6.
+ auto timeZone = zonedDateTime.timeZone();
+
+ // Step 7.
+ int64_t offsetNanoseconds;
+ if (!GetOffsetNanosecondsFor(cx, timeZone, instant, &offsetNanoseconds)) {
+ return false;
+ }
+
+ // Step 8.
+ auto temporalDateTime = GetPlainDateTimeFor(instant, offsetNanoseconds);
+
+ // Step 9.
+ Rooted<JSString*> offset(cx,
+ FormatUTCOffsetNanoseconds(cx, offsetNanoseconds));
+ if (!offset) {
+ return false;
+ }
+
+ // Step 10.
+ if (!fields.emplaceBack(NameToId(cx->names().calendar), calendar.toValue())) {
+ return false;
+ }
+
+ // Step 11.
+ if (!fields.emplaceBack(NameToId(cx->names().isoDay),
+ Int32Value(temporalDateTime.date.day))) {
+ return false;
+ }
+
+ // Step 12.
+ if (!fields.emplaceBack(NameToId(cx->names().isoHour),
+ Int32Value(temporalDateTime.time.hour))) {
+ return false;
+ }
+
+ // Step 13.
+ if (!fields.emplaceBack(NameToId(cx->names().isoMicrosecond),
+ Int32Value(temporalDateTime.time.microsecond))) {
+ return false;
+ }
+
+ // Step 14.
+ if (!fields.emplaceBack(NameToId(cx->names().isoMillisecond),
+ Int32Value(temporalDateTime.time.millisecond))) {
+ return false;
+ }
+
+ // Step 15.
+ if (!fields.emplaceBack(NameToId(cx->names().isoMinute),
+ Int32Value(temporalDateTime.time.minute))) {
+ return false;
+ }
+
+ // Step 16.
+ if (!fields.emplaceBack(NameToId(cx->names().isoMonth),
+ Int32Value(temporalDateTime.date.month))) {
+ return false;
+ }
+
+ // Step 17.
+ if (!fields.emplaceBack(NameToId(cx->names().isoNanosecond),
+ Int32Value(temporalDateTime.time.nanosecond))) {
+ return false;
+ }
+
+ // Step 18.
+ if (!fields.emplaceBack(NameToId(cx->names().isoSecond),
+ Int32Value(temporalDateTime.time.second))) {
+ return false;
+ }
+
+ // Step 19.
+ if (!fields.emplaceBack(NameToId(cx->names().isoYear),
+ Int32Value(temporalDateTime.date.year))) {
+ return false;
+ }
+
+ // Step 20.
+ if (!fields.emplaceBack(NameToId(cx->names().offset), StringValue(offset))) {
+ return false;
+ }
+
+ // Step 21.
+ if (!fields.emplaceBack(NameToId(cx->names().timeZone), timeZone.toValue())) {
+ return false;
+ }
+
+ // Step 22.
+ auto* obj =
+ NewPlainObjectWithUniqueNames(cx, fields.begin(), fields.length());
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.getISOFields ( )
+ */
+static bool ZonedDateTime_getISOFields(JSContext* cx, unsigned argc,
+ Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_getISOFields>(
+ cx, args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.getCalendar ( )
+ */
+static bool ZonedDateTime_getCalendar(JSContext* cx, const CallArgs& args) {
+ auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>();
+ Rooted<CalendarValue> calendar(cx, zonedDateTime->calendar());
+
+ // Step 3.
+ auto* obj = ToTemporalCalendarObject(cx, calendar);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.getCalendar ( )
+ */
+static bool ZonedDateTime_getCalendar(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_getCalendar>(cx,
+ args);
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.getTimeZone ( )
+ */
+static bool ZonedDateTime_getTimeZone(JSContext* cx, const CallArgs& args) {
+ auto* zonedDateTime = &args.thisv().toObject().as<ZonedDateTimeObject>();
+ Rooted<TimeZoneValue> timeZone(cx, zonedDateTime->timeZone());
+
+ // Step 3.
+ auto* obj = ToTemporalTimeZoneObject(cx, timeZone);
+ if (!obj) {
+ return false;
+ }
+
+ args.rval().setObject(*obj);
+ return true;
+}
+
+/**
+ * Temporal.ZonedDateTime.prototype.getTimeZone ( )
+ */
+static bool ZonedDateTime_getTimeZone(JSContext* cx, unsigned argc, Value* vp) {
+ // Steps 1-2.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<IsZonedDateTime, ZonedDateTime_getTimeZone>(cx,
+ args);
+}
+
+const JSClass ZonedDateTimeObject::class_ = {
+ "Temporal.ZonedDateTime",
+ JSCLASS_HAS_RESERVED_SLOTS(ZonedDateTimeObject::SLOT_COUNT) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_ZonedDateTime),
+ JS_NULL_CLASS_OPS,
+ &ZonedDateTimeObject::classSpec_,
+};
+
+const JSClass& ZonedDateTimeObject::protoClass_ = PlainObject::class_;
+
+static const JSFunctionSpec ZonedDateTime_methods[] = {
+ JS_FN("from", ZonedDateTime_from, 1, 0),
+ JS_FN("compare", ZonedDateTime_compare, 2, 0),
+ JS_FS_END,
+};
+
+static const JSFunctionSpec ZonedDateTime_prototype_methods[] = {
+ JS_FN("with", ZonedDateTime_with, 1, 0),
+ JS_FN("withPlainTime", ZonedDateTime_withPlainTime, 0, 0),
+ JS_FN("withPlainDate", ZonedDateTime_withPlainDate, 1, 0),
+ JS_FN("withTimeZone", ZonedDateTime_withTimeZone, 1, 0),
+ JS_FN("withCalendar", ZonedDateTime_withCalendar, 1, 0),
+ JS_FN("add", ZonedDateTime_add, 1, 0),
+ JS_FN("subtract", ZonedDateTime_subtract, 1, 0),
+ JS_FN("until", ZonedDateTime_until, 1, 0),
+ JS_FN("since", ZonedDateTime_since, 1, 0),
+ JS_FN("round", ZonedDateTime_round, 1, 0),
+ JS_FN("equals", ZonedDateTime_equals, 1, 0),
+ JS_FN("toString", ZonedDateTime_toString, 0, 0),
+ JS_FN("toLocaleString", ZonedDateTime_toLocaleString, 0, 0),
+ JS_FN("toJSON", ZonedDateTime_toJSON, 0, 0),
+ JS_FN("valueOf", ZonedDateTime_valueOf, 0, 0),
+ JS_FN("startOfDay", ZonedDateTime_startOfDay, 0, 0),
+ JS_FN("toInstant", ZonedDateTime_toInstant, 0, 0),
+ JS_FN("toPlainDate", ZonedDateTime_toPlainDate, 0, 0),
+ JS_FN("toPlainTime", ZonedDateTime_toPlainTime, 0, 0),
+ JS_FN("toPlainDateTime", ZonedDateTime_toPlainDateTime, 0, 0),
+ JS_FN("toPlainYearMonth", ZonedDateTime_toPlainYearMonth, 0, 0),
+ JS_FN("toPlainMonthDay", ZonedDateTime_toPlainMonthDay, 0, 0),
+ JS_FN("getISOFields", ZonedDateTime_getISOFields, 0, 0),
+ JS_FN("getCalendar", ZonedDateTime_getCalendar, 0, 0),
+ JS_FN("getTimeZone", ZonedDateTime_getTimeZone, 0, 0),
+ JS_FS_END,
+};
+
+static const JSPropertySpec ZonedDateTime_prototype_properties[] = {
+ JS_PSG("calendarId", ZonedDateTime_calendarId, 0),
+ JS_PSG("timeZoneId", ZonedDateTime_timeZoneId, 0),
+ JS_PSG("year", ZonedDateTime_year, 0),
+ JS_PSG("month", ZonedDateTime_month, 0),
+ JS_PSG("monthCode", ZonedDateTime_monthCode, 0),
+ JS_PSG("day", ZonedDateTime_day, 0),
+ JS_PSG("hour", ZonedDateTime_hour, 0),
+ JS_PSG("minute", ZonedDateTime_minute, 0),
+ JS_PSG("second", ZonedDateTime_second, 0),
+ JS_PSG("millisecond", ZonedDateTime_millisecond, 0),
+ JS_PSG("microsecond", ZonedDateTime_microsecond, 0),
+ JS_PSG("nanosecond", ZonedDateTime_nanosecond, 0),
+ JS_PSG("epochSeconds", ZonedDateTime_epochSeconds, 0),
+ JS_PSG("epochMilliseconds", ZonedDateTime_epochMilliseconds, 0),
+ JS_PSG("epochMicroseconds", ZonedDateTime_epochMicroseconds, 0),
+ JS_PSG("epochNanoseconds", ZonedDateTime_epochNanoseconds, 0),
+ JS_PSG("dayOfWeek", ZonedDateTime_dayOfWeek, 0),
+ JS_PSG("dayOfYear", ZonedDateTime_dayOfYear, 0),
+ JS_PSG("weekOfYear", ZonedDateTime_weekOfYear, 0),
+ JS_PSG("yearOfWeek", ZonedDateTime_yearOfWeek, 0),
+ JS_PSG("hoursInDay", ZonedDateTime_hoursInDay, 0),
+ JS_PSG("daysInWeek", ZonedDateTime_daysInWeek, 0),
+ JS_PSG("daysInMonth", ZonedDateTime_daysInMonth, 0),
+ JS_PSG("daysInYear", ZonedDateTime_daysInYear, 0),
+ JS_PSG("monthsInYear", ZonedDateTime_monthsInYear, 0),
+ JS_PSG("inLeapYear", ZonedDateTime_inLeapYear, 0),
+ JS_PSG("offsetNanoseconds", ZonedDateTime_offsetNanoseconds, 0),
+ JS_PSG("offset", ZonedDateTime_offset, 0),
+ JS_STRING_SYM_PS(toStringTag, "Temporal.ZonedDateTime", JSPROP_READONLY),
+ JS_PS_END,
+};
+
+const ClassSpec ZonedDateTimeObject::classSpec_ = {
+ GenericCreateConstructor<ZonedDateTimeConstructor, 2,
+ gc::AllocKind::FUNCTION>,
+ GenericCreatePrototype<ZonedDateTimeObject>,
+ ZonedDateTime_methods,
+ nullptr,
+ ZonedDateTime_prototype_methods,
+ ZonedDateTime_prototype_properties,
+ nullptr,
+ ClassSpec::DontDefineConstructor,
+};
diff --git a/js/src/builtin/temporal/ZonedDateTime.h b/js/src/builtin/temporal/ZonedDateTime.h
new file mode 100644
index 0000000000..73e3a3384f
--- /dev/null
+++ b/js/src/builtin/temporal/ZonedDateTime.h
@@ -0,0 +1,280 @@
+/* -*- 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/. */
+
+#ifndef builtin_temporal_ZonedDateTime_h
+#define builtin_temporal_ZonedDateTime_h
+
+#include "mozilla/Assertions.h"
+
+#include <stdint.h>
+
+#include "builtin/temporal/Calendar.h"
+#include "builtin/temporal/Instant.h"
+#include "builtin/temporal/TemporalTypes.h"
+#include "builtin/temporal/TimeZone.h"
+#include "js/RootingAPI.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "vm/NativeObject.h"
+
+class JS_PUBLIC_API JSTracer;
+
+namespace js {
+struct ClassSpec;
+}
+
+namespace js::temporal {
+
+class ZonedDateTimeObject : public NativeObject {
+ public:
+ static const JSClass class_;
+ static const JSClass& protoClass_;
+
+ static constexpr uint32_t SECONDS_SLOT = 0;
+ static constexpr uint32_t NANOSECONDS_SLOT = 1;
+ static constexpr uint32_t TIMEZONE_SLOT = 2;
+ static constexpr uint32_t CALENDAR_SLOT = 3;
+ static constexpr uint32_t SLOT_COUNT = 4;
+
+ int64_t seconds() const {
+ double seconds = getFixedSlot(SECONDS_SLOT).toNumber();
+ MOZ_ASSERT(-8'640'000'000'000 <= seconds && seconds <= 8'640'000'000'000);
+ return int64_t(seconds);
+ }
+
+ int32_t nanoseconds() const {
+ int32_t nanoseconds = getFixedSlot(NANOSECONDS_SLOT).toInt32();
+ MOZ_ASSERT(0 <= nanoseconds && nanoseconds <= 999'999'999);
+ return nanoseconds;
+ }
+
+ TimeZoneValue timeZone() const {
+ return TimeZoneValue(getFixedSlot(TIMEZONE_SLOT));
+ }
+
+ CalendarValue calendar() const {
+ return CalendarValue(getFixedSlot(CALENDAR_SLOT));
+ }
+
+ private:
+ static const ClassSpec classSpec_;
+};
+/**
+ * Extract the instant fields from the ZonedDateTime object.
+ */
+inline Instant ToInstant(const ZonedDateTimeObject* zonedDateTime) {
+ return {zonedDateTime->seconds(), zonedDateTime->nanoseconds()};
+}
+
+class ZonedDateTime {
+ Instant instant_;
+ TimeZoneValue timeZone_;
+ CalendarValue calendar_;
+
+ public:
+ ZonedDateTime() = default;
+
+ ZonedDateTime(const Instant& instant, const TimeZoneValue& timeZone,
+ const CalendarValue& calendar)
+ : instant_(instant), timeZone_(timeZone), calendar_(calendar) {
+ MOZ_ASSERT(IsValidEpochInstant(instant));
+ MOZ_ASSERT(timeZone);
+ MOZ_ASSERT(calendar);
+ }
+
+ explicit ZonedDateTime(const ZonedDateTimeObject* obj)
+ : ZonedDateTime(ToInstant(obj), obj->timeZone(), obj->calendar()) {}
+
+ const auto& instant() const { return instant_; }
+
+ const auto& timeZone() const { return timeZone_; }
+
+ const auto& calendar() const { return calendar_; }
+
+ explicit operator bool() const { return !!timeZone_ && !!calendar_; }
+
+ void trace(JSTracer* trc) {
+ timeZone_.trace(trc);
+ calendar_.trace(trc);
+ }
+
+ const auto* timeZoneDoNotUse() const { return &timeZone_; }
+ const auto* calendarDoNotUse() const { return &calendar_; }
+};
+
+enum class TemporalDisambiguation;
+enum class TemporalOffset;
+enum class TemporalOverflow;
+enum class TemporalUnit;
+
+/**
+ * CreateTemporalZonedDateTime ( epochNanoseconds, timeZone, calendar [ ,
+ * newTarget ] )
+ */
+ZonedDateTimeObject* CreateTemporalZonedDateTime(
+ JSContext* cx, const Instant& instant, JS::Handle<TimeZoneValue> timeZone,
+ JS::Handle<CalendarValue> calendar);
+
+/**
+ * AddDaysToZonedDateTime ( instant, dateTime, timeZoneRec, calendar, days [ ,
+ * overflow ] )
+ */
+bool AddDaysToZonedDateTime(JSContext* cx, const Instant& instant,
+ const PlainDateTime& dateTime,
+ JS::Handle<TimeZoneRecord> timeZone,
+ JS::Handle<CalendarValue> calendar, double days,
+ TemporalOverflow overflow, Instant* result);
+
+/**
+ * AddDaysToZonedDateTime ( instant, dateTime, timeZoneRec, calendar, days [ ,
+ * overflow ] )
+ */
+bool AddDaysToZonedDateTime(JSContext* cx, const Instant& instant,
+ const PlainDateTime& dateTime,
+ JS::Handle<TimeZoneRecord> timeZone,
+ JS::Handle<CalendarValue> calendar, double days,
+ Instant* result);
+
+/**
+ * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
+ * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds
+ * [ , precalculatedPlainDateTime [ , options ] ] )
+ */
+bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
+ JS::Handle<TimeZoneRecord> timeZone,
+ JS::Handle<CalendarRecord> calendar,
+ const Duration& duration, Instant* result);
+
+/**
+ * AddZonedDateTime ( epochNanoseconds, timeZoneRec, calendarRec, years, months,
+ * weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds
+ * [ , precalculatedPlainDateTime [ , options ] ] )
+ */
+bool AddZonedDateTime(JSContext* cx, const Instant& epochNanoseconds,
+ JS::Handle<TimeZoneRecord> timeZone,
+ JS::Handle<CalendarRecord> calendar,
+ const Duration& duration, const PlainDateTime& dateTime,
+ Instant* result);
+
+/**
+ * DifferenceZonedDateTime ( ns1, ns2, timeZoneRec, calendarRec, largestUnit,
+ * options, precalculatedPlainDateTime )
+ */
+bool DifferenceZonedDateTime(JSContext* cx, const Instant& ns1,
+ const Instant& ns2,
+ JS::Handle<TimeZoneRecord> timeZone,
+ JS::Handle<CalendarRecord> calendar,
+ TemporalUnit largestUnit,
+ const PlainDateTime& precalculatedPlainDateTime,
+ Duration* result);
+
+struct NanosecondsAndDays final {
+ JS::BigInt* days = nullptr;
+ int64_t daysInt = 0;
+ InstantSpan nanoseconds;
+ InstantSpan dayLength;
+
+ double daysNumber() const;
+
+ void trace(JSTracer* trc);
+
+ static NanosecondsAndDays from(int64_t days, const InstantSpan& nanoseconds,
+ const InstantSpan& dayLength) {
+ return {nullptr, days, nanoseconds, dayLength};
+ }
+
+ static NanosecondsAndDays from(JS::BigInt* days,
+ const InstantSpan& nanoseconds,
+ const InstantSpan& dayLength) {
+ return {days, 0, nanoseconds, dayLength};
+ }
+};
+
+/**
+ * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ ,
+ * precalculatedPlainDateTime ] )
+ */
+bool NanosecondsToDays(JSContext* cx, const InstantSpan& nanoseconds,
+ JS::Handle<ZonedDateTime> zonedRelativeTo,
+ JS::Handle<TimeZoneRecord> timeZone,
+ JS::MutableHandle<NanosecondsAndDays> result);
+
+/**
+ * NanosecondsToDays ( nanoseconds, zonedRelativeTo, timeZoneRec [ ,
+ * precalculatedPlainDateTime ] )
+ */
+bool NanosecondsToDays(JSContext* cx, const InstantSpan& nanoseconds,
+ JS::Handle<ZonedDateTime> zonedRelativeTo,
+ JS::Handle<TimeZoneRecord> timeZone,
+ const PlainDateTime& precalculatedPlainDateTime,
+ JS::MutableHandle<NanosecondsAndDays> result);
+
+enum class OffsetBehaviour { Option, Exact, Wall };
+
+enum class MatchBehaviour { MatchExactly, MatchMinutes };
+
+/**
+ * InterpretISODateTimeOffset ( year, month, day, hour, minute, second,
+ * millisecond, microsecond, nanosecond, offsetBehaviour, offsetNanoseconds,
+ * timeZoneRec, disambiguation, offsetOption, matchBehaviour )
+ */
+bool InterpretISODateTimeOffset(JSContext* cx, const PlainDateTime& dateTime,
+ OffsetBehaviour offsetBehaviour,
+ int64_t offsetNanoseconds,
+ JS::Handle<TimeZoneRecord> timeZone,
+ TemporalDisambiguation disambiguation,
+ TemporalOffset offsetOption,
+ MatchBehaviour matchBehaviour, Instant* result);
+
+} /* namespace js::temporal */
+
+namespace js {
+
+template <typename Wrapper>
+class WrappedPtrOperations<temporal::ZonedDateTime, Wrapper> {
+ const auto& container() const {
+ return static_cast<const Wrapper*>(this)->get();
+ }
+
+ public:
+ explicit operator bool() const { return bool(container()); }
+
+ const auto& instant() const { return container().instant(); }
+
+ JS::Handle<temporal::TimeZoneValue> timeZone() const {
+ return JS::Handle<temporal::TimeZoneValue>::fromMarkedLocation(
+ container().timeZoneDoNotUse());
+ }
+
+ JS::Handle<temporal::CalendarValue> calendar() const {
+ return JS::Handle<temporal::CalendarValue>::fromMarkedLocation(
+ container().calendarDoNotUse());
+ }
+};
+
+template <typename Wrapper>
+class WrappedPtrOperations<temporal::NanosecondsAndDays, Wrapper> {
+ const auto& object() const {
+ return static_cast<const Wrapper*>(this)->get();
+ }
+
+ public:
+ double daysNumber() const { return object().daysNumber(); }
+
+ JS::Handle<JS::BigInt*> days() const {
+ return JS::Handle<JS::BigInt*>::fromMarkedLocation(&object().days);
+ }
+
+ int64_t daysInt() const { return object().daysInt; }
+
+ temporal::InstantSpan nanoseconds() const { return object().nanoseconds; }
+
+ temporal::InstantSpan dayLength() const { return object().dayLength; }
+};
+
+} /* namespace js */
+
+#endif /* builtin_temporal_ZonedDateTime_h */
diff --git a/js/src/builtin/temporal/moz.build b/js/src/builtin/temporal/moz.build
new file mode 100644
index 0000000000..ae3bb618ad
--- /dev/null
+++ b/js/src/builtin/temporal/moz.build
@@ -0,0 +1,34 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+FINAL_LIBRARY = "js"
+
+# Includes should be relative to parent path
+LOCAL_INCLUDES += ["!../..", "../.."]
+
+include("../../js-config.mozbuild")
+include("../../js-cxxflags.mozbuild")
+
+if CONFIG["JS_HAS_TEMPORAL_API"]:
+ UNIFIED_SOURCES += [
+ "Calendar.cpp",
+ "Duration.cpp",
+ "Instant.cpp",
+ "Int96.cpp",
+ "PlainDate.cpp",
+ "PlainDateTime.cpp",
+ "PlainMonthDay.cpp",
+ "PlainTime.cpp",
+ "PlainYearMonth.cpp",
+ "Temporal.cpp",
+ "TemporalFields.cpp",
+ "TemporalNow.cpp",
+ "TemporalParser.cpp",
+ "TimeZone.cpp",
+ "ToString.cpp",
+ "Wrapped.cpp",
+ "ZonedDateTime.cpp",
+ ]