mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 09:00:22 +00:00
LibJS: Implement Temporal.Duration.prototype.round
Until we have re-implemented Temporal.PlainDate/ZonedDateTime, some of Temporal.Duration.prototype.round (and its invoked AOs) are left unimplemented.
This commit is contained in:
parent
4742775262
commit
5689621c2b
Notes:
github-actions[bot]
2024-11-21 00:05:45 +00:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/5689621c2b7 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2431 Reviewed-by: https://github.com/alimpfard Reviewed-by: https://github.com/shannonbooth ✅
7 changed files with 450 additions and 0 deletions
|
@ -43,6 +43,39 @@ StringView temporal_unit_to_string(Unit unit)
|
||||||
return temporal_units[to_underlying(unit)].singular_property_name;
|
return temporal_units[to_underlying(unit)].singular_property_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 13.14 ValidateTemporalRoundingIncrement ( increment, dividend, inclusive ), https://tc39.es/proposal-temporal/#sec-validatetemporalroundingincrement
|
||||||
|
ThrowCompletionOr<void> validate_temporal_rounding_increment(VM& vm, u64 increment, u64 dividend, bool inclusive)
|
||||||
|
{
|
||||||
|
u64 maximum = 0;
|
||||||
|
|
||||||
|
// 1. If inclusive is true, then
|
||||||
|
if (inclusive) {
|
||||||
|
// a. Let maximum be dividend.
|
||||||
|
maximum = dividend;
|
||||||
|
}
|
||||||
|
// 2. Else,
|
||||||
|
else {
|
||||||
|
// a. Assert: dividend > 1.
|
||||||
|
VERIFY(dividend > 1);
|
||||||
|
|
||||||
|
// b. Let maximum be dividend - 1.
|
||||||
|
maximum = dividend - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. If increment > maximum, throw a RangeError exception.
|
||||||
|
if (increment > maximum)
|
||||||
|
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
|
||||||
|
|
||||||
|
// 5. If dividend modulo increment ≠ 0, then
|
||||||
|
if (modulo(dividend, increment) != 0) {
|
||||||
|
// a. Throw a RangeError exception.
|
||||||
|
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, increment, "roundingIncrement");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Return UNUSED.
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
// 13.15 GetTemporalFractionalSecondDigitsOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalfractionalseconddigitsoption
|
// 13.15 GetTemporalFractionalSecondDigitsOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalfractionalseconddigitsoption
|
||||||
ThrowCompletionOr<Precision> get_temporal_fractional_second_digits_option(VM& vm, Object const& options)
|
ThrowCompletionOr<Precision> get_temporal_fractional_second_digits_option(VM& vm, Object const& options)
|
||||||
{
|
{
|
||||||
|
@ -324,6 +357,14 @@ UnitCategory temporal_unit_category(Unit unit)
|
||||||
return temporal_units[to_underlying(unit)].category;
|
return temporal_units[to_underlying(unit)].category;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 13.22 MaximumTemporalDurationRoundingIncrement ( unit ), https://tc39.es/proposal-temporal/#sec-temporal-maximumtemporaldurationroundingincrement
|
||||||
|
RoundingIncrement maximum_temporal_duration_rounding_increment(Unit unit)
|
||||||
|
{
|
||||||
|
// 1. Return the value from the "Maximum duration rounding increment" column of the row of Table 21 in which unit is
|
||||||
|
// in the "Value" column.
|
||||||
|
return temporal_units[to_underlying(unit)].maximum_duration_rounding_increment;
|
||||||
|
}
|
||||||
|
|
||||||
// AD-HOC
|
// AD-HOC
|
||||||
Crypto::UnsignedBigInteger const& temporal_unit_length_in_nanoseconds(Unit unit)
|
Crypto::UnsignedBigInteger const& temporal_unit_length_in_nanoseconds(Unit unit)
|
||||||
{
|
{
|
||||||
|
@ -930,4 +971,25 @@ ThrowCompletionOr<RoundingMode> get_rounding_mode_option(VM& vm, Object const& o
|
||||||
return static_cast<RoundingMode>(allowed_strings.first_index_of(string_value.as_string().utf8_string_view()).value());
|
return static_cast<RoundingMode>(allowed_strings.first_index_of(string_value.as_string().utf8_string_view()).value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 14.4.1.4 GetRoundingIncrementOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-getroundingincrementoption
|
||||||
|
ThrowCompletionOr<u64> get_rounding_increment_option(VM& vm, Object const& options)
|
||||||
|
{
|
||||||
|
// 1. Let value be ? Get(options, "roundingIncrement").
|
||||||
|
auto value = TRY(options.get(vm.names.roundingIncrement));
|
||||||
|
|
||||||
|
// 2. If value is undefined, return 1𝔽.
|
||||||
|
if (value.is_undefined())
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
// 3. Let integerIncrement be ? ToIntegerWithTruncation(value).
|
||||||
|
auto integer_increment = TRY(to_integer_with_truncation(vm, value, ErrorType::OptionIsNotValidValue, value, "roundingIncrement"sv));
|
||||||
|
|
||||||
|
// 4. If integerIncrement < 1 or integerIncrement > 10**9, throw a RangeError exception.
|
||||||
|
if (integer_increment < 1 || integer_increment > 1'000'000'000u)
|
||||||
|
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, value, "roundingIncrement");
|
||||||
|
|
||||||
|
// 5. Return integerIncrement.
|
||||||
|
return static_cast<u64>(integer_increment);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,7 @@ struct RelativeTo {
|
||||||
GC::Ptr<JS::Object> zoned_relative_to; // [[ZonedRelativeTo]]
|
GC::Ptr<JS::Object> zoned_relative_to; // [[ZonedRelativeTo]]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ThrowCompletionOr<void> validate_temporal_rounding_increment(VM&, u64 increment, u64 dividend, bool inclusive);
|
||||||
ThrowCompletionOr<Precision> get_temporal_fractional_second_digits_option(VM&, Object const& options);
|
ThrowCompletionOr<Precision> get_temporal_fractional_second_digits_option(VM&, Object const& options);
|
||||||
SecondsStringPrecision to_seconds_string_precision_record(UnitValue, Precision);
|
SecondsStringPrecision to_seconds_string_precision_record(UnitValue, Precision);
|
||||||
ThrowCompletionOr<UnitValue> get_temporal_unit_valued_option(VM&, Object const& options, PropertyKey const&, UnitGroup, UnitDefault const&, ReadonlySpan<UnitValue> extra_values = {});
|
ThrowCompletionOr<UnitValue> get_temporal_unit_valued_option(VM&, Object const& options, PropertyKey const&, UnitGroup, UnitDefault const&, ReadonlySpan<UnitValue> extra_values = {});
|
||||||
|
@ -110,6 +111,7 @@ ThrowCompletionOr<RelativeTo> get_temporal_relative_to_option(VM&, Object const&
|
||||||
Unit larger_of_two_temporal_units(Unit, Unit);
|
Unit larger_of_two_temporal_units(Unit, Unit);
|
||||||
bool is_calendar_unit(Unit);
|
bool is_calendar_unit(Unit);
|
||||||
UnitCategory temporal_unit_category(Unit);
|
UnitCategory temporal_unit_category(Unit);
|
||||||
|
RoundingIncrement maximum_temporal_duration_rounding_increment(Unit);
|
||||||
Crypto::UnsignedBigInteger const& temporal_unit_length_in_nanoseconds(Unit);
|
Crypto::UnsignedBigInteger const& temporal_unit_length_in_nanoseconds(Unit);
|
||||||
String format_fractional_seconds(u64, Precision);
|
String format_fractional_seconds(u64, Precision);
|
||||||
UnsignedRoundingMode get_unsigned_rounding_mode(RoundingMode, Sign);
|
UnsignedRoundingMode get_unsigned_rounding_mode(RoundingMode, Sign);
|
||||||
|
@ -183,5 +185,6 @@ ThrowCompletionOr<Value> get_option(VM& vm, Object const& options, PropertyKey c
|
||||||
}
|
}
|
||||||
|
|
||||||
ThrowCompletionOr<RoundingMode> get_rounding_mode_option(VM&, Object const& options, RoundingMode fallback);
|
ThrowCompletionOr<RoundingMode> get_rounding_mode_option(VM&, Object const& options, RoundingMode fallback);
|
||||||
|
ThrowCompletionOr<u64> get_rounding_increment_option(VM&, Object const& options);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include <AK/Math.h>
|
#include <AK/Math.h>
|
||||||
#include <AK/NumericLimits.h>
|
#include <AK/NumericLimits.h>
|
||||||
|
#include <LibCrypto/BigFraction/BigFraction.h>
|
||||||
#include <LibJS/Runtime/AbstractOperations.h>
|
#include <LibJS/Runtime/AbstractOperations.h>
|
||||||
#include <LibJS/Runtime/Intrinsics.h>
|
#include <LibJS/Runtime/Intrinsics.h>
|
||||||
#include <LibJS/Runtime/Realm.h>
|
#include <LibJS/Runtime/Realm.h>
|
||||||
|
@ -740,6 +741,21 @@ ThrowCompletionOr<TimeDuration> round_time_duration(VM& vm, TimeDuration const&
|
||||||
return TRY(round_time_duration_to_increment(vm, time_duration, divisor.multiplied_by(increment), rounding_mode));
|
return TRY(round_time_duration_to_increment(vm, time_duration, divisor.multiplied_by(increment), rounding_mode));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 7.5.31 TotalTimeDuration ( timeDuration, unit ), https://tc39.es/proposal-temporal/#sec-temporal-totaltimeduration
|
||||||
|
double total_time_duration(TimeDuration const& time_duration, Unit unit)
|
||||||
|
{
|
||||||
|
// 1. Let divisor be the value in the "Length in Nanoseconds" column of the row of Table 21 whose "Value" column contains unit.
|
||||||
|
auto const& divisor = temporal_unit_length_in_nanoseconds(unit);
|
||||||
|
|
||||||
|
// 2. NOTE: The following step cannot be implemented directly using floating-point arithmetic when 𝔽(timeDuration) is
|
||||||
|
// not a safe integer. The division can be implemented in C++ with the __float128 type if the compiler supports it,
|
||||||
|
// or with software emulation such as in the SoftFP library.
|
||||||
|
|
||||||
|
// 3. Return timeDuration / divisor.
|
||||||
|
auto result = Crypto::BigFraction { time_duration } / Crypto::BigFraction { Crypto::SignedBigInteger { divisor } };
|
||||||
|
return result.to_double();
|
||||||
|
}
|
||||||
|
|
||||||
// 7.5.39 TemporalDurationToString ( duration, precision ), https://tc39.es/proposal-temporal/#sec-temporal-temporaldurationtostring
|
// 7.5.39 TemporalDurationToString ( duration, precision ), https://tc39.es/proposal-temporal/#sec-temporal-temporaldurationtostring
|
||||||
String temporal_duration_to_string(Duration const& duration, Precision precision)
|
String temporal_duration_to_string(Duration const& duration, Precision precision)
|
||||||
{
|
{
|
||||||
|
|
|
@ -121,6 +121,7 @@ i8 compare_time_duration(TimeDuration const&, TimeDuration const&);
|
||||||
ThrowCompletionOr<TimeDuration> round_time_duration_to_increment(VM&, TimeDuration const&, Crypto::UnsignedBigInteger const& increment, RoundingMode);
|
ThrowCompletionOr<TimeDuration> round_time_duration_to_increment(VM&, TimeDuration const&, Crypto::UnsignedBigInteger const& increment, RoundingMode);
|
||||||
i8 time_duration_sign(TimeDuration const&);
|
i8 time_duration_sign(TimeDuration const&);
|
||||||
ThrowCompletionOr<TimeDuration> round_time_duration(VM&, TimeDuration const&, Crypto::UnsignedBigInteger const& increment, Unit, RoundingMode);
|
ThrowCompletionOr<TimeDuration> round_time_duration(VM&, TimeDuration const&, Crypto::UnsignedBigInteger const& increment, Unit, RoundingMode);
|
||||||
|
double total_time_duration(TimeDuration const&, Unit);
|
||||||
String temporal_duration_to_string(Duration const&, Precision);
|
String temporal_duration_to_string(Duration const&, Precision);
|
||||||
ThrowCompletionOr<GC::Ref<Duration>> add_durations(VM&, ArithmeticOperation, Duration const&, Value);
|
ThrowCompletionOr<GC::Ref<Duration>> add_durations(VM&, ArithmeticOperation, Duration const&, Value);
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ void DurationPrototype::initialize(Realm& realm)
|
||||||
define_native_function(realm, vm.names.abs, abs, 0, attr);
|
define_native_function(realm, vm.names.abs, abs, 0, attr);
|
||||||
define_native_function(realm, vm.names.add, add, 1, attr);
|
define_native_function(realm, vm.names.add, add, 1, attr);
|
||||||
define_native_function(realm, vm.names.subtract, subtract, 1, attr);
|
define_native_function(realm, vm.names.subtract, subtract, 1, attr);
|
||||||
|
define_native_function(realm, vm.names.round, round, 1, attr);
|
||||||
define_native_function(realm, vm.names.toString, to_string, 0, attr);
|
define_native_function(realm, vm.names.toString, to_string, 0, attr);
|
||||||
define_native_function(realm, vm.names.toJSON, to_json, 0, attr);
|
define_native_function(realm, vm.names.toJSON, to_json, 0, attr);
|
||||||
define_native_function(realm, vm.names.toLocaleString, to_locale_string, 0, attr);
|
define_native_function(realm, vm.names.toLocaleString, to_locale_string, 0, attr);
|
||||||
|
@ -217,6 +218,199 @@ JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::subtract)
|
||||||
return TRY(add_durations(vm, ArithmeticOperation::Subtract, duration, other));
|
return TRY(add_durations(vm, ArithmeticOperation::Subtract, duration, other));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 7.3.20 Temporal.Duration.prototype.round ( roundTo ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.round
|
||||||
|
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::round)
|
||||||
|
{
|
||||||
|
auto& realm = *vm.current_realm();
|
||||||
|
|
||||||
|
auto round_to_value = vm.argument(0);
|
||||||
|
|
||||||
|
// 1. Let duration be the this value.
|
||||||
|
// 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
|
||||||
|
auto duration = TRY(typed_this_object(vm));
|
||||||
|
|
||||||
|
// 3. If roundTo is undefined, then
|
||||||
|
if (round_to_value.is_undefined()) {
|
||||||
|
// a. Throw a TypeError exception.
|
||||||
|
return vm.throw_completion<TypeError>(ErrorType::TemporalMissingOptionsObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
GC::Ptr<Object> round_to;
|
||||||
|
|
||||||
|
// 4. If roundTo is a String, then
|
||||||
|
if (round_to_value.is_string()) {
|
||||||
|
// a. Let paramString be roundTo.
|
||||||
|
auto param_string = round_to_value;
|
||||||
|
|
||||||
|
// b. Set roundTo to OrdinaryObjectCreate(null).
|
||||||
|
round_to = Object::create(realm, nullptr);
|
||||||
|
|
||||||
|
// c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString).
|
||||||
|
MUST(round_to->create_data_property_or_throw(vm.names.smallestUnit, param_string));
|
||||||
|
}
|
||||||
|
// 5. Else,
|
||||||
|
else {
|
||||||
|
// a. Set roundTo to ? GetOptionsObject(roundTo).
|
||||||
|
round_to = TRY(get_options_object(vm, round_to_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Let smallestUnitPresent be true.
|
||||||
|
bool smallest_unit_present = true;
|
||||||
|
|
||||||
|
// 7. Let largestUnitPresent be true.
|
||||||
|
bool largest_unit_present = true;
|
||||||
|
|
||||||
|
// 8. NOTE: The following steps read options and perform independent validation in alphabetical order
|
||||||
|
// (GetTemporalRelativeToOption reads "relativeTo", GetRoundingIncrementOption reads "roundingIncrement" and
|
||||||
|
// GetRoundingModeOption reads "roundingMode").
|
||||||
|
|
||||||
|
// 9. Let largestUnit be ? GetTemporalUnitValuedOption(roundTo, "largestUnit", DATETIME, UNSET, « auto »).
|
||||||
|
auto largest_unit = TRY(get_temporal_unit_valued_option(vm, *round_to, vm.names.largestUnit, UnitGroup::DateTime, Unset {}, { { Auto {} } }));
|
||||||
|
|
||||||
|
// 10. Let relativeToRecord be ? GetTemporalRelativeToOption(roundTo).
|
||||||
|
// 11. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]].
|
||||||
|
// 12. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]].
|
||||||
|
auto [zoned_relative_to, plain_relative_to] = TRY(get_temporal_relative_to_option(vm, *round_to));
|
||||||
|
|
||||||
|
// 13. Let roundingIncrement be ? GetRoundingIncrementOption(roundTo).
|
||||||
|
auto rounding_increment = TRY(get_rounding_increment_option(vm, *round_to));
|
||||||
|
|
||||||
|
// 14. Let roundingMode be ? GetRoundingModeOption(roundTo, HALF-EXPAND).
|
||||||
|
auto rounding_mode = TRY(get_rounding_mode_option(vm, *round_to, RoundingMode::HalfExpand));
|
||||||
|
|
||||||
|
// 15. Let smallestUnit be ? GetTemporalUnitValuedOption(roundTo, "smallestUnit", DATETIME, UNSET).
|
||||||
|
auto smallest_unit = TRY(get_temporal_unit_valued_option(vm, *round_to, vm.names.smallestUnit, UnitGroup::DateTime, Unset {}));
|
||||||
|
|
||||||
|
// 16. If smallestUnit is UNSET, then
|
||||||
|
if (smallest_unit.has<Unset>()) {
|
||||||
|
// a. Set smallestUnitPresent to false.
|
||||||
|
smallest_unit_present = false;
|
||||||
|
|
||||||
|
// b. Set smallestUnit to NANOSECOND.
|
||||||
|
smallest_unit = Unit::Nanosecond;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto smallest_unit_value = smallest_unit.get<Unit>();
|
||||||
|
|
||||||
|
// 17. Let existingLargestUnit be DefaultTemporalLargestUnit(duration).
|
||||||
|
auto existing_largest_unit = default_temporal_largest_unit(duration);
|
||||||
|
|
||||||
|
// 18. Let defaultLargestUnit be LargerOfTwoTemporalUnits(existingLargestUnit, smallestUnit).
|
||||||
|
auto default_largest_unit = larger_of_two_temporal_units(existing_largest_unit, smallest_unit_value);
|
||||||
|
|
||||||
|
// 19. If largestUnit is UNSET, then
|
||||||
|
if (largest_unit.has<Unset>()) {
|
||||||
|
// a. Set largestUnitPresent to false.
|
||||||
|
largest_unit_present = false;
|
||||||
|
|
||||||
|
// b. Set largestUnit to defaultLargestUnit.
|
||||||
|
largest_unit = default_largest_unit;
|
||||||
|
}
|
||||||
|
// 20. Else if largestUnit is AUTO, then
|
||||||
|
else if (largest_unit.has<Auto>()) {
|
||||||
|
// a. Set largestUnit to defaultLargestUnit.
|
||||||
|
largest_unit = default_largest_unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 21. If smallestUnitPresent is false and largestUnitPresent is false, then
|
||||||
|
if (!smallest_unit_present && !largest_unit_present) {
|
||||||
|
// a. Throw a RangeError exception.
|
||||||
|
return vm.throw_completion<RangeError>(ErrorType::TemporalMissingUnits);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto largest_unit_value = largest_unit.get<Unit>();
|
||||||
|
|
||||||
|
// 22. If LargerOfTwoTemporalUnits(largestUnit, smallestUnit) is not largestUnit, throw a RangeError exception.
|
||||||
|
if (larger_of_two_temporal_units(largest_unit_value, smallest_unit_value) != largest_unit_value)
|
||||||
|
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidUnitRange, temporal_unit_to_string(smallest_unit_value), temporal_unit_to_string(largest_unit_value));
|
||||||
|
|
||||||
|
// 23. Let maximum be MaximumTemporalDurationRoundingIncrement(smallestUnit).
|
||||||
|
auto maximum = maximum_temporal_duration_rounding_increment(smallest_unit_value);
|
||||||
|
|
||||||
|
// 24. If maximum is not UNSET, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false).
|
||||||
|
if (!maximum.has<Unset>())
|
||||||
|
TRY(validate_temporal_rounding_increment(vm, rounding_increment, maximum.get<u64>(), false));
|
||||||
|
|
||||||
|
// 25. If roundingIncrement > 1, and largestUnit is not smallestUnit, and TemporalUnitCategory(smallestUnit) is DATE,
|
||||||
|
// throw a RangeError exception.
|
||||||
|
if (rounding_increment > 1 && largest_unit_value != smallest_unit_value && temporal_unit_category(smallest_unit_value) == UnitCategory::Date)
|
||||||
|
return vm.throw_completion<RangeError>(ErrorType::OptionIsNotValidValue, rounding_increment, "roundingIncrement");
|
||||||
|
|
||||||
|
// 26. If zonedRelativeTo is not undefined, then
|
||||||
|
if (zoned_relative_to) {
|
||||||
|
// a. Let internalDuration be ToInternalDurationRecord(duration).
|
||||||
|
auto internal_duration = to_internal_duration_record(vm, duration);
|
||||||
|
|
||||||
|
// FIXME: b. Let timeZone be zonedRelativeTo.[[TimeZone]].
|
||||||
|
// FIXME: c. Let calendar be zonedRelativeTo.[[Calendar]].
|
||||||
|
// FIXME: d. Let relativeEpochNs be zonedRelativeTo.[[EpochNanoseconds]].
|
||||||
|
// FIXME: e. Let targetEpochNs be ? AddZonedDateTime(relativeEpochNs, timeZone, calendar, internalDuration, constrain).
|
||||||
|
// FIXME: f. Set internalDuration to ? DifferenceZonedDateTimeWithRounding(relativeEpochNs, targetEpochNs, timeZone, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode).
|
||||||
|
|
||||||
|
// g. If TemporalUnitCategory(largestUnit) is date, set largestUnit to hour.
|
||||||
|
if (temporal_unit_category(largest_unit_value) == UnitCategory::Date)
|
||||||
|
largest_unit_value = Unit::Hour;
|
||||||
|
|
||||||
|
// h. Return ? TemporalDurationFromInternal(internalDuration, largestUnit).
|
||||||
|
return TRY(temporal_duration_from_internal(vm, internal_duration, largest_unit_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 27. If plainRelativeTo is not undefined, then
|
||||||
|
if (plain_relative_to) {
|
||||||
|
// a. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration).
|
||||||
|
auto internal_duration = to_internal_duration_record_with_24_hour_days(vm, duration);
|
||||||
|
|
||||||
|
// FIXME: b. Let targetTime be AddTime(MidnightTimeRecord(), internalDuration.[[Time]]).
|
||||||
|
// FIXME: c. Let calendar be plainRelativeTo.[[Calendar]].
|
||||||
|
// FIXME: d. Let dateDuration be ! AdjustDateDurationRecord(internalDuration.[[Date]], targetTime.[[Days]]).
|
||||||
|
// FIXME: e. Let targetDate be ? CalendarDateAdd(calendar, plainRelativeTo.[[ISODate]], dateDuration, constrain).
|
||||||
|
// FIXME: f. Let isoDateTime be CombineISODateAndTimeRecord(plainRelativeTo.[[ISODate]], MidnightTimeRecord()).
|
||||||
|
// FIXME: g. Let targetDateTime be CombineISODateAndTimeRecord(targetDate, targetTime).
|
||||||
|
// FIXME: h. Set internalDuration to ? DifferencePlainDateTimeWithRounding(isoDateTime, targetDateTime, calendar, largestUnit, roundingIncrement, smallestUnit, roundingMode).
|
||||||
|
|
||||||
|
// i. Return ? TemporalDurationFromInternal(internalDuration, largestUnit).
|
||||||
|
return TRY(temporal_duration_from_internal(vm, internal_duration, largest_unit_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 28. If IsCalendarUnit(existingLargestUnit) is true, or IsCalendarUnit(largestUnit) is true, throw a RangeError exception.
|
||||||
|
if (is_calendar_unit(existing_largest_unit))
|
||||||
|
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidLargestUnit, temporal_unit_to_string(existing_largest_unit));
|
||||||
|
if (is_calendar_unit(largest_unit_value))
|
||||||
|
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidLargestUnit, temporal_unit_to_string(largest_unit_value));
|
||||||
|
|
||||||
|
// 29. Assert: IsCalendarUnit(smallestUnit) is false.
|
||||||
|
VERIFY(!is_calendar_unit(smallest_unit_value));
|
||||||
|
|
||||||
|
// 30. Let internalDuration be ToInternalDurationRecordWith24HourDays(duration).
|
||||||
|
auto internal_duration = to_internal_duration_record_with_24_hour_days(vm, duration);
|
||||||
|
|
||||||
|
// 31. If smallestUnit is DAY, then
|
||||||
|
if (smallest_unit_value == Unit::Day) {
|
||||||
|
// a. Let fractionalDays be TotalTimeDuration(internalDuration.[[Time]], DAY).
|
||||||
|
auto fractional_days = total_time_duration(internal_duration.time, Unit::Day);
|
||||||
|
|
||||||
|
// b. Let days be RoundNumberToIncrement(fractionalDays, roundingIncrement, roundingMode).
|
||||||
|
auto days = round_number_to_increment(fractional_days, rounding_increment, rounding_mode);
|
||||||
|
|
||||||
|
// c. Let dateDuration be ? CreateDateDurationRecord(0, 0, 0, days).
|
||||||
|
auto date_duration = TRY(create_date_duration_record(vm, 0, 0, 0, days));
|
||||||
|
|
||||||
|
// d. Set internalDuration to ! CombineDateAndTimeDuration(dateDuration, 0).
|
||||||
|
internal_duration = MUST(combine_date_and_time_duration(vm, date_duration, TimeDuration { 0 }));
|
||||||
|
}
|
||||||
|
// 32. Else,
|
||||||
|
else {
|
||||||
|
// a. Let timeDuration be ? RoundTimeDuration(internalDuration.[[Time]], roundingIncrement, smallestUnit, roundingMode).
|
||||||
|
auto time_duration = TRY(round_time_duration(vm, internal_duration.time, Crypto::UnsignedBigInteger { rounding_increment }, smallest_unit_value, rounding_mode));
|
||||||
|
|
||||||
|
// b. Set internalDuration to ! CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration).
|
||||||
|
internal_duration = MUST(combine_date_and_time_duration(vm, zero_date_duration(vm), move(time_duration)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 33. Return ? TemporalDurationFromInternal(internalDuration, largestUnit).
|
||||||
|
return TRY(temporal_duration_from_internal(vm, internal_duration, largest_unit_value));
|
||||||
|
}
|
||||||
|
|
||||||
// 7.3.22 Temporal.Duration.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tostring
|
// 7.3.22 Temporal.Duration.prototype.toString ( [ options ] ), https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tostring
|
||||||
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::to_string)
|
JS_DEFINE_NATIVE_FUNCTION(DurationPrototype::to_string)
|
||||||
{
|
{
|
||||||
|
|
|
@ -35,6 +35,7 @@ private:
|
||||||
JS_DECLARE_NATIVE_FUNCTION(abs);
|
JS_DECLARE_NATIVE_FUNCTION(abs);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(add);
|
JS_DECLARE_NATIVE_FUNCTION(add);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(subtract);
|
JS_DECLARE_NATIVE_FUNCTION(subtract);
|
||||||
|
JS_DECLARE_NATIVE_FUNCTION(round);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(to_string);
|
JS_DECLARE_NATIVE_FUNCTION(to_string);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(to_json);
|
JS_DECLARE_NATIVE_FUNCTION(to_json);
|
||||||
JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
|
JS_DECLARE_NATIVE_FUNCTION(to_locale_string);
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
describe("correct behavior", () => {
|
||||||
|
test("length is 1", () => {
|
||||||
|
expect(Temporal.Duration.prototype.round).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("basic functionality", () => {
|
||||||
|
const duration = new Temporal.Duration(0, 0, 0, 21, 7, 10, 100, 200, 300, 400);
|
||||||
|
const values = [
|
||||||
|
["nanosecond", "P21DT7H11M40.2003004S"],
|
||||||
|
["microsecond", "P21DT7H11M40.2003S"],
|
||||||
|
["millisecond", "P21DT7H11M40.2S"],
|
||||||
|
["second", "P21DT7H11M40S"],
|
||||||
|
["minute", "P21DT7H12M"],
|
||||||
|
["hour", "P21DT7H"],
|
||||||
|
["day", "P21D"],
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const [smallestUnit, durationString] of values) {
|
||||||
|
const singularRoundedDuration = duration.round({ smallestUnit });
|
||||||
|
const pluralRoundedDuration = duration.round({ smallestUnit: `${smallestUnit}s` });
|
||||||
|
|
||||||
|
// Passing in a string is treated as though { smallestUnit: "<string value>" } was passed in.
|
||||||
|
const singularRoundedDurationWithString = duration.round(smallestUnit);
|
||||||
|
const pluralRoundedDurationWithString = duration.round(`${smallestUnit}s`);
|
||||||
|
|
||||||
|
expect(singularRoundedDuration.toString()).toBe(durationString);
|
||||||
|
expect(singularRoundedDurationWithString.toString()).toBe(durationString);
|
||||||
|
expect(pluralRoundedDuration.toString()).toBe(durationString);
|
||||||
|
expect(pluralRoundedDurationWithString.toString()).toBe(durationString);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("largestUnit option", () => {
|
||||||
|
const duration = new Temporal.Duration(0, 0, 0, 21, 7, 10, 100, 200, 300, 400);
|
||||||
|
|
||||||
|
// Using strings is not sufficient here, for example, the nanosecond case will produce "PT1840300.2003004S" which is 1840300 s, 200 ms, 300 us, 400 ns
|
||||||
|
const values = [
|
||||||
|
["nanosecond", { nanoseconds: 1840300200300400 }],
|
||||||
|
["microsecond", { microseconds: 1840300200300, nanoseconds: 400 }],
|
||||||
|
["millisecond", { milliseconds: 1840300200, microseconds: 300, nanoseconds: 400 }],
|
||||||
|
[
|
||||||
|
"second",
|
||||||
|
{ seconds: 1840300, milliseconds: 200, microseconds: 300, nanoseconds: 400 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"minute",
|
||||||
|
{
|
||||||
|
minutes: 30671,
|
||||||
|
seconds: 40,
|
||||||
|
milliseconds: 200,
|
||||||
|
microseconds: 300,
|
||||||
|
nanoseconds: 400,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"hour",
|
||||||
|
{
|
||||||
|
hours: 511,
|
||||||
|
minutes: 11,
|
||||||
|
seconds: 40,
|
||||||
|
milliseconds: 200,
|
||||||
|
microseconds: 300,
|
||||||
|
nanoseconds: 400,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"day",
|
||||||
|
{
|
||||||
|
days: 21,
|
||||||
|
hours: 7,
|
||||||
|
minutes: 11,
|
||||||
|
seconds: 40,
|
||||||
|
milliseconds: 200,
|
||||||
|
microseconds: 300,
|
||||||
|
nanoseconds: 400,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const [largestUnit, durationLike] of values) {
|
||||||
|
const singularRoundedDuration = duration.round({ largestUnit });
|
||||||
|
const pluralRoundedDuration = duration.round({ largestUnit: `${largestUnit}s` });
|
||||||
|
|
||||||
|
const propertiesToCheck = Object.keys(durationLike);
|
||||||
|
|
||||||
|
for (const property of propertiesToCheck) {
|
||||||
|
expect(singularRoundedDuration[property]).toBe(durationLike[property]);
|
||||||
|
expect(pluralRoundedDuration[property]).toBe(durationLike[property]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("errors", () => {
|
||||||
|
test("this value must be a Temporal.Duration object", () => {
|
||||||
|
expect(() => {
|
||||||
|
Temporal.Duration.prototype.round.call("foo");
|
||||||
|
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Duration");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("missing options object", () => {
|
||||||
|
const duration = new Temporal.Duration(1);
|
||||||
|
expect(() => {
|
||||||
|
duration.round();
|
||||||
|
}).toThrowWithMessage(TypeError, "Required options object is missing or undefined");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("invalid rounding mode", () => {
|
||||||
|
const duration = new Temporal.Duration(1);
|
||||||
|
expect(() => {
|
||||||
|
duration.round({ smallestUnit: "second", roundingMode: "serenityOS" });
|
||||||
|
}).toThrowWithMessage(
|
||||||
|
RangeError,
|
||||||
|
"serenityOS is not a valid value for option roundingMode"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("invalid smallest unit", () => {
|
||||||
|
const duration = new Temporal.Duration(1);
|
||||||
|
expect(() => {
|
||||||
|
duration.round({ smallestUnit: "serenityOS" });
|
||||||
|
}).toThrowWithMessage(
|
||||||
|
RangeError,
|
||||||
|
"serenityOS is not a valid value for option smallestUnit"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("increment may not be NaN", () => {
|
||||||
|
const duration = new Temporal.Duration(1);
|
||||||
|
expect(() => {
|
||||||
|
duration.round({ smallestUnit: "second", roundingIncrement: NaN });
|
||||||
|
}).toThrowWithMessage(RangeError, "NaN is not a valid value for option roundingIncrement");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("increment may smaller than 1 or larger than maximum", () => {
|
||||||
|
const duration = new Temporal.Duration(1);
|
||||||
|
expect(() => {
|
||||||
|
duration.round({ smallestUnit: "second", roundingIncrement: -1 });
|
||||||
|
}).toThrowWithMessage(RangeError, "-1 is not a valid value for option roundingIncrement");
|
||||||
|
expect(() => {
|
||||||
|
duration.round({ smallestUnit: "second", roundingIncrement: 0 });
|
||||||
|
}).toThrowWithMessage(RangeError, "0 is not a valid value for option roundingIncrement");
|
||||||
|
expect(() => {
|
||||||
|
duration.round({ smallestUnit: "second", roundingIncrement: Infinity });
|
||||||
|
}).toThrowWithMessage(
|
||||||
|
RangeError,
|
||||||
|
"Infinity is not a valid value for option roundingIncrement"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("must provide one or both of smallestUnit or largestUnit", () => {
|
||||||
|
const duration = new Temporal.Duration(1);
|
||||||
|
expect(() => {
|
||||||
|
duration.round({});
|
||||||
|
}).toThrowWithMessage(RangeError, "One or both of smallestUnit or largestUnit is required");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("relativeTo is required when duration has calendar units", () => {
|
||||||
|
const duration = new Temporal.Duration(1);
|
||||||
|
expect(() => {
|
||||||
|
duration.round({ largestUnit: "second" });
|
||||||
|
}).toThrowWithMessage(RangeError, "Largest unit must not be year");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Spec Issue: https://github.com/tc39/proposal-temporal/issues/2124
|
||||||
|
// Spec Fix: https://github.com/tc39/proposal-temporal/commit/66f7464aaec64d3cd21fb2ec37f6502743b9a730
|
||||||
|
test("balancing calendar units with largestUnit set to 'year' and relativeTo unset throws instead of crashing", () => {
|
||||||
|
const duration = new Temporal.Duration(1);
|
||||||
|
expect(() => {
|
||||||
|
duration.round({ largestUnit: "year" });
|
||||||
|
}).toThrowWithMessage(RangeError, "Largest unit must not be year");
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in a new issue