From 3729a910f63f73964355e5ea49b3c785930d8c48 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Fri, 6 May 2022 19:01:15 +0200 Subject: [PATCH] LibJS: Refactor Temporal add/subtract to common AOs This is an editorial change in the Temporal spec. See: - https://github.com/tc39/proposal-temporal/commit/2f96efc - https://github.com/tc39/proposal-temporal/commit/fbff635 --- .../Runtime/Temporal/AbstractOperations.h | 5 + .../LibJS/Runtime/Temporal/Duration.cpp | 22 +++ .../LibJS/Runtime/Temporal/Duration.h | 1 + .../Runtime/Temporal/DurationPrototype.cpp | 38 ++--- .../LibJS/Runtime/Temporal/Instant.cpp | 16 ++ .../LibJS/Runtime/Temporal/Instant.h | 4 +- .../Runtime/Temporal/InstantPrototype.cpp | 20 +-- .../LibJS/Runtime/Temporal/PlainDateTime.cpp | 25 +++ .../LibJS/Runtime/Temporal/PlainDateTime.h | 1 + .../Temporal/PlainDateTimePrototype.cpp | 44 ++--- .../LibJS/Runtime/Temporal/PlainTime.cpp | 19 +++ .../LibJS/Runtime/Temporal/PlainTime.h | 1 + .../Runtime/Temporal/PlainTimePrototype.cpp | 26 +-- .../LibJS/Runtime/Temporal/PlainYearMonth.cpp | 90 ++++++++++ .../LibJS/Runtime/Temporal/PlainYearMonth.h | 2 + .../Temporal/PlainYearMonthPrototype.cpp | 155 ++---------------- .../LibJS/Runtime/Temporal/ZonedDateTime.cpp | 25 +++ .../LibJS/Runtime/Temporal/ZonedDateTime.h | 1 + .../Temporal/ZonedDateTimePrototype.cpp | 44 ++--- 19 files changed, 259 insertions(+), 280 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h index 120f6e63a4..fd4880dea0 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h @@ -17,6 +17,11 @@ namespace JS::Temporal { +enum class ArithmeticOperation { + Add, + Subtract, +}; + enum class OptionType { Boolean, String, diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp index 6bc904e001..de7a9df375 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp @@ -1719,4 +1719,26 @@ String temporal_duration_to_string(double years, double months, double weeks, do return result.to_string(); } +// 7.5.29 AddDurationToOrSubtractDurationFromDuration ( operation, duration, other, options ), https://tc39.es/proposal-temporal/#sec-temporal-adddurationtoorsubtractdurationfromduration +ThrowCompletionOr add_duration_to_or_subtract_duration_from_duration(GlobalObject& global_object, ArithmeticOperation operation, Duration const& duration, Value other_value, Value options_value) +{ + // 1. If operation is subtract, let sign be -1. Otherwise, let sign be 1. + i8 sign = operation == ArithmeticOperation::Subtract ? -1 : 1; + + // 2. Set other to ? ToTemporalDurationRecord(other). + auto other = TRY(to_temporal_duration_record(global_object, other_value)); + + // 3. Set options to ? GetOptionsObject(options). + auto const* options = TRY(get_options_object(global_object, options_value)); + + // 4. Let relativeTo be ? ToRelativeTemporalObject(options). + auto relative_to = TRY(to_relative_temporal_object(global_object, *options)); + + // 5. Let result be ? AddDuration(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], sign × other.[[Years]], sign × other.[[Months]], sign × other.[[Weeks]], sign × other.[[Days]], sign × other.[[Hours]], sign × other.[[Minutes]], sign × other.[[Seconds]], sign × other.[[Milliseconds]], sign × other.[[Microseconds]], sign × other.[[Nanoseconds]], relativeTo). + auto result = TRY(add_duration(global_object, duration.years(), duration.months(), duration.weeks(), duration.days(), duration.hours(), duration.minutes(), duration.seconds(), duration.milliseconds(), duration.microseconds(), duration.nanoseconds(), sign * other.years, sign * other.months, sign * other.weeks, sign * other.days, sign * other.hours, sign * other.minutes, sign * other.seconds, sign * other.milliseconds, sign * other.microseconds, sign * other.nanoseconds, relative_to)); + + // 6. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]). + return MUST(create_temporal_duration(global_object, result.years, result.months, result.weeks, result.days, result.hours, result.minutes, result.seconds, result.milliseconds, result.microseconds, result.nanoseconds)); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h index 6d9adb0db4..c9934ce127 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h @@ -159,6 +159,7 @@ ThrowCompletionOr round_duration(GlobalObject&, double years, d ThrowCompletionOr adjust_rounded_duration_days(GlobalObject& global_object, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, u32 increment, StringView unit, StringView rounding_mode, Object* relative_to_object = nullptr); ThrowCompletionOr to_limited_temporal_duration(GlobalObject&, Value temporal_duration_like, Vector const& disallowed_fields); String temporal_duration_to_string(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Variant const& precision); +ThrowCompletionOr add_duration_to_or_subtract_duration_from_duration(GlobalObject&, ArithmeticOperation, Duration const&, Value other_value, Value options_value); // 7.5.22 DaysUntil ( earlier, later ), https://tc39.es/proposal-temporal/#sec-temporal-daysuntil template diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp index 28cafb5cdf..026c070407 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp @@ -294,47 +294,29 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::abs) // 7.3.18 Temporal.Duration.prototype.add ( other [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.add JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::add) { + auto other = vm.argument(0); + auto options = vm.argument(1); + // 1. Let duration be the this value. // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). auto* duration = TRY(typed_this_object(global_object)); - // 3. Set other to ? ToTemporalDurationRecord(other). - auto other = TRY(to_temporal_duration_record(global_object, vm.argument(0))); - - // 4. Set options to ? GetOptionsObject(options). - auto const* options = TRY(get_options_object(global_object, vm.argument(1))); - - // 5. Let relativeTo be ? ToRelativeTemporalObject(options). - auto relative_to = TRY(to_relative_temporal_object(global_object, *options)); - - // 6. Let result be ? AddDuration(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], other.[[Years]], other.[[Months]], other.[[Weeks]], other.[[Days]], other.[[Hours]], other.[[Minutes]], other.[[Seconds]], other.[[Milliseconds]], other.[[Microseconds]], other.[[Nanoseconds]], relativeTo). - auto result = TRY(add_duration(global_object, duration->years(), duration->months(), duration->weeks(), duration->days(), duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), duration->nanoseconds(), other.years, other.months, other.weeks, other.days, other.hours, other.minutes, other.seconds, other.milliseconds, other.microseconds, other.nanoseconds, relative_to)); - - // 7. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]). - return MUST(create_temporal_duration(global_object, result.years, result.months, result.weeks, result.days, result.hours, result.minutes, result.seconds, result.milliseconds, result.microseconds, result.nanoseconds)); + // 3. Return ? AddDurationToOrSubtractDurationFromDuration(add, duration, other, options). + return TRY(add_duration_to_or_subtract_duration_from_duration(global_object, ArithmeticOperation::Add, *duration, other, options)); } // 7.3.19 Temporal.Duration.prototype.subtract ( other [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.subtract JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::subtract) { + auto other = vm.argument(0); + auto options = vm.argument(1); + // 1. Let duration be the this value. // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). auto* duration = TRY(typed_this_object(global_object)); - // 3. Set other to ? ToTemporalDurationRecord(other). - auto other = TRY(to_temporal_duration_record(global_object, vm.argument(0))); - - // 4. Set options to ? GetOptionsObject(options). - auto const* options = TRY(get_options_object(global_object, vm.argument(1))); - - // 5. Let relativeTo be ? ToRelativeTemporalObject(options). - auto relative_to = TRY(to_relative_temporal_object(global_object, *options)); - - // 6. Let result be ? AddDuration(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], -other.[[Years]], -other.[[Months]], -other.[[Weeks]], -other.[[Days]], -other.[[Hours]], -other.[[Minutes]], -other.[[Seconds]], -other.[[Milliseconds]], -other.[[Microseconds]], -other.[[Nanoseconds]], relativeTo). - auto result = TRY(add_duration(global_object, duration->years(), duration->months(), duration->weeks(), duration->days(), duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), duration->nanoseconds(), -other.years, -other.months, -other.weeks, -other.days, -other.hours, -other.minutes, -other.seconds, -other.milliseconds, -other.microseconds, -other.nanoseconds, relative_to)); - - // 7. Return ! CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]). - return MUST(create_temporal_duration(global_object, result.years, result.months, result.weeks, result.days, result.hours, result.minutes, result.seconds, result.milliseconds, result.microseconds, result.nanoseconds)); + // 3. Return ? AddDurationToOrSubtractDurationFromDuration(subtract, duration, other, options). + return TRY(add_duration_to_or_subtract_duration_from_duration(global_object, ArithmeticOperation::Subtract, *duration, other, options)); } // 7.3.20 Temporal.Duration.prototype.round ( roundTo ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.round diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp index 7e9f585b34..62191bb78b 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp @@ -281,4 +281,20 @@ ThrowCompletionOr temporal_instant_to_string(GlobalObject& global_object return String::formatted("{}{}", date_time_string, time_zone_string); } +// 8.5.10 AddDurationToOrSubtractDurationFromInstant ( operation, instant, temporalDurationLike ), https://tc39.es/proposal-temporal/#sec-temporal-adddurationtoorsubtractdurationfrominstant +ThrowCompletionOr add_duration_to_or_subtract_duration_from_instant(GlobalObject& global_object, ArithmeticOperation operation, Instant const& instant, Value temporal_duration_like) +{ + // 1. If operation is subtract, let sign be -1. Otherwise, let sign be 1. + i8 sign = operation == ArithmeticOperation::Subtract ? -1 : 1; + + // 2. Let duration be ? ToLimitedTemporalDuration(temporalDurationLike, « "years", "months", "weeks", "days" »). + auto duration = TRY(to_limited_temporal_duration(global_object, temporal_duration_like, { "years"sv, "months"sv, "weeks"sv, "days"sv })); + + // 3. Let ns be ? AddInstant(instant.[[Nanoseconds]], sign × duration.[[Hours]], sign × duration.[[Minutes]], sign × duration.[[Seconds]], sign × duration.[[Milliseconds]], sign × duration.[[Microseconds]], sign × duration.[[Nanoseconds]]). + auto* ns = TRY(add_instant(global_object, instant.nanoseconds(), sign * duration.hours, sign * duration.minutes, sign * duration.seconds, sign * duration.milliseconds, sign * duration.microseconds, sign * duration.nanoseconds)); + + // 4. Return ! CreateTemporalInstant(ns). + return MUST(create_temporal_instant(global_object, *ns)); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h index 2943a16ca7..b76a6e6796 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Linus Groh + * Copyright (c) 2021-2022, Linus Groh * Copyright (c) 2021, Idan Horowitz * * SPDX-License-Identifier: BSD-2-Clause @@ -12,6 +12,7 @@ #include #include #include +#include namespace JS::Temporal { @@ -45,5 +46,6 @@ ThrowCompletionOr add_instant(GlobalObject&, BigInt const& epoch_nanose BigInt* difference_instant(GlobalObject&, BigInt const& nanoseconds1, BigInt const& nanoseconds2, u64 rounding_increment, StringView smallest_unit, StringView rounding_mode); BigInt* round_temporal_instant(GlobalObject&, BigInt const& nanoseconds, u64 increment, StringView unit, StringView rounding_mode); ThrowCompletionOr temporal_instant_to_string(GlobalObject&, Instant&, Value time_zone, Variant const& precision); +ThrowCompletionOr add_duration_to_or_subtract_duration_from_instant(GlobalObject&, ArithmeticOperation, Instant const&, Value temporal_duration_like); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp index 70091627e4..df501cab7f 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp @@ -126,14 +126,8 @@ JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::add) // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). auto* instant = TRY(typed_this_object(global_object)); - // 3. Let duration be ? ToLimitedTemporalDuration(temporalDurationLike, « "years", "months", "weeks", "days" »). - auto duration = TRY(to_limited_temporal_duration(global_object, temporal_duration_like, { "years"sv, "months"sv, "weeks"sv, "days"sv })); - - // 4. Let ns be ? AddInstant(instant.[[Nanoseconds]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). - auto* ns = TRY(add_instant(global_object, instant->nanoseconds(), duration.hours, duration.minutes, duration.seconds, duration.milliseconds, duration.microseconds, duration.nanoseconds)); - - // 5. Return ! CreateTemporalInstant(ns). - return MUST(create_temporal_instant(global_object, *ns)); + // 3. Return ? AddDurationToOrSubtractDurationFromInstant(add, instant, temporalDurationLike). + return TRY(add_duration_to_or_subtract_duration_from_instant(global_object, ArithmeticOperation::Add, *instant, temporal_duration_like)); } // 8.3.8 Temporal.Instant.prototype.subtract ( temporalDurationLike ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.subtract @@ -145,14 +139,8 @@ JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::subtract) // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). auto* instant = TRY(typed_this_object(global_object)); - // 3. Let duration be ? ToLimitedTemporalDuration(temporalDurationLike, « "years", "months", "weeks", "days" »). - auto duration = TRY(to_limited_temporal_duration(global_object, temporal_duration_like, { "years"sv, "months"sv, "weeks"sv, "days"sv })); - - // 4. Let ns be ? AddInstant(instant.[[Nanoseconds]], -duration.[[Hours]], -duration.[[Minutes]], -duration.[[Seconds]], -duration.[[Milliseconds]], -duration.[[Microseconds]], -duration.[[Nanoseconds]]). - auto* ns = TRY(add_instant(global_object, instant->nanoseconds(), -duration.hours, -duration.minutes, -duration.seconds, -duration.milliseconds, -duration.microseconds, -duration.nanoseconds)); - - // 5. Return ! CreateTemporalInstant(ns). - return MUST(create_temporal_instant(global_object, *ns)); + // 3. Return ? AddDurationToOrSubtractDurationFromInstant(subtract, instant, temporalDurationLike). + return TRY(add_duration_to_or_subtract_duration_from_instant(global_object, ArithmeticOperation::Subtract, *instant, temporal_duration_like)); } // 8.3.9 Temporal.Instant.prototype.until ( other [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.until diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp index dbcab74172..aa26904530 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp @@ -391,4 +391,29 @@ ThrowCompletionOr difference_iso_date_time(GlobalObject& global_ return create_duration_record(date_difference->years(), date_difference->months(), date_difference->weeks(), balance_result.days, balance_result.hours, balance_result.minutes, balance_result.seconds, balance_result.milliseconds, balance_result.microseconds, balance_result.nanoseconds); } +// 5.5.12 AddDurationToOrSubtractDurationFromPlainDateTime ( operation, dateTime, temporalDurationLike, options ), https://tc39.es/proposal-temporal/#sec-temporal-adddurationtoorsubtractdurationfromplaindatetime +ThrowCompletionOr add_duration_to_or_subtract_duration_from_plain_date_time(GlobalObject& global_object, ArithmeticOperation operation, PlainDateTime& date_time, Value temporal_duration_like, Value options_value) +{ + // 1. If operation is subtract, let sign be -1. Otherwise, let sign be 1. + i8 sign = operation == ArithmeticOperation::Subtract ? -1 : 1; + + // 2. Let duration be ? ToTemporalDurationRecord(temporalDurationLike). + auto duration = TRY(to_temporal_duration_record(global_object, temporal_duration_like)); + + // 3. Set options to ? GetOptionsObject(options). + auto* options = TRY(get_options_object(global_object, options_value)); + + // 4. Let result be ? AddDateTime(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]], dateTime.[[Calendar]], sign × duration.[[Years]], sign × duration.[[Months]], sign × duration.[[Weeks]], sign × duration.[[Days]], sign × duration.[[Hours]], sign × duration.[[Minutes]], sign × duration.[[Seconds]], sign × duration.[[Milliseconds]], sign × duration.[[Microseconds]], sign × duration.[[Nanoseconds]], options). + auto result = TRY(add_date_time(global_object, date_time.iso_year(), date_time.iso_month(), date_time.iso_day(), date_time.iso_hour(), date_time.iso_minute(), date_time.iso_second(), date_time.iso_millisecond(), date_time.iso_microsecond(), date_time.iso_nanosecond(), date_time.calendar(), sign * duration.years, sign * duration.months, sign * duration.weeks, sign * duration.days, sign * duration.hours, sign * duration.minutes, sign * duration.seconds, sign * duration.milliseconds, sign * duration.microseconds, sign * duration.nanoseconds, options)); + + // 5. Assert: IsValidISODate(result.[[Year]], result.[[Month]], result.[[Day]]) is true. + VERIFY(is_valid_iso_date(result.year, result.month, result.day)); + + // 6. Assert: IsValidTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]) is true. + VERIFY(is_valid_time(result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond)); + + // 7. Return ? CreateTemporalDateTime(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], dateTime.[[Calendar]]). + return create_temporal_date_time(global_object, result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond, date_time.calendar()); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h index 072be07103..dd5d4d2f4e 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h @@ -74,5 +74,6 @@ i8 compare_iso_date_time(i32 year1, u8 month1, u8 day1, u8 hour1, u8 minute1, u8 ThrowCompletionOr add_date_time(GlobalObject&, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Object& calendar, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Object* options); ISODateTime round_iso_date_time(i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, u64 increment, StringView unit, StringView rounding_mode, Optional day_length = {}); ThrowCompletionOr difference_iso_date_time(GlobalObject&, i32 year1, u8 month1, u8 day1, u8 hour1, u8 minute1, u8 second1, u16 millisecond1, u16 microsecond1, u16 nanosecond1, i32 year2, u8 month2, u8 day2, u8 hour2, u8 minute2, u8 second2, u16 millisecond2, u16 microsecond2, u16 nanosecond2, Object& calendar, StringView largest_unit, Object const* options = nullptr); +ThrowCompletionOr add_duration_to_or_subtract_duration_from_plain_date_time(GlobalObject&, ArithmeticOperation, PlainDateTime&, Value temporal_duration_like, Value options_value); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.cpp index 00ff75892b..e6901bc5f7 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.cpp @@ -461,53 +461,29 @@ JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::with_calendar) // 5.3.26 Temporal.PlainDateTime.prototype.add ( temporalDurationLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.add JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::add) { + auto temporal_duration_like = vm.argument(0); + auto options = vm.argument(1); + // 1. Let dateTime be the this value. // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]). auto* date_time = TRY(typed_this_object(global_object)); - // 3. Let duration be ? ToTemporalDurationRecord(temporalDurationLike). - auto duration = TRY(to_temporal_duration_record(global_object, vm.argument(0))); - - // 4. Set options to ? GetOptionsObject(options). - auto* options = TRY(get_options_object(global_object, vm.argument(1))); - - // 5. Let result be ? AddDateTime(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]], dateTime.[[Calendar]], duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], options). - auto result = TRY(add_date_time(global_object, date_time->iso_year(), date_time->iso_month(), date_time->iso_day(), date_time->iso_hour(), date_time->iso_minute(), date_time->iso_second(), date_time->iso_millisecond(), date_time->iso_microsecond(), date_time->iso_nanosecond(), date_time->calendar(), duration.years, duration.months, duration.weeks, duration.days, duration.hours, duration.minutes, duration.seconds, duration.milliseconds, duration.microseconds, duration.nanoseconds, options)); - - // 6. Assert: IsValidISODate(result.[[Year]], result.[[Month]], result.[[Day]]) is true. - VERIFY(is_valid_iso_date(result.year, result.month, result.day)); - - // 7. Assert: IsValidTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]) is true. - VERIFY(is_valid_time(result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond)); - - // 8. Return ? CreateTemporalDateTime(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], dateTime.[[Calendar]]). - return TRY(create_temporal_date_time(global_object, result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond, date_time->calendar())); + // 3. Return ? AddDurationToOrSubtractDurationFromPlainDateTime(add, dateTime, temporalDurationLike, options). + return TRY(add_duration_to_or_subtract_duration_from_plain_date_time(global_object, ArithmeticOperation::Add, *date_time, temporal_duration_like, options)); } // 5.3.27 Temporal.PlainDateTime.prototype.subtract ( temporalDurationLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.subtract JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::subtract) { + auto temporal_duration_like = vm.argument(0); + auto options = vm.argument(1); + // 1. Let dateTime be the this value. // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]). auto* date_time = TRY(typed_this_object(global_object)); - // 3. Let duration be ? ToTemporalDurationRecord(temporalDurationLike). - auto duration = TRY(to_temporal_duration_record(global_object, vm.argument(0))); - - // 4. Set options to ? GetOptionsObject(options). - auto* options = TRY(get_options_object(global_object, vm.argument(1))); - - // 5. Let result be ? AddDateTime(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]], dateTime.[[Calendar]], -duration.[[Years]], -duration.[[Months]], -duration.[[Weeks]], -duration.[[Days]], -duration.[[Hours]], -duration.[[Minutes]], -duration.[[Seconds]], -duration.[[Milliseconds]], -duration.[[Microseconds]], -duration.[[Nanoseconds]], options). - auto result = TRY(add_date_time(global_object, date_time->iso_year(), date_time->iso_month(), date_time->iso_day(), date_time->iso_hour(), date_time->iso_minute(), date_time->iso_second(), date_time->iso_millisecond(), date_time->iso_microsecond(), date_time->iso_nanosecond(), date_time->calendar(), -duration.years, -duration.months, -duration.weeks, -duration.days, -duration.hours, -duration.minutes, -duration.seconds, -duration.milliseconds, -duration.microseconds, -duration.nanoseconds, options)); - - // 6. Assert: IsValidISODate(result.[[Year]], result.[[Month]], result.[[Day]]) is true. - VERIFY(is_valid_iso_date(result.year, result.month, result.day)); - - // 7. Assert: IsValidTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]) is true. - VERIFY(is_valid_time(result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond)); - - // 8. Return ? CreateTemporalDateTime(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], dateTime.[[Calendar]]). - return TRY(create_temporal_date_time(global_object, result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond, date_time->calendar())); + // 3. Return ? AddDurationToOrSubtractDurationFromPlainDateTime(subtract, dateTime, temporalDurationLike, options). + return TRY(add_duration_to_or_subtract_duration_from_plain_date_time(global_object, ArithmeticOperation::Subtract, *date_time, temporal_duration_like, options)); } // 5.3.28 Temporal.PlainDateTime.prototype.until ( other [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.since diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp index fe95f09c6c..be7c157d7b 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp @@ -618,4 +618,23 @@ DaysAndTime round_time(u8 hour, u8 minute, u8 second, u16 millisecond, u16 micro return balance_time(hour, minute, second, millisecond, microsecond, result); } +// 4.5.14 AddDurationToOrSubtractDurationFromPlainTime ( operation, temporalTime, temporalDurationLike ), https://tc39.es/proposal-temporal/#sec-temporal-adddurationtoorsubtractdurationfromplaintime +ThrowCompletionOr add_duration_to_or_subtract_duration_from_plain_time(GlobalObject& global_object, ArithmeticOperation operation, PlainTime const& temporal_time, Value temporal_duration_like) +{ + // 1. If operation is subtract, let sign be -1. Otherwise, let sign be 1. + i8 sign = operation == ArithmeticOperation::Subtract ? -1 : 1; + + // 2. Let duration be ? ToTemporalDurationRecord(temporalDurationLike). + auto duration = TRY(to_temporal_duration_record(global_object, temporal_duration_like)); + + // 3. Let result be ! AddTime(temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]], temporalTime.[[ISONanosecond]], sign × duration.[[Hours]], sign × duration.[[Minutes]], sign × duration.[[Seconds]], sign × duration.[[Milliseconds]], sign × duration.[[Microseconds]], sign × duration.[[Nanoseconds]]). + auto result = add_time(temporal_time.iso_hour(), temporal_time.iso_minute(), temporal_time.iso_second(), temporal_time.iso_millisecond(), temporal_time.iso_microsecond(), temporal_time.iso_nanosecond(), sign * duration.hours, sign * duration.minutes, sign * duration.seconds, sign * duration.milliseconds, sign * duration.microseconds, sign * duration.nanoseconds); + + // 4. Assert: IsValidTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]) is true. + VERIFY(is_valid_time(result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond)); + + // 5. Return ? CreateTemporalTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]). + return TRY(create_temporal_time(global_object, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond)); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h index a785bfe720..fcf74a0ea2 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h @@ -106,5 +106,6 @@ String temporal_time_to_string(u8 hour, u8 minute, u8 second, u16 millisecond, u i8 compare_temporal_time(u8 hour1, u8 minute1, u8 second1, u16 millisecond1, u16 microsecond1, u16 nanosecond1, u8 hour2, u8 minute2, u8 second2, u16 millisecond2, u16 microsecond2, u16 nanosecond2); DaysAndTime add_time(u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds); DaysAndTime round_time(u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, u64 increment, StringView unit, StringView rounding_mode, Optional day_length_ns = {}); +ThrowCompletionOr add_duration_to_or_subtract_duration_from_plain_time(GlobalObject&, ArithmeticOperation, PlainTime const&, Value temporal_duration_like); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp index b50484a92c..6dc23a3de8 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp @@ -145,17 +145,8 @@ JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::add) // 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]). auto* temporal_time = TRY(typed_this_object(global_object)); - // 3. Let duration be ? ToTemporalDurationRecord(temporalDurationLike). - auto duration = TRY(to_temporal_duration_record(global_object, temporal_duration_like)); - - // 4. Let result be ! AddTime(temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]], temporalTime.[[ISONanosecond]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]]). - auto result = add_time(temporal_time->iso_hour(), temporal_time->iso_minute(), temporal_time->iso_second(), temporal_time->iso_millisecond(), temporal_time->iso_microsecond(), temporal_time->iso_nanosecond(), duration.hours, duration.minutes, duration.seconds, duration.milliseconds, duration.microseconds, duration.nanoseconds); - - // 5. Assert: IsValidTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]) is true. - VERIFY(is_valid_time(result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond)); - - // 6. Return ? CreateTemporalTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]). - return TRY(create_temporal_time(global_object, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond)); + // 3. Return ? AddDurationToOrSubtractDurationFromPlainTime(add, temporalTime, temporalDurationLike). + return TRY(add_duration_to_or_subtract_duration_from_plain_time(global_object, ArithmeticOperation::Add, *temporal_time, temporal_duration_like)); } // 4.3.11 Temporal.PlainTime.prototype.subtract ( temporalDurationLike ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.subtract @@ -167,17 +158,8 @@ JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::subtract) // 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]). auto* temporal_time = TRY(typed_this_object(global_object)); - // 3. Let duration be ? ToTemporalDurationRecord(temporalDurationLike). - auto duration = TRY(to_temporal_duration_record(global_object, temporal_duration_like)); - - // 4. Let result be ! AddTime(temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]], temporalTime.[[ISONanosecond]], -duration.[[Hours]], -duration.[[Minutes]], -duration.[[Seconds]], -duration.[[Milliseconds]], -duration.[[Microseconds]], -duration.[[Nanoseconds]]). - auto result = add_time(temporal_time->iso_hour(), temporal_time->iso_minute(), temporal_time->iso_second(), temporal_time->iso_millisecond(), temporal_time->iso_microsecond(), temporal_time->iso_nanosecond(), -duration.hours, -duration.minutes, -duration.seconds, -duration.milliseconds, -duration.microseconds, -duration.nanoseconds); - - // 5. Assert: IsValidTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]) is true. - VERIFY(is_valid_time(result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond)); - - // 6. Return ? CreateTemporalTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]). - return TRY(create_temporal_time(global_object, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond)); + // 3. Return ? AddDurationToOrSubtractDurationFromPlainTime(subtract, temporalTime, temporalDurationLike). + return TRY(add_duration_to_or_subtract_duration_from_plain_time(global_object, ArithmeticOperation::Subtract, *temporal_time, temporal_duration_like)); } // 4.3.12 Temporal.PlainTime.prototype.with ( temporalTimeLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.with diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp index bd93aeb874..8f0c1ab0d1 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.cpp @@ -5,9 +5,11 @@ */ #include +#include #include #include #include +#include #include #include #include @@ -255,4 +257,92 @@ ThrowCompletionOr temporal_year_month_to_string(GlobalObject& global_obj return String::formatted("{}{}", result, calendar_string); } +// 9.5.9 AddDurationToOrSubtractDurationFromPlainYearMonth ( operation, yearMonth, temporalDurationLike, options ), https://tc39.es/proposal-temporal/#sec-temporal-addtemporalplainyearmonth +ThrowCompletionOr add_duration_to_or_subtract_duration_from_plain_year_month(GlobalObject& global_object, ArithmeticOperation operation, PlainYearMonth& year_month, Value temporal_duration_like, Value options_value) +{ + auto& vm = global_object.vm(); + + // 1. Let duration be ? ToTemporalDurationRecord(temporalDurationLike). + auto duration = TRY(to_temporal_duration_record(global_object, temporal_duration_like)); + + // 2. If operation is subtract, then + if (operation == ArithmeticOperation::Subtract) { + // a. Set duration to ! CreateNegatedTemporalDuration(duration). + // FIXME: According to the spec CreateNegatedTemporalDuration takes a Temporal.Duration object, + // not a record, so we have to do some trickery. If they want to accept anything that has + // the required internal slots, this should be updated in the AO's description. + // We also have to convert back to a Duration Record afterwards to match the initial type. + auto* actual_duration = MUST(create_temporal_duration(global_object, duration.years, duration.months, duration.weeks, duration.days, duration.hours, duration.minutes, duration.seconds, duration.milliseconds, duration.microseconds, duration.nanoseconds)); + auto* negated_duration = create_negated_temporal_duration(global_object, *actual_duration); + duration = MUST(to_temporal_duration_record(global_object, negated_duration)); + } + + // 3. 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, Crypto::SignedBigInteger::create_from((i64)duration.nanoseconds), "day"sv)); + + // 4. Set options to ? GetOptionsObject(options). + auto* options = TRY(get_options_object(global_object, options_value)); + + // 5. Let calendar be yearMonth.[[Calendar]]. + auto& calendar = year_month.calendar(); + + // 6. Let fieldNames be ? CalendarFields(calendar, « "monthCode", "year" »). + auto field_names = TRY(calendar_fields(global_object, calendar, { "monthCode"sv, "year"sv })); + + // 7. Let fields be ? PrepareTemporalFields(yearMonth, fieldNames, «»). + auto* fields = TRY(prepare_temporal_fields(global_object, year_month, field_names, {})); + + // 8. Set sign to ! 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. Perform ! CreateDataPropertyOrThrow(fields, "day", day). + MUST(fields->create_data_property_or_throw(vm.names.day, Value(day))); + + // 12. Let date be ? CalendarDateFromFields(calendar, fields, undefined). + auto* date = TRY(calendar_date_from_fields(global_object, calendar, *fields, nullptr)); + + // 13. 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%). + auto* options_copy = Object::create(global_object, global_object.object_prototype()); + + // 15. 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 + 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)); + } + + // 17. 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, «»). + auto* added_date_fields = TRY(prepare_temporal_fields(global_object, *added_date, field_names, {})); + + // 19. Return ? CalendarYearMonthFromFields(calendar, addedDateFields, optionsCopy). + return calendar_year_month_from_fields(global_object, calendar, *added_date_fields, options_copy); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h index 77197f99bb..f2e3750b5e 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonth.h @@ -7,6 +7,7 @@ #pragma once #include +#include namespace JS::Temporal { @@ -47,5 +48,6 @@ ISOYearMonth balance_iso_year_month(double year, double month); ISOYearMonth constrain_iso_year_month(double year, double month); ThrowCompletionOr create_temporal_year_month(GlobalObject&, i32 iso_year, u8 iso_month, Object& calendar, u8 reference_iso_day, FunctionObject const* new_target = nullptr); ThrowCompletionOr temporal_year_month_to_string(GlobalObject&, PlainYearMonth&, StringView show_calendar); +ThrowCompletionOr add_duration_to_or_subtract_duration_from_plain_year_month(GlobalObject&, ArithmeticOperation, PlainYearMonth&, Value temporal_duration_like, Value options_value); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.cpp index 94a92357b3..cf436ab5f2 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainYearMonthPrototype.cpp @@ -239,164 +239,29 @@ JS_DEFINE_NATIVE_FUNCTION(PlainYearMonthPrototype::with) // 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) { + 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(global_object)); - // 3. Let duration be ? ToTemporalDurationRecord(temporalDurationLike). - auto duration = TRY(to_temporal_duration_record(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, 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 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; - - // 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)); - - // b. Let day be ? ToPositiveInteger(dayFromCalendar). - day = TRY(to_positive_integer(global_object, day_from_calendar)); - } - // 11. Else, - else { - // a. Let day be 1. - day = 1; - } - - // 12. Perform ! CreateDataPropertyOrThrow(fields, "day", day). - MUST(fields->create_data_property_or_throw(vm.names.day, Value(day))); - - // 13. Let date be ? CalendarDateFromFields(calendar, fields, undefined). - auto* date = TRY(calendar_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)); - - // 15. Let optionsCopy be OrdinaryObjectCreate(%Object.prototype%). - auto* options_copy = Object::create(global_object, global_object.object_prototype()); - - // 16. Let entries be ? EnumerableOwnPropertyNames(options, key+value). - auto entries = TRY(options->enumerable_own_property_names(Object::PropertyKind::KeyAndValue)); - - // 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); - - // a. Perform ! CreateDataPropertyOrThrow(optionsCopy, nextEntry[0], nextEntry[1]). - MUST(options_copy->create_data_property_or_throw(key, value)); - } - - // 18. Let addedDate be ? CalendarDateAdd(calendar, date, durationToAdd, options). - auto* added_date = TRY(calendar_date_add(global_object, calendar, date, *duration_to_add, options)); - - // 19. Let addedDateFields be ? PrepareTemporalFields(addedDate, fieldNames, «»). - auto* added_date_fields = TRY(prepare_temporal_fields(global_object, *added_date, field_names, {})); - - // 20. Return ? CalendarYearMonthFromFields(calendar, addedDateFields, optionsCopy). - return TRY(calendar_year_month_from_fields(global_object, calendar, *added_date_fields, options_copy)); + // 3. Return ? AddDurationToOrSubtractDurationFromPlainYearMonth(add, yearMonth, temporalDurationLike, options). + return TRY(add_duration_to_or_subtract_duration_from_plain_year_month(global_object, ArithmeticOperation::Add, *year_month, temporal_duration_like, options)); } // 9.3.13 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(global_object)); - // 3. Let duration be ? ToTemporalDurationRecord(temporalDurationLike). - auto duration_record = TRY(to_temporal_duration_record(global_object, vm.argument(0))); - - // 4. Set duration to ! CreateNegatedTemporalDuration(duration). - // FIXME: According to the spec CreateNegatedTemporalDuration takes a Temporal.Duration object, - // not a record, so we have to do some trickery. If they want to accept anything that has - // the required internal slots, this should be updated in the AO's description. - auto* duration = MUST(create_temporal_duration(global_object, duration_record.years, duration_record.months, duration_record.weeks, duration_record.days, duration_record.hours, duration_record.minutes, duration_record.seconds, duration_record.milliseconds, duration_record.microseconds, duration_record.nanoseconds)); - duration = create_negated_temporal_duration(global_object, *duration); - - // 5. 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(), Crypto::SignedBigInteger::create_from((i64)duration->nanoseconds()), "day"sv)); - - // 6. Set options to ? GetOptionsObject(options). - auto* options = TRY(get_options_object(global_object, vm.argument(1))); - - // 7. Let calendar be yearMonth.[[Calendar]]. - auto& calendar = year_month->calendar(); - - // 8. Let fieldNames be ? CalendarFields(calendar, « "monthCode", "year" »). - auto field_names = TRY(calendar_fields(global_object, calendar, { "monthCode"sv, "year"sv })); - - // 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; - - // 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)); - - // b. Let day be ? ToPositiveInteger(dayFromCalendar). - day = TRY(to_positive_integer(global_object, day_from_calendar)); - } - // 12. Else, - else { - // a. Let day be 1. - day = 1; - } - - // 13. Perform ! CreateDataPropertyOrThrow(fields, "day", day). - MUST(fields->create_data_property_or_throw(vm.names.day, Value(day))); - - // 14. Let date be ? CalendarDateFromFields(calendar, fields, undefined). - auto* date = TRY(calendar_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)); - - // 16. Let optionsCopy be OrdinaryObjectCreate(%Object.prototype%). - auto* options_copy = Object::create(global_object, global_object.object_prototype()); - - // 17. Let entries be ? EnumerableOwnPropertyNames(options, key+value). - auto entries = TRY(options->enumerable_own_property_names(Object::PropertyKind::KeyAndValue)); - - // 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); - - // a. Perform ! CreateDataPropertyOrThrow(optionsCopy, nextEntry[0], nextEntry[1]). - MUST(options_copy->create_data_property_or_throw(key, value)); - } - - // 19. Let addedDate be ? CalendarDateAdd(calendar, date, durationToAdd, options). - auto* added_date = TRY(calendar_date_add(global_object, calendar, date, *duration_to_add, options)); - - // 20. Let addedDateFields be ? PrepareTemporalFields(addedDate, fieldNames, «»). - auto* added_date_fields = TRY(prepare_temporal_fields(global_object, *added_date, field_names, {})); - - // 21. Return ? CalendarYearMonthFromFields(calendar, addedDateFields, optionsCopy). - return TRY(calendar_year_month_from_fields(global_object, calendar, *added_date_fields, options_copy)); + // 3. Return ? AddDurationToOrSubtractDurationFromPlainYearMonth(add, yearMonth, temporalDurationLike, options). + return TRY(add_duration_to_or_subtract_duration_from_plain_year_month(global_object, ArithmeticOperation::Subtract, *year_month, temporal_duration_like, options)); } // 9.3.14 Temporal.PlainYearMonth.prototype.until ( other [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.until diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp index 0b5da351d6..f8729746b7 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp @@ -536,4 +536,29 @@ ThrowCompletionOr nanoseconds_to_days(GlobalObject& glo return NanosecondsToDaysResult { .days = days, .nanoseconds = move(nanoseconds), .day_length = fabs(day_length_ns.to_double()) }; } +// 6.5.8 AddDurationToOrSubtractDurationFromZonedDateTime ( operation, zonedDateTime, temporalDurationLike, options ), https://tc39.es/proposal-temporal/#sec-temporal-adddurationtoOrsubtractdurationfromzoneddatetime +ThrowCompletionOr add_duration_to_or_subtract_duration_from_zoned_date_time(GlobalObject& global_object, ArithmeticOperation operation, ZonedDateTime& zoned_date_time, Value temporal_duration_like, Value options_value) +{ + // 1. If operation is subtract, let sign be -1. Otherwise, let sign be 1. + i8 sign = operation == ArithmeticOperation::Subtract ? -1 : 1; + + // 2. Let duration be ? ToTemporalDurationRecord(temporalDurationLike). + auto duration = TRY(to_temporal_duration_record(global_object, temporal_duration_like)); + + // 3. Set options to ? GetOptionsObject(options). + auto* options = TRY(get_options_object(global_object, options_value)); + + // 4. Let timeZone be zonedDateTime.[[TimeZone]]. + auto& time_zone = zoned_date_time.time_zone(); + + // 5. Let calendar be zonedDateTime.[[Calendar]]. + auto& calendar = zoned_date_time.calendar(); + + // 6. Let epochNanoseconds be ? AddZonedDateTime(zonedDateTime.[[Nanoseconds]], timeZone, calendar, sign × duration.[[Years]], sign × duration.[[Months]], sign × duration.[[Weeks]], sign × duration.[[Days]], sign × duration.[[Hours]], sign × duration.[[Minutes]], sign × duration.[[Seconds]], sign × duration.[[Milliseconds]], sign × duration.[[Microseconds]], sign × duration.[[Nanoseconds]], options). + auto* epoch_nanoseconds = TRY(add_zoned_date_time(global_object, zoned_date_time.nanoseconds(), &time_zone, calendar, sign * duration.years, sign * duration.months, sign * duration.weeks, sign * duration.days, sign * duration.hours, sign * duration.minutes, sign * duration.seconds, sign * duration.milliseconds, sign * duration.microseconds, sign * duration.nanoseconds, options)); + + // 7. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar). + return MUST(create_temporal_zoned_date_time(global_object, *epoch_nanoseconds, time_zone, calendar)); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.h b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.h index e68c9f9d77..8a75f6efa3 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.h @@ -57,5 +57,6 @@ ThrowCompletionOr temporal_zoned_date_time_to_string(GlobalObject&, Zone ThrowCompletionOr add_zoned_date_time(GlobalObject&, BigInt const& epoch_nanoseconds, Value time_zone, Object& calendar, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Object* options = nullptr); ThrowCompletionOr difference_zoned_date_time(GlobalObject&, BigInt const& nanoseconds1, BigInt const& nanoseconds2, Object& time_zone, Object& calendar, StringView largest_unit, Object const* options = nullptr); ThrowCompletionOr nanoseconds_to_days(GlobalObject&, Crypto::SignedBigInteger nanoseconds, Value relative_to); +ThrowCompletionOr add_duration_to_or_subtract_duration_from_zoned_date_time(GlobalObject&, ArithmeticOperation, ZonedDateTime&, Value temporal_duration_like, Value options_value); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp index 62a6847c35..d7b9f61dd4 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp @@ -895,53 +895,29 @@ JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::with_calendar) // 6.3.35 Temporal.ZonedDateTime.prototype.add ( temporalDurationLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.add JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::add) { + auto temporal_duration_like = vm.argument(0); + auto options = vm.argument(1); + // 1. Let zonedDateTime be the this value. // 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]). auto* zoned_date_time = TRY(typed_this_object(global_object)); - // 3. Let duration be ? ToTemporalDurationRecord(temporalDurationLike). - auto duration = TRY(to_temporal_duration_record(global_object, vm.argument(0))); - - // 4. Set options to ? GetOptionsObject(options). - auto* options = TRY(get_options_object(global_object, vm.argument(1))); - - // 5. Let timeZone be zonedDateTime.[[TimeZone]]. - auto& time_zone = zoned_date_time->time_zone(); - - // 6. Let calendar be zonedDateTime.[[Calendar]]. - auto& calendar = zoned_date_time->calendar(); - - // 7. Let epochNanoseconds be ? AddZonedDateTime(zonedDateTime.[[Nanoseconds]], timeZone, calendar, duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], options). - auto* epoch_nanoseconds = TRY(add_zoned_date_time(global_object, zoned_date_time->nanoseconds(), &time_zone, calendar, duration.years, duration.months, duration.weeks, duration.days, duration.hours, duration.minutes, duration.seconds, duration.milliseconds, duration.microseconds, duration.nanoseconds, options)); - - // 8. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar). - return MUST(create_temporal_zoned_date_time(global_object, *epoch_nanoseconds, time_zone, calendar)); + // 3. Return ? AddDurationToOrSubtractDurationFromZonedDateTime(add, zonedDateTime, temporalDurationLike, options). + return TRY(add_duration_to_or_subtract_duration_from_zoned_date_time(global_object, ArithmeticOperation::Add, *zoned_date_time, temporal_duration_like, options)); } // 6.3.36 Temporal.ZonedDateTime.prototype.subtract ( temporalDurationLike [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.subtract JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::subtract) { + auto temporal_duration_like = vm.argument(0); + auto options = vm.argument(1); + // 1. Let zonedDateTime be the this value. // 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]). auto* zoned_date_time = TRY(typed_this_object(global_object)); - // 3. Let duration be ? ToTemporalDurationRecord(temporalDurationLike). - auto duration = TRY(to_temporal_duration_record(global_object, vm.argument(0))); - - // 4. Set options to ? GetOptionsObject(options). - auto* options = TRY(get_options_object(global_object, vm.argument(1))); - - // 5. Let timeZone be zonedDateTime.[[TimeZone]]. - auto& time_zone = zoned_date_time->time_zone(); - - // 6. Let calendar be zonedDateTime.[[Calendar]]. - auto& calendar = zoned_date_time->calendar(); - - // 7. Let epochNanoseconds be ? AddZonedDateTime(zonedDateTime.[[Nanoseconds]], timeZone, calendar, -duration.[[Years]], -duration.[[Months]], -duration.[[Weeks]], -duration.[[Days]], -duration.[[Hours]], -duration.[[Minutes]], -duration.[[Seconds]], -duration.[[Milliseconds]], -duration.[[Microseconds]], -duration.[[Nanoseconds]], options). - auto* epoch_nanoseconds = TRY(add_zoned_date_time(global_object, zoned_date_time->nanoseconds(), &time_zone, calendar, -duration.years, -duration.months, -duration.weeks, -duration.days, -duration.hours, -duration.minutes, -duration.seconds, -duration.milliseconds, -duration.microseconds, -duration.nanoseconds, options)); - - // 8. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar). - return MUST(create_temporal_zoned_date_time(global_object, *epoch_nanoseconds, time_zone, calendar)); + // 3. Return ? AddDurationToOrSubtractDurationFromZonedDateTime(subtract, zonedDateTime, temporalDurationLike, options). + return TRY(add_duration_to_or_subtract_duration_from_zoned_date_time(global_object, ArithmeticOperation::Subtract, *zoned_date_time, temporal_duration_like, options)); } // 6.3.37 Temporal.ZonedDateTime.prototype.until ( other [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.until