mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-04-19 02:10:26 +00:00
476 lines
23 KiB
C++
476 lines
23 KiB
C++
/*
|
||
* 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));
|
||
}
|
||
|
||
}
|