1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 02:07:36 +00:00

LibJS: Correct PlainYearMonth arithmetic for non-ISO calendars

This is a normative change in the Temporal spec.

See: 61e8dd0
This commit is contained in:
Linus Groh 2022-03-31 00:48:57 +01:00
parent b020b8eea2
commit cfb04765fa
7 changed files with 48 additions and 36 deletions

View file

@ -457,7 +457,7 @@ ThrowCompletionOr<Object*> get_temporal_calendar_with_iso_default(GlobalObject&
}
// 12.1.24 DateFromFields ( calendar, fields, options ), https://tc39.es/proposal-temporal/#sec-temporal-datefromfields
ThrowCompletionOr<PlainDate*> date_from_fields(GlobalObject& global_object, Object& calendar, Object const& fields, Object const& options)
ThrowCompletionOr<PlainDate*> date_from_fields(GlobalObject& global_object, Object& calendar, Object const& fields, Object const* options)
{
auto& vm = global_object.vm();
@ -465,7 +465,7 @@ ThrowCompletionOr<PlainDate*> date_from_fields(GlobalObject& global_object, Obje
// 2. Assert: Type(fields) is Object.
// 3. Let date be ? Invoke(calendar, "dateFromFields", « fields, options »).
auto date = TRY(Value(&calendar).invoke(global_object, vm.names.dateFromFields, &fields, &options));
auto date = TRY(Value(&calendar).invoke(global_object, vm.names.dateFromFields, &fields, options ?: js_undefined()));
// 4. Perform ? RequireInternalSlot(date, [[InitializedTemporalDate]]).
auto* date_object = TRY(date.to_object(global_object));

View file

@ -55,7 +55,7 @@ ThrowCompletionOr<Value> calendar_era_year(GlobalObject&, Object& calendar, Obje
ThrowCompletionOr<Object*> to_temporal_calendar(GlobalObject&, Value);
ThrowCompletionOr<Object*> to_temporal_calendar_with_iso_default(GlobalObject&, Value);
ThrowCompletionOr<Object*> get_temporal_calendar_with_iso_default(GlobalObject&, Object&);
ThrowCompletionOr<PlainDate*> date_from_fields(GlobalObject&, Object& calendar, Object const& fields, Object const& options);
ThrowCompletionOr<PlainDate*> date_from_fields(GlobalObject&, Object& calendar, Object const& fields, Object const* options = nullptr);
ThrowCompletionOr<PlainYearMonth*> year_month_from_fields(GlobalObject&, Object& calendar, Object const& fields, Object const* options = nullptr);
ThrowCompletionOr<PlainMonthDay*> month_day_from_fields(GlobalObject& global_object, Object& calendar, Object const& fields, Object const* options = nullptr);
String format_calendar_annotation(StringView id, StringView show_calendar);

View file

@ -119,7 +119,7 @@ ThrowCompletionOr<PlainDate*> to_temporal_date(GlobalObject& global_object, Valu
auto* fields = TRY(prepare_temporal_fields(global_object, item_object, field_names, {}));
// g. Return ? DateFromFields(calendar, fields, options).
return date_from_fields(global_object, *calendar, *fields, *options);
return date_from_fields(global_object, *calendar, *fields, options);
}
// 4. Perform ? ToTemporalOverflow(options).

View file

@ -421,7 +421,7 @@ JS_DEFINE_NATIVE_FUNCTION(PlainDatePrototype::with)
fields = TRY(prepare_temporal_fields(global_object, *fields, field_names, {}));
// 12. Return ? DateFromFields(calendar, fields, options).
return TRY(date_from_fields(global_object, calendar, *fields, *options));
return TRY(date_from_fields(global_object, calendar, *fields, options));
}
// 3.3.22 Temporal.PlainDate.prototype.withCalendar ( calendarLike ), https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.withcalendar

View file

@ -111,7 +111,7 @@ ThrowCompletionOr<ISODateTime> interpret_temporal_date_time_fields(GlobalObject&
auto overflow = TRY(to_temporal_overflow(global_object, options));
// 3. Let temporalDate be ? DateFromFields(calendar, fields, options).
auto* temporal_date = TRY(date_from_fields(global_object, calendar, fields, options));
auto* temporal_date = TRY(date_from_fields(global_object, calendar, fields, &options));
// 4. Let timeResult be ? RegulateTime(timeResult.[[Hour]], timeResult.[[Minute]], timeResult.[[Second]], timeResult.[[Millisecond]], timeResult.[[Microsecond]], timeResult.[[Nanosecond]], overflow).
auto time_result = TRY(regulate_time(global_object, unregulated_time_result.hour, unregulated_time_result.minute, unregulated_time_result.second, unregulated_time_result.millisecond, unregulated_time_result.microsecond, unregulated_time_result.nanosecond, overflow));

View file

@ -252,7 +252,7 @@ JS_DEFINE_NATIVE_FUNCTION(PlainMonthDayPrototype::to_plain_date)
MUST(options->create_data_property_or_throw(vm.names.overflow, js_string(vm, vm.names.reject.as_string())));
// 14. Return ? DateFromFields(calendar, mergedFields, options).
return TRY(date_from_fields(global_object, calendar, *merged_fields, *options));
return TRY(date_from_fields(global_object, calendar, *merged_fields, options));
}
// 10.3.13 Temporal.PlainMonthDay.prototype.getISOFields ( ), https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.getisofields

View file

@ -258,12 +258,15 @@ JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::add)
// 7. Let fieldNames be ? CalendarFields(calendar, « "monthCode", "year" »).
auto field_names = TRY(calendar_fields(global_object, calendar, { "monthCode"sv, "year"sv }));
// 8. Let sign be ! DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], balanceResult.[[Days]], 0, 0, 0, 0, 0, 0).
// 8. Let fields be ? PrepareTemporalFields(yearMonth, fieldNames, «»).
auto* fields = TRY(prepare_temporal_fields(global_object, *year_month, field_names, {}));
// 9. Let sign be ! DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], balanceResult.[[Days]], 0, 0, 0, 0, 0, 0).
auto sign = duration_sign(duration.years, duration.months, duration.weeks, balance_result.days, 0, 0, 0, 0, 0, 0);
double day;
// 9. If sign < 0, then
// 10. If sign < 0, then
if (sign < 0) {
// a. Let dayFromCalendar be ? CalendarDaysInMonth(calendar, yearMonth).
auto day_from_calendar = TRY(calendar_days_in_month(global_object, calendar, *year_month));
@ -271,25 +274,28 @@ JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::add)
// b. Let day be ? ToPositiveInteger(dayFromCalendar).
day = TRY(to_positive_integer(global_object, day_from_calendar));
}
// 10. Else,
// 11. Else,
else {
// a. Let day be 1.
day = 1;
}
// 11. Let date be ? CreateTemporalDate(yearMonth.[[ISOYear]], yearMonth.[[ISOMonth]], day, calendar).
auto* date = TRY(create_temporal_date(global_object, year_month->iso_year(), year_month->iso_month(), day, calendar));
// 12. Perform ! CreateDataPropertyOrThrow(fields, "day", day).
MUST(fields->create_data_property_or_throw(vm.names.day, Value(day)));
// 12. Let durationToAdd be ! CreateTemporalDuration(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], balanceResult.[[Days]], 0, 0, 0, 0, 0, 0).
// 13. Let date be ? DateFromFields(calendar, fields, undefined).
auto* date = TRY(date_from_fields(global_object, calendar, *fields, nullptr));
// 14. Let durationToAdd be ! CreateTemporalDuration(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], balanceResult.[[Days]], 0, 0, 0, 0, 0, 0).
auto* duration_to_add = MUST(create_temporal_duration(global_object, duration.years, duration.months, duration.weeks, balance_result.days, 0, 0, 0, 0, 0, 0));
// 13. Let optionsCopy be OrdinaryObjectCreate(%Object.prototype%).
// 15. Let optionsCopy be OrdinaryObjectCreate(%Object.prototype%).
auto* options_copy = Object::create(global_object, global_object.object_prototype());
// 14. Let entries be ? EnumerableOwnPropertyNames(options, key+value).
// 16. Let entries be ? EnumerableOwnPropertyNames(options, key+value).
auto entries = TRY(options->enumerable_own_property_names(Object::PropertyKind::KeyAndValue));
// 15. For each element nextEntry of entries, do
// 17. For each element nextEntry of entries, do
for (auto& next_entry : entries) {
auto key = MUST(next_entry.as_array().get_without_side_effects(0).to_property_key(global_object));
auto value = next_entry.as_array().get_without_side_effects(1);
@ -298,13 +304,13 @@ JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::add)
MUST(options_copy->create_data_property_or_throw(key, value));
}
// 16. Let addedDate be ? CalendarDateAdd(calendar, date, durationToAdd, options).
// 18. Let addedDate be ? CalendarDateAdd(calendar, date, durationToAdd, options).
auto* added_date = TRY(calendar_date_add(global_object, calendar, date, *duration_to_add, options));
// 17. Let addedDateFields be ? PrepareTemporalFields(addedDate, fieldNames, «»).
// 19. Let addedDateFields be ? PrepareTemporalFields(addedDate, fieldNames, «»).
auto* added_date_fields = TRY(prepare_temporal_fields(global_object, *added_date, field_names, {}));
// 18. Return ? YearMonthFromFields(calendar, addedDateFields, optionsCopy).
// 20. Return ? YearMonthFromFields(calendar, addedDateFields, optionsCopy).
return TRY(year_month_from_fields(global_object, calendar, *added_date_fields, options_copy));
}
@ -337,12 +343,15 @@ JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::subtract)
// 8. Let fieldNames be ? CalendarFields(calendar, « "monthCode", "year" »).
auto field_names = TRY(calendar_fields(global_object, calendar, { "monthCode"sv, "year"sv }));
// 9. Let sign be ! DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], balanceResult.[[Days]], 0, 0, 0, 0, 0, 0).
// 9. Let fields be ? PrepareTemporalFields(yearMonth, fieldNames, «»).
auto* fields = TRY(prepare_temporal_fields(global_object, *year_month, field_names, {}));
// 10. Let sign be ! DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], balanceResult.[[Days]], 0, 0, 0, 0, 0, 0).
auto sign = duration_sign(duration->years(), duration->months(), duration->weeks(), balance_result.days, 0, 0, 0, 0, 0, 0);
double day;
// 10. If sign < 0, then
// 11. If sign < 0, then
if (sign < 0) {
// a. Let dayFromCalendar be ? CalendarDaysInMonth(calendar, yearMonth).
auto day_from_calendar = TRY(calendar_days_in_month(global_object, calendar, *year_month));
@ -350,25 +359,28 @@ JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::subtract)
// b. Let day be ? ToPositiveInteger(dayFromCalendar).
day = TRY(to_positive_integer(global_object, day_from_calendar));
}
// 11. Else,
// 12. Else,
else {
// a. Let day be 1.
day = 1;
}
// 12. Let date be ? CreateTemporalDate(yearMonth.[[ISOYear]], yearMonth.[[ISOMonth]], day, calendar).
auto* date = TRY(create_temporal_date(global_object, year_month->iso_year(), year_month->iso_month(), day, calendar));
// 13. Perform ! CreateDataPropertyOrThrow(fields, "day", day).
MUST(fields->create_data_property_or_throw(vm.names.day, Value(day)));
// 13. Let durationToAdd be ! CreateTemporalDuration(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], balanceResult.[[Days]], 0, 0, 0, 0, 0, 0).
// 14. Let date be ? DateFromFields(calendar, fields, undefined).
auto* date = TRY(date_from_fields(global_object, calendar, *fields, nullptr));
// 15. Let durationToAdd be ! CreateTemporalDuration(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], balanceResult.[[Days]], 0, 0, 0, 0, 0, 0).
auto* duration_to_add = MUST(create_temporal_duration(global_object, duration->years(), duration->months(), duration->weeks(), balance_result.days, 0, 0, 0, 0, 0, 0));
// 14. Let optionsCopy be OrdinaryObjectCreate(%Object.prototype%).
// 16. Let optionsCopy be OrdinaryObjectCreate(%Object.prototype%).
auto* options_copy = Object::create(global_object, global_object.object_prototype());
// 15. Let entries be ? EnumerableOwnPropertyNames(options, key+value).
// 17. Let entries be ? EnumerableOwnPropertyNames(options, key+value).
auto entries = TRY(options->enumerable_own_property_names(Object::PropertyKind::KeyAndValue));
// 16. For each element nextEntry of entries, do
// 18. For each element nextEntry of entries, do
for (auto& next_entry : entries) {
auto key = MUST(next_entry.as_array().get_without_side_effects(0).to_property_key(global_object));
auto value = next_entry.as_array().get_without_side_effects(1);
@ -377,13 +389,13 @@ JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::subtract)
MUST(options_copy->create_data_property_or_throw(key, value));
}
// 17. Let addedDate be ? CalendarDateAdd(calendar, date, durationToAdd, options).
// 19. Let addedDate be ? CalendarDateAdd(calendar, date, durationToAdd, options).
auto* added_date = TRY(calendar_date_add(global_object, calendar, date, *duration_to_add, options));
// 18. Let addedDateFields be ? PrepareTemporalFields(addedDate, fieldNames, «»).
// 20. Let addedDateFields be ? PrepareTemporalFields(addedDate, fieldNames, «»).
auto* added_date_fields = TRY(prepare_temporal_fields(global_object, *added_date, field_names, {}));
// 19. Return ? YearMonthFromFields(calendar, addedDateFields, optionsCopy).
// 21. Return ? YearMonthFromFields(calendar, addedDateFields, optionsCopy).
return TRY(year_month_from_fields(global_object, calendar, *added_date_fields, options_copy));
}
@ -436,7 +448,7 @@ JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::until)
// 16. Let otherDate be ? DateFromFields(calendar, otherFields).
// FIXME: Spec doesn't pass required options, see https://github.com/tc39/proposal-temporal/issues/1685.
auto* other_date = TRY(date_from_fields(global_object, calendar, *other_fields, *options));
auto* other_date = TRY(date_from_fields(global_object, calendar, *other_fields, options));
// 17. Let thisFields be ? PrepareTemporalFields(yearMonth, fieldNames, «»).
auto* this_fields = TRY(prepare_temporal_fields(global_object, *year_month, field_names, {}));
@ -446,7 +458,7 @@ JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::until)
// 19. Let thisDate be ? DateFromFields(calendar, thisFields).
// FIXME: Spec doesn't pass required options, see https://github.com/tc39/proposal-temporal/issues/1685.
auto* this_date = TRY(date_from_fields(global_object, calendar, *this_fields, *options));
auto* this_date = TRY(date_from_fields(global_object, calendar, *this_fields, options));
// 20. Let untilOptions be ? MergeLargestUnitOption(options, largestUnit).
auto* until_options = TRY(merge_largest_unit_option(global_object, *options, *largest_unit));
@ -519,7 +531,7 @@ JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::since)
// 17. Let otherDate be ? DateFromFields(calendar, otherFields).
// FIXME: Spec doesn't pass required options, see https://github.com/tc39/proposal-temporal/issues/1685.
auto* other_date = TRY(date_from_fields(global_object, calendar, *other_fields, *options));
auto* other_date = TRY(date_from_fields(global_object, calendar, *other_fields, options));
// 18. Let thisFields be ? PrepareTemporalFields(yearMonth, fieldNames, «»).
auto* this_fields = TRY(prepare_temporal_fields(global_object, *year_month, field_names, {}));
@ -529,7 +541,7 @@ JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::since)
// 20. Let thisDate be ? DateFromFields(calendar, thisFields).
// FIXME: Spec doesn't pass required options, see https://github.com/tc39/proposal-temporal/issues/1685.
auto* this_date = TRY(date_from_fields(global_object, calendar, *this_fields, *options));
auto* this_date = TRY(date_from_fields(global_object, calendar, *this_fields, options));
// 21. Let untilOptions be ? MergeLargestUnitOption(options, largestUnit).
auto* until_options = TRY(merge_largest_unit_option(global_object, *options, *largest_unit));
@ -677,7 +689,7 @@ JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::to_plain_date)
MUST(options->create_data_property_or_throw(vm.names.overflow, js_string(vm, vm.names.reject.as_string())));
// 14. Return ? DateFromFields(calendar, mergedFields, options).
return TRY(date_from_fields(global_object, calendar, *merged_fields, *options));
return TRY(date_from_fields(global_object, calendar, *merged_fields, options));
}
// 9.3.22 Temporal.PlainYearMonth.prototype.getISOFields ( ), https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.getisofields