diff --git a/Libraries/LibJS/Runtime/Temporal/Duration.cpp b/Libraries/LibJS/Runtime/Temporal/Duration.cpp index 03b35d95fff..1a59e9d2e92 100644 --- a/Libraries/LibJS/Runtime/Temporal/Duration.cpp +++ b/Libraries/LibJS/Runtime/Temporal/Duration.cpp @@ -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 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> temporal_duration_from_internal(VM& vm, InternalDuration const& internal_duration, Unit largest_unit) { diff --git a/Libraries/LibJS/Runtime/Temporal/Duration.h b/Libraries/LibJS/Runtime/Temporal/Duration.h index ab3696e08aa..70697331d9a 100644 --- a/Libraries/LibJS/Runtime/Temporal/Duration.h +++ b/Libraries/LibJS/Runtime/Temporal/Duration.h @@ -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 to_date_duration_record_without_time(VM&, Duration const&); ThrowCompletionOr> temporal_duration_from_internal(VM&, InternalDuration const&, Unit largest_unit); ThrowCompletionOr create_date_duration_record(VM&, double years, double months, double weeks, double days); ThrowCompletionOr adjust_date_duration_record(VM&, DateDuration const&, double days, Optional weeks = {}, Optional months = {}); diff --git a/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp b/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp index 292089d9175..2e425df1418 100644 --- a/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp +++ b/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp @@ -270,4 +270,73 @@ ThrowCompletionOr> difference_temporal_plain_year_month(VM& vm return result; } +// 9.5.8 AddDurationToYearMonth ( operation, yearMonth, temporalDurationLike, options ) +ThrowCompletionOr> 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)); +} + } diff --git a/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h b/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h index 0e51648e4d9..a505a33868d 100644 --- a/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h +++ b/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h @@ -44,5 +44,6 @@ ISOYearMonth balance_iso_year_month(double year, double month); ThrowCompletionOr> create_temporal_year_month(VM&, ISODate, String calendar, GC::Ptr new_target = {}); String temporal_year_month_to_string(PlainYearMonth const&, ShowCalendar); ThrowCompletionOr> difference_temporal_plain_year_month(VM&, DurationOperation, PlainYearMonth const&, Value other, Value options); +ThrowCompletionOr> add_duration_to_year_month(VM&, ArithmeticOperation, PlainYearMonth const&, Value temporal_duration_like, Value options); } diff --git a/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.cpp b/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.cpp index 893186b3869..bdbdb8a9966 100644 --- a/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.cpp +++ b/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.cpp @@ -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) { diff --git a/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.h b/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.h index 8f8215fe47d..2696ec31438 100644 --- a/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.h +++ b/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.h @@ -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); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.add.js b/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.add.js new file mode 100644 index 00000000000..27f7396efb0 --- /dev/null +++ b/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.add.js @@ -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"); + }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.subtract.js b/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.subtract.js new file mode 100644 index 00000000000..1c04a95cca4 --- /dev/null +++ b/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.subtract.js @@ -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"); + }); +});