From b2097f4059625a6bc8c757c5f6cb3b3fb04644a7 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Fri, 27 Jan 2023 10:18:11 -0500 Subject: [PATCH] LibJS+LibLocale: Port Intl.DateTimeFormat to String --- .../LibLocale/GenerateDateTimeFormatData.cpp | 207 ++++++++++-------- Tests/LibLocale/TestDateTimeFormat.cpp | 6 +- .../ClockSettings/TimeZoneSettingsWidget.cpp | 4 +- .../LibJS/Runtime/Intl/DateTimeFormat.cpp | 93 ++++---- .../LibJS/Runtime/Intl/DateTimeFormat.h | 19 +- .../Intl/DateTimeFormatConstructor.cpp | 14 +- .../Libraries/LibLocale/DateTimeFormat.cpp | 42 ++-- Userland/Libraries/LibLocale/DateTimeFormat.h | 35 +-- 8 files changed, 223 insertions(+), 197 deletions(-) diff --git a/Meta/Lagom/Tools/CodeGenerators/LibLocale/GenerateDateTimeFormatData.cpp b/Meta/Lagom/Tools/CodeGenerators/LibLocale/GenerateDateTimeFormatData.cpp index 67ab671002..83d5655e31 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibLocale/GenerateDateTimeFormatData.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibLocale/GenerateDateTimeFormatData.cpp @@ -715,7 +715,7 @@ static constexpr auto is_char(char ch) // "{hour}:{minute} {ampm}" becomes "{hour}:{minute}" (remove the space before {ampm}) // "{ampm} {hour}" becomes "{hour}" (remove the space after {ampm}) // "{hour}:{minute} {ampm} {timeZoneName}" becomes "{hour}:{minute} {timeZoneName}" (remove one of the spaces around {ampm}) -static DeprecatedString remove_period_from_pattern(DeprecatedString pattern) +static ErrorOr remove_period_from_pattern(String pattern) { auto is_surrounding_space = [&](auto code_point_iterator) { if (code_point_iterator.done()) @@ -738,7 +738,7 @@ static DeprecatedString remove_period_from_pattern(DeprecatedString pattern) }; for (auto remove : AK::Array { "({ampm})"sv, "{ampm}"sv, "({dayPeriod})"sv, "{dayPeriod}"sv }) { - auto index = pattern.find(remove); + auto index = pattern.find_byte_offset(remove); if (!index.has_value()) continue; @@ -751,25 +751,27 @@ static DeprecatedString remove_period_from_pattern(DeprecatedString pattern) if (auto it = utf8_pattern.iterator_at_byte_offset(*index + remove.length()); it != utf8_pattern.end()) after_removal = it; + auto pattern_view = pattern.bytes_as_string_view(); + if (is_surrounding_space(before_removal) && !is_opening(after_removal)) { - pattern = DeprecatedString::formatted("{}{}", - pattern.substring_view(0, *index - before_removal.underlying_code_point_length_in_bytes()), - pattern.substring_view(*index + remove.length())); + pattern = TRY(String::formatted("{}{}", + pattern_view.substring_view(0, *index - before_removal.underlying_code_point_length_in_bytes()), + pattern_view.substring_view(*index + remove.length()))); } else if (is_surrounding_space(after_removal) && !is_closing(before_removal)) { - pattern = DeprecatedString::formatted("{}{}", - pattern.substring_view(0, *index), - pattern.substring_view(*index + remove.length() + after_removal.underlying_code_point_length_in_bytes())); + pattern = TRY(String::formatted("{}{}", + pattern_view.substring_view(0, *index), + pattern_view.substring_view(*index + remove.length() + after_removal.underlying_code_point_length_in_bytes()))); } else { - pattern = DeprecatedString::formatted("{}{}", - pattern.substring_view(0, *index), - pattern.substring_view(*index + remove.length())); + pattern = TRY(String::formatted("{}{}", + pattern_view.substring_view(0, *index), + pattern_view.substring_view(*index + remove.length()))); } } return pattern; } -static Optional parse_date_time_pattern_raw(DeprecatedString pattern, DeprecatedString skeleton, CLDR& cldr) +static ErrorOr> parse_date_time_pattern_raw(DeprecatedString pattern, DeprecatedString skeleton, CLDR& cldr) { // https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table using Locale::CalendarPatternStyle; @@ -819,7 +821,7 @@ static Optional parse_date_time_pattern_raw(DeprecatedString pa // Quarter else if (all_of(segment, is_any_of("qQ"sv))) { // Intl.DateTimeFormat does not support quarter formatting, so drop these patterns. - return {}; + return OptionalNone {}; } // Month @@ -838,13 +840,13 @@ static Optional parse_date_time_pattern_raw(DeprecatedString pa format.month = CalendarPatternStyle::Narrow; } else if (all_of(segment, is_char('l'))) { // Using 'l' for month formatting is deprecated by TR-35, ensure it is not used. - return {}; + return OptionalNone {}; } // Week else if (all_of(segment, is_any_of("wW"sv))) { // Intl.DateTimeFormat does not support week formatting, so drop these patterns. - return {}; + return OptionalNone {}; } // Day @@ -876,7 +878,7 @@ static Optional parse_date_time_pattern_raw(DeprecatedString pa // TR-35 defines "e", "c", and "cc" as as numeric, and "ee" as 2-digit, but those // pattern styles are not supported by Intl.DateTimeFormat. if (segment.length() <= 2) - return {}; + return OptionalNone {}; if (segment.length() == 4) format.weekday = CalendarPatternStyle::Long; @@ -915,7 +917,7 @@ static Optional parse_date_time_pattern_raw(DeprecatedString pa format.hour = CalendarPatternStyle::TwoDigit; } else if (all_of(segment, is_any_of("jJC"sv))) { // TR-35 indicates these should not be used. - return {}; + return OptionalNone {}; } // Minute @@ -943,7 +945,7 @@ static Optional parse_date_time_pattern_raw(DeprecatedString pa format.fractional_second_digits = static_cast(segment.length()); } else if (all_of(segment, is_char('A'))) { // Intl.DateTimeFormat does not support millisecond formatting, so drop these patterns. - return {}; + return OptionalNone {}; } // Zone @@ -976,30 +978,30 @@ static Optional parse_date_time_pattern_raw(DeprecatedString pa } } - pattern = builder.build(); + auto parsed_pattern = TRY(builder.to_string()); if (hour12) { - format.pattern = remove_period_from_pattern(pattern); - format.pattern12 = move(pattern); + format.pattern = TRY(remove_period_from_pattern(parsed_pattern)); + format.pattern12 = move(parsed_pattern); } else { - format.pattern = move(pattern); + format.pattern = move(parsed_pattern); } return format; } -static Optional parse_date_time_pattern(DeprecatedString pattern, DeprecatedString skeleton, CLDR& cldr) +static ErrorOr> parse_date_time_pattern(DeprecatedString pattern, DeprecatedString skeleton, CLDR& cldr) { - auto format = parse_date_time_pattern_raw(move(pattern), move(skeleton), cldr); + auto format = TRY(parse_date_time_pattern_raw(move(pattern), move(skeleton), cldr)); if (!format.has_value()) - return {}; + return OptionalNone {}; - format->pattern_index = cldr.unique_strings.ensure(move(format->pattern)); + format->pattern_index = cldr.unique_strings.ensure(format->pattern.to_deprecated_string()); if (format->pattern12.has_value()) - format->pattern12_index = cldr.unique_strings.ensure(format->pattern12.release_value()); + format->pattern12_index = cldr.unique_strings.ensure(format->pattern12->to_deprecated_string()); - return cldr.unique_patterns.ensure(format.release_value()); + return Optional { cldr.unique_patterns.ensure(format.release_value()) }; } template @@ -1008,7 +1010,7 @@ static constexpr bool char_is_one_of(char ch, Chars&&... chars) return ((ch == chars) || ...); } -static void parse_interval_patterns(Calendar& calendar, JsonObject const& interval_formats_object, CLDR& cldr) +static ErrorOr parse_interval_patterns(Calendar& calendar, JsonObject const& interval_formats_object, CLDR& cldr) { // https://unicode.org/reports/tr35/tr35-dates.html#intervalFormats CalendarRangePatternList range_formats {}; @@ -1060,28 +1062,29 @@ static void parse_interval_patterns(Calendar& calendar, JsonObject const& interv HashMap partitions; StringView last_partition; - auto begin_index = pattern.find('{'); + auto begin_index = pattern.find_byte_offset('{'); size_t end_index = 0; while (begin_index.has_value()) { - end_index = pattern.find('}', *begin_index).value(); + end_index = pattern.find_byte_offset('}', *begin_index).value(); - auto partition = pattern.substring_view(*begin_index, end_index - *begin_index); + auto partition = pattern.bytes_as_string_view().substring_view(*begin_index, end_index - *begin_index); if (partitions.contains(partition)) break; partitions.set(partition, *begin_index); last_partition = partition; - begin_index = pattern.find('{', end_index + 1); + begin_index = pattern.find_byte_offset('{', end_index + 1); } VERIFY(begin_index.has_value() && !last_partition.is_empty()); auto start_range_end = partitions.get(last_partition).value() + last_partition.length() + 1; - auto start_range = pattern.substring_view(0, start_range_end); - auto separator = pattern.substring_view(start_range_end, *begin_index - start_range_end); - auto end_range = pattern.substring_view(*begin_index); + auto pattern_view = pattern.bytes_as_string_view(); + auto start_range = pattern_view.substring_view(0, start_range_end); + auto separator = pattern_view.substring_view(start_range_end, *begin_index - start_range_end); + auto end_range = pattern_view.substring_view(*begin_index); CalendarRangePattern format {}; format.skeleton_index = cldr.unique_strings.ensure(skeleton); @@ -1097,21 +1100,21 @@ static void parse_interval_patterns(Calendar& calendar, JsonObject const& interv return format; }; - interval_formats_object.for_each_member([&](auto const& skeleton, auto const& value) { + TRY(interval_formats_object.try_for_each_member([&](auto const& skeleton, auto const& value) -> ErrorOr { if (skeleton == "intervalFormatFallback"sv) { auto range_format = split_default_range_pattern(skeleton, value.as_string()); calendar.default_range_format = cldr.unique_range_patterns.ensure(move(range_format)); - return; + return {}; } - value.as_object().for_each_member([&](auto const& field, auto const& pattern) { + TRY(value.as_object().try_for_each_member([&](auto const& field, auto const& pattern) -> ErrorOr { if (field.ends_with("alt-variant"sv)) - return; + return {}; VERIFY(field.length() == 1); auto name = name_of_field(field[0]); - auto format = parse_date_time_pattern_raw(pattern.as_string(), skeleton, cldr).release_value(); + auto format = TRY(parse_date_time_pattern_raw(pattern.as_string(), skeleton, cldr)).release_value(); auto range_format = split_range_pattern(skeleton, name, format.pattern, format); range_formats.append(cldr.unique_range_patterns.ensure(move(range_format))); @@ -1122,26 +1125,34 @@ static void parse_interval_patterns(Calendar& calendar, JsonObject const& interv } else { range12_formats.append(range_formats.last()); } - }); - }); + + return {}; + })); + + return {}; + })); calendar.range_formats = cldr.unique_range_pattern_lists.ensure(move(range_formats)); calendar.range12_formats = cldr.unique_range_pattern_lists.ensure(move(range12_formats)); + + return {}; } -static void generate_default_patterns(CalendarPatternList& formats, CLDR& cldr) +static ErrorOr generate_default_patterns(CalendarPatternList& formats, CLDR& cldr) { // For compatibility with ICU, we generate a list of default patterns for every locale: // https://github.com/unicode-org/icu/blob/release-71-1/icu4c/source/i18n/dtptngen.cpp#L1343-L1354= static constexpr auto default_patterns = Array { "G"sv, "y"sv, "M"sv, "E"sv, "D"sv, "F"sv, "d"sv, "a"sv, "B"sv, "H"sv, "mm"sv, "ss"sv, "SS"sv, "v"sv }; for (auto pattern : default_patterns) { - auto index = parse_date_time_pattern(pattern, pattern, cldr); + auto index = TRY(parse_date_time_pattern(pattern, pattern, cldr)); VERIFY(index.has_value()); if (!formats.contains_slow(*index)) formats.append(*index); } + + return {}; } static void generate_missing_patterns(Calendar& calendar, CalendarPatternList& formats, Vector date_formats, Vector time_formats, CLDR& cldr) @@ -1390,12 +1401,12 @@ static ErrorOr parse_calendars(DeprecatedString locale_calendars_path, CLD auto const& dates_object = locale_object.get_object("dates"sv).value(); auto const& calendars_object = dates_object.get_object("calendars"sv).value(); - auto parse_patterns = [&](auto const& patterns_object, auto const& skeletons_object, Vector* patterns) { - auto parse_pattern = [&](auto name) { + auto parse_patterns = [&](auto const& patterns_object, auto const& skeletons_object, Vector* patterns) -> ErrorOr { + auto parse_pattern = [&](auto name) -> ErrorOr { auto format = patterns_object.get_deprecated_string(name); auto skeleton = skeletons_object.get_deprecated_string(name); - auto format_index = parse_date_time_pattern(format.value(), skeleton.value_or(DeprecatedString::empty()), cldr).value(); + auto format_index = TRY(parse_date_time_pattern(format.value(), skeleton.value_or(DeprecatedString::empty()), cldr)).value(); if (patterns) patterns->append(cldr.unique_patterns.get(format_index)); @@ -1404,19 +1415,19 @@ static ErrorOr parse_calendars(DeprecatedString locale_calendars_path, CLD }; CalendarFormat formats {}; - formats.full_format = parse_pattern("full"sv); - formats.long_format = parse_pattern("long"sv); - formats.medium_format = parse_pattern("medium"sv); - formats.short_format = parse_pattern("short"sv); + formats.full_format = TRY(parse_pattern("full"sv)); + formats.long_format = TRY(parse_pattern("long"sv)); + formats.medium_format = TRY(parse_pattern("medium"sv)); + formats.short_format = TRY(parse_pattern("short"sv)); return cldr.unique_formats.ensure(move(formats)); }; - calendars_object.for_each_member([&](auto const& calendar_name, JsonValue const& value) { + TRY(calendars_object.try_for_each_member([&](auto const& calendar_name, JsonValue const& value) -> ErrorOr { // The generic calendar is not a supported Unicode calendar key, so skip it: // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#unicode_calendar_keys if (calendar_name == "generic"sv) - return; + return {}; Calendar calendar {}; CalendarPatternList available_formats {}; @@ -1429,21 +1440,21 @@ static ErrorOr parse_calendars(DeprecatedString locale_calendars_path, CLD auto const& date_formats_object = value.as_object().get_object("dateFormats"sv).value(); auto const& date_skeletons_object = value.as_object().get_object("dateSkeletons"sv).value(); - calendar.date_formats = parse_patterns(date_formats_object, date_skeletons_object, &date_formats); + calendar.date_formats = TRY(parse_patterns(date_formats_object, date_skeletons_object, &date_formats)); auto const& time_formats_object = value.as_object().get_object("timeFormats"sv).value(); auto const& time_skeletons_object = value.as_object().get_object("timeSkeletons"sv).value(); - calendar.time_formats = parse_patterns(time_formats_object, time_skeletons_object, &time_formats); + calendar.time_formats = TRY(parse_patterns(time_formats_object, time_skeletons_object, &time_formats)); auto const& standard_date_time_formats_object = value.as_object().get_object("dateTimeFormats-atTime"sv)->get_object("standard"sv).value(); - calendar.date_time_formats = parse_patterns(standard_date_time_formats_object, JsonObject {}, nullptr); + calendar.date_time_formats = TRY(parse_patterns(standard_date_time_formats_object, JsonObject {}, nullptr)); auto const& date_time_formats_object = value.as_object().get_object("dateTimeFormats"sv).value(); auto const& available_formats_object = date_time_formats_object.get_object("availableFormats"sv).value(); - available_formats_object.for_each_member([&](auto const& skeleton, JsonValue const& pattern) { - auto pattern_index = parse_date_time_pattern(pattern.as_string(), skeleton, cldr); + TRY(available_formats_object.try_for_each_member([&](auto const& skeleton, JsonValue const& pattern) -> ErrorOr { + auto pattern_index = TRY(parse_date_time_pattern(pattern.as_string(), skeleton, cldr)); if (!pattern_index.has_value()) - return; + return {}; auto const& format = cldr.unique_patterns.get(*pattern_index); if (format.contains_only_date_fields()) @@ -1453,18 +1464,22 @@ static ErrorOr parse_calendars(DeprecatedString locale_calendars_path, CLD if (!available_formats.contains_slow(*pattern_index)) available_formats.append(*pattern_index); - }); + + return {}; + })); auto const& interval_formats_object = date_time_formats_object.get_object("intervalFormats"sv).value(); - parse_interval_patterns(calendar, interval_formats_object, cldr); + TRY(parse_interval_patterns(calendar, interval_formats_object, cldr)); - generate_default_patterns(available_formats, cldr); + TRY(generate_default_patterns(available_formats, cldr)); generate_missing_patterns(calendar, available_formats, move(date_formats), move(time_formats), cldr); parse_calendar_symbols(calendar, value.as_object(), cldr); calendar.available_formats = cldr.unique_pattern_lists.ensure(move(available_formats)); locale.calendars.set(calendar_name, cldr.unique_calendars.ensure(move(calendar))); - }); + + return {}; + })); return {}; } @@ -1736,7 +1751,9 @@ static ErrorOr generate_unicode_locale_implementation(Core::Stream::Buffer generator.append(R"~~~( #include #include +#include #include +#include #include #include #include @@ -1778,13 +1795,13 @@ static void convert_calendar_fields(SourceType const& source, TargetType& target } struct CalendarPatternImpl { - CalendarPattern to_unicode_calendar_pattern() const { + ErrorOr to_unicode_calendar_pattern() const { CalendarPattern calendar_pattern {}; - calendar_pattern.skeleton = decode_string(skeleton); - calendar_pattern.pattern = decode_string(pattern); + calendar_pattern.skeleton = TRY(String::from_utf8(decode_string(skeleton))); + calendar_pattern.pattern = TRY(String::from_utf8(decode_string(pattern))); if (pattern12 != 0) - calendar_pattern.pattern12 = decode_string(pattern12); + calendar_pattern.pattern12 = TRY(String::from_utf8(decode_string(pattern12))); convert_calendar_fields(*this, calendar_pattern); return calendar_pattern; @@ -1808,14 +1825,14 @@ struct CalendarPatternImpl { }; struct CalendarRangePatternImpl { - CalendarRangePattern to_unicode_calendar_range_pattern() const { + ErrorOr to_unicode_calendar_range_pattern() const { CalendarRangePattern calendar_range_pattern {}; if (field != -1) calendar_range_pattern.field = static_cast(field); - calendar_range_pattern.start_range = decode_string(start_range); + calendar_range_pattern.start_range = TRY(String::from_utf8(decode_string(start_range))); calendar_range_pattern.separator = decode_string(separator); - calendar_range_pattern.end_range = decode_string(end_range); + calendar_range_pattern.end_range = TRY(String::from_utf8(decode_string(end_range))); convert_calendar_fields(*this, calendar_range_pattern); return calendar_range_pattern; @@ -1848,13 +1865,13 @@ struct CalendarRangePatternImpl { generator.append(R"~~~( struct CalendarFormatImpl { - CalendarFormat to_unicode_calendar_format() const { + ErrorOr to_unicode_calendar_format() const { CalendarFormat calendar_format {}; - calendar_format.full_format = s_calendar_patterns[full_format].to_unicode_calendar_pattern(); - calendar_format.long_format = s_calendar_patterns[long_format].to_unicode_calendar_pattern(); - calendar_format.medium_format = s_calendar_patterns[medium_format].to_unicode_calendar_pattern(); - calendar_format.short_format = s_calendar_patterns[short_format].to_unicode_calendar_pattern(); + calendar_format.full_format = TRY(s_calendar_patterns[full_format].to_unicode_calendar_pattern()); + calendar_format.long_format = TRY(s_calendar_patterns[long_format].to_unicode_calendar_pattern()); + calendar_format.medium_format = TRY(s_calendar_patterns[medium_format].to_unicode_calendar_pattern()); + calendar_format.short_format = TRY(s_calendar_patterns[short_format].to_unicode_calendar_pattern()); return calendar_format; } @@ -2110,59 +2127,59 @@ static CalendarData const* find_calendar_data(StringView locale, StringView cale return lookup_calendar(*default_calendar); } -Optional get_calendar_date_format(StringView locale, StringView calendar) +ErrorOr> get_calendar_date_format(StringView locale, StringView calendar) { if (auto const* data = find_calendar_data(locale, calendar); data != nullptr) { auto const& formats = s_calendar_formats.at(data->date_formats); - return formats.to_unicode_calendar_format(); + return TRY(formats.to_unicode_calendar_format()); } - return {}; + return OptionalNone {}; } -Optional get_calendar_time_format(StringView locale, StringView calendar) +ErrorOr> get_calendar_time_format(StringView locale, StringView calendar) { if (auto const* data = find_calendar_data(locale, calendar); data != nullptr) { auto const& formats = s_calendar_formats.at(data->time_formats); - return formats.to_unicode_calendar_format(); + return TRY(formats.to_unicode_calendar_format()); } - return {}; + return OptionalNone {}; } -Optional get_calendar_date_time_format(StringView locale, StringView calendar) +ErrorOr> get_calendar_date_time_format(StringView locale, StringView calendar) { if (auto const* data = find_calendar_data(locale, calendar); data != nullptr) { auto const& formats = s_calendar_formats.at(data->date_time_formats); - return formats.to_unicode_calendar_format(); + return TRY(formats.to_unicode_calendar_format()); } - return {}; + return OptionalNone {}; } -Vector get_calendar_available_formats(StringView locale, StringView calendar) +ErrorOr> get_calendar_available_formats(StringView locale, StringView calendar) { Vector result {}; if (auto const* data = find_calendar_data(locale, calendar); data != nullptr) { auto const& available_formats = s_calendar_pattern_lists.at(data->available_formats); - result.ensure_capacity(available_formats.size()); + TRY(result.try_ensure_capacity(available_formats.size())); for (auto const& format : available_formats) - result.unchecked_append(s_calendar_patterns[format].to_unicode_calendar_pattern()); + result.unchecked_append(TRY(s_calendar_patterns[format].to_unicode_calendar_pattern())); } return result; } -Optional get_calendar_default_range_format(StringView locale, StringView calendar) +ErrorOr> get_calendar_default_range_format(StringView locale, StringView calendar) { if (auto const* data = find_calendar_data(locale, calendar); data != nullptr) { auto const& pattern = s_calendar_range_patterns[data->default_range_format]; - return pattern.to_unicode_calendar_range_pattern(); + return TRY(pattern.to_unicode_calendar_range_pattern()); } - return {}; + return OptionalNone {}; } -Vector get_calendar_range_formats(StringView locale, StringView calendar, StringView skeleton) +ErrorOr> get_calendar_range_formats(StringView locale, StringView calendar, StringView skeleton) { Vector result {}; @@ -2173,14 +2190,14 @@ Vector get_calendar_range_formats(StringView locale, Strin auto const& pattern = s_calendar_range_patterns[format]; if (skeleton == decode_string(pattern.skeleton)) - result.append(pattern.to_unicode_calendar_range_pattern()); + TRY(result.try_append(TRY(pattern.to_unicode_calendar_range_pattern()))); } } return result; } -Vector get_calendar_range12_formats(StringView locale, StringView calendar, StringView skeleton) +ErrorOr> get_calendar_range12_formats(StringView locale, StringView calendar, StringView skeleton) { Vector result {}; @@ -2191,7 +2208,7 @@ Vector get_calendar_range12_formats(StringView locale, Str auto const& pattern = s_calendar_range_patterns[format]; if (skeleton == decode_string(pattern.skeleton)) - result.append(pattern.to_unicode_calendar_range_pattern()); + TRY(result.try_append(TRY(pattern.to_unicode_calendar_range_pattern()))); } } diff --git a/Tests/LibLocale/TestDateTimeFormat.cpp b/Tests/LibLocale/TestDateTimeFormat.cpp index 0ba07fd546..608e3c1669 100644 --- a/Tests/LibLocale/TestDateTimeFormat.cpp +++ b/Tests/LibLocale/TestDateTimeFormat.cpp @@ -75,7 +75,7 @@ TEST_CASE(time_zone_name) constexpr auto jan_1_2022 = AK::Time::from_seconds(1640995200); // Saturday, January 1, 2022 12:00:00 AM for (auto const& test : test_data) { - auto time_zone = Locale::format_time_zone(test.locale, test.time_zone, test.style, jan_1_2022); + auto time_zone = MUST(Locale::format_time_zone(test.locale, test.time_zone, test.style, jan_1_2022)); EXPECT_EQ(time_zone, test.expected_result); } } @@ -125,7 +125,7 @@ TEST_CASE(time_zone_name_dst) constexpr auto sep_19_2022 = AK::Time::from_seconds(1663553728); // Monday, September 19, 2022 2:15:28 AM for (auto const& test : test_data) { - auto time_zone = Locale::format_time_zone(test.locale, test.time_zone, test.style, sep_19_2022); + auto time_zone = MUST(Locale::format_time_zone(test.locale, test.time_zone, test.style, sep_19_2022)); EXPECT_EQ(time_zone, test.expected_result); } } @@ -182,7 +182,7 @@ TEST_CASE(format_time_zone_offset) }; for (auto const& test : test_data) { - auto time_zone = Locale::format_time_zone(test.locale, test.time_zone, test.style, test.time); + auto time_zone = MUST(Locale::format_time_zone(test.locale, test.time_zone, test.style, test.time)); EXPECT_EQ(time_zone, test.expected_result); } } diff --git a/Userland/Applications/ClockSettings/TimeZoneSettingsWidget.cpp b/Userland/Applications/ClockSettings/TimeZoneSettingsWidget.cpp index a302f583b9..fee8be799f 100644 --- a/Userland/Applications/ClockSettings/TimeZoneSettingsWidget.cpp +++ b/Userland/Applications/ClockSettings/TimeZoneSettingsWidget.cpp @@ -135,8 +135,8 @@ void TimeZoneSettingsWidget::set_time_zone_location() auto locale = Locale::default_locale(); auto now = AK::Time::now_realtime(); - auto name = Locale::format_time_zone(locale, m_time_zone, Locale::CalendarPatternStyle::Long, now); - auto offset = Locale::format_time_zone(locale, m_time_zone, Locale::CalendarPatternStyle::LongOffset, now); + auto name = Locale::format_time_zone(locale, m_time_zone, Locale::CalendarPatternStyle::Long, now).release_value_but_fixme_should_propagate_errors(); + auto offset = Locale::format_time_zone(locale, m_time_zone, Locale::CalendarPatternStyle::LongOffset, now).release_value_but_fixme_should_propagate_errors(); m_time_zone_text = DeprecatedString::formatted("{}\n({})", name, offset); } diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormat.cpp b/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormat.cpp index edb634daac..401365583d 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormat.cpp +++ b/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormat.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022, Tim Flynn + * Copyright (c) 2021-2023, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -153,13 +154,13 @@ ThrowCompletionOr to_date_time_options(VM& vm, Value options_value, Opt } // 11.5.2 DateTimeStyleFormat ( dateStyle, timeStyle, styles ), https://tc39.es/ecma402/#sec-date-time-style-format -Optional<::Locale::CalendarPattern> date_time_style_format(StringView data_locale, DateTimeFormat& date_time_format) +ThrowCompletionOr> date_time_style_format(VM& vm, StringView data_locale, DateTimeFormat& date_time_format) { ::Locale::CalendarPattern time_format {}; ::Locale::CalendarPattern date_format {}; - auto get_pattern = [&](auto type, auto style) -> Optional<::Locale::CalendarPattern> { - auto formats = ::Locale::get_calendar_format(data_locale, date_time_format.calendar(), type); + auto get_pattern = [&](auto type, auto style) -> ThrowCompletionOr> { + auto formats = TRY_OR_THROW_OOM(vm, ::Locale::get_calendar_format(data_locale, date_time_format.calendar(), type)); if (formats.has_value()) { switch (style) { @@ -174,16 +175,16 @@ Optional<::Locale::CalendarPattern> date_time_style_format(StringView data_local } } - return {}; + return OptionalNone {}; }; // 1. If timeStyle is not undefined, then if (date_time_format.has_time_style()) { // a. Assert: timeStyle is one of "full", "long", "medium", or "short". // b. Let timeFormat be styles.[[TimeFormat]].[[]]. - auto pattern = get_pattern(::Locale::CalendarFormatType::Time, date_time_format.time_style()); + auto pattern = MUST_OR_THROW_OOM(get_pattern(::Locale::CalendarFormatType::Time, date_time_format.time_style())); if (!pattern.has_value()) - return {}; + return OptionalNone {}; time_format = pattern.release_value(); } @@ -192,9 +193,9 @@ Optional<::Locale::CalendarPattern> date_time_style_format(StringView data_local if (date_time_format.has_date_style()) { // a. Assert: dateStyle is one of "full", "long", "medium", or "short". // b. Let dateFormat be styles.[[DateFormat]].[[]]. - auto pattern = get_pattern(::Locale::CalendarFormatType::Date, date_time_format.date_style()); + auto pattern = MUST_OR_THROW_OOM(get_pattern(::Locale::CalendarFormatType::Date, date_time_format.date_style())); if (!pattern.has_value()) - return {}; + return OptionalNone {}; date_format = pattern.release_value(); } @@ -216,12 +217,13 @@ Optional<::Locale::CalendarPattern> date_time_style_format(StringView data_local }); // d. Let connector be styles.[[DateTimeFormat]].[[]]. - auto connector = get_pattern(::Locale::CalendarFormatType::DateTime, date_time_format.date_style()); + auto connector = MUST_OR_THROW_OOM(get_pattern(::Locale::CalendarFormatType::DateTime, date_time_format.date_style())); if (!connector.has_value()) - return {}; + return OptionalNone {}; // e. Let pattern be the string connector with the substring "{0}" replaced with timeFormat.[[pattern]] and the substring "{1}" replaced with dateFormat.[[pattern]]. - auto pattern = connector->pattern.replace("{0}"sv, time_format.pattern, ReplaceMode::FirstOnly).replace("{1}"sv, date_format.pattern, ReplaceMode::FirstOnly); + auto pattern = TRY_OR_THROW_OOM(vm, connector->pattern.replace("{0}"sv, time_format.pattern, ReplaceMode::FirstOnly)); + pattern = TRY_OR_THROW_OOM(vm, pattern.replace("{1}"sv, date_format.pattern, ReplaceMode::FirstOnly)); // f. Set format.[[pattern]] to pattern. format.pattern = move(pattern); @@ -229,7 +231,8 @@ Optional<::Locale::CalendarPattern> date_time_style_format(StringView data_local // g. If timeFormat has a [[pattern12]] field, then if (time_format.pattern12.has_value()) { // i. Let pattern12 be the string connector with the substring "{0}" replaced with timeFormat.[[pattern12]] and the substring "{1}" replaced with dateFormat.[[pattern]]. - auto pattern12 = connector->pattern.replace("{0}"sv, *time_format.pattern12, ReplaceMode::FirstOnly).replace("{1}"sv, date_format.pattern, ReplaceMode::FirstOnly); + auto pattern12 = TRY_OR_THROW_OOM(vm, connector->pattern.replace("{0}"sv, *time_format.pattern12, ReplaceMode::FirstOnly)); + pattern12 = TRY_OR_THROW_OOM(vm, pattern12.replace("{1}"sv, date_format.pattern, ReplaceMode::FirstOnly)); // ii. Set format.[[pattern12]] to pattern12. format.pattern12 = move(pattern12); @@ -238,7 +241,7 @@ Optional<::Locale::CalendarPattern> date_time_style_format(StringView data_local // NOTE: Our implementation of steps h-j differ from the spec. LibUnicode does not attach range patterns to the // format pattern; rather, lookups for range patterns are performed separately based on the format pattern's // skeleton. So we form a new skeleton here and defer the range pattern lookups. - format.skeleton = ::Locale::combine_skeletons(date_format.skeleton, time_format.skeleton); + format.skeleton = TRY_OR_THROW_OOM(vm, ::Locale::combine_skeletons(date_format.skeleton, time_format.skeleton)); // k. Return format. return format; @@ -629,7 +632,7 @@ ThrowCompletionOr> format_date_time_pattern(VM& vm, Dat // d. Else if p is equal to "dayPeriod", then else if (part == "dayPeriod"sv) { - DeprecatedString formatted_value; + String formatted_value; // i. Let f be the value of dateTimeFormat's internal slot whose name is the Internal Slot column of the matching row. auto style = date_time_format.day_period(); @@ -637,10 +640,10 @@ ThrowCompletionOr> format_date_time_pattern(VM& vm, Dat // ii. Let fv be a String value representing the day period of tm in the form given by f; the String value depends upon the implementation and the effective locale of dateTimeFormat. auto symbol = resolve_day_period(data_locale, date_time_format.calendar(), style, pattern_parts, local_time); if (symbol.has_value()) - formatted_value = *symbol; + formatted_value = TRY_OR_THROW_OOM(vm, String::from_utf8(*symbol)); // iii. Append a new Record { [[Type]]: p, [[Value]]: fv } as the last element of the list result. - result.append({ "dayPeriod"sv, TRY_OR_THROW_OOM(vm, String::from_deprecated_string(formatted_value)) }); + result.append({ "dayPeriod"sv, move(formatted_value) }); } // e. Else if p is equal to "timeZoneName", then @@ -654,15 +657,15 @@ ThrowCompletionOr> format_date_time_pattern(VM& vm, Dat // iii. Let fv be a String value representing v in the form given by f; the String value depends upon the implementation and the effective locale of dateTimeFormat. // The String value may also depend on the value of the [[InDST]] field of tm if f is "short", "long", "shortOffset", or "longOffset". // If the implementation does not have a localized representation of f, then use the String value of v itself. - auto formatted_value = ::Locale::format_time_zone(data_locale, value, style, local_time.time_since_epoch()); + auto formatted_value = TRY_OR_THROW_OOM(vm, ::Locale::format_time_zone(data_locale, value, style, local_time.time_since_epoch())); // iv. Append a new Record { [[Type]]: p, [[Value]]: fv } as the last element of the list result. - result.append({ "timeZoneName"sv, TRY_OR_THROW_OOM(vm, String::from_deprecated_string(formatted_value)) }); + result.append({ "timeZoneName"sv, move(formatted_value) }); } // f. Else if p matches a Property column of the row in Table 6, then else if (auto style_and_value = find_calendar_field(part, date_time_format, range_format_options, local_time); style_and_value.has_value()) { - DeprecatedString formatted_value; + String formatted_value; // i. If rangeFormatOptions is not undefined, let f be the value of rangeFormatOptions's field whose name matches p. // ii. Else, let f be the value of dateTimeFormat's internal slot whose name is the Internal Slot column of the matching row. @@ -705,20 +708,20 @@ ThrowCompletionOr> format_date_time_pattern(VM& vm, Dat // viii. If f is "numeric", then case ::Locale::CalendarPatternStyle::Numeric: // 1. Let fv be FormatNumeric(nf, v). - formatted_value = MUST_OR_THROW_OOM(format_numeric(vm, *number_format, Value(value))).to_deprecated_string(); + formatted_value = MUST_OR_THROW_OOM(format_numeric(vm, *number_format, Value(value))); break; // ix. Else if f is "2-digit", then case ::Locale::CalendarPatternStyle::TwoDigit: // 1. Let fv be FormatNumeric(nf2, v). - formatted_value = MUST_OR_THROW_OOM(format_numeric(vm, *number_format2, Value(value))).to_deprecated_string(); + formatted_value = MUST_OR_THROW_OOM(format_numeric(vm, *number_format2, Value(value))); // 2. If the "length" property of fv is greater than 2, let fv be the substring of fv containing the last two characters. // NOTE: The first length check here isn't enough, but lets us avoid UTF-16 transcoding when the formatted value is ASCII. - if (formatted_value.length() > 2) { + if (formatted_value.bytes_as_string_view().length() > 2) { auto utf16_formatted_value = TRY(Utf16String::create(vm, formatted_value)); if (utf16_formatted_value.length_in_code_units() > 2) - formatted_value = TRY_OR_THROW_OOM(vm, utf16_formatted_value.substring_view(utf16_formatted_value.length_in_code_units() - 2).to_deprecated_string()); + formatted_value = TRY_OR_THROW_OOM(vm, utf16_formatted_value.substring_view(utf16_formatted_value.length_in_code_units() - 2).to_utf8()); } break; @@ -741,7 +744,11 @@ ThrowCompletionOr> format_date_time_pattern(VM& vm, Dat else if (part == "weekday"sv) symbol = ::Locale::get_calendar_weekday_symbol(data_locale, date_time_format.calendar(), style, static_cast<::Locale::Weekday>(value)); - formatted_value = symbol.value_or(DeprecatedString::number(value)); + if (symbol.has_value()) + formatted_value = TRY_OR_THROW_OOM(vm, String::from_utf8(*symbol)); + else + formatted_value = TRY_OR_THROW_OOM(vm, String::number(value)); + break; } @@ -750,12 +757,12 @@ ThrowCompletionOr> format_date_time_pattern(VM& vm, Dat } // xi. Append a new Record { [[Type]]: p, [[Value]]: fv } as the last element of the list result. - result.append({ style_and_value->name, TRY_OR_THROW_OOM(vm, String::from_deprecated_string(formatted_value)) }); + result.append({ style_and_value->name, move(formatted_value) }); } // g. Else if p is equal to "ampm", then else if (part == "ampm"sv) { - DeprecatedString formatted_value; + String formatted_value; // i. Let v be tm.[[Hour]]. auto value = local_time.hour; @@ -764,17 +771,17 @@ ThrowCompletionOr> format_date_time_pattern(VM& vm, Dat if (value > 11) { // 1. Let fv be an implementation and locale dependent String value representing "post meridiem". auto symbol = ::Locale::get_calendar_day_period_symbol(data_locale, date_time_format.calendar(), ::Locale::CalendarPatternStyle::Short, ::Locale::DayPeriod::PM); - formatted_value = symbol.value_or("PM"sv); + formatted_value = TRY_OR_THROW_OOM(vm, String::from_utf8(symbol.value_or("PM"sv))); } // iii. Else, else { // 1. Let fv be an implementation and locale dependent String value representing "ante meridiem". auto symbol = ::Locale::get_calendar_day_period_symbol(data_locale, date_time_format.calendar(), ::Locale::CalendarPatternStyle::Short, ::Locale::DayPeriod::AM); - formatted_value = symbol.value_or("AM"sv); + formatted_value = TRY_OR_THROW_OOM(vm, String::from_utf8(symbol.value_or("AM"sv))); } // iv. Append a new Record { [[Type]]: "dayPeriod", [[Value]]: fv } as the last element of the list result. - result.append({ "dayPeriod"sv, TRY_OR_THROW_OOM(vm, String::from_deprecated_string(formatted_value)) }); + result.append({ "dayPeriod"sv, move(formatted_value) }); } // h. Else if p is equal to "relatedYear", then @@ -830,22 +837,22 @@ ThrowCompletionOr> partition_date_time_pattern(VM& vm, } // 11.5.8 FormatDateTime ( dateTimeFormat, x ), https://tc39.es/ecma402/#sec-formatdatetime -ThrowCompletionOr format_date_time(VM& vm, DateTimeFormat& date_time_format, double time) +ThrowCompletionOr format_date_time(VM& vm, DateTimeFormat& date_time_format, double time) { // 1. Let parts be ? PartitionDateTimePattern(dateTimeFormat, x). auto parts = TRY(partition_date_time_pattern(vm, date_time_format, time)); // 2. Let result be the empty String. - StringBuilder result; + ThrowableStringBuilder result(vm); // 3. For each Record { [[Type]], [[Value]] } part in parts, do for (auto& part : parts) { // a. Set result to the string-concatenation of result and part.[[Value]]. - result.append(move(part.value)); + TRY(result.append(part.value)); } // 4. Return result. - return result.build(); + return result.to_string(); } // 11.5.9 FormatDateTimeToParts ( dateTimeFormat, x ), https://tc39.es/ecma402/#sec-formatdatetimetoparts @@ -1089,7 +1096,7 @@ ThrowCompletionOr> partition_date_time_range_ // 14. If rangePattern is undefined, then if (!range_pattern.has_value()) { // a. Let rangePattern be rangePatterns.[[Default]]. - range_pattern = ::Locale::get_calendar_default_range_format(date_time_format.data_locale(), date_time_format.calendar()); + range_pattern = TRY_OR_THROW_OOM(vm, ::Locale::get_calendar_default_range_format(date_time_format.data_locale(), date_time_format.calendar())); // Non-standard, range_pattern will be empty if Unicode data generation is disabled. if (!range_pattern.has_value()) @@ -1100,11 +1107,11 @@ ThrowCompletionOr> partition_date_time_range_ auto const& pattern = date_time_format.pattern(); if (range_pattern->start_range.contains("{0}"sv)) { - range_pattern->start_range = range_pattern->start_range.replace("{0}"sv, pattern, ReplaceMode::FirstOnly); - range_pattern->end_range = range_pattern->end_range.replace("{1}"sv, pattern, ReplaceMode::FirstOnly); + range_pattern->start_range = TRY_OR_THROW_OOM(vm, range_pattern->start_range.replace("{0}"sv, pattern, ReplaceMode::FirstOnly)); + range_pattern->end_range = TRY_OR_THROW_OOM(vm, range_pattern->end_range.replace("{1}"sv, pattern, ReplaceMode::FirstOnly)); } else { - range_pattern->start_range = range_pattern->start_range.replace("{1}"sv, pattern, ReplaceMode::FirstOnly); - range_pattern->end_range = range_pattern->end_range.replace("{0}"sv, pattern, ReplaceMode::FirstOnly); + range_pattern->start_range = TRY_OR_THROW_OOM(vm, range_pattern->start_range.replace("{1}"sv, pattern, ReplaceMode::FirstOnly)); + range_pattern->end_range = TRY_OR_THROW_OOM(vm, range_pattern->end_range.replace("{0}"sv, pattern, ReplaceMode::FirstOnly)); } // FIXME: The above is not sufficient. For example, if the start date is days before the end date, and only the timeStyle @@ -1146,22 +1153,22 @@ ThrowCompletionOr> partition_date_time_range_ } // 11.5.11 FormatDateTimeRange ( dateTimeFormat, x, y ), https://tc39.es/ecma402/#sec-formatdatetimerange -ThrowCompletionOr format_date_time_range(VM& vm, DateTimeFormat& date_time_format, double start, double end) +ThrowCompletionOr format_date_time_range(VM& vm, DateTimeFormat& date_time_format, double start, double end) { // 1. Let parts be ? PartitionDateTimeRangePattern(dateTimeFormat, x, y). auto parts = TRY(partition_date_time_range_pattern(vm, date_time_format, start, end)); // 2. Let result be the empty String. - StringBuilder result; + ThrowableStringBuilder result(vm); // 3. For each Record { [[Type]], [[Value]], [[Source]] } part in parts, do for (auto& part : parts) { // a. Set result to the string-concatenation of result and part.[[Value]]. - result.append(move(part.value)); + TRY(result.append(part.value)); } // 4. Return result. - return result.build(); + return result.to_string(); } // 11.5.12 FormatDateTimeRangeToParts ( dateTimeFormat, x, y ), https://tc39.es/ecma402/#sec-formatdatetimerangetoparts diff --git a/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormat.h b/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormat.h index 4077f664c9..daee2514a8 100644 --- a/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormat.h +++ b/Userland/Libraries/LibJS/Runtime/Intl/DateTimeFormat.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021-2022, Tim Flynn + * Copyright (c) 2021-2023, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ @@ -7,7 +7,6 @@ #pragma once #include -#include #include #include #include @@ -62,8 +61,8 @@ public: void set_hour_cycle(::Locale::HourCycle hour_cycle) { m_hour_cycle = hour_cycle; } void clear_hour_cycle() { m_hour_cycle.clear(); } - DeprecatedString const& time_zone() const { return m_time_zone; } - void set_time_zone(DeprecatedString time_zone) { m_time_zone = move(time_zone); } + String const& time_zone() const { return m_time_zone; } + void set_time_zone(String time_zone) { m_time_zone = move(time_zone); } bool has_date_style() const { return m_date_style.has_value(); } Style date_style() const { return *m_date_style; }; @@ -75,8 +74,8 @@ public: StringView time_style_string() const { return style_to_string(*m_time_style); }; void set_time_style(StringView style) { m_time_style = style_from_string(style); }; - DeprecatedString const& pattern() const { return Patterns::pattern; }; - void set_pattern(DeprecatedString pattern) { Patterns::pattern = move(pattern); } + String const& pattern() const { return Patterns::pattern; }; + void set_pattern(String pattern) { Patterns::pattern = move(pattern); } Span<::Locale::CalendarRangePattern const> range_patterns() const { return m_range_patterns.span(); }; void set_range_patterns(Vector<::Locale::CalendarRangePattern> range_patterns) { m_range_patterns = move(range_patterns); } @@ -139,7 +138,7 @@ private: String m_calendar; // [[Calendar]] String m_numbering_system; // [[NumberingSystem]] Optional<::Locale::HourCycle> m_hour_cycle; // [[HourCycle]] - DeprecatedString m_time_zone; // [[TimeZone]] + String m_time_zone; // [[TimeZone]] Optional