diff options
author | Idan Horowitz <idan.horowitz@gmail.com> | 2021-09-06 21:21:57 +0300 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-09-06 22:15:39 +0100 |
commit | 24b78fff7db473de34473fa980687c5535533d8c (patch) | |
tree | 381ada5747dff6bb4f5d3dffba5b2cbb049ccfb9 | |
parent | bcdad5767006ca03728324c656b03888507f75a1 (diff) | |
download | serenity-24b78fff7db473de34473fa980687c5535533d8c.zip |
LibJS: Add a bunch of Temporal Abstract Operations
These will allow us to (partially) implement
Temporal.Instant.prototype.{until, since}
8 files changed, 373 insertions, 1 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 2fbf591ce0..6edf4c61bd 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -267,6 +267,7 @@ namespace JS { P(keyFor) \ P(keys) \ P(language) \ + P(largestUnit) \ P(lastIndex) \ P(lastIndexOf) \ P(length) \ diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index b2098d431a..17f13df4ef 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h @@ -190,6 +190,7 @@ M(TemporalInvalidPlainYearMonth, "Invalid plain year month") \ M(TemporalInvalidTime, "Invalid time") \ M(TemporalInvalidTimeZoneName, "Invalid time zone name") \ + M(TemporalInvalidUnitRange, "Invalid unit range, {} is larger than {}") \ M(TemporalMissingOptionsObject, "Required options object is missing or undefined") \ M(TemporalMissingRequiredProperty, "Required property {} is missing or undefined") \ M(TemporalPropertyMustBeFinite, "Property must not be Infinity") \ diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index bd4cf6e2ee..886aeac635 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -399,6 +399,46 @@ static HashMap<StringView, StringView> plural_to_singular_units = { { "nanoseconds"sv, "nanosecond"sv } }; +// 13.17 ToLargestTemporalUnit ( normalizedOptions, disallowedUnits, fallback [ , autoValue ] ), https://tc39.es/proposal-temporal/#sec-temporal-tolargesttemporalunit +Optional<String> to_largest_temporal_unit(GlobalObject& global_object, Object& normalized_options, Vector<StringView> const& disallowed_units, String const& fallback, Optional<String> auto_value) +{ + auto& vm = global_object.vm(); + + // 1. Assert: disallowedUnits does not contain fallback. + // 2. Assert: disallowedUnits does not contain "auto". + // 3. Assert: autoValue is not present or fallback is "auto". + VERIFY(!auto_value.has_value() || fallback == "auto"sv); + // 4. Assert: autoValue is not present or disallowedUnits does not contain autoValue. + + // 5. Let largestUnit be ? GetOption(normalizedOptions, "largestUnit", ยซ String ยป, ยซ "auto", "year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds", "microsecond", "microseconds", "nanosecond", "nanoseconds" ยป, fallback). + auto largest_unit_value = get_option(global_object, normalized_options, vm.names.largestUnit, { OptionType::String }, { "auto"sv, "year"sv, "years"sv, "month"sv, "months"sv, "week"sv, "weeks"sv, "day"sv, "days"sv, "hour"sv, "hours"sv, "minute"sv, "minutes"sv, "second"sv, "seconds"sv, "millisecond"sv, "milliseconds"sv, "microsecond"sv, "microseconds"sv, "nanosecond"sv, "nanoseconds"sv }, js_string(vm, fallback)); + if (vm.exception()) + return {}; + auto largest_unit = largest_unit_value.as_string().string(); + + // 6. If largestUnit is "auto" and autoValue is present, then + if (largest_unit == "auto"sv && auto_value.has_value()) { + // a. Return autoValue. + return auto_value; + } + + // 7. If largestUnit is in the Plural column of Table 12, then + if (auto singular_unit = plural_to_singular_units.get(largest_unit); singular_unit.has_value()) { + // a. Set largestUnit to the corresponding Singular value of the same row. + largest_unit = singular_unit.value(); + } + + // 8. If disallowedUnits contains largestUnit, then + if (disallowed_units.contains_slow(largest_unit)) { + // a. Throw a RangeError exception. + vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, largest_unit, vm.names.largestUnit.as_string()); + return {}; + } + + // 9. Return largestUnit. + return largest_unit; +} + // 13.18 ToSmallestTemporalUnit ( normalizedOptions, disallowedUnits, fallback ), https://tc39.es/proposal-temporal/#sec-temporal-tosmallesttemporalunit Optional<String> to_smallest_temporal_unit(GlobalObject& global_object, Object& normalized_options, Vector<StringView> const& disallowed_units, Optional<String> fallback) { @@ -426,7 +466,7 @@ Optional<String> to_smallest_temporal_unit(GlobalObject& global_object, Object& // 4. If disallowedUnits contains smallestUnit, then if (disallowed_units.contains_slow(smallest_unit)) { // a. Throw a RangeError exception. - vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, smallest_unit, "smallestUnit"); + vm.throw_exception<RangeError>(global_object, ErrorType::OptionIsNotValidValue, smallest_unit, vm.names.smallestUnit.as_string()); return {}; } @@ -434,6 +474,129 @@ Optional<String> to_smallest_temporal_unit(GlobalObject& global_object, Object& return smallest_unit; } +// 13.22 ValidateTemporalUnitRange ( largestUnit, smallestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-validatetemporalunitrange +void validate_temporal_unit_range(GlobalObject& global_object, String const& largest_unit, String const& smallest_unit) +{ + auto& vm = global_object.vm(); + + // 1. If smallestUnit is "year" and largestUnit is not "year", then + if (smallest_unit == "year"sv && largest_unit != "year"sv) { + // a. Throw a RangeError exception. + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit); + return; + } + // 2. If smallestUnit is "month" and largestUnit is not "year" or "month", then + if (smallest_unit == "month"sv && !largest_unit.is_one_of("year"sv, "month"sv)) { + // a. Throw a RangeError exception. + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit); + return; + } + // 3. If smallestUnit is "week" and largestUnit is not one of "year", "month", or "week", then + if (smallest_unit == "week"sv && !largest_unit.is_one_of("year"sv, "month"sv, "week"sv)) { + // a. Throw a RangeError exception. + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit); + return; + } + // 4. If smallestUnit is "day" and largestUnit is not one of "year", "month", "week", or "day", then + if (smallest_unit == "day"sv && !largest_unit.is_one_of("year"sv, "month"sv, "week"sv, "day"sv)) { + // a. Throw a RangeError exception. + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit); + return; + } + // 5. If smallestUnit is "hour" and largestUnit is not one of "year", "month", "week", "day", or "hour", then + if (smallest_unit == "hour"sv && !largest_unit.is_one_of("year"sv, "month"sv, "week"sv, "day"sv, "hour"sv)) { + // a. Throw a RangeError exception. + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit); + return; + } + // 6. If smallestUnit is "minute" and largestUnit is "second", "millisecond", "microsecond", or "nanosecond", then + if (smallest_unit == "minute"sv && largest_unit.is_one_of("second"sv, "millisecond"sv, "microsecond"sv, "nanosecond"sv)) { + // a. Throw a RangeError exception. + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit); + return; + } + // 7. If smallestUnit is "second" and largestUnit is "millisecond", "microsecond", or "nanosecond", then + if (smallest_unit == "second"sv && largest_unit.is_one_of("millisecond"sv, "microsecond"sv, "nanosecond"sv)) { + // a. Throw a RangeError exception. + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit); + return; + } + // 8. If smallestUnit is "millisecond" and largestUnit is "microsecond" or "nanosecond", then + if (smallest_unit == "millisecond"sv && largest_unit.is_one_of("microsecond"sv, "nanosecond"sv)) { + // a. Throw a RangeError exception. + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit); + return; + } + // 9. If smallestUnit is "microsecond" and largestUnit is "nanosecond", then + if (smallest_unit == "microsecond"sv && largest_unit == "nanosecond"sv) { + // a. Throw a RangeError exception. + vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidUnitRange, smallest_unit, largest_unit); + return; + } +} + +// 13.23 LargerOfTwoTemporalUnits ( u1, u2 ), https://tc39.es/proposal-temporal/#sec-temporal-largeroftwotemporalunits +String larger_of_two_temporal_units(StringView unit1, StringView unit2) +{ + // 1. If either u1 or u2 is "year", return "year". + if (unit1 == "year"sv || unit2 == "year"sv) + return "year"sv; + // 2. If either u1 or u2 is "month", return "month". + if (unit1 == "month"sv || unit2 == "month"sv) + return "month"sv; + // 3. If either u1 or u2 is "week", return "week". + if (unit1 == "week"sv || unit2 == "week"sv) + return "week"sv; + // 4. If either u1 or u2 is "day", return "day". + if (unit1 == "day"sv || unit2 == "day"sv) + return "day"sv; + // 5. If either u1 or u2 is "hour", return "hour". + if (unit1 == "hour"sv || unit2 == "hour"sv) + return "hour"sv; + // 6. If either u1 or u2 is "minute", return "minute". + if (unit1 == "minute"sv || unit2 == "minute"sv) + return "minute"sv; + // 7. If either u1 or u2 is "second", return "second". + if (unit1 == "second"sv || unit2 == "second"sv) + return "second"sv; + // 8. If either u1 or u2 is "millisecond", return "millisecond". + if (unit1 == "millisecond"sv || unit2 == "millisecond"sv) + return "millisecond"sv; + // 9. If either u1 or u2 is "microsecond", return "microsecond". + if (unit1 == "microsecond"sv || unit2 == "microsecond"sv) + return "microsecond"sv; + // 10. Return "nanosecond". + return "nanosecond"sv; +} + +// 13.25 MaximumTemporalDurationRoundingIncrement ( unit ), https://tc39.es/proposal-temporal/#sec-temporal-maximumtemporaldurationroundingincrement +Optional<u16> maximum_temporal_duration_rounding_increment(StringView unit) +{ + // 1. If unit is "year", "month", "week", or "day", then + if (unit.is_one_of("year"sv, "month"sv, "week"sv, "day"sv)) { + // a. Return undefined. + return {}; + } + + // 2. If unit is "hour", then + if (unit == "hour"sv) { + // a. Return 24. + return 24; + } + + // 3. If unit is "minute" or "second", then + if (unit.is_one_of("minute"sv, "second"sv)) { + // a. Return 60. + return 60; + } + + // 4. Assert: unit is one of "millisecond", "microsecond", or "nanosecond". + VERIFY(unit.is_one_of("millisecond"sv, "microsecond"sv, "nanosecond"sv)); + + // 5. Return 1000. + return 1000; +} + // 13.27 FormatSecondsStringPart ( second, millisecond, microsecond, nanosecond, precision ), https://tc39.es/proposal-temporal/#sec-temporal-formatsecondsstringpart String format_seconds_string_part(u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Variant<String, u8> const& precision) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h index 182c790b47..3ab433e614 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h @@ -86,7 +86,11 @@ Optional<String> to_temporal_rounding_mode(GlobalObject&, Object& normalized_opt Optional<String> to_show_calendar_option(GlobalObject&, Object& normalized_options); u64 to_temporal_rounding_increment(GlobalObject&, Object& normalized_options, Optional<double> dividend, bool inclusive); Optional<SecondsStringPrecision> to_seconds_string_precision(GlobalObject&, Object& normalized_options); +Optional<String> to_largest_temporal_unit(GlobalObject&, Object& normalized_options, Vector<StringView> const& disallowed_units, String const& fallback, Optional<String> auto_value); Optional<String> to_smallest_temporal_unit(GlobalObject&, Object& normalized_options, Vector<StringView> const& disallowed_units, Optional<String> fallback); +void validate_temporal_unit_range(GlobalObject&, String const& largest_unit, String const& smallest_unit); +String larger_of_two_temporal_units(StringView, StringView); +Optional<u16> maximum_temporal_duration_rounding_increment(StringView unit); String format_seconds_string_part(u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Variant<String, u8> const& precision); double constrain_to_range(double x, double minimum, double maximum); BigInt* round_number_to_increment(GlobalObject&, BigInt const&, u64 increment, String const& rounding_mode); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp index 48e132131e..ed5ac78571 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.cpp @@ -10,6 +10,7 @@ #include <LibJS/Runtime/Temporal/AbstractOperations.h> #include <LibJS/Runtime/Temporal/Duration.h> #include <LibJS/Runtime/Temporal/DurationConstructor.h> +#include <LibJS/Runtime/Temporal/ZonedDateTime.h> namespace JS::Temporal { @@ -269,6 +270,182 @@ Duration* create_temporal_duration(GlobalObject& global_object, double years, do return object; } +// 7.5.10 TotalDurationNanoseconds ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, offsetShift ), https://tc39.es/proposal-temporal/#sec-temporal-totaldurationnanoseconds +BigInt* total_duration_nanoseconds(GlobalObject& global_object, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, BigInt const& nanoseconds, double offset_shift) +{ + auto& vm = global_object.vm(); + + // 1. Assert: offsetShift is an integer. + VERIFY(offset_shift == trunc(offset_shift)); + + // 2. Set nanoseconds to โ(nanoseconds). + auto result_nanoseconds = nanoseconds.big_integer(); + + // TODO: Add a way to create SignedBigIntegers from doubles with full precision and remove this restriction + VERIFY(AK::is_within_range<i64>(days) && AK::is_within_range<i64>(hours) && AK::is_within_range<i64>(minutes) && AK::is_within_range<i64>(seconds) && AK::is_within_range<i64>(milliseconds) && AK::is_within_range<i64>(microseconds)); + + // 3. If days โ 0, then + if (days != 0) { + // a. Set nanoseconds to nanoseconds โ offsetShift. + result_nanoseconds = result_nanoseconds.minus(Crypto::SignedBigInteger::create_from(offset_shift)); + } + // 4. Set hours to โ(hours) + โ(days) ร 24. + auto total_hours = Crypto::SignedBigInteger::create_from(hours).plus(Crypto::SignedBigInteger::create_from(days).multiplied_by(Crypto::UnsignedBigInteger(24))); + // 5. Set minutes to โ(minutes) + hours ร 60. + auto total_minutes = Crypto::SignedBigInteger::create_from(minutes).plus(total_hours.multiplied_by(Crypto::UnsignedBigInteger(60))); + // 6. Set seconds to โ(seconds) + minutes ร 60. + auto total_seconds = Crypto::SignedBigInteger::create_from(seconds).plus(total_minutes.multiplied_by(Crypto::UnsignedBigInteger(60))); + // 7. Set milliseconds to โ(milliseconds) + seconds ร 1000. + auto total_milliseconds = Crypto::SignedBigInteger::create_from(milliseconds).plus(total_seconds.multiplied_by(Crypto::UnsignedBigInteger(1000))); + // 8. Set microseconds to โ(microseconds) + milliseconds ร 1000. + auto total_microseconds = Crypto::SignedBigInteger::create_from(microseconds).plus(total_milliseconds.multiplied_by(Crypto::UnsignedBigInteger(1000))); + // 9. Return nanoseconds + microseconds ร 1000. + return js_bigint(vm, result_nanoseconds.plus(total_microseconds.multiplied_by(Crypto::UnsignedBigInteger(1000)))); +} + +// 7.5.11 BalanceDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit [ , relativeTo ] ), https://tc39.es/proposal-temporal/#sec-temporal-balanceduration +Optional<BalancedDuration> balance_duration(GlobalObject& global_object, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, BigInt const& nanoseconds, String const& largest_unit, Object* relative_to) +{ + auto& vm = global_object.vm(); + // 1. If relativeTo is not present, set relativeTo to undefined. + + Crypto::SignedBigInteger total_nanoseconds; + // 2. If Type(relativeTo) is Object and relativeTo has an [[InitializedTemporalZonedDateTime]] internal slot, then + if (relative_to && is<ZonedDateTime>(*relative_to)) { + // a. Let endNs be ? AddZonedDateTime(relativeTo.[[Nanoseconds]], relativeTo.[[TimeZone]], relativeTo.[[Calendar]], 0, 0, 0, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds). + TODO(); + if (vm.exception()) + return {}; + // b. Set nanoseconds to endNs โ relativeTo.[[Nanoseconds]]. + } + // 3. Else, + else { + // a. Set nanoseconds to โค(! TotalDurationNanoseconds(days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, 0)). + total_nanoseconds = total_duration_nanoseconds(global_object, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, 0)->big_integer(); + } + + // 4. If largestUnit is one of "year", "month", "week", or "day", then + if (largest_unit.is_one_of("year"sv, "month"sv, "week"sv, "day"sv)) { + // a. Let result be ? NanosecondsToDays(nanoseconds, relativeTo). + TODO(); + if (vm.exception()) + return {}; + + // b. Set days to result.[[Days]]. + + // c. Set nanoseconds to result.[[Nanoseconds]]. + } + // 5. Else, + else { + // a. Set days to 0. + days = 0; + } + // 6. Set hours, minutes, seconds, milliseconds, and microseconds to 0. + hours = 0; + minutes = 0; + seconds = 0; + milliseconds = 0; + microseconds = 0; + + // 7. Set nanoseconds to โ(nanoseconds). + double result_nanoseconds = total_nanoseconds.to_double(); + + // 8. If nanoseconds < 0, let sign be โ1; else, let sign be 1. + i8 sign = total_nanoseconds.is_negative() ? -1 : 1; + + // 9. Set nanoseconds to abs(nanoseconds). + total_nanoseconds = Crypto::SignedBigInteger(total_nanoseconds.unsigned_value()); + result_nanoseconds = fabs(result_nanoseconds); + + // 10. If largestUnit is "year", "month", "week", "day", or "hour", then + if (largest_unit.is_one_of("year"sv, "month"sv, "day"sv, "hour"sv)) { + // a. Set microseconds to floor(nanoseconds / 1000). + auto nanoseconds_division_result = total_nanoseconds.divided_by(Crypto::UnsignedBigInteger(1000)); + // b. Set nanoseconds to nanoseconds modulo 1000. + result_nanoseconds = nanoseconds_division_result.remainder.to_double(); + // c. Set milliseconds to floor(microseconds / 1000). + auto microseconds_division_result = nanoseconds_division_result.quotient.divided_by(Crypto::UnsignedBigInteger(1000)); + // d. Set microseconds to microseconds modulo 1000. + microseconds = microseconds_division_result.remainder.to_double(); + // e. Set seconds to floor(milliseconds / 1000). + auto milliseconds_division_result = microseconds_division_result.quotient.divided_by(Crypto::UnsignedBigInteger(1000)); + // f. Set milliseconds to milliseconds modulo 1000. + milliseconds = milliseconds_division_result.remainder.to_double(); + // g. Set minutes to floor(seconds / 60). + auto seconds_division_result = milliseconds_division_result.quotient.divided_by(Crypto::UnsignedBigInteger(60)); + // h. Set seconds to seconds modulo 60. + seconds = seconds_division_result.remainder.to_double(); + // i. Set hours to floor(minutes / 60). + auto minutes_division_result = milliseconds_division_result.quotient.divided_by(Crypto::UnsignedBigInteger(60)); + hours = minutes_division_result.quotient.to_double(); + // j. Set minutes to minutes modulo 60. + minutes = minutes_division_result.remainder.to_double(); + } + // 11. Else if largestUnit is "minute", then + else if (largest_unit == "minute"sv) { + // a. Set microseconds to floor(nanoseconds / 1000). + auto nanoseconds_division_result = total_nanoseconds.divided_by(Crypto::UnsignedBigInteger(1000)); + // b. Set nanoseconds to nanoseconds modulo 1000. + result_nanoseconds = nanoseconds_division_result.remainder.to_double(); + // c. Set milliseconds to floor(microseconds / 1000). + auto microseconds_division_result = nanoseconds_division_result.quotient.divided_by(Crypto::UnsignedBigInteger(1000)); + // d. Set microseconds to microseconds modulo 1000. + microseconds = microseconds_division_result.remainder.to_double(); + // e. Set seconds to floor(milliseconds / 1000). + auto milliseconds_division_result = microseconds_division_result.quotient.divided_by(Crypto::UnsignedBigInteger(1000)); + // f. Set milliseconds to milliseconds modulo 1000. + milliseconds = milliseconds_division_result.remainder.to_double(); + // g. Set minutes to floor(seconds / 60). + auto seconds_division_result = milliseconds_division_result.quotient.divided_by(Crypto::UnsignedBigInteger(60)); + minutes = seconds_division_result.quotient.to_double(); + // h. Set seconds to seconds modulo 60. + seconds = seconds_division_result.remainder.to_double(); + } + // 12. Else if largestUnit is "second", then + else if (largest_unit == "second"sv) { + // a. Set microseconds to floor(nanoseconds / 1000). + auto nanoseconds_division_result = total_nanoseconds.divided_by(Crypto::UnsignedBigInteger(1000)); + // b. Set nanoseconds to nanoseconds modulo 1000. + result_nanoseconds = nanoseconds_division_result.remainder.to_double(); + // c. Set milliseconds to floor(microseconds / 1000). + auto microseconds_division_result = nanoseconds_division_result.quotient.divided_by(Crypto::UnsignedBigInteger(1000)); + // d. Set microseconds to microseconds modulo 1000. + microseconds = microseconds_division_result.remainder.to_double(); + // e. Set seconds to floor(milliseconds / 1000). + auto milliseconds_division_result = microseconds_division_result.quotient.divided_by(Crypto::UnsignedBigInteger(1000)); + seconds = milliseconds_division_result.quotient.to_double(); + // f. Set milliseconds to milliseconds modulo 1000. + milliseconds = milliseconds_division_result.remainder.to_double(); + } + // 13. Else if largestUnit is "millisecond", then + else if (largest_unit == "millisecond"sv) { + // a. Set microseconds to floor(nanoseconds / 1000). + auto nanoseconds_division_result = total_nanoseconds.divided_by(Crypto::UnsignedBigInteger(1000)); + // b. Set nanoseconds to nanoseconds modulo 1000. + result_nanoseconds = nanoseconds_division_result.remainder.to_double(); + // c. Set milliseconds to floor(microseconds / 1000). + auto microseconds_division_result = nanoseconds_division_result.quotient.divided_by(Crypto::UnsignedBigInteger(1000)); + milliseconds = microseconds_division_result.quotient.to_double(); + // d. Set microseconds to microseconds modulo 1000. + microseconds = microseconds_division_result.remainder.to_double(); + } + // 14. Else if largestUnit is "microsecond", then + else if (largest_unit == "microsecond"sv) { + // a. Set microseconds to floor(nanoseconds / 1000). + auto nanoseconds_division_result = total_nanoseconds.divided_by(Crypto::UnsignedBigInteger(1000)); + microseconds = nanoseconds_division_result.quotient.to_double(); + // b. Set nanoseconds to nanoseconds modulo 1000. + result_nanoseconds = nanoseconds_division_result.remainder.to_double(); + } + // 15. Else, + else { + // a. Assert: largestUnit is "nanosecond". + VERIFY(largest_unit == "nanosecond"sv); + } + // 16. Return the Record { [[Days]]: ๐ฝ(days), [[Hours]]: ๐ฝ(hours ร sign), [[Minutes]]: ๐ฝ(minutes ร sign), [[Seconds]]: ๐ฝ(seconds ร sign), [[Milliseconds]]: ๐ฝ(milliseconds ร sign), [[Microseconds]]: ๐ฝ(microseconds ร sign), [[Nanoseconds]]: ๐ฝ(nanoseconds ร sign) }. + return BalancedDuration { .days = days, .hours = hours * sign, .minutes = minutes * sign, .seconds = seconds * sign, .milliseconds = milliseconds * sign, .microseconds = microseconds * sign, .nanoseconds = result_nanoseconds * sign }; +} + // 7.5.19 ToLimitedTemporalDuration ( temporalDurationLike, disallowedFields ),https://tc39.es/proposal-temporal/#sec-temporal-tolimitedtemporalduration Optional<TemporalDuration> to_limited_temporal_duration(GlobalObject& global_object, Value temporal_duration_like, Vector<StringView> const& disallowed_fields) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h index 559d2498e5..c9f3f395e5 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Duration.h @@ -71,6 +71,17 @@ struct PartialDuration { Optional<double> nanoseconds; }; +// Used by BalanceDuration to temporarily hold values +struct BalancedDuration { + double days; + double hours; + double minutes; + double seconds; + double milliseconds; + double microseconds; + double nanoseconds; +}; + // Table 7: Properties of a TemporalDurationLike, https://tc39.es/proposal-temporal/#table-temporal-temporaldurationlike-properties template<typename StructT, typename ValueT> @@ -102,6 +113,8 @@ i8 duration_sign(double years, double months, double weeks, double days, double bool is_valid_duration(double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds); PartialDuration to_partial_duration(GlobalObject&, Value temporal_duration_like); Duration* create_temporal_duration(GlobalObject&, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, FunctionObject* new_target = nullptr); +BigInt* total_duration_nanoseconds(GlobalObject&, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, BigInt const& nanoseconds, double offset_shift); +Optional<BalancedDuration> balance_duration(GlobalObject&, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, BigInt const& nanoseconds, String const& largest_unit, Object* relative_to = nullptr); Optional<TemporalDuration> to_limited_temporal_duration(GlobalObject&, Value temporal_duration_like, Vector<StringView> const& disallowed_fields); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp index 1821fd2195..9b8b530bb3 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.cpp @@ -191,6 +191,18 @@ BigInt* add_instant(GlobalObject& global_object, BigInt const& epoch_nanoseconds return result; } +// 8.5.7 DifferenceInstant ( ns1, ns2, roundingIncrement, smallestUnit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-differenceinstant +BigInt* difference_instant(GlobalObject& global_object, BigInt const& nanoseconds1, BigInt const& nanoseconds2, u64 rounding_increment, StringView smallest_unit, StringView rounding_mode) +{ + auto& vm = global_object.vm(); + + // 1. Assert: Type(ns1) is BigInt. + // 2. Assert: Type(ns2) is BigInt. + + // 3. Return ! RoundTemporalInstant(ns2 โ ns1, roundingIncrement, smallestUnit, roundingMode). + return round_temporal_instant(global_object, *js_bigint(vm, nanoseconds2.big_integer().minus(nanoseconds1.big_integer())), rounding_increment, smallest_unit, rounding_mode); +} + // 8.5.8 RoundTemporalInstant ( ns, increment, unit, roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-roundtemporalinstant BigInt* round_temporal_instant(GlobalObject& global_object, BigInt const& nanoseconds, u64 increment, String const& unit, String const& rounding_mode) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h index 29ff469a57..3d67dbd870 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Instant.h @@ -41,6 +41,7 @@ Instant* to_temporal_instant(GlobalObject&, Value item); BigInt* parse_temporal_instant(GlobalObject&, String const& iso_string); i32 compare_epoch_nanoseconds(BigInt const&, BigInt const&); BigInt* add_instant(GlobalObject&, BigInt const& epoch_nanoseconds, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds); +BigInt* difference_instant(GlobalObject&, BigInt const& nanoseconds1, BigInt const& nanoseconds2, u64 rounding_increment, StringView smallest_unit, StringView rounding_mode); BigInt* round_temporal_instant(GlobalObject&, BigInt const& nanoseconds, u64 increment, String const& unit, String const& rounding_mode); Optional<String> temporal_instant_to_string(GlobalObject&, Instant&, Value time_zone, Variant<String, u8> const& precision); |