From 37fc9b6511cdbccc5f19a76b57238b3506406bb0 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Tue, 3 Dec 2024 09:22:02 -0500 Subject: [PATCH] LibJS: Apply editorial updates to Intl.DurationFormat To help prepare for its stage 4 promotion, this applies the editorial updates made to Intl.DurationFormat over the last few months. --- .../LibJS/Runtime/Intl/DurationFormat.cpp | 153 ++++++++++-------- Libraries/LibJS/Runtime/Intl/DurationFormat.h | 57 ++++--- .../Intl/DurationFormatConstructor.cpp | 46 +++--- .../Runtime/Intl/DurationFormatPrototype.cpp | 10 +- 4 files changed, 137 insertions(+), 129 deletions(-) diff --git a/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp b/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp index 185edf2d42e..b895765a0be 100644 --- a/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp +++ b/Libraries/LibJS/Runtime/Intl/DurationFormat.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, Idan Horowitz - * Copyright (c) 2022-2024, Tim Flynn + * Copyright (c) 2022-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -160,9 +160,9 @@ static GC::Ref construct_list_format(VM& vm, DurationFormat const& d // 1.1.3 ToDurationRecord ( input ), https://tc39.es/proposal-intl-duration-format/#sec-todurationrecord ThrowCompletionOr to_duration_record(VM& vm, Value input) { - // 1. If Type(input) is not Object, then + // 1. If input is not an Object, then if (!input.is_object()) { - // a. If Type(input) is String, throw a RangeError exception. + // a. If input is a String, throw a RangeError exception. if (input.is_string()) return vm.throw_completion(ErrorType::NotAnObject, input); @@ -173,7 +173,7 @@ ThrowCompletionOr to_duration_record(VM& vm, Value input) auto& input_object = input.as_object(); // 2. Let result be a new Duration Record with each field set to 0. - DurationRecord result = {}; + DurationRecord result {}; bool any_defined = false; auto set_duration_record_value = [&](auto const& name, auto& value_slot) -> ThrowCompletionOr { @@ -262,7 +262,7 @@ i8 duration_sign(DurationRecord const& duration) // 1.1.6 GetDurationUnitOptions ( unit, options, baseStyle, stylesList, digitalBase, prevStyle, twoDigitHours ), https://tc39.es/proposal-intl-duration-format/#sec-getdurationunitoptions ThrowCompletionOr get_duration_unit_options(VM& vm, String const& unit, Object const& options, StringView base_style, ReadonlySpan styles_list, StringView digital_base, StringView previous_style, bool two_digit_hours) { - // 1. Let style be ? GetOption(options, unit, string, stylesList, undefined). + // 1. Let style be ? GetOption(options, unit, STRING, stylesList, undefined). auto style_value = TRY(get_option(vm, options, unit.to_byte_string(), OptionType::String, styles_list, Empty {})); StringView style; @@ -323,7 +323,7 @@ ThrowCompletionOr get_duration_unit_options(VM& vm, String // 5. Let displayField be the string-concatenation of unit and "Display". auto display_field = MUST(String::formatted("{}Display", unit)); - // 6. Let display be ? GetOption(options, displayField, string, « "auto", "always" », displayDefault). + // 6. Let display be ? GetOption(options, displayField, STRING, « "auto", "always" », displayDefault). auto display_value = TRY(get_option(vm, options, display_field.to_byte_string(), OptionType::String, { "auto"sv, "always"sv }, display_default)); auto display = display_value.as_string().utf8_string(); @@ -367,8 +367,8 @@ ThrowCompletionOr get_duration_unit_options(VM& vm, String return DurationUnitOptions { .style = MUST(String::from_utf8(style)), .display = move(display) }; } -// 1.1.7 AddFractionalDigits ( durationFormat, duration ), https://tc39.es/proposal-intl-duration-format/#sec-addfractionaldigits -double add_fractional_digits(DurationFormat const& duration_format, DurationRecord const& duration) +// 1.1.7 ComputeFractionalDigits ( durationFormat, duration ), https://tc39.es/proposal-intl-duration-format/#sec-computefractionaldigits +double compute_fractional_digits(DurationFormat const& duration_format, DurationRecord const& duration) { // 1. Let result be 0. double result = 0; @@ -389,7 +389,7 @@ double add_fractional_digits(DurationFormat const& duration_format, DurationReco // ii. Let value be the value of duration's field whose name is the Value Field value of the current row. auto value = duration.*duration_instances_component.value_slot; - // iii. Set value to value / 10^exponent. + // iii. Set value to value / 10**exponent. value = value / pow(10, exponent); // iv. Set result to result + value. @@ -431,15 +431,15 @@ Vector format_numeric_hours(VM& vm, DurationFormat const& du { auto& realm = *vm.current_realm(); - // 1. Let hoursStyle be durationFormat.[[HoursStyle]]. + // 1. Let result be a new empty List. + Vector result; + + // 2. Let hoursStyle be durationFormat.[[HoursStyle]]. auto hours_style = duration_format.hours_style(); - // 2. Assert: hoursStyle is "numeric" or hoursStyle is "2-digit". + // 3. Assert: hoursStyle is "numeric" or hoursStyle is "2-digit". VERIFY(hours_style == DurationFormat::ValueStyle::Numeric || hours_style == DurationFormat::ValueStyle::TwoDigit); - // 3. Let result be a new empty List. - Vector result; - // 4. Let nfOpts be OrdinaryObjectCreate(null). auto number_format_options = Object::create(realm, nullptr); @@ -464,10 +464,10 @@ Vector format_numeric_hours(VM& vm, DurationFormat const& du // 9. Perform ! CreateDataPropertyOrThrow(nfOpts, "useGrouping", false). MUST(number_format_options->create_data_property_or_throw(vm.names.useGrouping, Value { false })); - // 10. Let nf be ! Construct(%NumberFormat%, « durationFormat.[[Locale]], nfOpts »). + // 10. Let nf be ! Construct(%Intl.NumberFormat%, « durationFormat.[[Locale]], nfOpts »). auto number_format = construct_number_format(vm, duration_format, number_format_options); - // 11. Let hoursParts be ! PartitionNumberPattern(nf, hoursValue). + // 11. Let hoursParts be PartitionNumberPattern(nf, hoursValue). auto hours_parts = partition_number_pattern(number_format, MathematicalValue { hours_value }); // 12. For each Record { [[Type]], [[Value]] } part of hoursParts, do @@ -492,10 +492,10 @@ Vector format_numeric_minutes(VM& vm, DurationFormat const& // 2. If hoursDisplayed is true, then if (hours_displayed) { - // a. Let separator be durationFormat.[[DigitalFormat]].[[HoursMinutesSeparator]]. - auto separator = duration_format.hours_minutes_separator(); + // a. Let separator be durationFormat.[[HourMinuteSeparator]]. + auto separator = duration_format.hour_minute_separator(); - // b. Append the Record { [[Type]]: "literal", [[Value]]: separator, [[Unit]]: empty } to result. + // b. Append the Record { [[Type]]: "literal", [[Value]]: separator, [[Unit]]: EMPTY } to result. result.append({ .type = "literal"sv, .value = move(separator), .unit = {} }); } @@ -529,10 +529,10 @@ Vector format_numeric_minutes(VM& vm, DurationFormat const& // 10. Perform ! CreateDataPropertyOrThrow(nfOpts, "useGrouping", false). MUST(number_format_options->create_data_property_or_throw(vm.names.useGrouping, Value { false })); - // 11. Let nf be ! Construct(%NumberFormat%, « durationFormat.[[Locale]], nfOpts »). + // 11. Let nf be ! Construct(%Intl.NumberFormat%, « durationFormat.[[Locale]], nfOpts »). auto number_format = construct_number_format(vm, duration_format, number_format_options); - // 12. Let minutesParts be ! PartitionNumberPattern(nf, minutesValue). + // 12. Let minutesParts be PartitionNumberPattern(nf, minutesValue). auto minutes_parts = partition_number_pattern(number_format, MathematicalValue { minutes_value }); // 13. For each Record { [[Type]], [[Value]] } part of minutesParts, do @@ -552,24 +552,24 @@ Vector format_numeric_seconds(VM& vm, DurationFormat const& { auto& realm = *vm.current_realm(); - // 1. Let secondsStyle be durationFormat.[[SecondsStyle]]. - auto seconds_style = duration_format.seconds_style(); - - // 2. Assert: secondsStyle is "numeric" or secondsStyle is "2-digit". - VERIFY(seconds_style == DurationFormat::ValueStyle::Numeric || seconds_style == DurationFormat::ValueStyle::TwoDigit); - - // 3. Let result be a new empty List. + // 1. Let result be a new empty List. Vector result; - // 4. If minutesDisplayed is true, then + // 2. If minutesDisplayed is true, then if (minutes_displayed) { - // a. Let separator be durationFormat.[[DigitalFormat]].[[MinutesSecondsSeparator]]. - auto separator = duration_format.minutes_seconds_separator(); + // a. Let separator be durationFormat.[[MinuteSecondSeparator]]. + auto separator = duration_format.minute_second_separator(); - // b. Append the Record { [[Type]]: "literal", [[Value]]: separator, [[Unit]]: empty } to result. + // b. Append the Record { [[Type]]: "literal", [[Value]]: separator, [[Unit]]: EMPTY } to result. result.append({ .type = "literal"sv, .value = move(separator), .unit = {} }); } + // 3. Let secondsStyle be durationFormat.[[SecondsStyle]]. + auto seconds_style = duration_format.seconds_style(); + + // 4. Assert: secondsStyle is "numeric" or secondsStyle is "2-digit". + VERIFY(seconds_style == DurationFormat::ValueStyle::Numeric || seconds_style == DurationFormat::ValueStyle::TwoDigit); + // 5. Let nfOpts be OrdinaryObjectCreate(null). auto number_format_options = Object::create(realm, nullptr); @@ -597,7 +597,7 @@ Vector format_numeric_seconds(VM& vm, DurationFormat const& u8 maximum_fraction_digits = 0; u8 minimum_fraction_digits = 0; - // 12. If durationFormat.[[FractionalDigits]] is undefined, then + // 11. If durationFormat.[[FractionalDigits]] is undefined, then if (!duration_format.has_fractional_digits()) { // a. Let maximumFractionDigits be 9𝔽. maximum_fraction_digits = 9; @@ -605,7 +605,7 @@ Vector format_numeric_seconds(VM& vm, DurationFormat const& // b. Let minimumFractionDigits be +0𝔽. minimum_fraction_digits = 0; } - // 13. Else, + // 12. Else, else { // a. Let maximumFractionDigits be durationFormat.[[FractionalDigits]]. maximum_fraction_digits = duration_format.fractional_digits(); @@ -614,21 +614,19 @@ Vector format_numeric_seconds(VM& vm, DurationFormat const& minimum_fraction_digits = duration_format.fractional_digits(); } - // 14. Perform ! CreateDataPropertyOrThrow(nfOpts, "maximumFractionDigits", maximumFractionDigits). + // 13. Perform ! CreateDataPropertyOrThrow(nfOpts, "maximumFractionDigits", maximumFractionDigits). MUST(number_format_options->create_data_property_or_throw(vm.names.maximumFractionDigits, Value { maximum_fraction_digits })); - // 15. Perform ! CreateDataPropertyOrThrow(nfOpts, "minimumFractionDigits", minimumFractionDigits). + // 14. Perform ! CreateDataPropertyOrThrow(nfOpts, "minimumFractionDigits", minimumFractionDigits). MUST(number_format_options->create_data_property_or_throw(vm.names.minimumFractionDigits, Value { minimum_fraction_digits })); - // 16. Perform ! CreateDataPropertyOrThrow(nfOpts, "roundingMode", "trunc"). + // 15. Perform ! CreateDataPropertyOrThrow(nfOpts, "roundingMode", "trunc"). MUST(number_format_options->create_data_property_or_throw(vm.names.roundingMode, PrimitiveString::create(vm, "trunc"sv))); - // FIXME: We obviously have to create the NumberFormat object after its options are fully initialized. - // https://github.com/tc39/proposal-intl-duration-format/pull/203 - // 11. Let nf be ! Construct(%NumberFormat%, « durationFormat.[[Locale]], nfOpts »). + // 16. Let nf be ! Construct(%Intl.NumberFormat%, « durationFormat.[[Locale]], nfOpts »). auto number_format = construct_number_format(vm, duration_format, number_format_options); - // 17. Let secondsParts be ! PartitionNumberPattern(nf, secondsValue). + // 17. Let secondsParts be PartitionNumberPattern(nf, secondsValue). auto seconds_parts = partition_number_pattern(number_format, MathematicalValue { seconds_value }); // 18. For each Record { [[Type]], [[Value]] } part of secondsParts, do @@ -669,8 +667,8 @@ Vector format_numeric_units(VM& vm, DurationFormat const& du // 8. If duration.[[Milliseconds]] is not 0 or duration.[[Microseconds]] is not 0 or duration.[[Nanoseconds]] is not 0, then if (duration.milliseconds != 0 || duration.microseconds != 0 || duration.nanoseconds != 0) { - // a. Set secondsValue to secondsValue + AddFractionalDigits(durationFormat, duration). - seconds_value += add_fractional_digits(duration_format, duration); + // a. Set secondsValue to secondsValue + ComputeFractionalDigits(durationFormat, duration). + seconds_value += compute_fractional_digits(duration_format, duration); } // 9. Let secondsDisplay be durationFormat.[[SecondsDisplay]]. @@ -681,18 +679,18 @@ Vector format_numeric_units(VM& vm, DurationFormat const& du // 11. If firstNumericUnit is "hours", then if (first_numeric_unit == "hours"sv) { - // a. If hoursValue is not 0 or hoursDisplay is not "auto", then - if (hours_value != 0 || hours_display != DurationFormat::Display::Auto) { + // a. If hoursValue is not 0 or hoursDisplay is "always", then + if (hours_value != 0 || hours_display == DurationFormat::Display::Always) { // i. Set hoursFormatted to true. hours_formatted = true; } } - // 12. If secondsValue is not 0 or secondsDisplay is not "auto", then + // 12. If secondsValue is not 0 or secondsDisplay is "always", then // a. Let secondsFormatted be true. // 13. Else, // a. Let secondsFormatted be false. - auto seconds_formatted = seconds_value != 0 || seconds_display != DurationFormat::Display::Auto; + auto seconds_formatted = seconds_value != 0 || seconds_display == DurationFormat::Display::Always; // 14. Let minutesFormatted be false. auto minutes_formatted = false; @@ -704,8 +702,8 @@ Vector format_numeric_units(VM& vm, DurationFormat const& du // i. Set minutesFormatted to true. minutes_formatted = true; } - // b. Else if minutesValue is not 0 or minutesDisplay is not "auto", then - else if (minutes_value != 0 || minutes_display != DurationFormat::Display::Auto) { + // b. Else if minutesValue is not 0 or minutesDisplay is "always", then + else if (minutes_value != 0 || minutes_display == DurationFormat::Display::Always) { // i. Set minutesFormatted to true. minutes_formatted = true; } @@ -717,13 +715,16 @@ Vector format_numeric_units(VM& vm, DurationFormat const& du if (sign_displayed) { // i. If hoursValue is 0 and DurationSign(duration) is -1, then if (hours_value == 0 && duration_sign(duration) == -1) { - // 1. Set hoursValue to negative-zero. + // 1. Set hoursValue to NEGATIVE-ZERO. hours_value = -0.0; } } - // b. Append FormatNumericHours(durationFormat, hoursValue, signDisplayed) to numericPartsList. - numeric_parts_list.extend(format_numeric_hours(vm, duration_format, hours_value, sign_displayed)); + // b. Let hoursParts be FormatNumericHours(durationFormat, hoursValue, signDisplayed). + auto hours_parts = format_numeric_hours(vm, duration_format, hours_value, sign_displayed); + + // b. Set numericPartsList to the list-concatenation of numericPartsList and hoursParts. + numeric_parts_list.extend(move(hours_parts)); // c. Set signDisplayed to false. sign_displayed = false; @@ -735,25 +736,28 @@ Vector format_numeric_units(VM& vm, DurationFormat const& du if (sign_displayed) { // i. If minutesValue is 0 and DurationSign(duration) is -1, then if (minutes_value == 0 && duration_sign(duration) == -1) { - // 1. Set minutesValue to negative-zero. + // 1. Set minutesValue to NEGATIVE-ZERO. minutes_value = -0.0; } } - // b. Append FormatNumericMinutes(durationFormat, minutesValue, hoursFormatted, signDisplayed) to numericPartsList. - numeric_parts_list.extend(format_numeric_minutes(vm, duration_format, minutes_value, hours_formatted, sign_displayed)); + // b. Let minutesParts be FormatNumericMinutes(durationFormat, minutesValue, hoursFormatted, signDisplayed). + auto minutes_parts = format_numeric_minutes(vm, duration_format, minutes_value, hours_formatted, sign_displayed); - // c. Set signDisplayed to false. + // c. Set numericPartsList to the list-concatenation of numericPartsList and minutesParts. + numeric_parts_list.extend(move(minutes_parts)); + + // d. Set signDisplayed to false. sign_displayed = false; } // 18. If secondsFormatted is true, then if (seconds_formatted) { - // a. Append FormatNumericSeconds(durationFormat, secondsValue, minutesFormatted, signDisplayed) to numericPartsList. - numeric_parts_list.extend(format_numeric_seconds(vm, duration_format, seconds_value, minutes_formatted, sign_displayed)); + // a. Let secondsParts be FormatNumericSeconds(durationFormat, secondsValue, minutesFormatted, signDisplayed). + auto seconds_parts = format_numeric_seconds(vm, duration_format, seconds_value, minutes_formatted, sign_displayed); - // b. Set signDisplayed to false. - sign_displayed = false; + // b. Set numericPartsList to the list-concatenation of numericPartsList and secondsParts. + numeric_parts_list.extend(move(seconds_parts)); } // 19. Return numericPartsList. @@ -784,7 +788,7 @@ Vector list_format_parts(VM& vm, DurationFormat const& durat auto locale_list_style = Unicode::style_to_string(static_cast(list_style)); MUST(list_format_options->create_data_property_or_throw(vm.names.style, PrimitiveString::create(vm, locale_list_style))); - // 6. Let lf be ! Construct(%ListFormat%, « durationFormat.[[Locale]], lfOpts »). + // 6. Let lf be ! Construct(%Intl.ListFormat%, « durationFormat.[[Locale]], lfOpts »). auto list_format = construct_list_format(vm, duration_format, list_format_options); // 7. Let strings be a new empty List. @@ -884,9 +888,17 @@ Vector partition_duration_format_pattern(VM& vm, DurationFor // e. If style is "numeric" or "2-digit", then if (style == DurationFormat::ValueStyle::Numeric || style == DurationFormat::ValueStyle::TwoDigit) { // i. Append FormatNumericUnits(durationFormat, duration, unit, signDisplayed) to result. - result.append(format_numeric_units(vm, duration_format, duration, unit, sign_displayed)); + // FIXME: Spec issue: This step should have been removed. See: + // https://github.com/tc39/proposal-intl-duration-format/issues/225 - // ii. Set numericUnitFound to true. + // ii. Let numericPartsList be FormatNumericUnits(durationFormat, duration, unit, signDisplayed). + auto numeric_parts_list = format_numeric_units(vm, duration_format, duration, unit, sign_displayed); + + // iii. If numericPartsList is not empty, append numericPartsList to result. + if (!numeric_parts_list.is_empty()) + result.append(move(numeric_parts_list)); + + // iv. Set numericUnitFound to true. numeric_unit_found = true; } // f. Else, @@ -898,9 +910,8 @@ Vector partition_duration_format_pattern(VM& vm, DurationFor if (unit.is_one_of("seconds"sv, "milliseconds"sv, "microseconds"sv)) { // 1. If NextUnitFractional(durationFormat, unit) is true, then if (next_unit_fractional(duration_format, unit)) { - - // a. Set value to value + AddFractionalDigits(durationFormat, duration). - value += add_fractional_digits(duration_format, duration); + // a. Set value to value + ComputeFractionalDigits(durationFormat, duration). + value += compute_fractional_digits(duration_format, duration); u8 maximum_fraction_digits = 0; u8 minimum_fraction_digits = 0; @@ -936,8 +947,8 @@ Vector partition_duration_format_pattern(VM& vm, DurationFor } } - // iii. If value is not 0 or display is not "auto", then - if (value != 0 || display != DurationFormat::Display::Auto) { + // iii. If value is not 0 or display is "always", then + if (value != 0 || display == DurationFormat::Display::Always) { // 1. Let numberingSystem be durationFormat.[[NumberingSystem]]. auto const& numbering_system = duration_format.numbering_system(); @@ -951,7 +962,7 @@ Vector partition_duration_format_pattern(VM& vm, DurationFor // b. If value is 0 and DurationSign(duration) is -1, then if (value == 0 && duration_sign(duration) == -1) { - // i. Set value to negative-zero. + // i. Set value to NEGATIVE-ZERO. value = -0.0; } } @@ -974,10 +985,10 @@ Vector partition_duration_format_pattern(VM& vm, DurationFor auto locale_style = Unicode::style_to_string(static_cast(style)); MUST(number_format_options->create_data_property_or_throw(vm.names.unitDisplay, PrimitiveString::create(vm, locale_style))); - // 9. Let nf be ! Construct(%NumberFormat%, « durationFormat.[[Locale]], nfOpts »). + // 9. Let nf be ! Construct(%Intl.NumberFormat%, « durationFormat.[[Locale]], nfOpts »). auto number_format = construct_number_format(vm, duration_format, number_format_options); - // 10. Let parts be ! PartitionNumberPattern(nf, value). + // 10. Let parts be PartitionNumberPattern(nf, value). auto parts = partition_number_pattern(number_format, MathematicalValue { value }); // 11. Let list be a new empty List. diff --git a/Libraries/LibJS/Runtime/Intl/DurationFormat.h b/Libraries/LibJS/Runtime/Intl/DurationFormat.h index 8dbfe4f47a9..770069a5c8b 100644 --- a/Libraries/LibJS/Runtime/Intl/DurationFormat.h +++ b/Libraries/LibJS/Runtime/Intl/DurationFormat.h @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, Idan Horowitz - * Copyright (c) 2022-2024, Tim Flynn + * Copyright (c) 2022-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -59,14 +59,11 @@ public: void set_numbering_system(String numbering_system) { m_numbering_system = move(numbering_system); } String const& numbering_system() const { return m_numbering_system; } - void set_hours_minutes_separator(String hours_minutes_separator) { m_hours_minutes_separator = move(hours_minutes_separator); } - String const& hours_minutes_separator() const { return m_hours_minutes_separator; } + void set_hour_minute_separator(String hour_minute_separator) { m_hour_minute_separator = move(hour_minute_separator); } + String const& hour_minute_separator() const { return m_hour_minute_separator; } - void set_minutes_seconds_separator(String minutes_seconds_separator) { m_minutes_seconds_separator = move(minutes_seconds_separator); } - String const& minutes_seconds_separator() const { return m_minutes_seconds_separator; } - - void set_two_digit_hours(bool two_digit_hours) { m_two_digit_hours = two_digit_hours; } - bool two_digit_hours() const { return m_two_digit_hours; } + void set_minute_second_separator(String minute_second_separator) { m_minute_second_separator = move(minute_second_separator); } + String const& minute_second_separator() const { return m_minute_second_separator; } void set_style(StringView style) { m_style = style_from_string(style); } Style style() const { return m_style; } @@ -168,11 +165,10 @@ private: static Display display_from_string(StringView display); static StringView display_to_string(Display); - String m_locale; // [[Locale]] - String m_numbering_system; // [[NumberingSystem]] - String m_hours_minutes_separator; // [[HourMinutesSeparator]] - String m_minutes_seconds_separator; // [[MinutesSecondsSeparator]] - bool m_two_digit_hours { false }; // [[TwoDigitHours]] + String m_locale; // [[Locale]] + String m_numbering_system; // [[NumberingSystem]] + String m_hour_minute_separator; // [[HourMinutesSeparator]] + String m_minute_second_separator; // [[MinutesSecondsSeparator]] Style m_style { Style::Long }; // [[Style]] ValueStyle m_years_style { ValueStyle::Long }; // [[YearsStyle]] @@ -224,22 +220,23 @@ struct DurationInstanceComponent { StringView digital_default; }; -// Table 1: Components of Duration Instances, https://tc39.es/proposal-intl-duration-format/#table-duration-component -static constexpr AK::Array date_values = { "long"sv, "short"sv, "narrow"sv }; -static constexpr AK::Array time_values = { "long"sv, "short"sv, "narrow"sv, "numeric"sv, "2-digit"sv }; -static constexpr AK::Array sub_second_values = { "long"sv, "short"sv, "narrow"sv, "numeric"sv }; -static constexpr AK::Array duration_instances_components { - DurationInstanceComponent { &DurationRecord::years, &DurationFormat::years_style, &DurationFormat::set_years_style, &DurationFormat::years_display, &DurationFormat::set_years_display, "years"sv, "year"sv, date_values, "short"sv }, - DurationInstanceComponent { &DurationRecord::months, &DurationFormat::months_style, &DurationFormat::set_months_style, &DurationFormat::months_display, &DurationFormat::set_months_display, "months"sv, "month"sv, date_values, "short"sv }, - DurationInstanceComponent { &DurationRecord::weeks, &DurationFormat::weeks_style, &DurationFormat::set_weeks_style, &DurationFormat::weeks_display, &DurationFormat::set_weeks_display, "weeks"sv, "week"sv, date_values, "short"sv }, - DurationInstanceComponent { &DurationRecord::days, &DurationFormat::days_style, &DurationFormat::set_days_style, &DurationFormat::days_display, &DurationFormat::set_days_display, "days"sv, "day"sv, date_values, "short"sv }, - DurationInstanceComponent { &DurationRecord::hours, &DurationFormat::hours_style, &DurationFormat::set_hours_style, &DurationFormat::hours_display, &DurationFormat::set_hours_display, "hours"sv, "hour"sv, time_values, "numeric"sv }, - DurationInstanceComponent { &DurationRecord::minutes, &DurationFormat::minutes_style, &DurationFormat::set_minutes_style, &DurationFormat::minutes_display, &DurationFormat::set_minutes_display, "minutes"sv, "minute"sv, time_values, "numeric"sv }, - DurationInstanceComponent { &DurationRecord::seconds, &DurationFormat::seconds_style, &DurationFormat::set_seconds_style, &DurationFormat::seconds_display, &DurationFormat::set_seconds_display, "seconds"sv, "second"sv, time_values, "numeric"sv }, - DurationInstanceComponent { &DurationRecord::milliseconds, &DurationFormat::milliseconds_style, &DurationFormat::set_milliseconds_style, &DurationFormat::milliseconds_display, &DurationFormat::set_milliseconds_display, "milliseconds"sv, "millisecond"sv, sub_second_values, "numeric"sv }, - DurationInstanceComponent { &DurationRecord::microseconds, &DurationFormat::microseconds_style, &DurationFormat::set_microseconds_style, &DurationFormat::microseconds_display, &DurationFormat::set_microseconds_display, "microseconds"sv, "microsecond"sv, sub_second_values, "numeric"sv }, - DurationInstanceComponent { &DurationRecord::nanoseconds, &DurationFormat::nanoseconds_style, &DurationFormat::set_nanoseconds_style, &DurationFormat::nanoseconds_display, &DurationFormat::set_nanoseconds_display, "nanoseconds"sv, "nanosecond"sv, sub_second_values, "numeric"sv }, -}; +// Table 2: DurationFormat instance internal slots and properties relevant to PartitionDurationFormatPattern, https://tc39.es/proposal-intl-duration-format/#table-partition-duration-format-pattern +// Table 3: Internal slots and property names of DurationFormat instances relevant to Intl.DurationFormat constructor, https://tc39.es/proposal-intl-duration-format/#table-durationformat +static constexpr auto date_values = AK::Array { "long"sv, "short"sv, "narrow"sv }; +static constexpr auto time_values = AK::Array { "long"sv, "short"sv, "narrow"sv, "numeric"sv, "2-digit"sv }; +static constexpr auto sub_second_values = AK::Array { "long"sv, "short"sv, "narrow"sv, "numeric"sv }; +static constexpr auto duration_instances_components = to_array({ + { &DurationRecord::years, &DurationFormat::years_style, &DurationFormat::set_years_style, &DurationFormat::years_display, &DurationFormat::set_years_display, "years"sv, "year"sv, date_values, "short"sv }, + { &DurationRecord::months, &DurationFormat::months_style, &DurationFormat::set_months_style, &DurationFormat::months_display, &DurationFormat::set_months_display, "months"sv, "month"sv, date_values, "short"sv }, + { &DurationRecord::weeks, &DurationFormat::weeks_style, &DurationFormat::set_weeks_style, &DurationFormat::weeks_display, &DurationFormat::set_weeks_display, "weeks"sv, "week"sv, date_values, "short"sv }, + { &DurationRecord::days, &DurationFormat::days_style, &DurationFormat::set_days_style, &DurationFormat::days_display, &DurationFormat::set_days_display, "days"sv, "day"sv, date_values, "short"sv }, + { &DurationRecord::hours, &DurationFormat::hours_style, &DurationFormat::set_hours_style, &DurationFormat::hours_display, &DurationFormat::set_hours_display, "hours"sv, "hour"sv, time_values, "numeric"sv }, + { &DurationRecord::minutes, &DurationFormat::minutes_style, &DurationFormat::set_minutes_style, &DurationFormat::minutes_display, &DurationFormat::set_minutes_display, "minutes"sv, "minute"sv, time_values, "numeric"sv }, + { &DurationRecord::seconds, &DurationFormat::seconds_style, &DurationFormat::set_seconds_style, &DurationFormat::seconds_display, &DurationFormat::set_seconds_display, "seconds"sv, "second"sv, time_values, "numeric"sv }, + { &DurationRecord::milliseconds, &DurationFormat::milliseconds_style, &DurationFormat::set_milliseconds_style, &DurationFormat::milliseconds_display, &DurationFormat::set_milliseconds_display, "milliseconds"sv, "millisecond"sv, sub_second_values, "numeric"sv }, + { &DurationRecord::microseconds, &DurationFormat::microseconds_style, &DurationFormat::set_microseconds_style, &DurationFormat::microseconds_display, &DurationFormat::set_microseconds_display, "microseconds"sv, "microsecond"sv, sub_second_values, "numeric"sv }, + { &DurationRecord::nanoseconds, &DurationFormat::nanoseconds_style, &DurationFormat::set_nanoseconds_style, &DurationFormat::nanoseconds_display, &DurationFormat::set_nanoseconds_display, "nanoseconds"sv, "nanosecond"sv, sub_second_values, "numeric"sv }, +}); struct DurationUnitOptions { String style; @@ -255,7 +252,7 @@ struct DurationFormatPart { ThrowCompletionOr to_duration_record(VM&, Value input); i8 duration_sign(DurationRecord const&); ThrowCompletionOr get_duration_unit_options(VM&, String const& unit, Object const& options, StringView base_style, ReadonlySpan styles_list, StringView digital_base, StringView previous_style, bool two_digit_hours); -double add_fractional_digits(DurationFormat const&, DurationRecord const&); +double compute_fractional_digits(DurationFormat const&, DurationRecord const&); bool next_unit_fractional(DurationFormat const&, StringView unit); Vector format_numeric_hours(VM&, DurationFormat const&, double hours_value, bool sign_displayed); Vector format_numeric_minutes(VM&, DurationFormat const&, double minutes_value, bool hours_displayed, bool sign_displayed); diff --git a/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp b/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp index 09732181758..351b211ad0e 100644 --- a/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp +++ b/Libraries/LibJS/Runtime/Intl/DurationFormatConstructor.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, Idan Horowitz - * Copyright (c) 2022-2024, Tim Flynn + * Copyright (c) 2022-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -52,7 +52,7 @@ ThrowCompletionOr> DurationFormatConstructor::construct(Function auto locales = vm.argument(0); auto options_value = vm.argument(1); - // 2. Let durationFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%DurationFormatPrototype%", « [[InitializedDurationFormat]], [[Locale]], [[DataLocale]], [[NumberingSystem]], [[Style]], [[YearsStyle]], [[YearsDisplay]], [[MonthsStyle]], [[MonthsDisplay]], [[WeeksStyle]], [[WeeksDisplay]], [[DaysStyle]], [[DaysDisplay]], [[HoursStyle]], [[HoursDisplay]], [[MinutesStyle]], [[MinutesDisplay]], [[SecondsStyle]], [[SecondsDisplay]], [[MillisecondsStyle]], [[MillisecondsDisplay]], [[MicrosecondsStyle]], [[MicrosecondsDisplay]], [[NanosecondsStyle]], [[NanosecondsDisplay]], [[HoursMinutesSeparator]], [[MinutesSecondsSeparator]], [[FractionalDigits]], [[TwoDigitHours]] »). + // 2. Let durationFormat be ? OrdinaryCreateFromConstructor(NewTarget, "%Intl.DurationFormatPrototype%", « [[InitializedDurationFormat]], [[Locale]], [[DataLocale]], [[NumberingSystem]], [[Style]], [[YearsStyle]], [[YearsDisplay]], [[MonthsStyle]], [[MonthsDisplay]], [[WeeksStyle]], [[WeeksDisplay]], [[DaysStyle]], [[DaysDisplay]], [[HoursStyle]], [[HoursDisplay]], [[MinutesStyle]], [[MinutesDisplay]], [[SecondsStyle]], [[SecondsDisplay]], [[MillisecondsStyle]], [[MillisecondsDisplay]], [[MicrosecondsStyle]], [[MicrosecondsDisplay]], [[NanosecondsStyle]], [[NanosecondsDisplay]], [[HourMinuteSeparator]], [[MinuteSecondSeparator]], [[FractionalDigits]] »). auto duration_format = TRY(ordinary_create_from_constructor(vm, new_target, &Intrinsics::intl_duration_format_prototype)); // 3. Let requestedLocales be ? CanonicalizeLocaleList(locales). @@ -61,10 +61,10 @@ ThrowCompletionOr> DurationFormatConstructor::construct(Function // 4. Let options be ? GetOptionsObject(options). auto options = TRY(Temporal::get_options_object(vm, options_value)); - // 5. Let matcher be ? GetOption(options, "localeMatcher", string, « "lookup", "best fit" », "best fit"). + // 5. Let matcher be ? GetOption(options, "localeMatcher", STRING, « "lookup", "best fit" », "best fit"). auto matcher = TRY(get_option(vm, *options, vm.names.localeMatcher, OptionType::String, { "lookup"sv, "best fit"sv }, "best fit"sv)); - // 6. Let numberingSystem be ? GetOption(options, "numberingSystem", string, undefined, undefined). + // 6. Let numberingSystem be ? GetOption(options, "numberingSystem", STRING, EMPTY, undefined). auto numbering_system = TRY(get_option(vm, *options, vm.names.numberingSystem, OptionType::String, {}, Empty {})); // 7. If numberingSystem is not undefined, then @@ -79,7 +79,7 @@ ThrowCompletionOr> DurationFormatConstructor::construct(Function opt.locale_matcher = matcher; opt.nu = locale_key_from_value(numbering_system); - // 9. Let r be ResolveLocale(%DurationFormat%.[[AvailableLocales]], requestedLocales, opt, %DurationFormat%.[[RelevantExtensionKeys]], %DurationFormat%.[[LocaleData]]). + // 9. Let r be ResolveLocale(%Intl.DurationFormat%.[[AvailableLocales]], requestedLocales, opt, %Intl.DurationFormat%.[[RelevantExtensionKeys]], %Intl.DurationFormat%.[[LocaleData]]). auto result = resolve_locale(requested_locales, opt, DurationFormat::relevant_extension_keys()); // 10. Let locale be r.[[locale]]. @@ -89,7 +89,8 @@ ThrowCompletionOr> DurationFormatConstructor::construct(Function duration_format->set_locale(move(locale)); // 12. Set durationFormat.[[DataLocale]] to r.[[dataLocale]]. - // NOTE: The [[dataLocale]] internal slot no longer exists. + // FIXME: Spec issue: The [[dataLocale]] internal slot no longer exists. See: + // https://github.com/tc39/proposal-intl-duration-format/issues/189 // 13. Let dataLocale be durationFormat.[[DataLocale]]. // 14. Let dataLocaleData be durationFormat.[[LocaleData]].[[]]. @@ -97,31 +98,30 @@ ThrowCompletionOr> DurationFormatConstructor::construct(Function auto digital_format = Unicode::digital_format(duration_format->locale()); // 16. Let twoDigitHours be digitalFormat.[[TwoDigitHours]]. - // 17. Set durationFormat.[[TwoDigitHours]] to twoDigitHours. - duration_format->set_two_digit_hours(digital_format.uses_two_digit_hours); + auto two_digit_hours = digital_format.uses_two_digit_hours; - // 18. Let hoursMinutesSeparator be digitalFormat.[[HoursMinutesSeparator]]. - // 19. Set durationFormat.[[HoursMinutesSeparator]] to hoursMinutesSeparator. - duration_format->set_hours_minutes_separator(move(digital_format.hours_minutes_separator)); + // 17. Let hourMinuteSeparator be digitalFormat.[[HourMinuteSeparator]]. + // 18. Set durationFormat.[[HourMinuteSeparator]] to hourMinuteSeparator. + duration_format->set_hour_minute_separator(move(digital_format.hours_minutes_separator)); - // 20. Let minutesSecondsSeparator be digitalFormat.[[MinutesSecondsSeparator]]. - // 21. Set durationFormat.[[MinutesSecondsSeparator]] to minutesSecondsSeparator. - duration_format->set_minutes_seconds_separator(move(digital_format.minutes_seconds_separator)); + // 19. Let minuteSecondSeparator be digitalFormat.[[MinuteSecondSeparator]]. + // 20. Set durationFormat.[[MinuteSecondSeparator]] to minuteSecondSeparator. + duration_format->set_minute_second_separator(move(digital_format.minutes_seconds_separator)); - // 22. Set durationFormat.[[NumberingSystem]] to r.[[nu]]. + // 21. Set durationFormat.[[NumberingSystem]] to r.[[nu]]. if (auto* resolved_numbering_system = result.nu.get_pointer()) duration_format->set_numbering_system(move(*resolved_numbering_system)); - // 23. Let style be ? GetOption(options, "style", string, « "long", "short", "narrow", "digital" », "short"). + // 22. Let style be ? GetOption(options, "style", STRING, « "long", "short", "narrow", "digital" », "short"). auto style = TRY(get_option(vm, *options, vm.names.style, OptionType::String, { "long"sv, "short"sv, "narrow"sv, "digital"sv }, "short"sv)); - // 24. Set durationFormat.[[Style]] to style. + // 23. Set durationFormat.[[Style]] to style. duration_format->set_style(style.as_string().utf8_string_view()); - // 25. Let prevStyle be the empty String. - String previous_style {}; + // 24. Let prevStyle be the empty String. + String previous_style; - // 26. For each row of Table 3, except the header row, in table order, do + // 25. For each row of Table 3, except the header row, in table order, do for (auto const& duration_instances_component : duration_instances_components) { // a. Let styleSlot be the Style Slot value of the current row. auto style_slot = duration_instances_component.set_style_slot; @@ -139,7 +139,7 @@ ThrowCompletionOr> DurationFormatConstructor::construct(Function auto digital_base = duration_instances_component.digital_default; // f. Let unitOptions be ? GetDurationUnitOptions(unit, options, style, valueList, digitalBase, prevStyle, twoDigitHours). - auto unit_options = TRY(get_duration_unit_options(vm, unit, *options, duration_format->style_string(), value_list, digital_base, previous_style, duration_format->two_digit_hours())); + auto unit_options = TRY(get_duration_unit_options(vm, unit, *options, duration_format->style_string(), value_list, digital_base, previous_style, two_digit_hours)); // g. Set the value of the styleSlot slot of durationFormat to unitOptions.[[Style]]. (duration_format->*style_slot)(unit_options.style); @@ -154,10 +154,10 @@ ThrowCompletionOr> DurationFormatConstructor::construct(Function } } - // 27. Set durationFormat.[[FractionalDigits]] to ? GetNumberOption(options, "fractionalDigits", 0, 9, undefined). + // 26. Set durationFormat.[[FractionalDigits]] to ? GetNumberOption(options, "fractionalDigits", 0, 9, undefined). duration_format->set_fractional_digits(Optional(TRY(get_number_option(vm, *options, vm.names.fractionalDigits, 0, 9, {})))); - // 28. Return durationFormat. + // 27. Return durationFormat. return duration_format; } diff --git a/Libraries/LibJS/Runtime/Intl/DurationFormatPrototype.cpp b/Libraries/LibJS/Runtime/Intl/DurationFormatPrototype.cpp index fc1ed17fd0d..a4ccc26b3a7 100644 --- a/Libraries/LibJS/Runtime/Intl/DurationFormatPrototype.cpp +++ b/Libraries/LibJS/Runtime/Intl/DurationFormatPrototype.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, Idan Horowitz - * Copyright (c) 2022-2024, Tim Flynn + * Copyright (c) 2022-2024, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -52,7 +52,7 @@ JS_DEFINE_NATIVE_FUNCTION(DurationFormatPrototype::format) // 5. Let result be a new empty String. StringBuilder result; - // 6. For each Record { [[Type]], [[Value]] } part in parts, do + // 6. For each Record { [[Type]], [[Value]], [[Unit]] } part in parts, do for (auto const& part : parts) { // a. Set result to the string-concatenation of result and part.[[Value]]. result.append(part.value); @@ -81,9 +81,9 @@ JS_DEFINE_NATIVE_FUNCTION(DurationFormatPrototype::format_to_parts) auto result = MUST(Array::create(realm, 0)); // 6. Let n be 0. - // 7. For each { [[Type]], [[Value]] } part in parts, do + // 7. For each Record { [[Type]], [[Value]], [[Unit]] } part in parts, do for (auto [n, part] : enumerate(parts)) { - // a. Let obj be OrdinaryObjectCreate(%ObjectPrototype%). + // a. Let obj be OrdinaryObjectCreate(%Object.prototype%). auto object = Object::create(realm, realm.intrinsics().object_prototype()); // b. Perform ! CreateDataPropertyOrThrow(obj, "type", part.[[Type]]). @@ -99,7 +99,7 @@ JS_DEFINE_NATIVE_FUNCTION(DurationFormatPrototype::format_to_parts) // e. Perform ! CreateDataPropertyOrThrow(result, ! ToString(n), obj). MUST(result->create_data_property_or_throw(n, object)); - // f. Increment n by 1. + // f. Set n to n + 1. } // 8. Return result.