
These fields are modified by an invocation to CalendarResolveFields. All callers currently don't care about these modifications, so they move the CalendarFields struct into the AO. But it turns out that at least one AO will care (InterpretTemporalDateTimeFields), thus we will need to take by reference here.
342 lines
17 KiB
C++
342 lines
17 KiB
C++
/*
|
|
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
|
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <LibJS/Runtime/AbstractOperations.h>
|
|
#include <LibJS/Runtime/Intrinsics.h>
|
|
#include <LibJS/Runtime/Realm.h>
|
|
#include <LibJS/Runtime/Temporal/Calendar.h>
|
|
#include <LibJS/Runtime/Temporal/Duration.h>
|
|
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
|
|
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
|
#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
|
|
#include <LibJS/Runtime/Temporal/PlainYearMonthConstructor.h>
|
|
#include <LibJS/Runtime/VM.h>
|
|
|
|
namespace JS::Temporal {
|
|
|
|
GC_DEFINE_ALLOCATOR(PlainYearMonth);
|
|
|
|
// 9 Temporal.PlainYearMonth Objects, https://tc39.es/proposal-temporal/#sec-temporal-plainyearmonth-objects
|
|
PlainYearMonth::PlainYearMonth(ISODate iso_date, String calendar, Object& prototype)
|
|
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
|
, m_iso_date(iso_date)
|
|
, m_calendar(move(calendar))
|
|
{
|
|
}
|
|
|
|
// 9.5.2 ToTemporalYearMonth ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalyearmonth
|
|
ThrowCompletionOr<GC::Ref<PlainYearMonth>> to_temporal_year_month(VM& vm, Value item, Value options)
|
|
{
|
|
// 1. If options is not present, set options to undefined.
|
|
|
|
// 2. If item is an Object, then
|
|
if (item.is_object()) {
|
|
auto const& object = item.as_object();
|
|
|
|
// a. If item has an [[InitializedTemporalYearMonth]] internal slot, then
|
|
if (is<PlainYearMonth>(object)) {
|
|
auto const& plain_year_month = static_cast<PlainYearMonth const&>(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 ! CreateTemporalYearMonth(item.[[ISODate]], item.[[Calendar]]).
|
|
return MUST(create_temporal_year_month(vm, plain_year_month.iso_date(), plain_year_month.calendar()));
|
|
}
|
|
|
|
// b. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(item).
|
|
auto calendar = TRY(get_temporal_calendar_identifier_with_iso_default(vm, object));
|
|
|
|
// c. Let fields be ? PrepareCalendarFields(calendar, item, « YEAR, MONTH, MONTH-CODE », «», «»).
|
|
auto fields = TRY(prepare_calendar_fields(vm, calendar, object, { { CalendarField::Year, CalendarField::Month, CalendarField::MonthCode } }, {}, CalendarFieldList {}));
|
|
|
|
// d. Let resolvedOptions be ? GetOptionsObject(options).
|
|
auto resolved_options = TRY(get_options_object(vm, options));
|
|
|
|
// e. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
|
|
auto overflow = TRY(get_temporal_overflow_option(vm, resolved_options));
|
|
|
|
// f. Let isoDate be ? CalendarYearMonthFromFields(calendar, fields, overflow).
|
|
auto iso_date = TRY(calendar_year_month_from_fields(vm, calendar, fields, overflow));
|
|
|
|
// g. Return ! CreateTemporalYearMonth(isoDate, calendar).
|
|
return MUST(create_temporal_year_month(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::TemporalInvalidPlainYearMonth);
|
|
|
|
// 4. Let result be ? ParseISODateTime(item, « TemporalYearMonthString »).
|
|
auto parse_result = TRY(parse_iso_date_time(vm, item.as_string().utf8_string_view(), { { Production::TemporalYearMonthString } }));
|
|
|
|
// 5. Let calendar be result.[[Calendar]].
|
|
// 6. If calendar is empty, set calendar to "iso8601".
|
|
auto calendar = parse_result.calendar.value_or("iso8601"_string);
|
|
|
|
// 7. Set calendar to ? CanonicalizeCalendar(calendar).
|
|
calendar = TRY(canonicalize_calendar(vm, calendar));
|
|
|
|
// 8. Let isoDate be CreateISODateRecord(result.[[Year]], result.[[Month]], result.[[Day]]).
|
|
auto iso_date = create_iso_date_record(*parse_result.year, parse_result.month, parse_result.day);
|
|
|
|
// 9. Set result to ISODateToFields(calendar, isoDate, YEAR-MONTH).
|
|
auto result = iso_date_to_fields(calendar, iso_date, DateType::YearMonth);
|
|
|
|
// 10. Let resolvedOptions be ? GetOptionsObject(options).
|
|
auto resolved_options = TRY(get_options_object(vm, options));
|
|
|
|
// 11. Perform ? GetTemporalOverflowOption(resolvedOptions).
|
|
TRY(get_temporal_overflow_option(vm, resolved_options));
|
|
|
|
// 12. NOTE: The following operation is called with CONSTRAIN regardless of the value of overflow, in order for the
|
|
// calendar to store a canonical value in the [[Day]] field of the [[ISODate]] internal slot of the result.
|
|
// 13. Set isoDate to ? CalendarYearMonthFromFields(calendar, result, CONSTRAIN).
|
|
iso_date = TRY(calendar_year_month_from_fields(vm, calendar, result, Overflow::Constrain));
|
|
|
|
// 14. Return ! CreateTemporalYearMonth(isoDate, calendar).
|
|
return MUST(create_temporal_year_month(vm, iso_date, move(calendar)));
|
|
}
|
|
|
|
// 9.5.3 ISOYearMonthWithinLimits ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isoyearmonthwithinlimits
|
|
bool iso_year_month_within_limits(ISODate iso_date)
|
|
{
|
|
// 1. If isoDate.[[Year]] < -271821 or isoDate.[[Year]] > 275760, then
|
|
if (iso_date.year < -271821 || iso_date.year > 275760) {
|
|
// a. Return false.
|
|
return false;
|
|
}
|
|
|
|
// 2. If isoDate.[[Year]] = -271821 and isoDate.[[Month]] < 4, then
|
|
if (iso_date.year == -271821 && iso_date.month < 4) {
|
|
// a. Return false.
|
|
return false;
|
|
}
|
|
|
|
// 3. If isoDate.[[Year]] = 275760 and isoDate.[[Month]] > 9, then
|
|
if (iso_date.year == 275760 && iso_date.month > 9) {
|
|
// a. Return false.
|
|
return false;
|
|
}
|
|
|
|
// 4. Return true.
|
|
return true;
|
|
}
|
|
|
|
// 9.5.4 BalanceISOYearMonth ( year, month ), https://tc39.es/proposal-temporal/#sec-temporal-balanceisoyearmonth
|
|
ISOYearMonth balance_iso_year_month(double year, double month)
|
|
{
|
|
// 1. Set year to year + floor((month - 1) / 12).
|
|
year += floor((month - 1.0) / 12.0);
|
|
|
|
// 2. Set month to ((month - 1) modulo 12) + 1.
|
|
month = modulo(month - 1, 12.0) + 1.0;
|
|
|
|
// 3. Return ISO Year-Month Record { [[Year]]: year, [[Month]]: month }.
|
|
return { .year = static_cast<i32>(year), .month = static_cast<u8>(month) };
|
|
}
|
|
|
|
// 9.5.5 CreateTemporalYearMonth ( isoDate, calendar [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalyearmonth
|
|
ThrowCompletionOr<GC::Ref<PlainYearMonth>> create_temporal_year_month(VM& vm, ISODate iso_date, String calendar, GC::Ptr<FunctionObject> new_target)
|
|
{
|
|
auto& realm = *vm.current_realm();
|
|
|
|
// 1. If ISOYearMonthWithinLimits(isoDate) is false, throw a RangeError exception.
|
|
if (!iso_year_month_within_limits(iso_date))
|
|
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidPlainYearMonth);
|
|
|
|
// 2. If newTarget is not present, set newTarget to %Temporal.PlainYearMonth%.
|
|
if (!new_target)
|
|
new_target = realm.intrinsics().temporal_plain_year_month_constructor();
|
|
|
|
// 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainYearMonth.prototype%", « [[InitializedTemporalYearMonth]], [[ISODate]], [[Calendar]] »).
|
|
// 4. Set object.[[ISODate]] to isoDate.
|
|
// 5. Set object.[[Calendar]] to calendar.
|
|
auto object = TRY(ordinary_create_from_constructor<PlainYearMonth>(vm, *new_target, &Intrinsics::temporal_plain_year_month_prototype, iso_date, move(calendar)));
|
|
|
|
// 6. Return object.
|
|
return object;
|
|
}
|
|
|
|
// 9.5.6 TemporalYearMonthToString ( yearMonth, showCalendar ), https://tc39.es/proposal-temporal/#sec-temporal-temporalyearmonthtostring
|
|
String temporal_year_month_to_string(PlainYearMonth const& year_month, ShowCalendar show_calendar)
|
|
{
|
|
// 1. Let year be PadISOYear(yearMonth.[[ISODate]].[[Year]]).
|
|
auto year = pad_iso_year(year_month.iso_date().year);
|
|
|
|
// 2. Let month be ToZeroPaddedDecimalString(yearMonth.[[ISODate]].[[Month]], 2).
|
|
// 3. Let result be the string-concatenation of year, the code unit 0x002D (HYPHEN-MINUS), and month.
|
|
auto result = MUST(String::formatted("{}-{:02}", year, year_month.iso_date().month));
|
|
|
|
// 4. If showCalendar is one of always or critical, or if yearMonth.[[Calendar]] is not "iso8601", then
|
|
if (show_calendar == ShowCalendar::Always || show_calendar == ShowCalendar::Critical || year_month.calendar() != "iso8601"sv) {
|
|
// a. Let day be ToZeroPaddedDecimalString(yearMonth.[[ISODate]].[[Day]], 2).
|
|
// b. Set result to the string-concatenation of result, the code unit 0x002D (HYPHEN-MINUS), and day.
|
|
result = MUST(String::formatted("{}-{:02}", result, year_month.iso_date().day));
|
|
}
|
|
|
|
// 5. Let calendarString be FormatCalendarAnnotation(yearMonth.[[Calendar]], showCalendar).
|
|
auto calendar_string = format_calendar_annotation(year_month.calendar(), show_calendar);
|
|
|
|
// 6. Set result to the string-concatenation of result and calendarString.
|
|
result = MUST(String::formatted("{}{}", result, calendar_string));
|
|
|
|
// 7. Return result.
|
|
return result;
|
|
}
|
|
|
|
// 9.5.7 DifferenceTemporalPlainYearMonth ( operation, yearMonth, other, options ), https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalplainyearmonth
|
|
ThrowCompletionOr<GC::Ref<Duration>> difference_temporal_plain_year_month(VM& vm, DurationOperation operation, PlainYearMonth const& year_month, Value other_value, Value options)
|
|
{
|
|
// 1. Set other to ? ToTemporalYearMonth(other).
|
|
auto other = TRY(to_temporal_year_month(vm, other_value));
|
|
|
|
// 2. Let calendar be yearMonth.[[Calendar]].
|
|
auto const& calendar = year_month.calendar();
|
|
|
|
// 3. If CalendarEquals(calendar, other.[[Calendar]]) is false, throw a RangeError exception.
|
|
if (!calendar_equals(calendar, other->calendar()))
|
|
return vm.throw_completion<RangeError>(ErrorType::TemporalDifferentCalendars);
|
|
|
|
// 4. Let resolvedOptions be ? GetOptionsObject(options).
|
|
auto resolved_options = TRY(get_options_object(vm, options));
|
|
|
|
// 5. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, DATE, « WEEK, DAY », MONTH, YEAR).
|
|
auto settings = TRY(get_difference_settings(vm, operation, resolved_options, UnitGroup::Date, { { Unit::Week, Unit::Day } }, Unit::Month, Unit::Year));
|
|
|
|
// 6. If CompareISODate(yearMonth.[[ISODate]], other.[[ISODate]]) = 0, then
|
|
if (compare_iso_date(year_month.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));
|
|
}
|
|
|
|
// 7. Let thisFields be ISODateToFields(calendar, yearMonth.[[ISODate]], YEAR-MONTH).
|
|
auto this_fields = iso_date_to_fields(calendar, year_month.iso_date(), DateType::YearMonth);
|
|
|
|
// 8. Set thisFields.[[Day]] to 1.
|
|
this_fields.day = 1;
|
|
|
|
// 9. Let thisDate be ? CalendarDateFromFields(calendar, thisFields, CONSTRAIN).
|
|
auto this_date = TRY(calendar_date_from_fields(vm, calendar, this_fields, Overflow::Constrain));
|
|
|
|
// 10. Let otherFields be ISODateToFields(calendar, other.[[ISODate]], YEAR-MONTH).
|
|
auto other_fields = iso_date_to_fields(calendar, other->iso_date(), DateType::YearMonth);
|
|
|
|
// 11. Set otherFields.[[Day]] to 1.
|
|
other_fields.day = 1;
|
|
|
|
// 12. Let otherDate be ? CalendarDateFromFields(calendar, otherFields, CONSTRAIN).
|
|
auto other_date = TRY(calendar_date_from_fields(vm, calendar, other_fields, Overflow::Constrain));
|
|
|
|
// 13. Let dateDifference be CalendarDateUntil(calendar, thisDate, otherDate, settings.[[LargestUnit]]).
|
|
auto date_difference = calendar_date_until(vm, calendar, this_date, other_date, settings.largest_unit);
|
|
|
|
// 14. Let yearsMonthsDifference be ! AdjustDateDurationRecord(dateDifference, 0, 0).
|
|
auto years_months_difference = MUST(adjust_date_duration_record(vm, date_difference, 0, 0));
|
|
|
|
// 15. Let duration be ! CombineDateAndTimeDuration(yearsMonthsDifference, 0).
|
|
auto duration = MUST(combine_date_and_time_duration(vm, years_months_difference, TimeDuration { 0 }));
|
|
|
|
// 16. If settings.[[SmallestUnit]] is not MONTH or settings.[[RoundingIncrement]] ≠ 1, then
|
|
if (settings.smallest_unit != Unit::Month || settings.rounding_increment != 1) {
|
|
// a. Let isoDateTime be CombineISODateAndTimeRecord(thisDate, MidnightTimeRecord()).
|
|
auto iso_date_time = combine_iso_date_and_time_record(this_date, midnight_time_record());
|
|
|
|
// b. Let isoDateTimeOther be CombineISODateAndTimeRecord(otherDate, MidnightTimeRecord()).
|
|
auto iso_date_time_other = combine_iso_date_and_time_record(other_date, midnight_time_record());
|
|
|
|
// c. Let destEpochNs be GetUTCEpochNanoseconds(isoDateTimeOther).
|
|
auto dest_epoch_ns = get_utc_epoch_nanoseconds(iso_date_time_other);
|
|
|
|
// d. Set duration to ? RoundRelativeDuration(duration, destEpochNs, isoDateTime, UNSET, calendar, settings.[[LargestUnit]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]).
|
|
duration = TRY(round_relative_duration(vm, move(duration), dest_epoch_ns, iso_date_time, {}, calendar, settings.largest_unit, settings.rounding_increment, settings.smallest_unit, settings.rounding_mode));
|
|
}
|
|
|
|
// 17. Let result be ? TemporalDurationFromInternal(duration, DAY).
|
|
auto result = TRY(temporal_duration_from_internal(vm, duration, Unit::Day));
|
|
|
|
// 18. If operation is SINCE, set result to CreateNegatedTemporalDuration(result).
|
|
if (operation == DurationOperation::Since)
|
|
result = create_negated_temporal_duration(vm, result);
|
|
|
|
// 19. Return result.
|
|
return result;
|
|
}
|
|
|
|
// 9.5.8 AddDurationToYearMonth ( operation, yearMonth, temporalDurationLike, options )
|
|
ThrowCompletionOr<GC::Ref<PlainYearMonth>> add_duration_to_year_month(VM& vm, ArithmeticOperation operation, PlainYearMonth const& year_month, Value temporal_duration_like, Value options)
|
|
{
|
|
// 1. Let duration be ? ToTemporalDuration(temporalDurationLike).
|
|
auto duration = TRY(to_temporal_duration(vm, temporal_duration_like));
|
|
|
|
// 2. If operation is SUBTRACT, set duration to CreateNegatedTemporalDuration(duration).
|
|
if (operation == ArithmeticOperation::Subtract)
|
|
duration = create_negated_temporal_duration(vm, duration);
|
|
|
|
// 3. Let resolvedOptions be ? GetOptionsObject(options).
|
|
auto resolved_options = TRY(get_options_object(vm, options));
|
|
|
|
// 4. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
|
|
auto overflow = TRY(get_temporal_overflow_option(vm, resolved_options));
|
|
|
|
// 5. Let sign be DurationSign(duration).
|
|
auto sign = duration_sign(duration);
|
|
|
|
// 6. Let calendar be yearMonth.[[Calendar]].
|
|
auto const& calendar = year_month.calendar();
|
|
|
|
// 7. Let fields be ISODateToFields(calendar, yearMonth.[[ISODate]], YEAR-MONTH).
|
|
auto fields = iso_date_to_fields(calendar, year_month.iso_date(), DateType::YearMonth);
|
|
|
|
// 8. Set fields.[[Day]] to 1.
|
|
fields.day = 1;
|
|
|
|
// 9. Let intermediateDate be ? CalendarDateFromFields(calendar, fields, CONSTRAIN).
|
|
auto intermediate_date = TRY(calendar_date_from_fields(vm, calendar, fields, Overflow::Constrain));
|
|
|
|
ISODate date;
|
|
|
|
// 10. If sign < 0, then
|
|
if (sign < 0) {
|
|
// a. Let oneMonthDuration be ! CreateDateDurationRecord(0, 1, 0, 0).
|
|
auto one_month_duration = MUST(create_date_duration_record(vm, 0, 1, 0, 0));
|
|
|
|
// b. Let nextMonth be ? CalendarDateAdd(calendar, intermediateDate, oneMonthDuration, CONSTRAIN).
|
|
auto next_month = TRY(calendar_date_add(vm, calendar, intermediate_date, one_month_duration, Overflow::Constrain));
|
|
|
|
// c. Let date be BalanceISODate(nextMonth.[[Year]], nextMonth.[[Month]], nextMonth.[[Day]] - 1).
|
|
date = balance_iso_date(next_month.year, next_month.month, next_month.day - 1);
|
|
|
|
// d. Assert: ISODateWithinLimits(date) is true.
|
|
VERIFY(iso_date_within_limits(date));
|
|
}
|
|
// 11. Else,
|
|
else {
|
|
// a. Let date be intermediateDate.
|
|
date = intermediate_date;
|
|
}
|
|
|
|
// 12. Let durationToAdd be ? ToDateDurationRecordWithoutTime(duration).
|
|
auto duration_to_add = TRY(to_date_duration_record_without_time(vm, duration));
|
|
|
|
// 13. Let addedDate be ? CalendarDateAdd(calendar, date, durationToAdd, overflow).
|
|
auto added_date = TRY(calendar_date_add(vm, calendar, date, duration_to_add, overflow));
|
|
|
|
// 14. Let addedDateFields be ISODateToFields(calendar, addedDate, YEAR-MONTH).
|
|
auto added_date_fields = iso_date_to_fields(calendar, added_date, DateType::YearMonth);
|
|
|
|
// 15. Let isoDate be ? CalendarYearMonthFromFields(calendar, addedDateFields, overflow).
|
|
auto iso_date = TRY(calendar_year_month_from_fields(vm, calendar, added_date_fields, overflow));
|
|
|
|
// 16. Return ! CreateTemporalYearMonth(isoDate, calendar).
|
|
return MUST(create_temporal_year_month(vm, iso_date, calendar));
|
|
}
|
|
|
|
}
|