From d0c29c9735a4e15bdb4ccae02111a253da8b6042 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Fri, 19 Nov 2021 00:50:31 +0000 Subject: [PATCH] LibJS: Allow string as parameter in Temporal's round() / total() This is a normative change in the Temporal spec. See: https://github.com/tc39/proposal-temporal/commit/1f0c586 --- .../Runtime/Temporal/DurationPrototype.cpp | 63 ++++++++++------- .../Runtime/Temporal/InstantPrototype.cpp | 36 +++++++--- .../Temporal/PlainDateTimePrototype.cpp | 42 ++++++++---- .../Runtime/Temporal/PlainTimePrototype.cpp | 48 ++++++++----- .../Temporal/ZonedDateTimePrototype.cpp | 68 ++++++++++++------- .../Instant/Instant.prototype.round.js | 7 ++ .../PlainDateTime.prototype.round.js | 7 ++ .../PlainTime/PlainTime.prototype.round.js | 7 ++ .../ZonedDateTime.prototype.round.js | 10 +++ 9 files changed, 199 insertions(+), 89 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp index ba25e2bf41..6e2ae46a40 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp @@ -288,92 +288,107 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::abs) return TRY(create_temporal_duration(global_object, 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.21 Temporal.Duration.prototype.total ( options ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.total +// 7.3.21 Temporal.Duration.prototype.total ( totalOf ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.total JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::total) { // 1. Let duration be the this value. // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). auto* duration = TRY(typed_this_object(global_object)); - // 3. If options is undefined, throw a TypeError exception. + // 3. If totalOf is undefined, throw a TypeError exception. if (vm.argument(0).is_undefined()) return vm.throw_completion(global_object, ErrorType::TemporalMissingOptionsObject); - // 4. Set options to ? GetOptionsObject(options). - auto* options = TRY(get_options_object(global_object, vm.argument(0))); + Object* total_of; - // 5. Let relativeTo be ? ToRelativeTemporalObject(options). - auto relative_to = TRY(to_relative_temporal_object(global_object, *options)); + // 4. If Type(totalOf) is String, then + if (vm.argument(0).is_string()) { + // a. Let paramString be totalOf. - // 6. Let unit be ? ToTemporalDurationTotalUnit(options). - auto unit = TRY(to_temporal_duration_total_unit(global_object, *options)); + // b. Set totalOf to ! OrdinaryObjectCreate(null). + total_of = Object::create(global_object, nullptr); - // 7. Let unbalanceResult be ? UnbalanceDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], unit, relativeTo). + // c. Perform ! CreateDataPropertyOrThrow(totalOf, "unit", paramString). + MUST(total_of->create_data_property_or_throw(vm.names.unit, vm.argument(0))); + } + // 5. Else, + else { + // a. Set totalOf to ? GetOptionsObject(totalOf). + total_of = TRY(get_options_object(global_object, vm.argument(0))); + } + + // 6. Let relativeTo be ? ToRelativeTemporalObject(totalOf). + auto relative_to = TRY(to_relative_temporal_object(global_object, *total_of)); + + // 7. Let unit be ? ToTemporalDurationTotalUnit(totalOf). + auto unit = TRY(to_temporal_duration_total_unit(global_object, *total_of)); + + // 8. Let unbalanceResult be ? UnbalanceDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], unit, relativeTo). auto unbalance_result = TRY(unbalance_duration_relative(global_object, duration->years(), duration->months(), duration->weeks(), duration->days(), unit, relative_to)); - // 8. Let intermediate be undefined. + // 9. Let intermediate be undefined. ZonedDateTime* intermediate = nullptr; - // 9. If relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then + // 10. If relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then if (relative_to.is_object() && is(relative_to.as_object())) { // a. Set intermediate to ? MoveRelativeZonedDateTime(relativeTo, unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], 0). intermediate = TRY(move_relative_zoned_date_time(global_object, static_cast(relative_to.as_object()), unbalance_result.years, unbalance_result.months, unbalance_result.weeks, 0)); } - // 10. Let balanceResult be ? BalanceDuration(unbalanceResult.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], unit, intermediate). + // 11. Let balanceResult be ? BalanceDuration(unbalanceResult.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], unit, intermediate). auto balance_result = TRY(balance_duration(global_object, unbalance_result.days, duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), *js_bigint(vm, Crypto::SignedBigInteger::create_from(duration->nanoseconds())), unit, intermediate)); - // 11. Let roundResult be ? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], balanceResult.[[Days]], balanceResult.[[Hours]], balanceResult.[[Minutes]], balanceResult.[[Seconds]], balanceResult.[[Milliseconds]], balanceResult.[[Microseconds]], balanceResult.[[Nanoseconds]], 1, unit, "trunc", relativeTo). + // 12. Let roundResult be ? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], balanceResult.[[Days]], balanceResult.[[Hours]], balanceResult.[[Minutes]], balanceResult.[[Seconds]], balanceResult.[[Milliseconds]], balanceResult.[[Microseconds]], balanceResult.[[Nanoseconds]], 1, unit, "trunc", relativeTo). auto round_result = TRY(round_duration(global_object, unbalance_result.years, unbalance_result.months, unbalance_result.weeks, balance_result.days, balance_result.hours, balance_result.minutes, balance_result.seconds, balance_result.milliseconds, balance_result.microseconds, balance_result.nanoseconds, 1, unit, "trunc"sv, relative_to.is_object() ? &relative_to.as_object() : nullptr)); double whole; - // 12. If unit is "year", then + // 13. If unit is "year", then if (unit == "year"sv) { // a. Let whole be roundResult.[[Years]]. whole = round_result.years; } - // 13. Else if unit is "month", then + // 14. Else if unit is "month", then else if (unit == "month"sv) { // a. Let whole be roundResult.[[Months]]. whole = round_result.months; } - // 14. Else if unit is "week", then + // 15. Else if unit is "week", then else if (unit == "week"sv) { // a. Let whole be roundResult.[[Weeks]]. whole = round_result.weeks; } - // 15. Else if unit is "day", then + // 16. Else if unit is "day", then else if (unit == "day"sv) { // a. Let whole be roundResult.[[Days]]. whole = round_result.days; } - // 16. Else if unit is "hour", then + // 17. Else if unit is "hour", then else if (unit == "hour"sv) { // a. Let whole be roundResult.[[Hours]]. whole = round_result.hours; } - // 17. Else if unit is "minute", then + // 18. Else if unit is "minute", then else if (unit == "minute"sv) { // a. Let whole be roundResult.[[Minutes]]. whole = round_result.minutes; } - // 18. Else if unit is "second", then + // 19. Else if unit is "second", then else if (unit == "second"sv) { // a. Let whole be roundResult.[[Seconds]]. whole = round_result.seconds; } - // 19. Else if unit is "millisecond", then + // 20. Else if unit is "millisecond", then else if (unit == "millisecond"sv) { // a. Let whole be roundResult.[[Milliseconds]]. whole = round_result.milliseconds; } - // 20. Else if unit is "microsecond", then + // 21. Else if unit is "microsecond", then else if (unit == "microsecond"sv) { // a. Let whole be roundResult.[[Microseconds]]. whole = round_result.microseconds; } - // 21. Else, + // 22. Else, else { // a. Assert: unit is "nanosecond". VERIFY(unit == "nanosecond"sv); @@ -382,7 +397,7 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::total) whole = round_result.nanoseconds; } - // 22. Return whole + roundResult.[[Remainder]]. + // 23. Return whole + roundResult.[[Remainder]]. return whole + round_result.remainder; } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp index 0c7544da17..dee87a2efa 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp @@ -243,24 +243,40 @@ JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::since) return TRY(create_temporal_duration(global_object, 0, 0, 0, 0, result.hours, result.minutes, result.seconds, result.milliseconds, result.microseconds, result.nanoseconds)); } -// 8.3.11 Temporal.Instant.prototype.round ( options ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.round +// 8.3.11 Temporal.Instant.prototype.round ( roundTo ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.round JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::round) { // 1. Let instant be the this value. // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). auto* instant = TRY(typed_this_object(global_object)); - // 3. If options is undefined, then + // 3. If roundTo is undefined, then if (vm.argument(0).is_undefined()) { // a. Throw a TypeError exception. return vm.throw_completion(global_object, ErrorType::TemporalMissingOptionsObject); } - // 4. Set options to ? GetOptionsObject(options). - auto* options = TRY(get_options_object(global_object, vm.argument(0))); + Object* round_to; - // 5. Let smallestUnit be ? ToSmallestTemporalUnit(options, « "year", "month", "week", "day" », undefined). - auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *options, { "year"sv, "month"sv, "week"sv, "day"sv }, {})); + // 4. If Type(roundTo) is String, then + if (vm.argument(0).is_string()) { + // a. Let paramString be roundTo. + + // b. Set roundTo to ! OrdinaryObjectCreate(null). + round_to = Object::create(global_object, nullptr); + + // FIXME: "_smallestUnit_" is a spec bug, see https://github.com/tc39/proposal-temporal/pull/1931 + // c. Perform ! CreateDataPropertyOrThrow(roundTo, "_smallestUnit_", paramString). + MUST(round_to->create_data_property_or_throw(vm.names.smallestUnit, vm.argument(0))); + } + // 5. Else, + else { + // a. Set roundTo to ? GetOptionsObject(roundTo). + round_to = TRY(get_options_object(global_object, vm.argument(0))); + } + + // 6. Let smallestUnit be ? ToSmallestTemporalUnit(roundTo, « "year", "month", "week", "day" », undefined). + auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *round_to, { "year"sv, "month"sv, "week"sv, "day"sv }, {})); // 6. If smallestUnit is undefined, throw a RangeError exception. if (!smallest_unit_value.has_value()) @@ -269,8 +285,8 @@ JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::round) // At this point smallest_unit_value can only be a string auto& smallest_unit = *smallest_unit_value; - // 7. Let roundingMode be ? ToTemporalRoundingMode(options, "halfExpand"). - auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *options, "halfExpand")); + // 7. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand"). + auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *round_to, "halfExpand")); double maximum; // 8. If smallestUnit is "hour", then @@ -306,8 +322,8 @@ JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::round) maximum = 86400000000000; } - // 14. Let roundingIncrement be ? ToTemporalRoundingIncrement(options, maximum, true). - auto rounding_increment = TRY(to_temporal_rounding_increment(global_object, *options, maximum, true)); + // 14. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo, maximum, true). + auto rounding_increment = TRY(to_temporal_rounding_increment(global_object, *round_to, maximum, true)); // 15. Let roundedNs be ! RoundTemporalInstant(instant.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode). auto* rounded_ns = round_temporal_instant(global_object, instant->nanoseconds(), rounding_increment, smallest_unit, rounding_mode); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.cpp index 5670e626bd..80c0175f53 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.cpp @@ -506,42 +506,58 @@ JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::subtract) 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())); } -// 5.3.30 Temporal.PlainDateTime.prototype.round ( options ), https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.round +// 5.3.30 Temporal.PlainDateTime.prototype.round ( roundTo ), https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.round JS_DEFINE_NATIVE_FUNCTION(PlainDateTimePrototype::round) { // 1. Let dateTime be the this value. // 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalDateTime]]). auto* date_time = TRY(typed_this_object(global_object)); - // 3. If options is undefined, then + // 3. If roundTo is undefined, then if (vm.argument(0).is_undefined()) { // a. Throw a TypeError exception. return vm.throw_completion(global_object, ErrorType::TemporalMissingOptionsObject); } - // 4. Set options to ? GetOptionsObject(options). - auto* options = TRY(get_options_object(global_object, vm.argument(0))); + Object* round_to; - // 5. Let smallestUnit be ? ToSmallestTemporalUnit(options, « "year", "month", "week" », undefined). - auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *options, { "year"sv, "month"sv, "week"sv }, {})); + // 4. If Type(roundTo) is String, then + if (vm.argument(0).is_string()) { + // a. Let paramString be roundTo. - // 6. If smallestUnit is undefined, throw a RangeError exception. + // b. Set roundTo to ! OrdinaryObjectCreate(null). + round_to = Object::create(global_object, nullptr); + + // FIXME: "_smallestUnit_" is a spec bug, see https://github.com/tc39/proposal-temporal/pull/1931 + // c. Perform ! CreateDataPropertyOrThrow(roundTo, "_smallestUnit_", paramString). + MUST(round_to->create_data_property_or_throw(vm.names.smallestUnit, vm.argument(0))); + } + // 5. Else, + else { + // a. Set roundTo to ? GetOptionsObject(roundTo). + round_to = TRY(get_options_object(global_object, vm.argument(0))); + } + + // 6. Let smallestUnit be ? ToSmallestTemporalUnit(roundTo, « "year", "month", "week" », undefined). + auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *round_to, { "year"sv, "month"sv, "week"sv }, {})); + + // 7. If smallestUnit is undefined, throw a RangeError exception. if (!smallest_unit_value.has_value()) return vm.throw_completion(global_object, ErrorType::OptionIsNotValidValue, vm.names.undefined.as_string(), "smallestUnit"); // NOTE: At this point smallest_unit_value can only be a string auto& smallest_unit = *smallest_unit_value; - // 7. Let roundingMode be ? ToTemporalRoundingMode(options, "halfExpand"). - auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *options, "halfExpand")); + // 8. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand"). + auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *round_to, "halfExpand")); - // 8. Let roundingIncrement be ? ToTemporalDateTimeRoundingIncrement(options, smallestUnit). - auto rounding_increment = TRY(to_temporal_date_time_rounding_increment(global_object, *options, smallest_unit)); + // 9. Let roundingIncrement be ? ToTemporalDateTimeRoundingIncrement(roundTo, smallestUnit). + auto rounding_increment = TRY(to_temporal_date_time_rounding_increment(global_object, *round_to, smallest_unit)); - // 9. Let result be ! RoundISODateTime(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode). + // 10. Let result be ! RoundISODateTime(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode). auto result = round_iso_date_time(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(), rounding_increment, smallest_unit, rounding_mode); - // 10. Return ? CreateTemporalDateTime(result.[[Year]], result.[[Month]], result.[[Day]], result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], dateTime.[[Calendar]]). + // 11. 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())); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp index e1cb81b70a..9c7f6281b4 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp @@ -250,60 +250,76 @@ JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::with) return TRY(create_temporal_time(global_object, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond)); } -// 4.3.15 Temporal.PlainTime.prototype.round ( options ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.round +// 4.3.15 Temporal.PlainTime.prototype.round ( roundTo ), https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.round JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::round) { // 1. Let temporalTime be the this value. // 2. Perform ? RequireInternalSlot(temporalTime, [[InitializedTemporalTime]]). auto* temporal_time = TRY(typed_this_object(global_object)); - // 3. If options is undefined, then + // 3. If roundTo is undefined, then if (vm.argument(0).is_undefined()) { // a. Throw a TypeError exception. return vm.throw_completion(global_object, ErrorType::TemporalMissingOptionsObject); } - // 4. Set options to ? GetOptionsObject(options). - auto* options = TRY(get_options_object(global_object, vm.argument(0))); + Object* round_to; - // 5. Let smallestUnit be ? ToSmallestTemporalUnit(options, « "year", "month", "week", "day" », undefined). - auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *options, { "year"sv, "month"sv, "week"sv, "day"sv }, {})); + // 4. If Type(roundTo) is String, then + if (vm.argument(0).is_string()) { + // a. Let paramString be roundTo. - // 6. If smallestUnit is undefined, throw a RangeError exception. + // b. Set roundTo to ! OrdinaryObjectCreate(null). + round_to = Object::create(global_object, nullptr); + + // FIXME: "_smallestUnit_" is a spec bug, see https://github.com/tc39/proposal-temporal/pull/1931 + // c. Perform ! CreateDataPropertyOrThrow(roundTo, "_smallestUnit_", paramString). + MUST(round_to->create_data_property_or_throw(vm.names.smallestUnit, vm.argument(0))); + } + // 5. Else, + else { + // a. Set roundTo to ? GetOptionsObject(roundTo). + round_to = TRY(get_options_object(global_object, vm.argument(0))); + } + + // 6. Let smallestUnit be ? ToSmallestTemporalUnit(roundTo, « "year", "month", "week", "day" », undefined). + auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *round_to, { "year"sv, "month"sv, "week"sv, "day"sv }, {})); + + // 7. If smallestUnit is undefined, throw a RangeError exception. if (!smallest_unit_value.has_value()) return vm.throw_completion(global_object, ErrorType::OptionIsNotValidValue, vm.names.undefined.as_string(), "smallestUnit"); // NOTE: At this point smallest_unit_value can only be a string auto& smallest_unit = *smallest_unit_value; - // 7. Let roundingMode be ? ToTemporalRoundingMode(options, "halfExpand"). - auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *options, "halfExpand")); + // 8. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand"). + auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *round_to, "halfExpand")); double maximum; - // 8. If smallestUnit is "hour", then + // 9. If smallestUnit is "hour", then if (smallest_unit == "hour"sv) { // a. Let maximum be 24. maximum = 24; } - // 9. Else if smallestUnit is "minute" or "second", then + // 10. Else if smallestUnit is "minute" or "second", then else if (smallest_unit == "minute"sv || smallest_unit == "second"sv) { // a. Let maximum be 60. maximum = 60; } - // 10. Else, + // 11. Else, else { // a. Let maximum be 1000. maximum = 1000; } - // 11. Let roundingIncrement be ? ToTemporalRoundingIncrement(options, maximum, false). - auto rounding_increment = TRY(to_temporal_rounding_increment(global_object, *options, maximum, false)); + // 12. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo, maximum, false). + auto rounding_increment = TRY(to_temporal_rounding_increment(global_object, *round_to, maximum, false)); - // 12. Let result be ! RoundTime(temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]], temporalTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode). + // 13. Let result be ! RoundTime(temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], temporalTime.[[ISOMicrosecond]], temporalTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode). auto result = round_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(), rounding_increment, smallest_unit, rounding_mode); - // 13. Return ? CreateTemporalTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]). + // 14. 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/ZonedDateTimePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp index ca80f79bc4..6388a6a368 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp @@ -942,85 +942,101 @@ JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::subtract) return MUST(create_temporal_zoned_date_time(global_object, *epoch_nanoseconds, time_zone, calendar)); } -// 6.3.39 Temporal.ZonedDateTime.prototype.round ( options ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.round +// 6.3.39 Temporal.ZonedDateTime.prototype.round ( roundTo ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.round JS_DEFINE_NATIVE_FUNCTION(ZonedDateTimePrototype::round) { // 1. Let zonedDateTime be the this value. // 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]). auto* zoned_date_time = TRY(typed_this_object(global_object)); - // 3. If options is undefined, then + // 3. If roundTo is undefined, then if (vm.argument(0).is_undefined()) { // a. Throw a TypeError exception. return vm.throw_completion(global_object, ErrorType::TemporalMissingOptionsObject); } - // 4. Set options to ? GetOptionsObject(options). - auto* options = TRY(get_options_object(global_object, vm.argument(0))); + Object* round_to; - // 5. Let smallestUnit be ? ToSmallestTemporalUnit(options, « "year", "month", "week" », undefined). - auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *options, { "year"sv, "month"sv, "week"sv }, {})); + // 4. If Type(roundTo) is String, then + if (vm.argument(0).is_string()) { + // a. Let paramString be roundTo. - // 6. If smallestUnit is undefined, throw a RangeError exception. + // b. Set roundTo to ! OrdinaryObjectCreate(null). + round_to = Object::create(global_object, nullptr); + + // FIXME: "_smallestUnit_" is a spec bug, see https://github.com/tc39/proposal-temporal/pull/1931 + // c. Perform ! CreateDataPropertyOrThrow(roundTo, "_smallestUnit_", paramString). + MUST(round_to->create_data_property_or_throw(vm.names.smallestUnit, vm.argument(0))); + } + // 5. Else, + else { + // a. Set roundTo to ? GetOptionsObject(roundTo). + round_to = TRY(get_options_object(global_object, vm.argument(0))); + } + + // 6. Let smallestUnit be ? ToSmallestTemporalUnit(roundTo, « "year", "month", "week" », undefined). + auto smallest_unit_value = TRY(to_smallest_temporal_unit(global_object, *round_to, { "year"sv, "month"sv, "week"sv }, {})); + + // 7. If smallestUnit is undefined, throw a RangeError exception. if (!smallest_unit_value.has_value()) return vm.throw_completion(global_object, ErrorType::OptionIsNotValidValue, vm.names.undefined.as_string(), "smallestUnit"); // NOTE: At this point smallest_unit_value can only be a string auto& smallest_unit = *smallest_unit_value; - // 7. Let roundingMode be ? ToTemporalRoundingMode(options, "halfExpand"). - auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *options, "halfExpand"sv)); + // 8. Let roundingMode be ? ToTemporalRoundingMode(roundTo, "halfExpand"). + auto rounding_mode = TRY(to_temporal_rounding_mode(global_object, *round_to, "halfExpand"sv)); - // 8. Let roundingIncrement be ? ToTemporalDateTimeRoundingIncrement(options, smallestUnit). - auto rounding_increment = TRY(to_temporal_date_time_rounding_increment(global_object, *options, smallest_unit)); + // 9. Let roundingIncrement be ? ToTemporalDateTimeRoundingIncrement(options, smallestUnit). + auto rounding_increment = TRY(to_temporal_date_time_rounding_increment(global_object, *round_to, smallest_unit)); - // 9. Let timeZone be zonedDateTime.[[TimeZone]]. + // 10. Let timeZone be zonedDateTime.[[TimeZone]]. auto& time_zone = zoned_date_time->time_zone(); - // 10. Let instant be ! CreateTemporalInstant(zonedDateTime.[[Nanoseconds]]). + // 11. Let instant be ! CreateTemporalInstant(zonedDateTime.[[Nanoseconds]]). auto* instant = MUST(create_temporal_instant(global_object, zoned_date_time->nanoseconds())); - // 11. Let calendar be zonedDateTime.[[Calendar]]. + // 12. Let calendar be zonedDateTime.[[Calendar]]. auto& calendar = zoned_date_time->calendar(); - // 12. Let temporalDateTime be ? BuiltinTimeZoneGetPlainDateTimeFor(timeZone, instant, calendar). + // 13. Let temporalDateTime be ? BuiltinTimeZoneGetPlainDateTimeFor(timeZone, instant, calendar). auto* temporal_date_time = TRY(builtin_time_zone_get_plain_date_time_for(global_object, &time_zone, *instant, calendar)); - // 13. Let isoCalendar be ! GetISO8601Calendar(). + // 14. Let isoCalendar be ! GetISO8601Calendar(). auto* iso_calendar = get_iso8601_calendar(global_object); - // 14. Let dtStart be ? CreateTemporalDateTime(temporalDateTime.[[ISOYear]], temporalDateTime.[[ISOMonth]], temporalDateTime.[[ISODay]], 0, 0, 0, 0, 0, 0, isoCalendar). + // 15. Let dtStart be ? CreateTemporalDateTime(temporalDateTime.[[ISOYear]], temporalDateTime.[[ISOMonth]], temporalDateTime.[[ISODay]], 0, 0, 0, 0, 0, 0, isoCalendar). auto* dt_start = TRY(create_temporal_date_time(global_object, temporal_date_time->iso_year(), temporal_date_time->iso_month(), temporal_date_time->iso_day(), 0, 0, 0, 0, 0, 0, *iso_calendar)); - // 15. Let instantStart be ? BuiltinTimeZoneGetInstantFor(timeZone, dtStart, "compatible"). + // 16. Let instantStart be ? BuiltinTimeZoneGetInstantFor(timeZone, dtStart, "compatible"). auto* instant_start = TRY(builtin_time_zone_get_instant_for(global_object, &time_zone, *dt_start, "compatible"sv)); - // 16. Let startNs be instantStart.[[Nanoseconds]]. + // 17. Let startNs be instantStart.[[Nanoseconds]]. auto& start_ns = instant_start->nanoseconds(); - // 17. Let endNs be ? AddZonedDateTime(startNs, timeZone, zonedDateTime.[[Calendar]], 0, 0, 0, 1, 0, 0, 0, 0, 0, 0). + // 18. Let endNs be ? AddZonedDateTime(startNs, timeZone, zonedDateTime.[[Calendar]], 0, 0, 0, 1, 0, 0, 0, 0, 0, 0). // TODO: Shouldn't `zonedDateTime.[[Calendar]]` be `calendar` for consistency? auto* end_ns = TRY(add_zoned_date_time(global_object, start_ns, &time_zone, zoned_date_time->calendar(), 0, 0, 0, 1, 0, 0, 0, 0, 0, 0)); - // 18. Let dayLengthNs be ℝ(endNs − startNs). + // 19. Let dayLengthNs be ℝ(endNs − startNs). auto day_length_ns = end_ns->big_integer().minus(start_ns.big_integer()).to_double(); - // 19. If dayLengthNs is 0, then + // 20. If dayLengthNs is 0, then if (day_length_ns == 0) { // a. Throw a RangeError exception. return vm.throw_completion(global_object, ErrorType::TemporalZonedDateTimeRoundZeroLengthDay); } - // 20. Let roundResult be ! RoundISODateTime(temporalDateTime.[[ISOYear]], temporalDateTime.[[ISOMonth]], temporalDateTime.[[ISODay]], temporalDateTime.[[ISOHour]], temporalDateTime.[[ISOMinute]], temporalDateTime.[[ISOSecond]], temporalDateTime.[[ISOMillisecond]], temporalDateTime.[[ISOMicrosecond]], temporalDateTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode, dayLengthNs). + // 21. Let roundResult be ! RoundISODateTime(temporalDateTime.[[ISOYear]], temporalDateTime.[[ISOMonth]], temporalDateTime.[[ISODay]], temporalDateTime.[[ISOHour]], temporalDateTime.[[ISOMinute]], temporalDateTime.[[ISOSecond]], temporalDateTime.[[ISOMillisecond]], temporalDateTime.[[ISOMicrosecond]], temporalDateTime.[[ISONanosecond]], roundingIncrement, smallestUnit, roundingMode, dayLengthNs). auto round_result = round_iso_date_time(temporal_date_time->iso_year(), temporal_date_time->iso_month(), temporal_date_time->iso_day(), temporal_date_time->iso_hour(), temporal_date_time->iso_minute(), temporal_date_time->iso_second(), temporal_date_time->iso_millisecond(), temporal_date_time->iso_microsecond(), temporal_date_time->iso_nanosecond(), rounding_increment, smallest_unit, rounding_mode, day_length_ns); - // 21. Let offsetNanoseconds be ? GetOffsetNanosecondsFor(timeZone, instant). + // 22. Let offsetNanoseconds be ? GetOffsetNanosecondsFor(timeZone, instant). auto offset_nanoseconds = TRY(get_offset_nanoseconds_for(global_object, &time_zone, *instant)); - // 22. Let epochNanoseconds be ? InterpretISODateTimeOffset(roundResult.[[Year]], roundResult.[[Month]], roundResult.[[Day]], roundResult.[[Hour]], roundResult.[[Minute]], roundResult.[[Second]], roundResult.[[Millisecond]], roundResult.[[Microsecond]], roundResult.[[Nanosecond]], option, offsetNanoseconds, timeZone, "compatible", "prefer", match exactly). + // 23. Let epochNanoseconds be ? InterpretISODateTimeOffset(roundResult.[[Year]], roundResult.[[Month]], roundResult.[[Day]], roundResult.[[Hour]], roundResult.[[Minute]], roundResult.[[Second]], roundResult.[[Millisecond]], roundResult.[[Microsecond]], roundResult.[[Nanosecond]], option, offsetNanoseconds, timeZone, "compatible", "prefer", match exactly). auto* epoch_nanoseconds = TRY(interpret_iso_date_time_offset(global_object, round_result.year, round_result.month, round_result.day, round_result.hour, round_result.minute, round_result.second, round_result.millisecond, round_result.microsecond, round_result.nanosecond, OffsetBehavior::Option, offset_nanoseconds, &time_zone, "compatible"sv, "prefer"sv, MatchBehavior::MatchExactly)); - // 23. Return ! CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar). + // 24. 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/Tests/builtins/Temporal/Instant/Instant.prototype.round.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.round.js index e1c213cde9..a9251dd2b9 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.round.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.round.js @@ -21,6 +21,13 @@ describe("correct behavior", () => { }).epochNanoseconds ).toBe(1800000000000n); }); + + test("string argument is implicitly converted to options object", () => { + const instant = new Temporal.Instant(1111111111111n); + expect( + instant.round("second").equals(instant.round({ smallestUnit: "second" })) + ).toBeTrue(); + }); }); describe("errors", () => { diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.round.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.round.js index 50f4397f19..e525cda525 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.round.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.round.js @@ -81,6 +81,13 @@ describe("correct behavior", () => { expect(fifthRoundedPlainDateTime.microsecond).toBe(0); expect(fifthRoundedPlainDateTime.nanosecond).toBe(0); }); + + test("string argument is implicitly converted to options object", () => { + const plainDateTime = new Temporal.PlainDateTime(2021, 11, 3, 18, 8, 10, 100, 200, 300); + expect( + plainDateTime.round("minute").equals(plainDateTime.round({ smallestUnit: "minute" })) + ).toBeTrue(); + }); }); describe("errors", () => { diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainTime/PlainTime.prototype.round.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainTime/PlainTime.prototype.round.js index 1300767abb..022f6f99a7 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainTime/PlainTime.prototype.round.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainTime/PlainTime.prototype.round.js @@ -61,6 +61,13 @@ describe("correct behavior", () => { expect(fifthRoundedPlainTime.microsecond).toBe(0); expect(fifthRoundedPlainTime.nanosecond).toBe(0); }); + + test("string argument is implicitly converted to options object", () => { + const plainTime = new Temporal.PlainTime(18, 15, 9, 100, 200, 300); + expect( + plainTime.round("minute").equals(plainTime.round({ smallestUnit: "minute" })) + ).toBeTrue(); + }); }); describe("errors", () => { diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/ZonedDateTime/ZonedDateTime.prototype.round.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/ZonedDateTime/ZonedDateTime.prototype.round.js index e608855927..80d64e6704 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/Temporal/ZonedDateTime/ZonedDateTime.prototype.round.js +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/ZonedDateTime/ZonedDateTime.prototype.round.js @@ -29,6 +29,16 @@ describe("correct behavior", () => { }).epochNanoseconds ).toBe(1800000000000n); }); + + test("string argument is implicitly converted to options object", () => { + const zonedDateTime = new Temporal.ZonedDateTime( + 1111111111111n, + new Temporal.TimeZone("UTC") + ); + expect( + zonedDateTime.round("second").equals(zonedDateTime.round({ smallestUnit: "second" })) + ).toBeTrue(); + }); }); describe("errors", () => {