From d2e4da62c89ffc604ccf022ed60a801030504e55 Mon Sep 17 00:00:00 2001 From: forchane Date: Wed, 20 Mar 2024 13:08:47 -0500 Subject: [PATCH] LibJS: Separate validation of roundingIncrement option This is an editorial change in the temporal spec. See: https://github.com/tc39/proposal-temporal/commit/712c449 --- .../Runtime/Temporal/AbstractOperations.cpp | 97 ++++++++++++------- .../Runtime/Temporal/AbstractOperations.h | 3 +- .../Runtime/Temporal/DurationPrototype.cpp | 36 ++++--- .../Runtime/Temporal/InstantPrototype.cpp | 13 ++- .../Runtime/Temporal/PlainTimePrototype.cpp | 16 ++- .../PlainTime/PlainTime.prototype.round.js | 18 ++++ 6 files changed, 125 insertions(+), 58 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index 56ecb584551..0805dc78c9c 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -274,48 +274,60 @@ ThrowCompletionOr to_show_offset_option(VM& vm, Object const& normalized } // 13.12 ToTemporalRoundingIncrement ( normalizedOptions, dividend, inclusive ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalroundingincrement -ThrowCompletionOr to_temporal_rounding_increment(VM& vm, Object const& normalized_options, Optional dividend, bool inclusive) +ThrowCompletionOr to_temporal_rounding_increment(VM& vm, Object const& normalized_options) { - double maximum; - // 1. If dividend is undefined, then - if (!dividend.has_value()) { - // a. Let maximum be +∞𝔽. - maximum = INFINITY; - } - // 2. Else if inclusive is true, then - else if (inclusive) { - // a. Let maximum be 𝔽(dividend). - maximum = *dividend; - } - // 3. Else if dividend is more than 1, then - else if (*dividend > 1) { - // a. Let maximum be 𝔽(dividend - 1). - maximum = *dividend - 1; - } - // 4. Else, - else { - // a. Let maximum be 1𝔽. - maximum = 1; - } - - // 5. Let increment be ? GetOption(normalizedOptions, "roundingIncrement", "number", undefined, 1𝔽). + // 1. Let increment be ? GetOption(normalizedOptions, "roundingIncrement", "number", undefined, 1𝔽) auto increment_value = TRY(get_option(vm, normalized_options, vm.names.roundingIncrement, OptionType::Number, {}, 1.0)); VERIFY(increment_value.is_number()); auto increment = increment_value.as_double(); - // 6. If increment < 1𝔽 or increment > maximum, throw a RangeError exception. - if (increment < 1 || increment > maximum) + // 2. If increment is not finite, throw a RangeError exception + if (!increment_value.is_finite_number()) return vm.throw_completion(ErrorType::OptionIsNotValidValue, increment, "roundingIncrement"); - // 7. Set increment to floor(ℝ(increment)). + // 3. If increment < 1𝔽, throw a RangeError exception. + if (increment < 1) { + return vm.throw_completion(ErrorType::OptionIsNotValidValue, increment, "roundingIncrement"); + } + + // 4. Return increment + return increment; +} + +// 13.13 ValidateTemporalRoundingIncrement ( increment, dividend, inclusive ), https://tc39.es/proposal-temporal/#sec-validatetemporalroundingincrement +ThrowCompletionOr validate_temporal_rounding_increment(VM& vm, double increment, double dividend, bool inclusive) +{ + double maximum; + // 1. If inclusive is true, then + if (inclusive) { + // a. Let maximum be 𝔽(dividend). + maximum = dividend; + } + // 2. Else if dividend is more than 1, then + else if (dividend > 1) { + // a. Let maximum be 𝔽(dividend - 1) + maximum = dividend - 1; + } + // 3. Else + else { + // a. Let maximum be 1𝔽. + maximum = 1; + } + // 4. If increment > maximum, throw a RangeError exception. + if (increment > maximum) { + return vm.throw_completion(ErrorType::OptionIsNotValidValue, increment, "roundingIncrement"); + } + + // 5. Set increment to floor(ℝ(increment)). auto floored_increment = static_cast(increment); - // 8. If dividend is not undefined and dividend modulo increment ≠ 0, then - if (dividend.has_value() && static_cast(*dividend) % floored_increment != 0) + // 6. If dividend modulo increment is not zero, then + if (modulo(floor(dividend), floored_increment) != 0) { // a. Throw a RangeError exception. return vm.throw_completion(ErrorType::OptionIsNotValidValue, increment, "roundingIncrement"); + } - // 9. Return increment. + // 7. Return increment. return floored_increment; } @@ -336,8 +348,11 @@ ThrowCompletionOr to_temporal_date_time_rounding_increment(VM& vm, Object c maximum = *maximum_temporal_duration_rounding_increment(smallest_unit); } - // 3. Return ? ToTemporalRoundingIncrement(normalizedOptions, maximum, false). - return to_temporal_rounding_increment(vm, normalized_options, maximum, false); + // 3. Let increment be ? ToTemporalRoundingIncrement(normalizedOptions). + auto increment = TRY(to_temporal_rounding_increment(vm, normalized_options)); + + // 4. Return ? ValidateTemporalRoundingIncrement(increment, maximum, false). + return validate_temporal_rounding_increment(vm, increment, maximum, false); } // 13.14 ToSecondsStringPrecision ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-tosecondsstringprecision @@ -1946,15 +1961,27 @@ ThrowCompletionOr get_difference_settings(VM& vm, Difference // 11. Let maximum be ! MaximumTemporalDurationRoundingIncrement(smallestUnit). auto maximum = maximum_temporal_duration_rounding_increment(*smallest_unit); - // 12. Let roundingIncrement be ? ToTemporalRoundingIncrement(options, maximum, false). - auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *options, Optional { maximum }, false)); + // 12. Let roundingIncrement be ? ToTemporalRoundingIncrement(options). + auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *options)); + + u64 floored_rounding_increment; + // 13. If maximum is undefined, then + if (!maximum.has_value()) { + // a. Set roundingIncrement to floor(ℝ(roundingIncrement)). + floored_rounding_increment = static_cast(rounding_increment); + } + // 14. Else + else { + // a. Set roundingIncrement to ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). + floored_rounding_increment = TRY(validate_temporal_rounding_increment(vm, rounding_increment, *maximum, false)); + } // 13. Return the Record { [[SmallestUnit]]: smallestUnit, [[LargestUnit]]: largestUnit, [[RoundingMode]]: roundingMode, [[RoundingIncrement]]: roundingIncrement, [[Options]]: options }. return DifferenceSettings { .smallest_unit = smallest_unit.release_value(), .largest_unit = largest_unit.release_value(), .rounding_mode = move(rounding_mode), - .rounding_increment = rounding_increment, + .rounding_increment = floored_rounding_increment, .options = *options, }; } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h index e99a4480597..71535f1924b 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h @@ -145,7 +145,8 @@ ThrowCompletionOr to_temporal_offset(VM&, Object const* options, StringV ThrowCompletionOr to_calendar_name_option(VM&, Object const& normalized_options); ThrowCompletionOr to_time_zone_name_option(VM&, Object const& normalized_options); ThrowCompletionOr to_show_offset_option(VM&, Object const& normalized_options); -ThrowCompletionOr to_temporal_rounding_increment(VM&, Object const& normalized_options, Optional dividend, bool inclusive); +ThrowCompletionOr to_temporal_rounding_increment(VM& vm, Object const& normalized_options); +ThrowCompletionOr validate_temporal_rounding_increment(VM& vm, double increment, double dividend, bool inclusive); ThrowCompletionOr to_temporal_date_time_rounding_increment(VM&, Object const& normalized_options, StringView smallest_unit); ThrowCompletionOr to_seconds_string_precision(VM&, Object const& normalized_options); ThrowCompletionOr> get_temporal_unit(VM&, Object const& normalized_options, PropertyKey const&, UnitGroup, TemporalUnitDefault const& default_, Vector const& extra_values = {}); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp index f3693c7485a..4d967bfebed 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/DurationPrototype.cpp @@ -413,36 +413,48 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::round) // 18. Let maximum be ! MaximumTemporalDurationRoundingIncrement(smallestUnit). auto maximum = maximum_temporal_duration_rounding_increment(*smallest_unit); - // 19. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo, maximum, false). - auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *round_to, Optional(maximum), false)); + // 19. Let roundingIncrement be ? ToTemporalRoundingIncrement(options). + auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *round_to)); - // 20. Let relativeTo be ? ToRelativeTemporalObject(roundTo). + u64 floored_rounding_increment; + // 20. If maximum is undefined, then + if (!maximum.has_value()) { + // a. Set roundingIncrement to floor(ℝ(roundingIncrement)). + floored_rounding_increment = static_cast(rounding_increment); + } + // 21. Else + else { + // a. Set roundingIncrement to ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). + floored_rounding_increment = TRY(validate_temporal_rounding_increment(vm, rounding_increment, *maximum, false)); + } + + // 22. Let relativeTo be ? ToRelativeTemporalObject(roundTo). auto relative_to = TRY(to_relative_temporal_object(vm, *round_to)); auto relative_to_value = relative_to_converted_to_value(relative_to); - // 21. Let unbalanceResult be ? UnbalanceDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], largestUnit, relativeTo). + // 23. Let unbalanceResult be ? UnbalanceDurationRelative(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], duration.[[Days]], largestUnit, relativeTo). auto unbalance_result = TRY(unbalance_duration_relative(vm, duration->years(), duration->months(), duration->weeks(), duration->days(), *largest_unit, relative_to_value)); auto calendar_record = TRY(create_calendar_methods_record_from_relative_to(vm, relative_to.plain_relative_to, relative_to.zoned_relative_to, { { CalendarMethod::DateAdd, CalendarMethod::DateUntil } })); - // 22. Let roundResult be (? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], unbalanceResult.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode, relativeTo)).[[DurationRecord]]. - auto round_result = TRY(round_duration(vm, unbalance_result.years, unbalance_result.months, unbalance_result.weeks, unbalance_result.days, duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), duration->nanoseconds(), rounding_increment, *smallest_unit, rounding_mode, relative_to_value.is_object() ? &relative_to_value.as_object() : nullptr, calendar_record)).duration_record; + // 24. Let roundResult be (? RoundDuration(unbalanceResult.[[Years]], unbalanceResult.[[Months]], unbalanceResult.[[Weeks]], unbalanceResult.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode, relativeTo)).[[DurationRecord]]. + auto round_result = TRY(round_duration(vm, unbalance_result.years, unbalance_result.months, unbalance_result.weeks, unbalance_result.days, duration->hours(), duration->minutes(), duration->seconds(), duration->milliseconds(), duration->microseconds(), duration->nanoseconds(), floored_rounding_increment, *smallest_unit, rounding_mode, relative_to_value.is_object() ? &relative_to_value.as_object() : nullptr, calendar_record)).duration_record; - // 23. Let adjustResult be ? AdjustRoundedDurationDays(roundResult.[[Years]], roundResult.[[Months]], roundResult.[[Weeks]], roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode, relativeTo). - auto adjust_result = TRY(adjust_rounded_duration_days(vm, round_result.years, round_result.months, round_result.weeks, round_result.days, round_result.hours, round_result.minutes, round_result.seconds, round_result.milliseconds, round_result.microseconds, round_result.nanoseconds, rounding_increment, *smallest_unit, rounding_mode, relative_to_value.is_object() ? &relative_to_value.as_object() : nullptr)); + // 25. Let adjustResult be ? AdjustRoundedDurationDays(roundResult.[[Years]], roundResult.[[Months]], roundResult.[[Weeks]], roundResult.[[Days]], roundResult.[[Hours]], roundResult.[[Minutes]], roundResult.[[Seconds]], roundResult.[[Milliseconds]], roundResult.[[Microseconds]], roundResult.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode, relativeTo). + auto adjust_result = TRY(adjust_rounded_duration_days(vm, round_result.years, round_result.months, round_result.weeks, round_result.days, round_result.hours, round_result.minutes, round_result.seconds, round_result.milliseconds, round_result.microseconds, round_result.nanoseconds, floored_rounding_increment, *smallest_unit, rounding_mode, relative_to_value.is_object() ? &relative_to_value.as_object() : nullptr)); - // 24. Let balanceResult be ? BalanceDurationRelative(adjustResult.[[Years]], adjustResult.[[Months]], adjustResult.[[Weeks]], adjustResult.[[Days]], largestUnit, relativeTo). + // 26. Let balanceResult be ? BalanceDurationRelative(adjustResult.[[Years]], adjustResult.[[Months]], adjustResult.[[Weeks]], adjustResult.[[Days]], largestUnit, relativeTo). auto balance_result = TRY(balance_duration_relative(vm, adjust_result.years, adjust_result.months, adjust_result.weeks, adjust_result.days, *largest_unit, relative_to_value)); - // 25. If Type(relativeTo) is Object and relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then + // 27. If Type(relativeTo) is Object and relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then if (relative_to.zoned_relative_to) { // a. Set relativeTo to ? MoveRelativeZonedDateTime(relativeTo, balanceResult.[[Years]], balanceResult.[[Months]], balanceResult.[[Weeks]], 0). relative_to_value = TRY(move_relative_zoned_date_time(vm, *relative_to.zoned_relative_to, balance_result.years, balance_result.months, balance_result.weeks, 0)); } - // 26. Let result be ? BalanceDuration(balanceResult.[[Days]], adjustResult.[[Hours]], adjustResult.[[Minutes]], adjustResult.[[Seconds]], adjustResult.[[Milliseconds]], adjustResult.[[Microseconds]], adjustResult.[[Nanoseconds]], largestUnit, relativeTo). + // 28. Let result be ? BalanceDuration(balanceResult.[[Days]], adjustResult.[[Hours]], adjustResult.[[Minutes]], adjustResult.[[Seconds]], adjustResult.[[Milliseconds]], adjustResult.[[Microseconds]], adjustResult.[[Nanoseconds]], largestUnit, relativeTo). auto result = TRY(balance_duration(vm, balance_result.days, adjust_result.hours, adjust_result.minutes, adjust_result.seconds, adjust_result.milliseconds, adjust_result.microseconds, Crypto::SignedBigInteger { adjust_result.nanoseconds }, *largest_unit, relative_to_value.is_object() ? &relative_to_value.as_object() : nullptr)); - // 27. Return ! CreateTemporalDuration(balanceResult.[[Years]], balanceResult.[[Months]], balanceResult.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]). + // 29. Return ! CreateTemporalDuration(balanceResult.[[Years]], balanceResult.[[Months]], balanceResult.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]). return MUST(create_temporal_duration(vm, balance_result.years, balance_result.months, balance_result.weeks, result.days, result.hours, result.minutes, result.seconds, result.milliseconds, result.microseconds, result.nanoseconds)); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp index 753227e5bd3..9a1737e36c7 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp @@ -254,13 +254,16 @@ JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::round) maximum = ns_per_day; } - // 14. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo, maximum, true). - auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *round_to, maximum, true)); + // 14. Let roundingIncrement be ? ToTemporalRoundingIncrement(options). + auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *round_to)); - // 15. Let roundedNs be ! RoundTemporalInstant(instant.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode). - auto* rounded_ns = round_temporal_instant(vm, instant->nanoseconds(), rounding_increment, smallest_unit, rounding_mode); + // 15. Set roundingIncrement to ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, true). + auto floored_rounding_increment = TRY(validate_temporal_rounding_increment(vm, rounding_increment, maximum, true)); - // 16. Return ! CreateTemporalInstant(roundedNs). + // 16. Let roundedNs be ! RoundTemporalInstant(instant.[[Nanoseconds]], roundingIncrement, smallestUnit, roundingMode). + auto* rounded_ns = round_temporal_instant(vm, instant->nanoseconds(), floored_rounding_increment, smallest_unit, rounding_mode); + + // 17. Return ! CreateTemporalInstant(roundedNs). return MUST(create_temporal_instant(vm, *rounded_ns)); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp index f79f164dc04..7e9a8713a2e 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTimePrototype.cpp @@ -306,13 +306,19 @@ JS_DEFINE_NATIVE_FUNCTION(PlainTimePrototype::round) // 8. Let maximum be ! MaximumTemporalDurationRoundingIncrement(smallestUnit). auto maximum = maximum_temporal_duration_rounding_increment(*smallest_unit); - // 9. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo, maximum, false). - auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *round_to, *maximum, false)); + // 9. Assert maximum is not undefined. + VERIFY(maximum.has_value()); - // 10. 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); + // 10. Let roundingIncrement be ? ToTemporalRoundingIncrement(roundTo). + auto rounding_increment = TRY(to_temporal_rounding_increment(vm, *round_to)); - // 11. Return ! CreateTemporalTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]). + // 11. Set roundingIncrement to ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false). + auto floored_rounding_increment = TRY(validate_temporal_rounding_increment(vm, rounding_increment, *maximum, false)); + + // 12. 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(), floored_rounding_increment, *smallest_unit, rounding_mode); + + // 13. Return ! CreateTemporalTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]]). return MUST(create_temporal_time(vm, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond)); } 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 022f6f99a79..6c7e32ffe32 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 @@ -122,5 +122,23 @@ describe("errors", () => { expect(() => { plainTime.round({ smallestUnit: "second", roundingIncrement: Infinity }); }).toThrowWithMessage(RangeError, "inf is not a valid value for option roundingIncrement"); + expect(() => { + plainTime.round({ smallestUnit: "hours", roundingIncrement: 24 }); + }).toThrowWithMessage(RangeError, "24 is not a valid value for option roundingIncrement"); + expect(() => { + plainTime.round({ smallestUnit: "minutes", roundingIncrement: 60 }); + }).toThrowWithMessage(RangeError, "60 is not a valid value for option roundingIncrement"); + expect(() => { + plainTime.round({ smallestUnit: "seconds", roundingIncrement: 60 }); + }).toThrowWithMessage(RangeError, "60 is not a valid value for option roundingIncrement"); + expect(() => { + plainTime.round({ smallestUnit: "milliseconds", roundingIncrement: 1000 }); + }).toThrowWithMessage(RangeError, "1000 is not a valid value for option roundingIncrement"); + expect(() => { + plainTime.round({ smallestUnit: "microseconds", roundingIncrement: 1000 }); + }).toThrowWithMessage(RangeError, "1000 is not a valid value for option roundingIncrement"); + expect(() => { + plainTime.round({ smallestUnit: "nanoseconds", roundingIncrement: 1000 }); + }).toThrowWithMessage(RangeError, "1000 is not a valid value for option roundingIncrement"); }); });