LibJS: Separate validation of roundingIncrement option

This is an editorial change in the temporal spec.

See: https://github.com/tc39/proposal-temporal/commit/712c449
This commit is contained in:
forchane 2024-03-20 13:08:47 -05:00 committed by Tim Flynn
parent 7b3ddd5e15
commit d2e4da62c8
Notes: sideshowbarker 2024-07-17 09:48:50 +09:00
6 changed files with 125 additions and 58 deletions

View file

@ -274,48 +274,60 @@ ThrowCompletionOr<String> to_show_offset_option(VM& vm, Object const& normalized
}
// 13.12 ToTemporalRoundingIncrement ( normalizedOptions, dividend, inclusive ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalroundingincrement
ThrowCompletionOr<u64> to_temporal_rounding_increment(VM& vm, Object const& normalized_options, Optional<double> dividend, bool inclusive)
ThrowCompletionOr<double> 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<RangeError>(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<RangeError>(ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
}
// 4. Return increment
return increment;
}
// 13.13 ValidateTemporalRoundingIncrement ( increment, dividend, inclusive ), https://tc39.es/proposal-temporal/#sec-validatetemporalroundingincrement
ThrowCompletionOr<u64> 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<RangeError>(ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
}
// 5. Set increment to floor((increment)).
auto floored_increment = static_cast<u64>(increment);
// 8. If dividend is not undefined and dividend modulo increment ≠ 0, then
if (dividend.has_value() && static_cast<u64>(*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<RangeError>(ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
}
// 9. Return increment.
// 7. Return increment.
return floored_increment;
}
@ -336,8 +348,11 @@ ThrowCompletionOr<u64> 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<DifferenceSettings> 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<double> { 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<u64>(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,
};
}

View file

@ -145,7 +145,8 @@ ThrowCompletionOr<String> to_temporal_offset(VM&, Object const* options, StringV
ThrowCompletionOr<String> to_calendar_name_option(VM&, Object const& normalized_options);
ThrowCompletionOr<String> to_time_zone_name_option(VM&, Object const& normalized_options);
ThrowCompletionOr<String> to_show_offset_option(VM&, Object const& normalized_options);
ThrowCompletionOr<u64> to_temporal_rounding_increment(VM&, Object const& normalized_options, Optional<double> dividend, bool inclusive);
ThrowCompletionOr<double> to_temporal_rounding_increment(VM& vm, Object const& normalized_options);
ThrowCompletionOr<u64> validate_temporal_rounding_increment(VM& vm, double increment, double dividend, bool inclusive);
ThrowCompletionOr<u64> to_temporal_date_time_rounding_increment(VM&, Object const& normalized_options, StringView smallest_unit);
ThrowCompletionOr<SecondsStringPrecision> to_seconds_string_precision(VM&, Object const& normalized_options);
ThrowCompletionOr<Optional<String>> get_temporal_unit(VM&, Object const& normalized_options, PropertyKey const&, UnitGroup, TemporalUnitDefault const& default_, Vector<StringView> const& extra_values = {});

View file

@ -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<double>(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<u64>(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));
}

View file

@ -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));
}

View file

@ -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));
}

View file

@ -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");
});
});