LibJS: Implement Temporal.PlainYearMonth.prototype.add/subtract

This commit is contained in:
Timothy Flynn 2024-11-21 19:09:21 -05:00 committed by Andreas Kling
parent cb5d1b5086
commit 35f22dcf79
Notes: github-actions[bot] 2024-11-22 18:56:24 +00:00
8 changed files with 154 additions and 0 deletions

View file

@ -108,6 +108,19 @@ InternalDuration to_internal_duration_record_with_24_hour_days(VM& vm, Duration
return MUST(combine_date_and_time_duration(vm, date_duration, move(time_duration)));
}
// 7.5.7 ToDateDurationRecordWithoutTime ( duration ), https://tc39.es/proposal-temporal/#sec-temporal-todatedurationrecordwithouttime
ThrowCompletionOr<DateDuration> to_date_duration_record_without_time(VM& vm, Duration const& duration)
{
// 1. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration).
auto internal_duration = to_internal_duration_record_with_24_hour_days(vm, duration);
// 2. Let days be truncate(internalDuration.[[Time]] / nsPerDay).
auto days = internal_duration.time.divided_by(NANOSECONDS_PER_DAY).quotient;
// 3. Return ? CreateDateDurationRecord(internalDuration.[[Date]].[[Years]], internalDuration.[[Date]].[[Months]], internalDuration.[[Date]].[[Weeks]], days).
return TRY(create_date_duration_record(vm, duration.years(), duration.months(), duration.weeks(), days.to_double()));
}
// 7.5.8 TemporalDurationFromInternal ( internalDuration, largestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-temporaldurationfrominternal
ThrowCompletionOr<GC::Ref<Duration>> temporal_duration_from_internal(VM& vm, InternalDuration const& internal_duration, Unit largest_unit)
{

View file

@ -116,6 +116,7 @@ struct CalendarNudgeResult {
DateDuration zero_date_duration(VM&);
InternalDuration to_internal_duration_record(VM&, Duration const&);
InternalDuration to_internal_duration_record_with_24_hour_days(VM&, Duration const&);
ThrowCompletionOr<DateDuration> to_date_duration_record_without_time(VM&, Duration const&);
ThrowCompletionOr<GC::Ref<Duration>> temporal_duration_from_internal(VM&, InternalDuration const&, Unit largest_unit);
ThrowCompletionOr<DateDuration> create_date_duration_record(VM&, double years, double months, double weeks, double days);
ThrowCompletionOr<DateDuration> adjust_date_duration_record(VM&, DateDuration const&, double days, Optional<double> weeks = {}, Optional<double> months = {});

View file

@ -270,4 +270,73 @@ ThrowCompletionOr<GC::Ref<Duration>> difference_temporal_plain_year_month(VM& vm
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, move(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, move(added_date_fields), overflow));
// 16. Return ! CreateTemporalYearMonth(isoDate, calendar).
return MUST(create_temporal_year_month(vm, iso_date, calendar));
}
}

View file

@ -44,5 +44,6 @@ ISOYearMonth balance_iso_year_month(double year, double month);
ThrowCompletionOr<GC::Ref<PlainYearMonth>> create_temporal_year_month(VM&, ISODate, String calendar, GC::Ptr<FunctionObject> new_target = {});
String temporal_year_month_to_string(PlainYearMonth const&, ShowCalendar);
ThrowCompletionOr<GC::Ref<Duration>> difference_temporal_plain_year_month(VM&, DurationOperation, PlainYearMonth const&, Value other, Value options);
ThrowCompletionOr<GC::Ref<PlainYearMonth>> add_duration_to_year_month(VM&, ArithmeticOperation, PlainYearMonth const&, Value temporal_duration_like, Value options);
}

View file

