123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- /*
- * Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #define AK_DONT_REPLACE_STD
- #include <LibUnicode/ICU.h>
- #include <LibUnicode/Locale.h>
- #include <LibUnicode/NumberFormat.h>
- #include <LibUnicode/PartitionRange.h>
- #include <LibUnicode/RelativeTimeFormat.h>
- #include <unicode/decimfmt.h>
- #include <unicode/numfmt.h>
- #include <unicode/reldatefmt.h>
- namespace Unicode {
- Optional<TimeUnit> time_unit_from_string(StringView time_unit)
- {
- if (time_unit == "second"sv)
- return TimeUnit::Second;
- if (time_unit == "minute"sv)
- return TimeUnit::Minute;
- if (time_unit == "hour"sv)
- return TimeUnit::Hour;
- if (time_unit == "day"sv)
- return TimeUnit::Day;
- if (time_unit == "week"sv)
- return TimeUnit::Week;
- if (time_unit == "month"sv)
- return TimeUnit::Month;
- if (time_unit == "quarter"sv)
- return TimeUnit::Quarter;
- if (time_unit == "year"sv)
- return TimeUnit::Year;
- return {};
- }
- StringView time_unit_to_string(TimeUnit time_unit)
- {
- switch (time_unit) {
- case TimeUnit::Second:
- return "second"sv;
- case TimeUnit::Minute:
- return "minute"sv;
- case TimeUnit::Hour:
- return "hour"sv;
- case TimeUnit::Day:
- return "day"sv;
- case TimeUnit::Week:
- return "week"sv;
- case TimeUnit::Month:
- return "month"sv;
- case TimeUnit::Quarter:
- return "quarter"sv;
- case TimeUnit::Year:
- return "year"sv;
- }
- VERIFY_NOT_REACHED();
- }
- static constexpr URelativeDateTimeUnit icu_time_unit(TimeUnit unit)
- {
- switch (unit) {
- case TimeUnit::Second:
- return URelativeDateTimeUnit::UDAT_REL_UNIT_SECOND;
- case TimeUnit::Minute:
- return URelativeDateTimeUnit::UDAT_REL_UNIT_MINUTE;
- case TimeUnit::Hour:
- return URelativeDateTimeUnit::UDAT_REL_UNIT_HOUR;
- case TimeUnit::Day:
- return URelativeDateTimeUnit::UDAT_REL_UNIT_DAY;
- case TimeUnit::Week:
- return URelativeDateTimeUnit::UDAT_REL_UNIT_WEEK;
- case TimeUnit::Month:
- return URelativeDateTimeUnit::UDAT_REL_UNIT_MONTH;
- case TimeUnit::Quarter:
- return URelativeDateTimeUnit::UDAT_REL_UNIT_QUARTER;
- case TimeUnit::Year:
- return URelativeDateTimeUnit::UDAT_REL_UNIT_YEAR;
- }
- VERIFY_NOT_REACHED();
- }
- NumericDisplay numeric_display_from_string(StringView numeric_display)
- {
- if (numeric_display == "always"sv)
- return NumericDisplay::Always;
- if (numeric_display == "auto"sv)
- return NumericDisplay::Auto;
- VERIFY_NOT_REACHED();
- }
- StringView numeric_display_to_string(NumericDisplay numeric_display)
- {
- switch (numeric_display) {
- case NumericDisplay::Always:
- return "always"sv;
- case NumericDisplay::Auto:
- return "auto"sv;
- }
- VERIFY_NOT_REACHED();
- }
- static constexpr UDateRelativeDateTimeFormatterStyle icu_relative_date_time_style(Style unit_display)
- {
- switch (unit_display) {
- case Style::Long:
- return UDAT_STYLE_LONG;
- case Style::Short:
- return UDAT_STYLE_SHORT;
- case Style::Narrow:
- return UDAT_STYLE_NARROW;
- }
- VERIFY_NOT_REACHED();
- }
- static constexpr StringView icu_relative_time_format_field_to_string(i32 field)
- {
- switch (field) {
- case PartitionRange::LITERAL_FIELD:
- return "literal"sv;
- case UNUM_INTEGER_FIELD:
- return "integer"sv;
- case UNUM_FRACTION_FIELD:
- return "fraction"sv;
- case UNUM_DECIMAL_SEPARATOR_FIELD:
- return "decimal"sv;
- case UNUM_GROUPING_SEPARATOR_FIELD:
- return "group"sv;
- }
- VERIFY_NOT_REACHED();
- }
- class RelativeTimeFormatImpl : public RelativeTimeFormat {
- public:
- explicit RelativeTimeFormatImpl(NonnullOwnPtr<icu::RelativeDateTimeFormatter> formatter)
- : m_formatter(move(formatter))
- {
- }
- virtual ~RelativeTimeFormatImpl() override = default;
- virtual String format(double time, TimeUnit unit, NumericDisplay numeric_display) const override
- {
- UErrorCode status = U_ZERO_ERROR;
- auto formatted = format_impl(time, unit, numeric_display);
- auto formatted_time = formatted->toTempString(status);
- if (icu_failure(status))
- return {};
- return icu_string_to_string(formatted_time);
- }
- virtual Vector<Partition> format_to_parts(double time, TimeUnit unit, NumericDisplay numeric_display) const override
- {
- UErrorCode status = U_ZERO_ERROR;
- auto formatted = format_impl(time, unit, numeric_display);
- auto unit_string = time_unit_to_string(unit);
- auto formatted_time = formatted->toTempString(status);
- if (icu_failure(status))
- return {};
- Vector<Partition> result;
- Vector<PartitionRange> separators;
- auto create_partition = [&](i32 field, i32 begin, i32 end, bool is_unit) {
- Partition partition;
- partition.type = icu_relative_time_format_field_to_string(field);
- partition.value = icu_string_to_string(formatted_time.tempSubStringBetween(begin, end));
- if (is_unit)
- partition.unit = unit_string;
- result.append(move(partition));
- };
- icu::ConstrainedFieldPosition position;
- position.constrainCategory(UFIELD_CATEGORY_NUMBER);
- i32 previous_end_index = 0;
- while (static_cast<bool>(formatted->nextPosition(position, status)) && icu_success(status)) {
- if (position.getField() == UNUM_GROUPING_SEPARATOR_FIELD) {
- separators.empend(position.getField(), position.getStart(), position.getLimit());
- continue;
- }
- if (previous_end_index < position.getStart())
- create_partition(PartitionRange::LITERAL_FIELD, previous_end_index, position.getStart(), false);
- auto start = position.getStart();
- if (position.getField() == UNUM_INTEGER_FIELD) {
- for (auto const& separator : separators) {
- if (start >= separator.start)
- continue;
- create_partition(position.getField(), start, separator.start, true);
- create_partition(separator.field, separator.start, separator.end, true);
- start = separator.end;
- break;
- }
- }
- create_partition(position.getField(), start, position.getLimit(), true);
- previous_end_index = position.getLimit();
- }
- if (previous_end_index < formatted_time.length())
- create_partition(PartitionRange::LITERAL_FIELD, previous_end_index, formatted_time.length(), false);
- return result;
- }
- private:
- Optional<icu::FormattedRelativeDateTime> format_impl(double time, TimeUnit unit, NumericDisplay numeric_display) const
- {
- UErrorCode status = U_ZERO_ERROR;
- auto formatted = numeric_display == NumericDisplay::Always
- ? m_formatter->formatNumericToValue(time, icu_time_unit(unit), status)
- : m_formatter->formatToValue(time, icu_time_unit(unit), status);
- if (icu_failure(status))
- return {};
- return formatted;
- }
- NonnullOwnPtr<icu::RelativeDateTimeFormatter> m_formatter;
- };
- NonnullOwnPtr<RelativeTimeFormat> RelativeTimeFormat::create(StringView locale, Style style)
- {
- UErrorCode status = U_ZERO_ERROR;
- auto locale_data = LocaleData::for_locale(locale);
- VERIFY(locale_data.has_value());
- auto* number_formatter = icu::NumberFormat::createInstance(locale_data->locale(), UNUM_DECIMAL, status);
- VERIFY(locale_data.has_value());
- if (number_formatter->getDynamicClassID() == icu::DecimalFormat::getStaticClassID())
- static_cast<icu::DecimalFormat&>(*number_formatter).setMinimumGroupingDigits(UNUM_MINIMUM_GROUPING_DIGITS_AUTO);
- auto formatter = make<icu::RelativeDateTimeFormatter>(locale_data->locale(), number_formatter, icu_relative_date_time_style(style), UDISPCTX_CAPITALIZATION_NONE, status);
- VERIFY(icu_success(status));
- return make<RelativeTimeFormatImpl>(move(formatter));
- }
- }
|