From acbcd64cdc9e1957b9a28d789aaa3e082b1dec6c Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Sat, 27 Nov 2021 17:02:51 +0000 Subject: [PATCH] LibJS: Implement Temporal.PlainYearMonth.prototype.add() --- .../Temporal/PlainYearMonthPrototype.cpp | 75 +++++++++++++++++++ .../Temporal/PlainYearMonthPrototype.h | 1 + .../PlainYearMonth.prototype.add.js | 19 +++++ 3 files changed, 95 insertions(+) create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.add.js diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.cpp index 57ac3447f7..51f0e29db9 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.cpp @@ -5,9 +5,11 @@ */ #include +#include #include #include #include +#include #include #include @@ -41,6 +43,7 @@ void PlainYearMonthPrototype::initialize(GlobalObject& global_object) u8 attr = Attribute::Writable | Attribute::Configurable; define_native_function(vm.names.with, with, 1, attr); + define_native_function(vm.names.add, add, 1, attr); define_native_function(vm.names.equals, equals, 1, attr); define_native_function(vm.names.toString, to_string, 0, attr); define_native_function(vm.names.toLocaleString, to_locale_string, 0, attr); @@ -230,6 +233,78 @@ JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::with) return TRY(year_month_from_fields(global_object, calendar, *fields, options)); } +// 9.3.12 Temporal.PlainYearMonth.prototype.add ( temporalDurationLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.add +JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::add) +{ + // 1. Let yearMonth be the this value. + // 2. Perform ? RequireInternalSlot(yearMonth, [[InitializedTemporalYearMonth]]). + auto* year_month = TRY(typed_this_object(global_object)); + + // 3. Let duration be ? ToLimitedTemporalDuration(temporalDurationLike, « »). + auto duration = TRY(to_limited_temporal_duration(global_object, vm.argument(0), {})); + + // 4. Let balanceResult be ? BalanceDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day"). + auto balance_result = TRY(balance_duration(global_object, duration.days, duration.hours, duration.minutes, duration.seconds, duration.milliseconds, duration.microseconds, *js_bigint(vm, Crypto::SignedBigInteger::create_from((i64)duration.nanoseconds)), "day"sv)); + + // 5. Set options to ? GetOptionsObject(options). + auto* options = TRY(get_options_object(global_object, vm.argument(1))); + + // 6. Let calendar be yearMonth.[[Calendar]]. + auto& calendar = year_month->calendar(); + + // 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). + 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 + if (sign < 0) { + // a. Let dayFromCalendar be ? CalendarDaysInMonth(calendar, yearMonth). + auto day_from_calendar = TRY(calendar_days_in_month(global_object, calendar, *year_month)); + + // b. Let day be ? ToPositiveInteger(dayFromCalendar). + day = TRY(to_positive_integer(global_object, day_from_calendar)); + } + // 10. 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. Let durationToAdd be ? CreateTemporalDuration(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], balanceResult.[[Days]], 0, 0, 0, 0, 0, 0). + auto* duration_to_add = TRY(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%). + auto* options_copy = Object::create(global_object, global_object.object_prototype()); + + // 14. 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 + 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); + + // a. Perform ! CreateDataPropertyOrThrow(optionsCopy, nextEntry[0], nextEntry[1]). + MUST(options_copy->create_data_property_or_throw(key, value)); + } + + // 16. 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, «»). + auto* added_date_fields = TRY(prepare_temporal_fields(global_object, *added_date, field_names, {})); + + // 18. Return ? YearMonthFromFields(calendar, addedDateFields, optionsCopy). + return TRY(year_month_from_fields(global_object, calendar, *added_date_fields, options_copy)); +} + // 9.3.16 Temporal.PlainYearMonth.prototype.equals ( other ), https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.equals JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::equals) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.h index 143a8a8dc3..cdfa66159b 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.h @@ -31,6 +31,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(era_getter); JS_DECLARE_NATIVE_FUNCTION(era_year_getter); JS_DECLARE_NATIVE_FUNCTION(with); + JS_DECLARE_NATIVE_FUNCTION(add); JS_DECLARE_NATIVE_FUNCTION(equals); JS_DECLARE_NATIVE_FUNCTION(to_string); JS_DECLARE_NATIVE_FUNCTION(to_locale_string); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.add.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainYearMonth/PlainYearMonth.prototype.add.js new file mode 100644 index 0000000000..27f7396efb --- /dev/null +++ b/Userland/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"); + }); +});