@ -42,6 +42,8 @@ void PlainYearMonthPrototype::initialize(Realm& realm)
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(realm, vm.names.with, with, 1, attr);
define_native_function(realm, vm.names.add, add, 1, attr);
define_native_function(realm, vm.names.subtract, subtract, 1, attr);
define_native_function(realm, vm.names.until, until, 1, attr);
define_native_function(realm, vm.names.since, since, 1, attr);
define_native_function(realm, vm.names.equals, equals, 1, attr);
@ -173,6 +175,34 @@ JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::with)
return MUST(create_temporal_year_month(vm, iso_date, calendar));
}
// 9.3.14 Temporal.PlainYearMonth.prototype.add ( temporalDurationLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.add
JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::add)
{
auto temporal_duration_like = vm.argument(0);
auto options = vm.argument(1);
// 1. Let yearMonth be the this value.
// 2. Perform ? RequireInternalSlot(yearMonth, [[InitializedTemporalYearMonth]]).
auto year_month = TRY(typed_this_object(vm));
// 3. Return ? AddDurationToYearMonth(ADD, yearMonth, temporalDurationLike, options).
return TRY(add_duration_to_year_month(vm, ArithmeticOperation::Add, year_month, temporal_duration_like, options));
}
// 9.3.15 Temporal.PlainYearMonth.prototype.subtract ( temporalDurationLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.subtract
JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::subtract)
{
auto temporal_duration_like = vm.argument(0);
auto options = vm.argument(1);
// 1. Let yearMonth be the this value.
// 2. Perform ? RequireInternalSlot(yearMonth, [[InitializedTemporalYearMonth]]).
auto year_month = TRY(typed_this_object(vm));
// 3. Return ? AddDurationToYearMonth(SUBTRACT, yearMonth, temporalDurationLike, options).
return TRY(add_duration_to_year_month(vm, ArithmeticOperation::Subtract, year_month, temporal_duration_like, options));
}
// 9.3.16 Temporal.PlainYearMonth.prototype.until ( other [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.until
JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::until)
{

View file

@ -34,6 +34,8 @@ private:
JS_DECLARE_NATIVE_FUNCTION(months_in_year_getter);
JS_DECLARE_NATIVE_FUNCTION(in_leap_year_getter);
JS_DECLARE_NATIVE_FUNCTION(with);
JS_DECLARE_NATIVE_FUNCTION(add);
JS_DECLARE_NATIVE_FUNCTION(subtract);
JS_DECLARE_NATIVE_FUNCTION(until);
JS_DECLARE_NATIVE_FUNCTION(since);
JS_DECLARE_NATIVE_FUNCTION(equals);

View file

@ -0,0 +1,19 @@
describe("correct behavior", () => {
test("length is 1", () => {
expect(Temporal.PlainYearMonth.prototype.add).toHaveLength(1);
});
test("basic functionality", () => {
const plainYearMonth = new Temporal.PlainYearMonth(1970, 1);
const result = plainYearMonth.add(new Temporal.Duration(51, 6));
expect(result.equals(new Temporal.PlainYearMonth(2021, 7))).toBeTrue();
});
});
describe("errors", () => {
test("this value must be a Temporal.PlainYearMonth object", () => {
expect(() => {
Temporal.PlainYearMonth.prototype.add.call("foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainYearMonth");
});
});

View file

@ -0,0 +1,19 @@
describe("correct behavior", () => {
test("length is 1", () => {
expect(Temporal.PlainYearMonth.prototype.subtract).toHaveLength(1);
});
test("basic functionality", () => {
const plainYearMonth = new Temporal.PlainYearMonth(2021, 7);
const result = plainYearMonth.subtract(new Temporal.Duration(51, 6));
expect(result.equals(new Temporal.PlainYearMonth(1970, 1))).toBeTrue();
});
});
describe("errors", () => {
test("this value must be a Temporal.PlainYearMonth object", () => {
expect(() => {
Temporal.PlainYearMonth.prototype.subtract.call("foo");
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.PlainYearMonth");
});
});