1
0
mirror of https://github.com/SerenityOS/serenity synced 2024-07-08 23:10:47 +00:00

LibJS+LibLocale: Port Intl.DateTimeFormat to String

This commit is contained in:
Timothy Flynn 2023-01-27 10:18:11 -05:00 committed by Linus Groh
parent 970e9df245
commit b2097f4059
8 changed files with 223 additions and 197 deletions

View File

@ -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<String> 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<CalendarPattern> parse_date_time_pattern_raw(DeprecatedString pattern, DeprecatedString skeleton, CLDR& cldr)
static ErrorOr<Optional<CalendarPattern>> 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<CalendarPattern> 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<CalendarPattern> 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<CalendarPattern> 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<CalendarPattern> 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<CalendarPattern> parse_date_time_pattern_raw(DeprecatedString pa
format.fractional_second_digits = static_cast<u8>(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<CalendarPattern> 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<size_t> parse_date_time_pattern(DeprecatedString pattern, DeprecatedString skeleton, CLDR& cldr)
static ErrorOr<Optional<size_t>> 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<size_t> { cldr.unique_patterns.ensure(format.release_value()) };
}
template<typename... Chars>
@ -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<void> 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<StringView, size_t> 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<void> {
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<void> {
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<void> 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<CalendarPattern> date_formats, Vector<CalendarPattern> time_formats, CLDR& cldr)
@ -1390,12 +1401,12 @@ static ErrorOr<void> 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<CalendarPattern>* patterns) {
auto parse_pattern = [&](auto name) {
auto parse_patterns = [&](auto const& patterns_object, auto const& skeletons_object, Vector<CalendarPattern>* patterns) -> ErrorOr<size_t> {
auto parse_pattern = [&](auto name) -> ErrorOr<size_t> {
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<void> 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<void> {
// 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<void> 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<void> {
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<void> 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<void> generate_unicode_locale_implementation(Core::Stream::Buffer
generator.append(R"~~~(
#include <AK/Array.h>
#include <AK/BinarySearch.h>
#include <AK/Error.h>
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/StringView.h>
#include <LibLocale/DateTimeFormat.h>
#include <LibLocale/DateTimeFormatData.h>
@ -1778,13 +1795,13 @@ static void convert_calendar_fields(SourceType const& source, TargetType& target
}
struct CalendarPatternImpl {
CalendarPattern to_unicode_calendar_pattern() const {
ErrorOr<CalendarPattern> 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<CalendarRangePattern> to_unicode_calendar_range_pattern() const {
CalendarRangePattern calendar_range_pattern {};
if (field != -1)
calendar_range_pattern.field = static_cast<CalendarRangePattern::Field>(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<CalendarFormat> 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<CalendarFormat> get_calendar_date_format(StringView locale, StringView calendar)
ErrorOr<Optional<CalendarFormat>> 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<CalendarFormat> get_calendar_time_format(StringView locale, StringView calendar)
ErrorOr<Optional<CalendarFormat>> 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<CalendarFormat> get_calendar_date_time_format(StringView locale, StringView calendar)
ErrorOr<Optional<CalendarFormat>> 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<CalendarPattern> get_calendar_available_formats(StringView locale, StringView calendar)
ErrorOr<Vector<CalendarPattern>> get_calendar_available_formats(StringView locale, StringView calendar)
{
Vector<CalendarPattern> 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<CalendarRangePattern> get_calendar_default_range_format(StringView locale, StringView calendar)
ErrorOr<Optional<CalendarRangePattern>> 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<CalendarRangePattern> get_calendar_range_formats(StringView locale, StringView calendar, StringView skeleton)
ErrorOr<Vector<CalendarRangePattern>> get_calendar_range_formats(StringView locale, StringView calendar, StringView skeleton)
{
Vector<CalendarRangePattern> result {};
@ -2173,14 +2190,14 @@ Vector<CalendarRangePattern> 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<CalendarRangePattern> get_calendar_range12_formats(StringView locale, StringView calendar, StringView skeleton)
ErrorOr<Vector<CalendarRangePattern>> get_calendar_range12_formats(StringView locale, StringView calendar, StringView skeleton)
{
Vector<CalendarRangePattern> result {};
@ -2191,7 +2208,7 @@ Vector<CalendarRangePattern> 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())));
}
}

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2022, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -15,6 +15,7 @@
#include <LibJS/Runtime/Intl/NumberFormat.h>
#include <LibJS/Runtime/Intl/NumberFormatConstructor.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/ThrowableStringBuilder.h>
#include <LibJS/Runtime/Utf16String.h>
#include <LibLocale/Locale.h>
#include <LibLocale/NumberFormat.h>
@ -153,13 +154,13 @@ ThrowCompletionOr<Object*> 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<Optional<::Locale::CalendarPattern>> 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<Optional<::Locale::CalendarPattern>> {
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]].[[<timeStyle>]].
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]].[[<dateStyle>]].
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]].[[<dateStyle>]].
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<Vector<PatternPartition>> 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<Vector<PatternPartition>> 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<Vector<PatternPartition>> 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<Vector<PatternPartition>> 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<Vector<PatternPartition>> 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<Vector<PatternPartition>> 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<Vector<PatternPartition>> 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<Vector<PatternPartition>> partition_date_time_pattern(VM& vm,
}
// 11.5.8 FormatDateTime ( dateTimeFormat, x ), https://tc39.es/ecma402/#sec-formatdatetime
ThrowCompletionOr<DeprecatedString> format_date_time(VM& vm, DateTimeFormat& date_time_format, double time)
ThrowCompletionOr<String> 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<Vector<PatternPartitionWithSource>> 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<Vector<PatternPartitionWithSource>> 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<Vector<PatternPartitionWithSource>> partition_date_time_range_
}
// 11.5.11 FormatDateTimeRange ( dateTimeFormat, x, y ), https://tc39.es/ecma402/#sec-formatdatetimerange
ThrowCompletionOr<DeprecatedString> format_date_time_range(VM& vm, DateTimeFormat& date_time_format, double start, double end)
ThrowCompletionOr<String> 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

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2022, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -7,7 +7,6 @@
#pragma once
#include <AK/Array.h>
#include <AK/DeprecatedString.h>
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Time.h>
@ -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<Style> m_date_style; // [[DateStyle]]
Optional<Style> m_time_style; // [[TimeStyle]]
Vector<::Locale::CalendarRangePattern> m_range_patterns; // [[RangePatterns]]
@ -182,15 +181,15 @@ struct LocalTime {
};
ThrowCompletionOr<Object*> to_date_time_options(VM&, Value options_value, OptionRequired, OptionDefaults);
Optional<::Locale::CalendarPattern> date_time_style_format(StringView data_locale, DateTimeFormat& date_time_format);
ThrowCompletionOr<Optional<::Locale::CalendarPattern>> date_time_style_format(VM&, StringView data_locale, DateTimeFormat& date_time_format);
Optional<::Locale::CalendarPattern> basic_format_matcher(::Locale::CalendarPattern const& options, Vector<::Locale::CalendarPattern> formats);
Optional<::Locale::CalendarPattern> best_fit_format_matcher(::Locale::CalendarPattern const& options, Vector<::Locale::CalendarPattern> formats);
ThrowCompletionOr<Vector<PatternPartition>> format_date_time_pattern(VM&, DateTimeFormat&, Vector<PatternPartition> pattern_parts, double time, ::Locale::CalendarPattern const* range_format_options);
ThrowCompletionOr<Vector<PatternPartition>> partition_date_time_pattern(VM&, DateTimeFormat&, double time);
ThrowCompletionOr<DeprecatedString> format_date_time(VM&, DateTimeFormat&, double time);
ThrowCompletionOr<String> format_date_time(VM&, DateTimeFormat&, double time);
ThrowCompletionOr<Array*> format_date_time_to_parts(VM&, DateTimeFormat&, double time);
ThrowCompletionOr<Vector<PatternPartitionWithSource>> partition_date_time_range_pattern(VM&, DateTimeFormat&, double start, double end);
ThrowCompletionOr<DeprecatedString> format_date_time_range(VM&, DateTimeFormat&, double start, double end);
ThrowCompletionOr<String> format_date_time_range(VM&, DateTimeFormat&, double start, double end);
ThrowCompletionOr<Array*> format_date_time_range_to_parts(VM&, DateTimeFormat&, double start, double end);
ThrowCompletionOr<LocalTime> to_local_time(VM&, Crypto::SignedBigInteger const& epoch_ns, StringView calendar, StringView time_zone);

View File

@ -170,7 +170,7 @@ ThrowCompletionOr<DateTimeFormat*> initialize_date_time_format(VM& vm, DateTimeF
// Non-standard, default_hour_cycle will be empty if Unicode data generation is disabled.
if (!default_hour_cycle.has_value()) {
date_time_format.set_time_zone(default_time_zone());
date_time_format.set_time_zone(TRY_OR_THROW_OOM(vm, String::from_utf8(default_time_zone())));
return &date_time_format;
}
@ -237,7 +237,7 @@ ThrowCompletionOr<DateTimeFormat*> initialize_date_time_format(VM& vm, DateTimeF
}
// 32. Set dateTimeFormat.[[TimeZone]] to timeZone.
date_time_format.set_time_zone(time_zone.to_deprecated_string());
date_time_format.set_time_zone(move(time_zone));
// 33. Let formatOptions be a new Record.
::Locale::CalendarPattern format_options {};
@ -317,12 +317,12 @@ ThrowCompletionOr<DateTimeFormat*> initialize_date_time_format(VM& vm, DateTimeF
// b. Let styles be dataLocaleData.[[styles]].[[<resolvedCalendar>]].
// c. Let bestFormat be DateTimeStyleFormat(dateStyle, timeStyle, styles).
best_format = date_time_style_format(data_locale, date_time_format);
best_format = MUST_OR_THROW_OOM(date_time_style_format(vm, data_locale, date_time_format));
}
// 43. Else,
else {
// a. Let formats be dataLocaleData.[[formats]].[[<resolvedCalendar>]].
auto formats = ::Locale::get_calendar_available_formats(data_locale, date_time_format.calendar());
auto formats = TRY_OR_THROW_OOM(vm, ::Locale::get_calendar_available_formats(data_locale, date_time_format.calendar()));
// b. If matcher is "basic", then
if (TRY(matcher.as_string().utf8_string_view()) == "basic"sv) {
@ -347,7 +347,7 @@ ThrowCompletionOr<DateTimeFormat*> initialize_date_time_format(VM& vm, DateTimeF
}
});
DeprecatedString pattern;
String pattern;
Vector<::Locale::CalendarRangePattern> range_patterns;
// 45. If dateTimeFormat.[[Hour]] is undefined, then
@ -368,7 +368,7 @@ ThrowCompletionOr<DateTimeFormat*> initialize_date_time_format(VM& vm, DateTimeF
}
// b. Let rangePatterns be bestFormat.[[rangePatterns12]].
range_patterns = ::Locale::get_calendar_range12_formats(data_locale, date_time_format.calendar(), best_format->skeleton);
range_patterns = TRY_OR_THROW_OOM(vm, ::Locale::get_calendar_range12_formats(data_locale, date_time_format.calendar(), best_format->skeleton));
}
// 47. Else,
else {
@ -376,7 +376,7 @@ ThrowCompletionOr<DateTimeFormat*> initialize_date_time_format(VM& vm, DateTimeF
pattern = move(best_format->pattern);
// b. Let rangePatterns be bestFormat.[[rangePatterns]].
range_patterns = ::Locale::get_calendar_range_formats(data_locale, date_time_format.calendar(), best_format->skeleton);
range_patterns = TRY_OR_THROW_OOM(vm, ::Locale::get_calendar_range_formats(data_locale, date_time_format.calendar(), best_format->skeleton));
}
// 48. Set dateTimeFormat.[[Pattern]] to pattern.

View File

@ -169,7 +169,7 @@ Optional<Weekday> get_locale_weekend_end(StringView locale)
return find_regional_values_for_locale(locale, get_regional_weekend_end);
}
DeprecatedString combine_skeletons(StringView first, StringView second)
ErrorOr<String> combine_skeletons(StringView first, StringView second)
{
// https://unicode.org/reports/tr35/tr35-dates.html#availableFormats_appendItems
constexpr auto field_order = Array {
@ -207,14 +207,14 @@ DeprecatedString combine_skeletons(StringView first, StringView second)
}
}
return builder.build();
return builder.to_string();
}
Optional<CalendarFormat> __attribute__((weak)) get_calendar_date_format(StringView, StringView) { return {}; }
Optional<CalendarFormat> __attribute__((weak)) get_calendar_time_format(StringView, StringView) { return {}; }
Optional<CalendarFormat> __attribute__((weak)) get_calendar_date_time_format(StringView, StringView) { return {}; }
ErrorOr<Optional<CalendarFormat>> __attribute__((weak)) get_calendar_date_format(StringView, StringView) { return OptionalNone {}; }
ErrorOr<Optional<CalendarFormat>> __attribute__((weak)) get_calendar_time_format(StringView, StringView) { return OptionalNone {}; }
ErrorOr<Optional<CalendarFormat>> __attribute__((weak)) get_calendar_date_time_format(StringView, StringView) { return OptionalNone {}; }
Optional<CalendarFormat> get_calendar_format(StringView locale, StringView calendar, CalendarFormatType type)
ErrorOr<Optional<CalendarFormat>> get_calendar_format(StringView locale, StringView calendar, CalendarFormatType type)
{
switch (type) {
case CalendarFormatType::Date:
@ -228,10 +228,10 @@ Optional<CalendarFormat> get_calendar_format(StringView locale, StringView calen
}
}
Vector<CalendarPattern> __attribute__((weak)) get_calendar_available_formats(StringView, StringView) { return {}; }
Optional<CalendarRangePattern> __attribute__((weak)) get_calendar_default_range_format(StringView, StringView) { return {}; }
Vector<CalendarRangePattern> __attribute__((weak)) get_calendar_range_formats(StringView, StringView, StringView) { return {}; }
Vector<CalendarRangePattern> __attribute__((weak)) get_calendar_range12_formats(StringView, StringView, StringView) { return {}; }
ErrorOr<Vector<CalendarPattern>> __attribute__((weak)) get_calendar_available_formats(StringView, StringView) { return Vector<CalendarPattern> {}; }
ErrorOr<Optional<CalendarRangePattern>> __attribute__((weak)) get_calendar_default_range_format(StringView, StringView) { return OptionalNone {}; }
ErrorOr<Vector<CalendarRangePattern>> __attribute__((weak)) get_calendar_range_formats(StringView, StringView, StringView) { return Vector<CalendarRangePattern> {}; }
ErrorOr<Vector<CalendarRangePattern>> __attribute__((weak)) get_calendar_range12_formats(StringView, StringView, StringView) { return Vector<CalendarRangePattern> {}; }
Optional<StringView> __attribute__((weak)) get_calendar_era_symbol(StringView, StringView, CalendarPatternStyle, Era) { return {}; }
Optional<StringView> __attribute__((weak)) get_calendar_month_symbol(StringView, StringView, CalendarPatternStyle, Month) { return {}; }
Optional<StringView> __attribute__((weak)) get_calendar_weekday_symbol(StringView, StringView, CalendarPatternStyle, Weekday) { return {}; }
@ -241,18 +241,18 @@ Optional<StringView> __attribute__((weak)) get_calendar_day_period_symbol_for_ho
Optional<StringView> __attribute__((weak)) get_time_zone_name(StringView, StringView, CalendarPatternStyle, TimeZone::InDST) { return {}; }
Optional<TimeZoneFormat> __attribute__((weak)) get_time_zone_format(StringView) { return {}; }
static Optional<DeprecatedString> format_time_zone_offset(StringView locale, CalendarPatternStyle style, i64 offset_seconds)
static ErrorOr<Optional<String>> format_time_zone_offset(StringView locale, CalendarPatternStyle style, i64 offset_seconds)
{
auto formats = get_time_zone_format(locale);
if (!formats.has_value())
return {};
return OptionalNone {};
auto number_system = get_preferred_keyword_value_for_locale(locale, "nu"sv);
if (!number_system.has_value())
return {};
return OptionalNone {};
if (offset_seconds == 0)
return formats->gmt_zero_format;
return String::from_utf8(formats->gmt_zero_format);
auto sign = offset_seconds > 0 ? formats->symbol_ahead_sign : formats->symbol_behind_sign;
auto separator = offset_seconds > 0 ? formats->symbol_ahead_separator : formats->symbol_behind_separator;
@ -290,16 +290,16 @@ static Optional<DeprecatedString> format_time_zone_offset(StringView locale, Cal
}
// The digits used for hours, minutes and seconds fields in this format are the locale's default decimal digits.
auto result = replace_digits_for_number_system(*number_system, builder.build()).release_value_but_fixme_should_propagate_errors();
return formats->gmt_format.replace("{0}"sv, result, ReplaceMode::FirstOnly);
auto result = TRY(replace_digits_for_number_system(*number_system, builder.build()));
return TRY(String::from_utf8(formats->gmt_format)).replace("{0}"sv, result, ReplaceMode::FirstOnly);
}
// https://unicode.org/reports/tr35/tr35-dates.html#Time_Zone_Format_Terminology
DeprecatedString format_time_zone(StringView locale, StringView time_zone, CalendarPatternStyle style, AK::Time time)
ErrorOr<String> format_time_zone(StringView locale, StringView time_zone, CalendarPatternStyle style, AK::Time time)
{
auto offset = TimeZone::get_time_zone_offset(time_zone, time);
if (!offset.has_value())
return time_zone;
return String::from_utf8(time_zone);
switch (style) {
case CalendarPatternStyle::Short:
@ -307,12 +307,14 @@ DeprecatedString format_time_zone(StringView locale, StringView time_zone, Calen
case CalendarPatternStyle::ShortGeneric:
case CalendarPatternStyle::LongGeneric:
if (auto name = get_time_zone_name(locale, time_zone, style, offset->in_dst); name.has_value())
return *name;
return String::from_utf8(*name);
break;
case CalendarPatternStyle::ShortOffset:
case CalendarPatternStyle::LongOffset:
return format_time_zone_offset(locale, style, offset->seconds).value_or(time_zone);
if (auto formatted_offset = TRY(format_time_zone_offset(locale, style, offset->seconds)); formatted_offset.has_value())
return formatted_offset.release_value();
return String::from_utf8(time_zone);
default:
VERIFY_NOT_REACHED();

View File

@ -1,13 +1,14 @@
/*
* Copyright (c) 2021-2022, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/DeprecatedString.h>
#include <AK/Error.h>
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/StringView.h>
#include <AK/Time.h>
#include <AK/Types.h>
@ -111,9 +112,9 @@ struct CalendarPattern {
callback(time_zone_name, other.time_zone_name, Field::TimeZoneName);
}
DeprecatedString skeleton {};
DeprecatedString pattern {};
Optional<DeprecatedString> pattern12 {};
String skeleton {};
String pattern {};
Optional<String> pattern12 {};
Optional<HourCycle> hour_cycle {};
// https://unicode.org/reports/tr35/tr35-dates.html#Calendar_Fields
@ -145,9 +146,9 @@ struct CalendarRangePattern : public CalendarPattern {
};
Optional<Field> field {};
DeprecatedString start_range {};
String start_range {};
StringView separator {};
DeprecatedString end_range {};
String end_range {};
};
enum class CalendarFormatType : u8 {
@ -208,16 +209,16 @@ Optional<WeekendEndRegion> weekend_end_region_from_string(StringView weekend_end
Optional<Weekday> get_regional_weekend_end(StringView region);
Optional<Weekday> get_locale_weekend_end(StringView region);
DeprecatedString combine_skeletons(StringView first, StringView second);
ErrorOr<String> combine_skeletons(StringView first, StringView second);
Optional<CalendarFormat> get_calendar_date_format(StringView locale, StringView calendar);
Optional<CalendarFormat> get_calendar_time_format(StringView locale, StringView calendar);
Optional<CalendarFormat> get_calendar_date_time_format(StringView locale, StringView calendar);
Optional<CalendarFormat> get_calendar_format(StringView locale, StringView calendar, CalendarFormatType type);
Vector<CalendarPattern> get_calendar_available_formats(StringView locale, StringView calendar);
Optional<CalendarRangePattern> get_calendar_default_range_format(StringView locale, StringView calendar);
Vector<CalendarRangePattern> get_calendar_range_formats(StringView locale, StringView calendar, StringView skeleton);
Vector<CalendarRangePattern> get_calendar_range12_formats(StringView locale, StringView calendar, StringView skeleton);
ErrorOr<Optional<CalendarFormat>> get_calendar_date_format(StringView locale, StringView calendar);
ErrorOr<Optional<CalendarFormat>> get_calendar_time_format(StringView locale, StringView calendar);
ErrorOr<Optional<CalendarFormat>> get_calendar_date_time_format(StringView locale, StringView calendar);
ErrorOr<Optional<CalendarFormat>> get_calendar_format(StringView locale, StringView calendar, CalendarFormatType type);
ErrorOr<Vector<CalendarPattern>> get_calendar_available_formats(StringView locale, StringView calendar);
ErrorOr<Optional<CalendarRangePattern>> get_calendar_default_range_format(StringView locale, StringView calendar);
ErrorOr<Vector<CalendarRangePattern>> get_calendar_range_formats(StringView locale, StringView calendar, StringView skeleton);
ErrorOr<Vector<CalendarRangePattern>> get_calendar_range12_formats(StringView locale, StringView calendar, StringView skeleton);
Optional<StringView> get_calendar_era_symbol(StringView locale, StringView calendar, CalendarPatternStyle style, Era value);
Optional<StringView> get_calendar_month_symbol(StringView locale, StringView calendar, CalendarPatternStyle style, Month value);
@ -225,7 +226,7 @@ Optional<StringView> get_calendar_weekday_symbol(StringView locale, StringView c
Optional<StringView> get_calendar_day_period_symbol(StringView locale, StringView calendar, CalendarPatternStyle style, DayPeriod value);
Optional<StringView> get_calendar_day_period_symbol_for_hour(StringView locale, StringView calendar, CalendarPatternStyle style, u8 hour);
DeprecatedString format_time_zone(StringView locale, StringView time_zone, CalendarPatternStyle style, AK::Time time);
ErrorOr<String> format_time_zone(StringView locale, StringView time_zone, CalendarPatternStyle style, AK::Time time);
Optional<StringView> get_time_zone_name(StringView locale, StringView time_zone, CalendarPatternStyle style, TimeZone::InDST in_dst);
Optional<TimeZoneFormat> get_time_zone_format(StringView locale);