ladybird/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp
Timothy Flynn a41f2f56a8 LibJS+LibUnicode: Migrate some Temporal calendar types to LibUnicode
These will be needed for calendar operations involving ICU.
2026-03-09 11:40:59 +01:00

476 lines
23 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2024, Shannon Booth <shannon@serenityos.org>
* Copyright (c) 2024-2026, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Checked.h>
#include <AK/NumericLimits.h>
#include <LibJS/Runtime/AbstractOperations.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/Temporal/DateEquations.h>
#include <LibJS/Runtime/Temporal/Duration.h>
#include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainDateConstructor.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/PlainTime.h>
#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
namespace JS::Temporal {
GC_DEFINE_ALLOCATOR(PlainDate);
// 3 Temporal.PlainDate Objects, https://tc39.es/proposal-temporal/#sec-temporal-plaindate-objects
PlainDate::PlainDate(ISODate iso_date, String calendar, Object& prototype)
: Object(ConstructWithPrototypeTag::Tag, prototype)
, m_iso_date(iso_date)
, m_calendar(move(calendar))
{
}
// 3.5.2 CreateISODateRecord ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-create-iso-date-record
ISODate create_iso_date_record(double year, double month, double day)
{
// 1. Assert: IsValidISODate(year, month, day) is true.
VERIFY(is_valid_iso_date(year, month, day));
// 2. Return ISO Date Record { [[Year]]: year, [[Month]]: month, [[Day]]: day }.
return { .year = static_cast<i32>(year), .month = static_cast<u8>(month), .day = static_cast<u8>(day) };
}
// 3.5.3 CreateTemporalDate ( isoDate, calendar [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporaldate
ThrowCompletionOr<GC::Ref<PlainDate>> create_temporal_date(VM& vm, ISODate iso_date, String calendar, GC::Ptr<FunctionObject> new_target)
{
auto& realm = *vm.current_realm();
// 1. If ISODateWithinLimits(isoDate) is false, throw a RangeError exception.
if (!iso_date_within_limits(iso_date))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidPlainDate);
// 2. If newTarget is not present, set newTarget to %Temporal.PlainDate%.
if (!new_target)
new_target = realm.intrinsics().temporal_plain_date_constructor();
// 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainDate.prototype%", « [[InitializedTemporalDate]], [[ISODate]], [[Calendar]] »).
// 4. Set object.[[ISODate]] to isoDate.
// 5. Set object.[[Calendar]] to calendar.
auto object = TRY(ordinary_create_from_constructor<PlainDate>(vm, *new_target, &Intrinsics::temporal_plain_date_prototype, iso_date, move(calendar)));
// 6. Return object.
return object;
}
// 3.5.4 ToTemporalDate ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaldate
ThrowCompletionOr<GC::Ref<PlainDate>> to_temporal_date(VM& vm, Value item, Value options)
{
// 1. If options is not present, set options to undefined.
// 2. If item is an Object, then
if (auto object = item.as_if<Object>()) {
// a. If item has an [[InitializedTemporalDate]] internal slot, then
if (auto const* plain_date = as_if<PlainDate>(*object)) {
// i. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));
// ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
TRY(get_temporal_overflow_option(vm, resolved_options));
// iii. Return ! CreateTemporalDate(item.[[ISODate]], item.[[Calendar]]).
return MUST(create_temporal_date(vm, plain_date->iso_date(), plain_date->calendar()));
}
// b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then
if (auto const* zoned_date_time = as_if<ZonedDateTime>(*object)) {
// i. Let isoDateTime be GetISODateTimeFor(item.[[TimeZone]], item.[[EpochNanoseconds]]).
auto iso_date_time = get_iso_date_time_for(zoned_date_time->time_zone(), zoned_date_time->epoch_nanoseconds()->big_integer());
// ii. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));
// iii. Perform ? GetTemporalOverflowOption(resolvedOptions).
TRY(get_temporal_overflow_option(vm, resolved_options));
// iv. Return ! CreateTemporalDate(isoDateTime.[[ISODate]], item.[[Calendar]]).
return MUST(create_temporal_date(vm, iso_date_time.iso_date, zoned_date_time->calendar()));
}
// c. If item has an [[InitializedTemporalDateTime]] internal slot, then
if (auto const* plain_date_time = as_if<PlainDateTime>(*object)) {
// i. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));
// ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
TRY(get_temporal_overflow_option(vm, resolved_options));
// iii. Return ! CreateTemporalDate(item.[[ISODateTime]].[[ISODate]], item.[[Calendar]]).
return MUST(create_temporal_date(vm, plain_date_time->iso_date_time().iso_date, plain_date_time->calendar()));
}
// d. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(item).
auto calendar = TRY(get_temporal_calendar_identifier_with_iso_default(vm, *object));
// e. Let fields be ? PrepareCalendarFields(calendar, item, « YEAR, MONTH, MONTH-CODE, DAY », «», «»).
auto fields = TRY(prepare_calendar_fields(vm, calendar, *object, { { CalendarField::Year, CalendarField::Month, CalendarField::MonthCode, CalendarField::Day } }, {}, CalendarFieldList {}));
// f. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));
// g. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
auto overflow = TRY(get_temporal_overflow_option(vm, resolved_options));
// h. Let isoDate be ? CalendarDateFromFields(calendar, fields, overflow).
auto iso_date = TRY(calendar_date_from_fields(vm, calendar, fields, overflow));
// i. Return ! CreateTemporalDate(isoDate, calendar).
return MUST(create_temporal_date(vm, iso_date, move(calendar)));
}
// 3. If item is not a String, throw a TypeError exception.
if (!item.is_string())
return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidPlainDate);
// 4. Let result be ? ParseISODateTime(item, « TemporalDateTimeString[~Zoned] »).
auto result = TRY(parse_iso_date_time(vm, item.as_string().utf8_string_view(), { { Production::TemporalDateTimeString } }));
// 5. Let calendar be result.[[Calendar]].
// 6. If calendar is empty, set calendar to "iso8601".
auto calendar = result.calendar.value_or("iso8601"_string);
// 7. Set calendar to ? CanonicalizeCalendar(calendar).
calendar = TRY(canonicalize_calendar(vm, calendar));
// 8. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));
// 9. Perform ? GetTemporalOverflowOption(resolvedOptions).
TRY(get_temporal_overflow_option(vm, resolved_options));
// 10. Let isoDate be CreateISODateRecord(result.[[Year]], result.[[Month]], result.[[Day]]).
auto iso_date = create_iso_date_record(*result.year, result.month, result.day);
// 11. Return ? CreateTemporalDate(isoDate, calendar).
return TRY(create_temporal_date(vm, iso_date, move(calendar)));
}
// 3.5.5 CompareSurpasses ( sign, year, monthOrCode, day, target ), https://tc39.es/proposal-temporal/#sec-temporal-comparesurpasses
bool compare_surpasses(i8 sign, i32 year, Variant<u8, String> const& month_or_code, u8 day, CalendarDate const& target)
{
// 1. If year ≠ target.[[Year]], then
if (year != target.year) {
// a. If sign × (year - target.[[Year]]) > 0, return true.
if (sign * (year - target.year) > 0)
return true;
}
// 2. Else if monthOrCode is a month code and monthOrCode is not target.[[MonthCode]], then
else if (auto const* month_code = month_or_code.get_pointer<String>(); month_code && *month_code != target.month_code) {
// a. If sign > 0, then
if (sign > 0) {
// i. If monthOrCode is lexicographically greater than target.[[MonthCode]], return true.
if (*month_code > target.month_code)
return true;
}
// b. Else,
else {
// i. If target.[[MonthCode]] is lexicographically greater than monthOrCode, return true.
if (target.month_code > *month_code)
return true;
}
}
// 3. Else if monthOrCode is an integer and monthOrCode ≠ target.[[Month]], then
else if (auto const* month = month_or_code.get_pointer<u8>(); month && *month != target.month) {
// a. If sign × (monthOrCode - target.[[Month]]) > 0, return true.
if (sign * (*month - target.month) > 0)
return true;
}
// 4. Else if day ≠ target.[[Day]], then
else if (day != target.day) {
// a. If sign × (day - target.[[Day]]) > 0, return true.
if (sign * (day - target.day) > 0)
return true;
}
// 5. Return false.
return false;
}
// 3.5.5 ISODateSurpasses ( sign, baseDate, isoDate2, years, months, weeks, days ), https://tc39.es/proposal-temporal/#sec-temporal-isodatesurpasses
bool iso_date_surpasses(VM& vm, i8 sign, ISODate base_date, ISODate iso_date2, double years, double months, double weeks, double days)
{
// 1. Let parts be CalendarISOToDate("iso8601", baseDate).
auto parts = calendar_iso_to_date(ISO8601_CALENDAR, base_date);
// 2. Let target be CalendarISOToDate("iso8601", isoDate2).
auto target = calendar_iso_to_date(ISO8601_CALENDAR, iso_date2);
// 3. Let y0 be parts.[[Year]] + years.
auto year0 = parts.year + years;
// 4. If CompareSurpasses(sign, y0, parts.[[MonthCode]], parts.[[Day]], target) is true, return true.
if (compare_surpasses(sign, year0, parts.month_code, parts.day, target))
return true;
// 5. If months = 0, return false.
if (months == 0)
return false;
// 6. Let m0 be parts.[[Month]] + months.
auto month0 = parts.month + months;
// 7. Let monthsAdded be BalanceISOYearMonth(y0, m0).
auto months_added = balance_iso_year_month(year0, month0);
// 8. If CompareSurpasses(sign, monthsAdded.[[Year]], monthsAdded.[[Month]], parts.[[Day]], target) is true, return true.
if (compare_surpasses(sign, months_added.year, months_added.month, parts.day, target))
return true;
// 9. If weeks = 0 and days = 0, return false.
if (weeks == 0 && days == 0)
return false;
// 10. Let regulatedDate be ! RegulateISODate(monthsAdded.[[Year]], monthsAdded.[[Month]], parts.[[Day]], CONSTRAIN).
auto regulated_date = MUST(regulate_iso_date(vm, months_added.year, months_added.month, parts.day, Overflow::Constrain));
// 11. Let daysInWeek be 7.
static constexpr auto days_in_week = 7.0;
// 12. Let balancedDate be AddDaysToISODate(regulatedDate, daysInWeek * weeks + days).
auto balanced_date = add_days_to_iso_date(regulated_date, (days_in_week * weeks) + days);
// 13. Return CompareSurpasses(sign, balancedDate.[[Year]], balancedDate.[[Month]], balancedDate.[[Day]], target).
return compare_surpasses(sign, balanced_date.year, balanced_date.month, balanced_date.day, target);
}
// 3.5.6 RegulateISODate ( year, month, day, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-regulateisodate
ThrowCompletionOr<ISODate> regulate_iso_date(VM& vm, double year, double month, double day, Overflow overflow)
{
switch (overflow) {
// 1. If overflow is CONSTRAIN, then
case Overflow::Constrain:
// a. Set month to the result of clamping month between 1 and 12.
month = clamp(month, 1, 12);
// b. Let daysInMonth be ISODaysInMonth(year, month).
// c. Set day to the result of clamping day between 1 and daysInMonth.
day = clamp(day, 1, iso_days_in_month(year, month));
// AD-HOC: We further clamp the year to the range allowed by ISODate.year, to ensure we do not overflow when we
// store the year as an integer.
using YearType = decltype(declval<ISODate>().year);
year = clamp(year, static_cast<double>(NumericLimits<YearType>::min()), static_cast<double>(NumericLimits<YearType>::max()));
break;
// 2. Else,
case Overflow::Reject:
// a. Assert: overflow is REJECT.
// b. If IsValidISODate(year, month, day) is false, throw a RangeError exception.
if (!is_valid_iso_date(year, month, day))
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidISODate);
break;
}
// 3. Return CreateISODateRecord(year, month, day).
return create_iso_date_record(year, month, day);
}
// 3.5.7 IsValidISODate ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidisodate
bool is_valid_iso_date(double year, double month, double day)
{
// AD-HOC: This is an optimization that allows us to treat these doubles as normal integers from this point onwards.
// This does not change the exposed behavior as the call to CreateISODateRecord will immediately check that
// these values are valid ISO values (years: [-271821, 275760], months: [1, 12], days: [1, 31]), all of
// which are subsets of this check.
if (!AK::is_within_range<i32>(year) || !AK::is_within_range<u8>(month) || !AK::is_within_range<u8>(day))
return false;
// 1. If month < 1 or month > 12, return false.
if (month < 1 || month > 12)
return false;
// 2. Let daysInMonth be ISODaysInMonth(year, month).
auto days_in_month = iso_days_in_month(year, month);
// 3. If day < 1 or day > daysInMonth, return false; else return true.
return day >= 1 && day <= days_in_month;
}
// 3.5.8 AddDaysToISODate ( isoDate, days ), https://tc39.es/proposal-temporal/#sec-temporal-adddaystoisodate
ISODate add_days_to_iso_date(ISODate iso_date, double days)
{
// 1. Let epochDays be ISODateToEpochDays(isoDate.[[Year]], isoDate.[[Month]] - 1, isoDate.[[Day]]) + days.
auto epoch_days = iso_date_to_epoch_days(iso_date.year, iso_date.month - 1, iso_date.day) + days;
// 2. Let ms be EpochDaysToEpochMs(epochDays, 0).
auto ms = epoch_days_to_epoch_ms(epoch_days, 0);
// 3. Return CreateISODateRecord(EpochTimeToEpochYear(ms), EpochTimeToMonthInYear(ms) + 1, EpochTimeToDate(ms)).
return create_iso_date_record(epoch_time_to_epoch_year(ms), epoch_time_to_month_in_year(ms) + 1.0, epoch_time_to_date(ms));
}
// 3.5.9 PadISOYear ( y ), https://tc39.es/proposal-temporal/#sec-temporal-padisoyear
String pad_iso_year(i32 year)
{
// 1. If y ≥ 0 and y ≤ 9999, return ToZeroPaddedDecimalString(y, 4).
if (year >= 0 && year <= 9999)
return MUST(String::formatted("{:04}", year));
// 2. If y > 0, let yearSign be "+"; else, let yearSign be "-".
auto year_sign = year > 0 ? '+' : '-';
// 3. Let year be ToZeroPaddedDecimalString(abs(y), 6).
// 4. Return the string-concatenation of yearSign and year.
return MUST(String::formatted("{}{:06}", year_sign, abs(year)));
}
// 3.5.10 TemporalDateToString ( temporalDate, showCalendar ), https://tc39.es/proposal-temporal/#sec-temporal-temporaldatetostring
String temporal_date_to_string(PlainDate const& temporal_date, ShowCalendar show_calendar)
{
// 1. Let year be PadISOYear(temporalDate.[[ISODate]].[[Year]]).
auto year = pad_iso_year(temporal_date.iso_date().year);
// 2. Let month be ToZeroPaddedDecimalString(temporalDate.[[ISODate]].[[Month]], 2).
auto month = temporal_date.iso_date().month;
// 3. Let day be ToZeroPaddedDecimalString(temporalDate.[[ISODate]].[[Day]], 2).
auto day = temporal_date.iso_date().day;
// 4. Let calendar be FormatCalendarAnnotation(temporalDate.[[Calendar]], showCalendar).
auto calendar = format_calendar_annotation(temporal_date.calendar(), show_calendar);
// 5. Return the string-concatenation of year, the code unit 0x002D (HYPHEN-MINUS), month, the code unit 0x002D (HYPHEN-MINUS), day, and calendar.
return MUST(String::formatted("{}-{:02}-{:02}{}", year, month, day, calendar));
}
// 3.5.11 ISODateWithinLimits ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isodatewithinlimits
bool iso_date_within_limits(ISODate iso_date)
{
// 1. Let isoDateTime be CombineISODateAndTimeRecord(isoDate, NoonTimeRecord()).
auto iso_date_time = combine_iso_date_and_time_record(iso_date, noon_time_record());
// 2. Return ISODateTimeWithinLimits(isoDateTime).
return iso_date_time_within_limits(iso_date_time);
}
// 3.5.12 CompareISODate ( isoDate1, isoDate2 ), https://tc39.es/proposal-temporal/#sec-temporal-compareisodate
i8 compare_iso_date(ISODate iso_date1, ISODate iso_date2)
{
// 1. If isoDate1.[[Year]] > isoDate2.[[Year]], return 1.
if (iso_date1.year > iso_date2.year)
return 1;
// 2. If isoDate1.[[Year]] < isoDate2.[[Year]], return -1.
if (iso_date1.year < iso_date2.year)
return -1;
// 3. If isoDate1.[[Month]] > isoDate2.[[Month]], return 1.
if (iso_date1.month > iso_date2.month)
return 1;
// 4. If isoDate1.[[Month]] < isoDate2.[[Month]], return -1.
if (iso_date1.month < iso_date2.month)
return -1;
// 5. If isoDate1.[[Day]] > isoDate2.[[Day]], return 1.
if (iso_date1.day > iso_date2.day)
return 1;
// 6. If isoDate1.[[Day]] < isoDate2.[[Day]], return -1.
if (iso_date1.day < iso_date2.day)
return -1;
// 7. Return 0.
return 0;
}
// 3.5.13 DifferenceTemporalPlainDate ( operation, temporalDate, other, options ), https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalplaindate
ThrowCompletionOr<GC::Ref<Duration>> difference_temporal_plain_date(VM& vm, DurationOperation operation, PlainDate const& temporal_date, Value other_value, Value options)
{
// 1. Set other to ? ToTemporalDate(other).
auto other = TRY(to_temporal_date(vm, other_value));
// 2. If CalendarEquals(temporalDate.[[Calendar]], other.[[Calendar]]) is false, throw a RangeError exception.
if (!calendar_equals(temporal_date.calendar(), other->calendar()))
return vm.throw_completion<RangeError>(ErrorType::TemporalDifferentCalendars);
// 3. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));
// 4. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, DATE, « », DAY, DAY).
auto settings = TRY(get_difference_settings(vm, operation, resolved_options, UnitGroup::Date, {}, Unit::Day, Unit::Day));
// 5. If CompareISODate(temporalDate.[[ISODate]], other.[[ISODate]]) = 0, then
if (compare_iso_date(temporal_date.iso_date(), other->iso_date()) == 0) {
// a. Return ! CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0).
return MUST(create_temporal_duration(vm, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0));
}
// 6. Let dateDifference be CalendarDateUntil(temporalDate.[[Calendar]], temporalDate.[[ISODate]], other.[[ISODate]], settings.[[LargestUnit]]).
auto date_difference = calendar_date_until(vm, temporal_date.calendar(), temporal_date.iso_date(), other->iso_date(), settings.largest_unit);
// 7. Let duration be CombineDateAndTimeDuration(dateDifference, 0).
auto duration = combine_date_and_time_duration(date_difference, TimeDuration { 0 });
// 8. If settings.[[SmallestUnit]] is not DAY or settings.[[RoundingIncrement]] ≠ 1, then
if (settings.smallest_unit != Unit::Day || settings.rounding_increment != 1) {
// a. Let isoDateTime be CombineISODateAndTimeRecord(temporalDate.[[ISODate]], MidnightTimeRecord()).
auto iso_date_time = combine_iso_date_and_time_record(temporal_date.iso_date(), midnight_time_record());
// b. Let originEpochNs be GetUTCEpochNanoseconds(isoDateTime).
auto origin_epoch_ns = get_utc_epoch_nanoseconds(iso_date_time);
// c. Let isoDateTimeOther be CombineISODateAndTimeRecord(other.[[ISODate]], MidnightTimeRecord()).
auto iso_date_time_other = combine_iso_date_and_time_record(other->iso_date(), midnight_time_record());
// d. Let destEpochNs be GetUTCEpochNanoseconds(isoDateTimeOther).
auto dest_epoch_ns = get_utc_epoch_nanoseconds(iso_date_time_other);
// e. Set duration to ? RoundRelativeDuration(duration, originEpochNs, destEpochNs, isoDateTime, UNSET, temporalDate.[[Calendar]], settings.[[LargestUnit]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]).
duration = TRY(round_relative_duration(vm, move(duration), origin_epoch_ns, dest_epoch_ns, iso_date_time, {}, temporal_date.calendar(), settings.largest_unit, settings.rounding_increment, settings.smallest_unit, settings.rounding_mode));
}
// 9. Let result be ! TemporalDurationFromInternal(duration, DAY).
auto result = MUST(temporal_duration_from_internal(vm, duration, Unit::Day));
// 10. If operation is since, set result to CreateNegatedTemporalDuration(result).
if (operation == DurationOperation::Since)
result = create_negated_temporal_duration(vm, result);
// 11. Return result.
return result;
}
// 3.5.14 AddDurationToDate ( operation, temporalDate, temporalDurationLike, options ), https://tc39.es/proposal-temporal/#sec-temporal-adddurationtodate
ThrowCompletionOr<GC::Ref<PlainDate>> add_duration_to_date(VM& vm, ArithmeticOperation operation, PlainDate const& temporal_date, Value temporal_duration_like, Value options)
{
// 1. Let calendar be temporalDate.[[Calendar]].
auto const& calendar = temporal_date.calendar();
// 2. Let duration be ? ToTemporalDuration(temporalDurationLike).
auto duration = TRY(to_temporal_duration(vm, temporal_duration_like));
// 3. If operation is SUBTRACT, set duration to CreateNegatedTemporalDuration(duration).
if (operation == ArithmeticOperation::Subtract)
duration = create_negated_temporal_duration(vm, duration);
// 4. Let dateDuration be ToDateDurationRecordWithoutTime(duration).
auto date_duration = to_date_duration_record_without_time(vm, duration);
// 5. Let resolvedOptions be ? GetOptionsObject(options).
auto resolved_options = TRY(get_options_object(vm, options));
// 6. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
auto overflow = TRY(get_temporal_overflow_option(vm, resolved_options));
// 7. Let result be ? CalendarDateAdd(calendar, temporalDate.[[ISODate]], dateDuration, overflow).
auto result = TRY(calendar_date_add(vm, calendar, temporal_date.iso_date(), date_duration, overflow));
// 8. Return ! CreateTemporalDate(result, calendar).
return MUST(create_temporal_date(vm, result, calendar));
}
}