From a80523be18f38165df02ffb7462b22517999f736 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 18 Nov 2024 12:48:10 -0500 Subject: [PATCH] LibJS: Implement mathematical Temporal.Duration prototypes Includes: Temporal.Duration.prototype.negated Temporal.Duration.prototype.abs Temporal.Duration.prototype.add Temporal.Duration.prototype.subtract --- Libraries/LibJS/Runtime/ErrorTypes.h | 1 + .../Runtime/Temporal/AbstractOperations.cpp | 20 ++ .../Runtime/Temporal/AbstractOperations.h | 6 + Libraries/LibJS/Runtime/Temporal/Duration.cpp | 254 ++++++++++++++++++ Libraries/LibJS/Runtime/Temporal/Duration.h | 5 + .../Runtime/Temporal/DurationPrototype.cpp | 52 ++++ .../Runtime/Temporal/DurationPrototype.h | 4 + .../Duration/Duration.prototype.abs.js | 53 ++++ .../Duration/Duration.prototype.add.js | 77 ++++++ .../Duration/Duration.prototype.negated.js | 42 +++ .../Duration/Duration.prototype.subtract.js | 77 ++++++ 11 files changed, 591 insertions(+) create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.abs.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.add.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.negated.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.subtract.js diff --git a/Libraries/LibJS/Runtime/ErrorTypes.h b/Libraries/LibJS/Runtime/ErrorTypes.h index 6d11555662f..83442c3a602 100644 --- a/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Libraries/LibJS/Runtime/ErrorTypes.h @@ -261,6 +261,7 @@ M(TemporalInvalidInstantString, "Invalid instant string '{}'") \ M(TemporalInvalidISODate, "Invalid ISO date") \ M(TemporalInvalidISODateTime, "Invalid ISO date time") \ + M(TemporalInvalidLargestUnit, "Largest unit must not be {}") \ M(TemporalInvalidMonthCode, "Invalid month code") \ M(TemporalInvalidMonthDayString, "Invalid month day string '{}'") \ M(TemporalInvalidMonthDayStringUTCDesignator, "Invalid month day string '{}': must not contain a UTC designator") \ diff --git a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index 029b0eaf4f3..9d3d2c40dba 100644 --- a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -55,6 +55,26 @@ ThrowCompletionOr get_temporal_relative_to_option(VM& vm, Object con return RelativeTo { .plain_relative_to = {}, .zoned_relative_to = {} }; } +// 13.19 LargerOfTwoTemporalUnits ( u1, u2 ), https://tc39.es/proposal-temporal/#sec-temporal-largeroftwotemporalunits +Unit larger_of_two_temporal_units(Unit unit1, Unit unit2) +{ + // 1. For each row of Table 21, except the header row, in table order, do + for (auto const& row : temporal_units) { + // a. Let unit be the value in the "Value" column of the row. + auto unit = row.value; + + // b. If u1 is unit, return unit. + if (unit1 == unit) + return unit; + + // c. If u2 is unit, return unit. + if (unit2 == unit) + return unit; + } + + VERIFY_NOT_REACHED(); +} + // 13.20 IsCalendarUnit ( unit ), https://tc39.es/proposal-temporal/#sec-temporal-iscalendarunit bool is_calendar_unit(Unit unit) { diff --git a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h index 97bf5723c47..ac20850e738 100644 --- a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h +++ b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h @@ -18,6 +18,11 @@ namespace JS::Temporal { +enum class ArithmeticOperation { + Add, + Subtract, +}; + // https://tc39.es/proposal-temporal/#sec-temporal-units enum class Unit { Year, @@ -56,6 +61,7 @@ struct RelativeTo { }; ThrowCompletionOr get_temporal_relative_to_option(VM&, Object const& options); +Unit larger_of_two_temporal_units(Unit, Unit); bool is_calendar_unit(Unit); UnitCategory temporal_unit_category(Unit); ThrowCompletionOr> parse_temporal_duration_string(VM&, StringView iso_string); diff --git a/Libraries/LibJS/Runtime/Temporal/Duration.cpp b/Libraries/LibJS/Runtime/Temporal/Duration.cpp index c58a9c2999c..d74bc15301f 100644 --- a/Libraries/LibJS/Runtime/Temporal/Duration.cpp +++ b/Libraries/LibJS/Runtime/Temporal/Duration.cpp @@ -88,6 +88,200 @@ InternalDuration to_internal_duration_record(VM& vm, Duration const& duration) return MUST(combine_date_and_time_duration(vm, date_duration, move(time_duration))); } +// 7.5.6 ToInternalDurationRecordWith24HourDays ( duration ), https://tc39.es/proposal-temporal/#sec-temporal-tointernaldurationrecordwith24hourdays +InternalDuration to_internal_duration_record_with_24_hour_days(VM& vm, Duration const& duration) +{ + // 1. Let timeDuration be TimeDurationFromComponents(duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). + auto time_duration = time_duration_from_components(duration.hours(), duration.minutes(), duration.seconds(), duration.milliseconds(), duration.microseconds(), duration.nanoseconds()); + + // 2. Set timeDuration to ! Add24HourDaysToTimeDuration(timeDuration, duration.[[Days]]). + time_duration = MUST(add_24_hour_days_to_time_duration(vm, time_duration, duration.days())); + + // 3. Let dateDuration be ! CreateDateDurationRecord(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], 0). + auto date_duration = MUST(create_date_duration_record(vm, duration.years(), duration.months(), duration.weeks(), 0)); + + // 4. Return ! CombineDateAndTimeDuration(dateDuration, timeDuration). + return MUST(combine_date_and_time_duration(vm, date_duration, move(time_duration))); +} + +// 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) +{ + // 1. Let days, hours, minutes, seconds, milliseconds, and microseconds be 0. + double days = 0; + double hours = 0; + double minutes = 0; + double seconds = 0; + double milliseconds = 0; + double microseconds = 0; + + // 2. Let sign be TimeDurationSign(internalDuration.[[Time]]). + auto sign = time_duration_sign(internal_duration.time); + + // 3. Let nanoseconds be abs(internalDuration.[[Time]]). + auto const& absolute_nanoseconds = internal_duration.time.unsigned_value(); + double nanoseconds = 0; + + // 4. If TemporalUnitCategory(largestUnit) is date, then + if (temporal_unit_category(largest_unit) == UnitCategory::Date) { + // a. Set microseconds to floor(nanoseconds / 1000). + auto nanoseconds_division_result = absolute_nanoseconds.divided_by(NANOSECONDS_PER_MICROSECOND); + + // b. Set nanoseconds to nanoseconds modulo 1000. + nanoseconds = nanoseconds_division_result.remainder.to_double(); + + // c. Set milliseconds to floor(microseconds / 1000). + auto microseconds_division_result = nanoseconds_division_result.quotient.divided_by(MICROSECONDS_PER_MILLISECOND); + + // d. Set microseconds to microseconds modulo 1000. + microseconds = microseconds_division_result.remainder.to_double(); + + // e. Set seconds to floor(milliseconds / 1000). + auto milliseconds_division_result = microseconds_division_result.quotient.divided_by(MILLISECONDS_PER_SECOND); + + // f. Set milliseconds to milliseconds modulo 1000. + milliseconds = milliseconds_division_result.remainder.to_double(); + + // g. Set minutes to floor(seconds / 60). + auto seconds_division_result = milliseconds_division_result.quotient.divided_by(SECONDS_PER_MINUTE); + + // h. Set seconds to seconds modulo 60. + seconds = seconds_division_result.remainder.to_double(); + + // i. Set hours to floor(minutes / 60). + auto minutes_division_result = seconds_division_result.quotient.divided_by(MINUTES_PER_HOUR); + + // j. Set minutes to minutes modulo 60. + minutes = minutes_division_result.remainder.to_double(); + + // k. Set days to floor(hours / 24). + auto hours_division_result = minutes_division_result.quotient.divided_by(HOURS_PER_DAY); + days = hours_division_result.quotient.to_double(); + + // l. Set hours to hours modulo 24. + hours = hours_division_result.remainder.to_double(); + } + // 5. Else if largestUnit is hour, then + else if (largest_unit == Unit::Hour) { + // a. Set microseconds to floor(nanoseconds / 1000). + auto nanoseconds_division_result = absolute_nanoseconds.divided_by(NANOSECONDS_PER_MICROSECOND); + + // b. Set nanoseconds to nanoseconds modulo 1000. + nanoseconds = nanoseconds_division_result.remainder.to_double(); + + // c. Set milliseconds to floor(microseconds / 1000). + auto microseconds_division_result = nanoseconds_division_result.quotient.divided_by(MICROSECONDS_PER_MILLISECOND); + + // d. Set microseconds to microseconds modulo 1000. + microseconds = microseconds_division_result.remainder.to_double(); + + // e. Set seconds to floor(milliseconds / 1000). + auto milliseconds_division_result = microseconds_division_result.quotient.divided_by(MILLISECONDS_PER_SECOND); + + // f. Set milliseconds to milliseconds modulo 1000. + milliseconds = milliseconds_division_result.remainder.to_double(); + + // g. Set minutes to floor(seconds / 60). + auto seconds_division_result = milliseconds_division_result.quotient.divided_by(SECONDS_PER_MINUTE); + + // h. Set seconds to seconds modulo 60. + seconds = seconds_division_result.remainder.to_double(); + + // i. Set hours to floor(minutes / 60). + auto minutes_division_result = seconds_division_result.quotient.divided_by(MINUTES_PER_HOUR); + hours = minutes_division_result.quotient.to_double(); + + // j. Set minutes to minutes modulo 60. + minutes = minutes_division_result.remainder.to_double(); + } + // 6. Else if largestUnit is minute, then + else if (largest_unit == Unit::Minute) { + // a. Set microseconds to floor(nanoseconds / 1000). + auto nanoseconds_division_result = absolute_nanoseconds.divided_by(Crypto::UnsignedBigInteger(NANOSECONDS_PER_MICROSECOND)); + + // b. Set nanoseconds to nanoseconds modulo 1000. + nanoseconds = nanoseconds_division_result.remainder.to_double(); + + // c. Set milliseconds to floor(microseconds / 1000). + auto microseconds_division_result = nanoseconds_division_result.quotient.divided_by(MICROSECONDS_PER_MILLISECOND); + + // d. Set microseconds to microseconds modulo 1000. + microseconds = microseconds_division_result.remainder.to_double(); + + // e. Set seconds to floor(milliseconds / 1000). + auto milliseconds_division_result = microseconds_division_result.quotient.divided_by(MILLISECONDS_PER_SECOND); + + // f. Set milliseconds to milliseconds modulo 1000. + milliseconds = milliseconds_division_result.remainder.to_double(); + + // g. Set minutes to floor(seconds / 60). + auto seconds_division_result = milliseconds_division_result.quotient.divided_by(SECONDS_PER_MINUTE); + minutes = seconds_division_result.quotient.to_double(); + + // h. Set seconds to seconds modulo 60. + seconds = seconds_division_result.remainder.to_double(); + } + // 7. Else if largestUnit is second, then + else if (largest_unit == Unit::Second) { + // a. Set microseconds to floor(nanoseconds / 1000). + auto nanoseconds_division_result = absolute_nanoseconds.divided_by(NANOSECONDS_PER_MICROSECOND); + + // b. Set nanoseconds to nanoseconds modulo 1000. + nanoseconds = nanoseconds_division_result.remainder.to_double(); + + // c. Set milliseconds to floor(microseconds / 1000). + auto microseconds_division_result = nanoseconds_division_result.quotient.divided_by(MICROSECONDS_PER_MILLISECOND); + + // d. Set microseconds to microseconds modulo 1000. + microseconds = microseconds_division_result.remainder.to_double(); + + // e. Set seconds to floor(milliseconds / 1000). + auto milliseconds_division_result = microseconds_division_result.quotient.divided_by(MILLISECONDS_PER_SECOND); + seconds = milliseconds_division_result.quotient.to_double(); + + // f. Set milliseconds to milliseconds modulo 1000. + milliseconds = milliseconds_division_result.remainder.to_double(); + } + // 8. Else if largestUnit is millisecond, then + else if (largest_unit == Unit::Millisecond) { + // a. Set microseconds to floor(nanoseconds / 1000). + auto nanoseconds_division_result = absolute_nanoseconds.divided_by(NANOSECONDS_PER_MICROSECOND); + + // b. Set nanoseconds to nanoseconds modulo 1000. + nanoseconds = nanoseconds_division_result.remainder.to_double(); + + // c. Set milliseconds to floor(microseconds / 1000). + auto microseconds_division_result = nanoseconds_division_result.quotient.divided_by(MICROSECONDS_PER_MILLISECOND); + milliseconds = microseconds_division_result.quotient.to_double(); + + // d. Set microseconds to microseconds modulo 1000. + microseconds = microseconds_division_result.remainder.to_double(); + } + // 9. Else if largestUnit is microsecond, then + else if (largest_unit == Unit::Microsecond) { + // a. Set microseconds to floor(nanoseconds / 1000). + auto nanoseconds_division_result = absolute_nanoseconds.divided_by(NANOSECONDS_PER_MICROSECOND); + microseconds = nanoseconds_division_result.quotient.to_double(); + + // b. Set nanoseconds to nanoseconds modulo 1000. + nanoseconds = nanoseconds_division_result.remainder.to_double(); + } + // 10. Else, + else { + // a. Assert: largestUnit is nanosecond. + VERIFY(largest_unit == Unit::Nanosecond); + nanoseconds = absolute_nanoseconds.to_double(); + } + + // 11. NOTE: When largestUnit is millisecond, microsecond, or nanosecond, milliseconds, microseconds, or nanoseconds + // may be an unsafe integer. In this case, care must be taken when implementing the calculation using floating + // point arithmetic. It can be implemented in C++ using std::fma(). String manipulation will also give an exact + // result, since the multiplication is by a power of 10. + + // 12. Return ? CreateTemporalDuration(internalDuration.[[Date]].[[Years]], internalDuration.[[Date]].[[Months]], internalDuration.[[Date]].[[Weeks]], internalDuration.[[Date]].[[Days]] + days × sign, hours × sign, minutes × sign, seconds × sign, milliseconds × sign, microseconds × sign, nanoseconds × sign). + return TRY(create_temporal_duration(vm, internal_duration.date.years, internal_duration.date.months, internal_duration.date.weeks, internal_duration.date.days + (days * sign), hours * sign, minutes * sign, seconds * sign, milliseconds * sign, microseconds * sign, nanoseconds * sign)); +} + // 7.5.9 CreateDateDurationRecord ( years, months, weeks, days ), https://tc39.es/proposal-temporal/#sec-temporal-createdatedurationrecord ThrowCompletionOr create_date_duration_record(VM& vm, double years, double months, double weeks, double days) { @@ -432,6 +626,13 @@ ThrowCompletionOr> create_temporal_duration(VM& vm, double yea return object; } +// 7.5.20 CreateNegatedTemporalDuration ( duration ), https://tc39.es/proposal-temporal/#sec-temporal-createnegatedtemporalduration +GC::Ref create_negated_temporal_duration(VM& vm, Duration const& duration) +{ + // 1. Return ! CreateTemporalDuration(-duration.[[Years]], -duration.[[Months]], -duration.[[Weeks]], -duration.[[Days]], -duration.[[Hours]], -duration.[[Minutes]], -duration.[[Seconds]], -duration.[[Milliseconds]], -duration.[[Microseconds]], -duration.[[Nanoseconds]]). + return MUST(create_temporal_duration(vm, -duration.years(), -duration.months(), -duration.weeks(), -duration.days(), -duration.hours(), -duration.minutes(), -duration.seconds(), -duration.milliseconds(), -duration.microseconds(), -duration.nanoseconds())); +} + // 7.5.21 TimeDurationFromComponents ( hours, minutes, seconds, milliseconds, microseconds, nanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-timedurationfromcomponents TimeDuration time_duration_from_components(double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds) { @@ -457,6 +658,20 @@ TimeDuration time_duration_from_components(double hours, double minutes, double return total_nanoseconds; } +// 7.5.22 AddTimeDuration ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal-addtimeduration +ThrowCompletionOr add_time_duration(VM& vm, TimeDuration const& one, TimeDuration const& two) +{ + // 1. Let result be one + two. + auto result = one.plus(two); + + // 2. If abs(result) > maxTimeDuration, throw a RangeError exception. + if (result.unsigned_value() > MAX_TIME_DURATION.unsigned_value()) + return vm.throw_completion(ErrorType::TemporalInvalidDuration); + + // 3. Return result. + return result; +} + // 7.5.23 Add24HourDaysToTimeDuration ( d, days ), https://tc39.es/proposal-temporal/#sec-temporal-add24hourdaystonormalizedtimeduration ThrowCompletionOr add_24_hour_days_to_time_duration(VM& vm, TimeDuration const& time_duration, double days) { @@ -501,4 +716,43 @@ i8 time_duration_sign(TimeDuration const& time_duration) return 0; } +// 7.5.40 AddDurations ( operation, duration, other ), https://tc39.es/proposal-temporal/#sec-temporal-adddurations +ThrowCompletionOr> add_durations(VM& vm, ArithmeticOperation operation, Duration const& duration, Value other_value) +{ + // 1. Set other to ? ToTemporalDuration(other). + auto other = TRY(to_temporal_duration(vm, other_value)); + + // 2. If operation is subtract, set other to CreateNegatedTemporalDuration(other). + if (operation == ArithmeticOperation::Subtract) + other = create_negated_temporal_duration(vm, other); + + // 3. Let largestUnit1 be DefaultTemporalLargestUnit(duration). + auto largest_unit1 = default_temporal_largest_unit(duration); + + // 4. Let largestUnit2 be DefaultTemporalLargestUnit(other). + auto largest_unit2 = default_temporal_largest_unit(other); + + // 5. Let largestUnit be LargerOfTwoTemporalUnits(largestUnit1, largestUnit2). + auto largest_unit = larger_of_two_temporal_units(largest_unit1, largest_unit2); + + // 6. If IsCalendarUnit(largestUnit) is true, throw a RangeError exception. + if (is_calendar_unit(largest_unit)) + return vm.throw_completion(ErrorType::TemporalInvalidLargestUnit, "a calendar unit"sv); + + // 7. Let d1 be ToInternalDurationRecordWith24HourDays(duration). + auto duration1 = to_internal_duration_record_with_24_hour_days(vm, duration); + + // 8. Let d2 be ToInternalDurationRecordWith24HourDays(other). + auto duration2 = to_internal_duration_record_with_24_hour_days(vm, other); + + // 9. Let timeResult be ? AddTimeDuration(d1.[[Time]], d2.[[Time]]). + auto time_result = TRY(add_time_duration(vm, duration1.time, duration2.time)); + + // 10. Let result be ! CombineDateAndTimeDuration(ZeroDateDuration(), timeResult). + auto result = MUST(combine_date_and_time_duration(vm, zero_date_duration(vm), move(time_result))); + + // 11. Return ? TemporalDurationFromInternal(result, largestUnit). + return TRY(temporal_duration_from_internal(vm, result, largest_unit)); +} + } diff --git a/Libraries/LibJS/Runtime/Temporal/Duration.h b/Libraries/LibJS/Runtime/Temporal/Duration.h index 3e82ef249e8..33d3818a8b4 100644 --- a/Libraries/LibJS/Runtime/Temporal/Duration.h +++ b/Libraries/LibJS/Runtime/Temporal/Duration.h @@ -102,6 +102,8 @@ struct InternalDuration { 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> 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 combine_date_and_time_duration(VM&, DateDuration, TimeDuration); ThrowCompletionOr> to_temporal_duration(VM&, Value); @@ -111,9 +113,12 @@ bool is_valid_duration(double years, double months, double weeks, double days, d Unit default_temporal_largest_unit(Duration const&); ThrowCompletionOr to_temporal_partial_duration_record(VM&, Value temporal_duration_like); ThrowCompletionOr> create_temporal_duration(VM&, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, GC::Ptr new_target = {}); +GC::Ref create_negated_temporal_duration(VM&, Duration const&); TimeDuration time_duration_from_components(double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds); +ThrowCompletionOr add_time_duration(VM&, TimeDuration const&, TimeDuration const&); ThrowCompletionOr add_24_hour_days_to_time_duration(VM&, TimeDuration const&, double days); i8 compare_time_duration(TimeDuration const&, TimeDuration const&); i8 time_duration_sign(TimeDuration const&); +ThrowCompletionOr> add_durations(VM&, ArithmeticOperation, Duration const&, Value); } diff --git a/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp b/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp index e3826931521..1c1e3069967 100644 --- a/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp +++ b/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp @@ -37,6 +37,10 @@ void DurationPrototype::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.negated, negated, 0, attr); + define_native_function(realm, vm.names.abs, abs, 0, attr); + define_native_function(realm, vm.names.add, add, 1, attr); + define_native_function(realm, vm.names.subtract, subtract, 1, attr); } // 7.3.3 get Temporal.Duration.prototype.years, https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.years @@ -162,4 +166,52 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::with) return TRY(create_temporal_duration(vm, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds)); } +// 7.3.16 Temporal.Duration.prototype.negated ( ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.negated +JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::negated) +{ + // 1. Let duration be the this value. + // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). + auto duration = TRY(typed_this_object(vm)); + + // 3. Return CreateNegatedTemporalDuration(duration). + return create_negated_temporal_duration(vm, duration); +} + +// 7.3.17 Temporal.Duration.prototype.abs ( ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.abs +JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::abs) +{ + // 1. Let duration be the this value. + // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). + auto duration = TRY(typed_this_object(vm)); + + // 3. Return ! CreateTemporalDuration(abs(duration.[[Years]]), abs(duration.[[Months]]), abs(duration.[[Weeks]]), abs(duration.[[Days]]), abs(duration.[[Hours]]), abs(duration.[[Minutes]]), abs(duration.[[Seconds]]), abs(duration.[[Milliseconds]]), abs(duration.[[Microseconds]]), abs(duration.[[Nanoseconds]])). + return MUST(create_temporal_duration(vm, fabs(duration->years()), fabs(duration->months()), fabs(duration->weeks()), fabs(duration->days()), fabs(duration->hours()), fabs(duration->minutes()), fabs(duration->seconds()), fabs(duration->milliseconds()), fabs(duration->microseconds()), fabs(duration->nanoseconds()))); +} + +// 7.3.18 Temporal.Duration.prototype.add ( other ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.add +JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::add) +{ + auto other = vm.argument(0); + + // 1. Let duration be the this value. + // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). + auto duration = TRY(typed_this_object(vm)); + + // 3. Return ? AddDurations(ADD, duration, other). + return TRY(add_durations(vm, ArithmeticOperation::Add, duration, other)); +} + +// 7.3.19 Temporal.Duration.prototype.subtract ( other ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.subtract +JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::subtract) +{ + auto other = vm.argument(0); + + // 1. Let duration be the this value. + // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). + auto duration = TRY(typed_this_object(vm)); + + // 3. Return ? AddDurations(SUBTRACT, duration, other). + return TRY(add_durations(vm, ArithmeticOperation::Subtract, duration, other)); +} + } diff --git a/Libraries/LibJS/Runtime/Temporal/DurationPrototype.h b/Libraries/LibJS/Runtime/Temporal/DurationPrototype.h index 1da86cedf24..bdfe489aa8f 100644 --- a/Libraries/LibJS/Runtime/Temporal/DurationPrototype.h +++ b/Libraries/LibJS/Runtime/Temporal/DurationPrototype.h @@ -31,6 +31,10 @@ private: JS_DECLARE_NATIVE_FUNCTION(sign_getter); JS_DECLARE_NATIVE_FUNCTION(blank_getter); JS_DECLARE_NATIVE_FUNCTION(with); + JS_DECLARE_NATIVE_FUNCTION(negated); + JS_DECLARE_NATIVE_FUNCTION(abs); + JS_DECLARE_NATIVE_FUNCTION(add); + JS_DECLARE_NATIVE_FUNCTION(subtract); }; } diff --git a/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.abs.js b/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.abs.js new file mode 100644 index 00000000000..ba80ce52722 --- /dev/null +++ b/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.abs.js @@ -0,0 +1,53 @@ +const DURATION_PROPERTIES = [ + "years", + "months", + "weeks", + "days", + "hours", + "minutes", + "seconds", + "milliseconds", + "microseconds", + "nanoseconds", +]; + +describe("correct behavior", () => { + test("length is 0", () => { + expect(Temporal.Duration.prototype.abs).toHaveLength(0); + }); + + test("basic functionality", () => { + let absoluteDuration; + + absoluteDuration = new Temporal.Duration(123).abs(); + expect(absoluteDuration.years).toBe(123); + + absoluteDuration = new Temporal.Duration(-123).abs(); + expect(absoluteDuration.years).toBe(123); + }); + + test("each property is made absolute", () => { + let values; + let duration; + + values = Array(DURATION_PROPERTIES.length).fill(-1); + duration = new Temporal.Duration(...values).abs(); + for (const property of DURATION_PROPERTIES) { + expect(duration[property]).toBe(1); + } + + values = Array(DURATION_PROPERTIES.length).fill(1); + duration = new Temporal.Duration(...values).abs(); + for (const property of DURATION_PROPERTIES) { + expect(duration[property]).toBe(1); + } + }); +}); + +describe("errors", () => { + test("this value must be a Temporal.Duration object", () => { + expect(() => { + Temporal.Duration.prototype.abs.call("foo"); + }).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration"); + }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.add.js b/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.add.js new file mode 100644 index 00000000000..ca59bc6f4c8 --- /dev/null +++ b/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.add.js @@ -0,0 +1,77 @@ +describe("correct behavior", () => { + test("length is 1", () => { + expect(Temporal.Duration.prototype.add).toHaveLength(1); + }); + + function checkCommonResults(durationResult) { + expect(durationResult.years).toBe(0); + expect(durationResult.months).toBe(0); + expect(durationResult.weeks).toBe(0); + expect(durationResult.days).toBe(3); + expect(durationResult.hours).toBe(3); + expect(durationResult.minutes).toBe(3); + expect(durationResult.seconds).toBe(3); + expect(durationResult.milliseconds).toBe(3); + expect(durationResult.microseconds).toBe(3); + expect(durationResult.nanoseconds).toBe(3); + } + + test("basic functionality", () => { + const duration = new Temporal.Duration(0, 0, 0, 2, 2, 2, 2, 2, 2, 2); + const oneDuration = new Temporal.Duration(0, 0, 0, 1, 1, 1, 1, 1, 1, 1); + const durationResult = duration.add(oneDuration); + + checkCommonResults(durationResult); + }); + + test("from duration-like", () => { + const duration = new Temporal.Duration(0, 0, 0, 2, 2, 2, 2, 2, 2, 2); + const oneDuration = { + days: 1, + hours: 1, + minutes: 1, + seconds: 1, + milliseconds: 1, + microseconds: 1, + nanoseconds: 1, + }; + const durationResult = duration.add(oneDuration); + + checkCommonResults(durationResult); + }); + + test("from string", () => { + const duration = new Temporal.Duration(0, 0, 0, 2, 2, 2, 2, 2, 2, 2); + const oneDuration = "P1DT1H1M1.001001001S"; + const durationResult = duration.add(oneDuration); + + checkCommonResults(durationResult); + }); +}); + +describe("errors", () => { + test("this value must be a Temporal.Duration object", () => { + expect(() => { + Temporal.Duration.prototype.add.call("foo"); + }).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration"); + }); + + test("relativeTo is required when duration has calendar units", () => { + const yearDuration = new Temporal.Duration(1); + const monthDuration = new Temporal.Duration(0, 1); + const weekDuration = new Temporal.Duration(0, 0, 1); + const durationToAdd = { seconds: 1 }; + + expect(() => { + yearDuration.add(durationToAdd); + }).toThrowWithMessage(RangeError, "Largest unit must not be a calendar unit"); + + expect(() => { + monthDuration.add(durationToAdd); + }).toThrowWithMessage(RangeError, "Largest unit must not be a calendar unit"); + + expect(() => { + weekDuration.add(durationToAdd); + }).toThrowWithMessage(RangeError, "Largest unit must not be a calendar unit"); + }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.negated.js b/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.negated.js new file mode 100644 index 00000000000..d96f5a04d00 --- /dev/null +++ b/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.negated.js @@ -0,0 +1,42 @@ +const DURATION_PROPERTIES = [ + "years", + "months", + "weeks", + "days", + "hours", + "minutes", + "seconds", + "milliseconds", + "microseconds", + "nanoseconds", +]; + +describe("correct behavior", () => { + test("length is 0", () => { + expect(Temporal.Duration.prototype.negated).toHaveLength(0); + }); + + test("basic functionality", () => { + const negativeDuration = new Temporal.Duration(123).negated(); + expect(negativeDuration.years).toBe(-123); + + const positiveDuration = new Temporal.Duration(-123).negated(); + expect(positiveDuration.years).toBe(123); + }); + + test("each property is negated", () => { + const values = Array(DURATION_PROPERTIES.length).fill(1); + const duration = new Temporal.Duration(...values).negated(); + for (const property of DURATION_PROPERTIES) { + expect(duration[property]).toBe(-1); + } + }); +}); + +describe("errors", () => { + test("this value must be a Temporal.Duration object", () => { + expect(() => { + Temporal.Duration.prototype.negated.call("foo"); + }).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration"); + }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.subtract.js b/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.subtract.js new file mode 100644 index 00000000000..ca48fcfa000 --- /dev/null +++ b/Libraries/LibJS/Tests/builtins/Temporal/Duration/Duration.prototype.subtract.js @@ -0,0 +1,77 @@ +describe("correct behavior", () => { + test("length is 1", () => { + expect(Temporal.Duration.prototype.subtract).toHaveLength(1); + }); + + function checkCommonResults(durationResult) { + expect(durationResult.years).toBe(0); + expect(durationResult.months).toBe(0); + expect(durationResult.weeks).toBe(0); + expect(durationResult.days).toBe(1); + expect(durationResult.hours).toBe(1); + expect(durationResult.minutes).toBe(1); + expect(durationResult.seconds).toBe(1); + expect(durationResult.milliseconds).toBe(1); + expect(durationResult.microseconds).toBe(1); + expect(durationResult.nanoseconds).toBe(1); + } + + test("basic functionality", () => { + const duration = new Temporal.Duration(0, 0, 0, 2, 2, 2, 2, 2, 2, 2); + const oneDuration = new Temporal.Duration(0, 0, 0, 1, 1, 1, 1, 1, 1, 1); + const durationResult = duration.subtract(oneDuration); + + checkCommonResults(durationResult); + }); + + test("from duration-like", () => { + const duration = new Temporal.Duration(0, 0, 0, 2, 2, 2, 2, 2, 2, 2); + const oneDuration = { + days: 1, + hours: 1, + minutes: 1, + seconds: 1, + milliseconds: 1, + microseconds: 1, + nanoseconds: 1, + }; + const durationResult = duration.subtract(oneDuration); + + checkCommonResults(durationResult); + }); + + test("from string", () => { + const duration = new Temporal.Duration(0, 0, 0, 2, 2, 2, 2, 2, 2, 2); + const oneDuration = "P1DT1H1M1.001001001S"; + const durationResult = duration.subtract(oneDuration); + + checkCommonResults(durationResult); + }); +}); + +describe("errors", () => { + test("this value must be a Temporal.Duration object", () => { + expect(() => { + Temporal.Duration.prototype.subtract.call("foo"); + }).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration"); + }); + + test("relativeTo is required when duration has calendar units", () => { + const yearDuration = new Temporal.Duration(1); + const monthDuration = new Temporal.Duration(0, 1); + const weekDuration = new Temporal.Duration(0, 0, 1); + const durationToSubtract = { seconds: 1 }; + + expect(() => { + yearDuration.subtract(durationToSubtract); + }).toThrowWithMessage(RangeError, "Largest unit must not be a calendar unit"); + + expect(() => { + monthDuration.subtract(durationToSubtract); + }).toThrowWithMessage(RangeError, "Largest unit must not be a calendar unit"); + + expect(() => { + weekDuration.subtract(durationToSubtract); + }).toThrowWithMessage(RangeError, "Largest unit must not be a calendar unit"); + }); +});