diff --git a/Userland/Libraries/LibLocale/CMakeLists.txt b/Userland/Libraries/LibLocale/CMakeLists.txt index df0ba54e170..4e58db81aa4 100644 --- a/Userland/Libraries/LibLocale/CMakeLists.txt +++ b/Userland/Libraries/LibLocale/CMakeLists.txt @@ -11,6 +11,7 @@ endif() set(SOURCES DateTimeFormat.cpp + DurationFormat.cpp DisplayNames.cpp ICU.cpp ListFormat.cpp diff --git a/Userland/Libraries/LibLocale/DurationFormat.cpp b/Userland/Libraries/LibLocale/DurationFormat.cpp new file mode 100644 index 00000000000..2815add78d9 --- /dev/null +++ b/Userland/Libraries/LibLocale/DurationFormat.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#define AK_DONT_REPLACE_STD + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Locale { + +static constexpr bool is_not_ascii_digit(u32 code_point) +{ + return !is_ascii_digit(code_point); +} + +DigitalFormat digital_format(StringView locale) +{ + UErrorCode status = U_ZERO_ERROR; + + auto locale_data = LocaleData::for_locale(locale); + if (!locale_data.has_value()) + return {}; + + if (auto const& digital_format = locale_data->digital_format(); digital_format.has_value()) + return *digital_format; + + RoundingOptions rounding_options; + rounding_options.type = RoundingType::SignificantDigits; + rounding_options.min_significant_digits = 1; + rounding_options.max_significant_digits = 2; + + auto number_formatter = NumberFormat::create(locale, "latn"sv, {}, rounding_options); + + auto icu_locale = adopt_own(*locale_data->locale().clone()); + icu_locale->setUnicodeKeywordValue("nu", "latn", status); + if (icu_failure(status)) + return {}; + + icu::MeasureFormat measurement_formatter(*icu_locale, UMEASFMT_WIDTH_NUMERIC, status); + if (icu_failure(status)) + return {}; + + auto measures = to_array({ + { 1, icu::MeasureUnit::createHour(status), status }, + { 22, icu::MeasureUnit::createMinute(status), status }, + { 33, icu::MeasureUnit::createSecond(status), status }, + }); + if (icu_failure(status)) + return {}; + + icu::UnicodeString formatted; + icu::FieldPosition position; + measurement_formatter.formatMeasures(measures.data(), static_cast(measures.size()), formatted, position, status); + if (icu_failure(status)) + return {}; + + auto hours_minutes_seconds = icu_string_to_string(formatted); + GenericLexer lexer { hours_minutes_seconds }; + + DigitalFormat digital_format; + + auto hours = lexer.consume_while(is_ascii_digit); + digital_format.uses_two_digit_hours = hours.length() == 2; + + auto hours_minutes_separator = lexer.consume_while(is_not_ascii_digit); + digital_format.hours_minutes_separator = MUST(String::from_utf8(hours_minutes_separator)); + + lexer.ignore_while(is_ascii_digit); + + auto minutes_seconds_separator = lexer.consume_while(is_not_ascii_digit); + digital_format.minutes_seconds_separator = MUST(String::from_utf8(minutes_seconds_separator)); + + locale_data->set_digital_format(move(digital_format)); + return *locale_data->digital_format(); +} + +} diff --git a/Userland/Libraries/LibLocale/DurationFormat.h b/Userland/Libraries/LibLocale/DurationFormat.h new file mode 100644 index 00000000000..8905d654aa9 --- /dev/null +++ b/Userland/Libraries/LibLocale/DurationFormat.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Locale { + +struct DigitalFormat { + String hours_minutes_separator { ":"_string }; + String minutes_seconds_separator { ":"_string }; + bool uses_two_digit_hours { false }; +}; + +DigitalFormat digital_format(StringView locale); + +} diff --git a/Userland/Libraries/LibLocale/ICU.h b/Userland/Libraries/LibLocale/ICU.h index 481f8af4df2..e6ed208e173 100644 --- a/Userland/Libraries/LibLocale/ICU.h +++ b/Userland/Libraries/LibLocale/ICU.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -42,6 +43,9 @@ public: icu::TimeZoneNames& time_zone_names(); + Optional const& digital_format() { return m_digital_format; } + void set_digital_format(DigitalFormat digital_format) { m_digital_format = move(digital_format); } + private: explicit LocaleData(icu::Locale locale); @@ -52,6 +56,8 @@ private: OwnPtr m_dialect_display_names; OwnPtr m_date_time_pattern_generator; OwnPtr m_time_zone_names; + + Optional m_digital_format; }; static constexpr bool icu_success(UErrorCode code)