123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999 |
- /*
- * Copyright (c) 2021-2024, Tim Flynn <trflynn89@serenityos.org>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #define AK_DONT_REPLACE_STD
- #include <AK/AllOf.h>
- #include <AK/Array.h>
- #include <AK/GenericLexer.h>
- #include <AK/StringBuilder.h>
- #include <AK/TypeCasts.h>
- #include <LibLocale/DateTimeFormat.h>
- #include <LibLocale/ICU.h>
- #include <LibLocale/Locale.h>
- #include <LibLocale/NumberFormat.h>
- #include <stdlib.h>
- #include <unicode/calendar.h>
- #include <unicode/datefmt.h>
- #include <unicode/dtitvfmt.h>
- #include <unicode/dtptngen.h>
- #include <unicode/gregocal.h>
- #include <unicode/smpdtfmt.h>
- #include <unicode/timezone.h>
- #include <unicode/ucal.h>
- namespace Locale {
- DateTimeStyle date_time_style_from_string(StringView style)
- {
- if (style == "full"sv)
- return DateTimeStyle::Full;
- if (style == "long"sv)
- return DateTimeStyle::Long;
- if (style == "medium"sv)
- return DateTimeStyle::Medium;
- if (style == "short"sv)
- return DateTimeStyle::Short;
- VERIFY_NOT_REACHED();
- }
- StringView date_time_style_to_string(DateTimeStyle style)
- {
- switch (style) {
- case DateTimeStyle::Full:
- return "full"sv;
- case DateTimeStyle::Long:
- return "long"sv;
- case DateTimeStyle::Medium:
- return "medium"sv;
- case DateTimeStyle::Short:
- return "short"sv;
- }
- VERIFY_NOT_REACHED();
- }
- static constexpr icu::DateFormat::EStyle icu_date_time_style(DateTimeStyle style)
- {
- switch (style) {
- case DateTimeStyle::Full:
- return icu::DateFormat::EStyle::kFull;
- case DateTimeStyle::Long:
- return icu::DateFormat::EStyle::kLong;
- case DateTimeStyle::Medium:
- return icu::DateFormat::EStyle::kMedium;
- case DateTimeStyle::Short:
- return icu::DateFormat::EStyle::kShort;
- }
- VERIFY_NOT_REACHED();
- }
- HourCycle hour_cycle_from_string(StringView hour_cycle)
- {
- if (hour_cycle == "h11"sv)
- return HourCycle::H11;
- if (hour_cycle == "h12"sv)
- return HourCycle::H12;
- if (hour_cycle == "h23"sv)
- return HourCycle::H23;
- if (hour_cycle == "h24"sv)
- return HourCycle::H24;
- VERIFY_NOT_REACHED();
- }
- StringView hour_cycle_to_string(HourCycle hour_cycle)
- {
- switch (hour_cycle) {
- case HourCycle::H11:
- return "h11"sv;
- case HourCycle::H12:
- return "h12"sv;
- case HourCycle::H23:
- return "h23"sv;
- case HourCycle::H24:
- return "h24"sv;
- }
- VERIFY_NOT_REACHED();
- }
- Optional<HourCycle> default_hour_cycle(StringView locale)
- {
- UErrorCode status = U_ZERO_ERROR;
- auto locale_data = LocaleData::for_locale(locale);
- if (!locale_data.has_value())
- return {};
- auto hour_cycle = locale_data->date_time_pattern_generator().getDefaultHourCycle(status);
- if (icu_failure(status))
- return {};
- switch (hour_cycle) {
- case UDAT_HOUR_CYCLE_11:
- return HourCycle::H11;
- case UDAT_HOUR_CYCLE_12:
- return HourCycle::H12;
- case UDAT_HOUR_CYCLE_23:
- return HourCycle::H23;
- case UDAT_HOUR_CYCLE_24:
- return HourCycle::H24;
- }
- VERIFY_NOT_REACHED();
- }
- static constexpr char icu_hour_cycle(Optional<HourCycle> const& hour_cycle, Optional<bool> const& hour12)
- {
- if (hour12.has_value())
- return *hour12 ? 'h' : 'H';
- if (!hour_cycle.has_value())
- return 'j';
- switch (*hour_cycle) {
- case HourCycle::H11:
- return 'K';
- case HourCycle::H12:
- return 'h';
- case HourCycle::H23:
- return 'H';
- case HourCycle::H24:
- return 'k';
- }
- VERIFY_NOT_REACHED();
- }
- CalendarPatternStyle calendar_pattern_style_from_string(StringView style)
- {
- if (style == "narrow"sv)
- return CalendarPatternStyle::Narrow;
- if (style == "short"sv)
- return CalendarPatternStyle::Short;
- if (style == "long"sv)
- return CalendarPatternStyle::Long;
- if (style == "numeric"sv)
- return CalendarPatternStyle::Numeric;
- if (style == "2-digit"sv)
- return CalendarPatternStyle::TwoDigit;
- if (style == "shortOffset"sv)
- return CalendarPatternStyle::ShortOffset;
- if (style == "longOffset"sv)
- return CalendarPatternStyle::LongOffset;
- if (style == "shortGeneric"sv)
- return CalendarPatternStyle::ShortGeneric;
- if (style == "longGeneric"sv)
- return CalendarPatternStyle::LongGeneric;
- VERIFY_NOT_REACHED();
- }
- StringView calendar_pattern_style_to_string(CalendarPatternStyle style)
- {
- switch (style) {
- case CalendarPatternStyle::Narrow:
- return "narrow"sv;
- case CalendarPatternStyle::Short:
- return "short"sv;
- case CalendarPatternStyle::Long:
- return "long"sv;
- case CalendarPatternStyle::Numeric:
- return "numeric"sv;
- case CalendarPatternStyle::TwoDigit:
- return "2-digit"sv;
- case CalendarPatternStyle::ShortOffset:
- return "shortOffset"sv;
- case CalendarPatternStyle::LongOffset:
- return "longOffset"sv;
- case CalendarPatternStyle::ShortGeneric:
- return "shortGeneric"sv;
- case CalendarPatternStyle::LongGeneric:
- return "longGeneric"sv;
- }
- VERIFY_NOT_REACHED();
- }
- // https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
- String CalendarPattern::to_pattern() const
- {
- // What we refer to as Narrow, Short, and Long, TR-35 refers to as Narrow, Abbreviated, and Wide.
- StringBuilder builder;
- if (era.has_value()) {
- switch (*era) {
- case CalendarPatternStyle::Narrow:
- builder.append("GGGGG"sv);
- break;
- case CalendarPatternStyle::Short:
- builder.append("G"sv);
- break;
- case CalendarPatternStyle::Long:
- builder.append("GGGG"sv);
- break;
- default:
- break;
- }
- }
- if (year.has_value()) {
- switch (*year) {
- case CalendarPatternStyle::Numeric:
- builder.append("y"sv);
- break;
- case CalendarPatternStyle::TwoDigit:
- builder.append("yy"sv);
- break;
- default:
- break;
- }
- }
- if (month.has_value()) {
- switch (*month) {
- case CalendarPatternStyle::Numeric:
- builder.append("M"sv);
- break;
- case CalendarPatternStyle::TwoDigit:
- builder.append("MM"sv);
- break;
- case CalendarPatternStyle::Narrow:
- builder.append("MMMMM"sv);
- break;
- case CalendarPatternStyle::Short:
- builder.append("MMM"sv);
- break;
- case CalendarPatternStyle::Long:
- builder.append("MMMM"sv);
- break;
- default:
- break;
- }
- }
- if (weekday.has_value()) {
- switch (*weekday) {
- case CalendarPatternStyle::Narrow:
- builder.append("EEEEE"sv);
- break;
- case CalendarPatternStyle::Short:
- builder.append("E"sv);
- break;
- case CalendarPatternStyle::Long:
- builder.append("EEEE"sv);
- break;
- default:
- break;
- }
- }
- if (day.has_value()) {
- switch (*day) {
- case CalendarPatternStyle::Numeric:
- builder.append("d"sv);
- break;
- case CalendarPatternStyle::TwoDigit:
- builder.append("dd"sv);
- break;
- default:
- break;
- }
- }
- if (day_period.has_value()) {
- switch (*day_period) {
- case CalendarPatternStyle::Narrow:
- builder.append("BBBBB"sv);
- break;
- case CalendarPatternStyle::Short:
- builder.append("B"sv);
- break;
- case CalendarPatternStyle::Long:
- builder.append("BBBB"sv);
- break;
- default:
- break;
- }
- }
- if (hour.has_value()) {
- auto hour_cycle_symbol = icu_hour_cycle(hour_cycle, hour12);
- switch (*hour) {
- case CalendarPatternStyle::Numeric:
- builder.append(hour_cycle_symbol);
- break;
- case CalendarPatternStyle::TwoDigit:
- builder.append_repeated(hour_cycle_symbol, 2);
- break;
- default:
- break;
- }
- }
- if (minute.has_value()) {
- switch (*minute) {
- case CalendarPatternStyle::Numeric:
- builder.append("m"sv);
- break;
- case CalendarPatternStyle::TwoDigit:
- builder.append("mm"sv);
- break;
- default:
- break;
- }
- }
- if (second.has_value()) {
- switch (*second) {
- case CalendarPatternStyle::Numeric:
- builder.append("s"sv);
- break;
- case CalendarPatternStyle::TwoDigit:
- builder.append("ss"sv);
- break;
- default:
- break;
- }
- }
- if (fractional_second_digits.has_value()) {
- for (u8 i = 0; i < *fractional_second_digits; ++i)
- builder.append("S"sv);
- }
- if (time_zone_name.has_value()) {
- switch (*time_zone_name) {
- case CalendarPatternStyle::Short:
- builder.append("z"sv);
- break;
- case CalendarPatternStyle::Long:
- builder.append("zzzz"sv);
- break;
- case CalendarPatternStyle::ShortOffset:
- builder.append("O"sv);
- break;
- case CalendarPatternStyle::LongOffset:
- builder.append("OOOO"sv);
- break;
- case CalendarPatternStyle::ShortGeneric:
- builder.append("v"sv);
- break;
- case CalendarPatternStyle::LongGeneric:
- builder.append("vvvv"sv);
- break;
- default:
- break;
- }
- }
- return MUST(builder.to_string());
- }
- // https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
- CalendarPattern CalendarPattern::create_from_pattern(StringView pattern)
- {
- GenericLexer lexer { pattern };
- CalendarPattern format {};
- while (!lexer.is_eof()) {
- if (lexer.next_is(is_quote)) {
- lexer.consume_quoted_string();
- continue;
- }
- auto starting_char = lexer.peek();
- auto segment = lexer.consume_while([&](char ch) { return ch == starting_char; });
- // Era
- if (all_of(segment, is_any_of("G"sv))) {
- if (segment.length() <= 3)
- format.era = CalendarPatternStyle::Short;
- else if (segment.length() == 4)
- format.era = CalendarPatternStyle::Long;
- else
- format.era = CalendarPatternStyle::Narrow;
- }
- // Year
- else if (all_of(segment, is_any_of("yYuUr"sv))) {
- if (segment.length() == 2)
- format.year = CalendarPatternStyle::TwoDigit;
- else
- format.year = CalendarPatternStyle::Numeric;
- }
- // Month
- else if (all_of(segment, is_any_of("ML"sv))) {
- if (segment.length() == 1)
- format.month = CalendarPatternStyle::Numeric;
- else if (segment.length() == 2)
- format.month = CalendarPatternStyle::TwoDigit;
- else if (segment.length() == 3)
- format.month = CalendarPatternStyle::Short;
- else if (segment.length() == 4)
- format.month = CalendarPatternStyle::Long;
- else if (segment.length() == 5)
- format.month = CalendarPatternStyle::Narrow;
- }
- // Weekday
- else if (all_of(segment, is_any_of("ecE"sv))) {
- if (segment.length() == 4)
- format.weekday = CalendarPatternStyle::Long;
- else if (segment.length() == 5)
- format.weekday = CalendarPatternStyle::Narrow;
- else
- format.weekday = CalendarPatternStyle::Short;
- }
- // Day
- else if (all_of(segment, is_any_of("d"sv))) {
- if (segment.length() == 1)
- format.day = CalendarPatternStyle::Numeric;
- else
- format.day = CalendarPatternStyle::TwoDigit;
- } else if (all_of(segment, is_any_of("DFg"sv))) {
- format.day = CalendarPatternStyle::Numeric;
- }
- // Day period
- else if (all_of(segment, is_any_of("B"sv))) {
- if (segment.length() == 4)
- format.day_period = CalendarPatternStyle::Long;
- else if (segment.length() == 5)
- format.day_period = CalendarPatternStyle::Narrow;
- else
- format.day_period = CalendarPatternStyle::Short;
- }
- // Hour
- else if (all_of(segment, is_any_of("hHKk"sv))) {
- switch (starting_char) {
- case 'K':
- format.hour_cycle = HourCycle::H11;
- break;
- case 'h':
- format.hour_cycle = HourCycle::H12;
- break;
- case 'H':
- format.hour_cycle = HourCycle::H23;
- break;
- case 'k':
- format.hour_cycle = HourCycle::H24;
- break;
- }
- if (segment.length() == 1)
- format.hour = CalendarPatternStyle::Numeric;
- else
- format.hour = CalendarPatternStyle::TwoDigit;
- }
- // Minute
- else if (all_of(segment, is_any_of("m"sv))) {
- if (segment.length() == 1)
- format.minute = CalendarPatternStyle::Numeric;
- else
- format.minute = CalendarPatternStyle::TwoDigit;
- }
- // Second
- else if (all_of(segment, is_any_of("s"sv))) {
- if (segment.length() == 1)
- format.second = CalendarPatternStyle::Numeric;
- else
- format.second = CalendarPatternStyle::TwoDigit;
- } else if (all_of(segment, is_any_of("S"sv))) {
- format.fractional_second_digits = static_cast<u8>(segment.length());
- }
- // Zone
- else if (all_of(segment, is_any_of("zV"sv))) {
- if (segment.length() < 4)
- format.time_zone_name = CalendarPatternStyle::Short;
- else
- format.time_zone_name = CalendarPatternStyle::Long;
- } else if (all_of(segment, is_any_of("ZOXx"sv))) {
- if (segment.length() < 4)
- format.time_zone_name = CalendarPatternStyle::ShortOffset;
- else
- format.time_zone_name = CalendarPatternStyle::LongOffset;
- } else if (all_of(segment, is_any_of("v"sv))) {
- if (segment.length() < 4)
- format.time_zone_name = CalendarPatternStyle::ShortGeneric;
- else
- format.time_zone_name = CalendarPatternStyle::LongGeneric;
- }
- }
- return format;
- }
- template<typename T, typename GetRegionalValues>
- static T find_regional_values_for_locale(StringView locale, GetRegionalValues&& get_regional_values)
- {
- auto has_value = [](auto const& container) {
- if constexpr (requires { container.has_value(); })
- return container.has_value();
- else
- return !container.is_empty();
- };
- if (auto regional_values = get_regional_values(locale); has_value(regional_values))
- return regional_values;
- auto return_default_values = [&]() { return get_regional_values("001"sv); };
- auto language = parse_unicode_language_id(locale);
- if (!language.has_value())
- return return_default_values();
- if (!language->region.has_value()) {
- if (auto maximized = add_likely_subtags(language->to_string()); maximized.has_value())
- language = parse_unicode_language_id(*maximized);
- }
- if (!language.has_value() || !language->region.has_value())
- return return_default_values();
- if (auto regional_values = get_regional_values(*language->region); has_value(regional_values))
- return regional_values;
- return return_default_values();
- }
- // ICU does not contain a field enumeration for "literal" partitions. Define a custom field so that we may provide a
- // type for those partitions.
- static constexpr i32 LITERAL_FIELD = -1;
- static constexpr StringView icu_date_time_format_field_to_string(i32 field)
- {
- switch (field) {
- case LITERAL_FIELD:
- return "literal"sv;
- case UDAT_ERA_FIELD:
- return "era"sv;
- case UDAT_YEAR_FIELD:
- case UDAT_EXTENDED_YEAR_FIELD:
- return "year"sv;
- case UDAT_YEAR_NAME_FIELD:
- return "yearName"sv;
- case UDAT_RELATED_YEAR_FIELD:
- return "relatedYear"sv;
- case UDAT_MONTH_FIELD:
- case UDAT_STANDALONE_MONTH_FIELD:
- return "month"sv;
- case UDAT_DAY_OF_WEEK_FIELD:
- case UDAT_DOW_LOCAL_FIELD:
- case UDAT_STANDALONE_DAY_FIELD:
- return "weekday"sv;
- case UDAT_DATE_FIELD:
- return "day"sv;
- case UDAT_AM_PM_FIELD:
- case UDAT_AM_PM_MIDNIGHT_NOON_FIELD:
- case UDAT_FLEXIBLE_DAY_PERIOD_FIELD:
- return "dayPeriod"sv;
- case UDAT_HOUR_OF_DAY1_FIELD:
- case UDAT_HOUR_OF_DAY0_FIELD:
- case UDAT_HOUR1_FIELD:
- case UDAT_HOUR0_FIELD:
- return "hour"sv;
- case UDAT_MINUTE_FIELD:
- return "minute"sv;
- case UDAT_SECOND_FIELD:
- return "second"sv;
- case UDAT_FRACTIONAL_SECOND_FIELD:
- return "fractionalSecond"sv;
- case UDAT_TIMEZONE_FIELD:
- case UDAT_TIMEZONE_RFC_FIELD:
- case UDAT_TIMEZONE_GENERIC_FIELD:
- case UDAT_TIMEZONE_SPECIAL_FIELD:
- case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD:
- case UDAT_TIMEZONE_ISO_FIELD:
- case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
- return "timeZoneName"sv;
- default:
- return "unknown"sv;
- }
- }
- static bool apply_hour_cycle_to_skeleton(icu::UnicodeString& skeleton, Optional<HourCycle> const& hour_cycle, Optional<bool> const& hour12)
- {
- auto hour_cycle_symbol = icu_hour_cycle(hour_cycle, hour12);
- if (hour_cycle_symbol == 'j')
- return false;
- bool changed_hour_cycle = false;
- bool inside_quote = false;
- for (i32 i = 0; i < skeleton.length(); ++i) {
- switch (skeleton[i]) {
- case '\'':
- inside_quote = !inside_quote;
- break;
- case 'h':
- case 'H':
- case 'k':
- case 'K':
- if (!inside_quote && static_cast<char>(skeleton[i]) != hour_cycle_symbol) {
- skeleton.setCharAt(i, hour_cycle_symbol);
- changed_hour_cycle = true;
- }
- break;
- default:
- break;
- }
- }
- return changed_hour_cycle;
- }
- static void apply_time_zone_to_formatter(icu::SimpleDateFormat& formatter, icu::Locale const& locale, StringView time_zone_identifier)
- {
- UErrorCode status = U_ZERO_ERROR;
- auto* time_zone = icu::TimeZone::createTimeZone(icu_string(time_zone_identifier));
- auto* calendar = icu::Calendar::createInstance(time_zone, locale, status);
- VERIFY(icu_success(status));
- if (calendar->getDynamicClassID() == icu::GregorianCalendar::getStaticClassID()) {
- // https://tc39.es/ecma262/#sec-time-values-and-time-range
- // A time value supports a slightly smaller range of -8,640,000,000,000,000 to 8,640,000,000,000,000 milliseconds.
- static constexpr double ECMA_262_MINIMUM_TIME = -8.64E15;
- auto* gregorian_calendar = static_cast<icu::GregorianCalendar*>(calendar);
- gregorian_calendar->setGregorianChange(ECMA_262_MINIMUM_TIME, status);
- VERIFY(icu_success(status));
- }
- formatter.adoptCalendar(calendar);
- }
- static bool is_formatted_range_actually_a_range(icu::FormattedDateInterval const& formatted)
- {
- UErrorCode status = U_ZERO_ERROR;
- auto result = formatted.toTempString(status);
- if (icu_failure(status))
- return false;
- icu::ConstrainedFieldPosition position;
- position.constrainCategory(UFIELD_CATEGORY_DATE_INTERVAL_SPAN);
- auto has_range = static_cast<bool>(formatted.nextPosition(position, status));
- if (icu_failure(status))
- return false;
- return has_range;
- }
- struct Range {
- constexpr bool contains(i32 position) const
- {
- return start <= position && position < end;
- }
- i32 start { 0 };
- i32 end { 0 };
- };
- class DateTimeFormatImpl : public DateTimeFormat {
- public:
- DateTimeFormatImpl(icu::Locale& locale, icu::UnicodeString const& pattern, StringView time_zone_identifier, NonnullOwnPtr<icu::SimpleDateFormat> formatter)
- : m_locale(locale)
- , m_pattern(CalendarPattern::create_from_pattern(icu_string_to_string(pattern)))
- , m_formatter(move(formatter))
- {
- apply_time_zone_to_formatter(*m_formatter, m_locale, time_zone_identifier);
- }
- virtual ~DateTimeFormatImpl() override = default;
- virtual CalendarPattern const& chosen_pattern() const override { return m_pattern; }
- virtual String format(double time) const override
- {
- auto formatted_time = format_impl(time);
- if (!formatted_time.has_value())
- return {};
- return icu_string_to_string(*formatted_time);
- }
- virtual Vector<Partition> format_to_parts(double time) const override
- {
- icu::FieldPositionIterator iterator;
- auto formatted_time = format_impl(time, &iterator);
- if (!formatted_time.has_value())
- return {};
- Vector<Partition> result;
- auto create_partition = [&](i32 field, i32 begin, i32 end) {
- Partition partition;
- partition.type = icu_date_time_format_field_to_string(field);
- partition.value = icu_string_to_string(formatted_time->tempSubStringBetween(begin, end));
- partition.source = "shared"sv;
- result.append(move(partition));
- };
- icu::FieldPosition position;
- i32 previous_end_index = 0;
- while (static_cast<bool>(iterator.next(position))) {
- if (previous_end_index < position.getBeginIndex())
- create_partition(LITERAL_FIELD, previous_end_index, position.getBeginIndex());
- if (position.getField() >= 0)
- create_partition(position.getField(), position.getBeginIndex(), position.getEndIndex());
- previous_end_index = position.getEndIndex();
- }
- if (previous_end_index < formatted_time->length())
- create_partition(LITERAL_FIELD, previous_end_index, formatted_time->length());
- return result;
- }
- virtual String format_range(double start, double end) const override
- {
- UErrorCode status = U_ZERO_ERROR;
- auto formatted = format_range_impl(start, end);
- if (!formatted.has_value())
- return {};
- if (!is_formatted_range_actually_a_range(*formatted))
- return format(start);
- auto formatted_time = formatted->toTempString(status);
- if (icu_failure(status))
- return {};
- return icu_string_to_string(formatted_time);
- }
- virtual Vector<Partition> format_range_to_parts(double start, double end) const override
- {
- UErrorCode status = U_ZERO_ERROR;
- auto formatted = format_range_impl(start, end);
- if (!formatted.has_value())
- return {};
- if (!is_formatted_range_actually_a_range(*formatted))
- return format_to_parts(start);
- auto formatted_time = formatted->toTempString(status);
- if (icu_failure(status))
- return {};
- icu::ConstrainedFieldPosition position;
- i32 previous_end_index = 0;
- Vector<Partition> result;
- Optional<Range> start_range;
- Optional<Range> end_range;
- auto create_partition = [&](i32 field, i32 begin, i32 end) {
- Partition partition;
- partition.type = icu_date_time_format_field_to_string(field);
- partition.value = icu_string_to_string(formatted_time.tempSubStringBetween(begin, end));
- if (start_range.has_value() && start_range->contains(begin))
- partition.source = "startRange"sv;
- else if (end_range.has_value() && end_range->contains(begin))
- partition.source = "endRange"sv;
- else
- partition.source = "shared"sv;
- result.append(move(partition));
- };
- while (static_cast<bool>(formatted->nextPosition(position, status)) && icu_success(status)) {
- if (previous_end_index < position.getStart())
- create_partition(LITERAL_FIELD, previous_end_index, position.getStart());
- if (position.getCategory() == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) {
- auto& range = position.getField() == 0 ? start_range : end_range;
- range = Range { position.getStart(), position.getLimit() };
- } else if (position.getCategory() == UFIELD_CATEGORY_DATE) {
- create_partition(position.getField(), position.getStart(), position.getLimit());
- }
- previous_end_index = position.getLimit();
- }
- if (previous_end_index < formatted_time.length())
- create_partition(LITERAL_FIELD, previous_end_index, formatted_time.length());
- return result;
- }
- private:
- Optional<icu::UnicodeString> format_impl(double time, icu::FieldPositionIterator* iterator = nullptr) const
- {
- UErrorCode status = U_ZERO_ERROR;
- icu::UnicodeString formatted_time;
- m_formatter->format(time, formatted_time, iterator, status);
- if (icu_failure(status))
- return {};
- return formatted_time;
- }
- Optional<icu::FormattedDateInterval> format_range_impl(double start, double end) const
- {
- UErrorCode status = U_ZERO_ERROR;
- if (!m_range_formatter) {
- icu::UnicodeString pattern;
- m_formatter->toPattern(pattern);
- auto skeleton = icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status);
- if (icu_failure(status))
- return {};
- auto* formatter = icu::DateIntervalFormat::createInstance(skeleton, m_locale, status);
- if (icu_failure(status))
- return {};
- m_range_formatter = adopt_own(*formatter);
- m_range_formatter->setTimeZone(m_formatter->getTimeZone());
- }
- auto start_calendar = adopt_own(*m_formatter->getCalendar()->clone());
- start_calendar->setTime(start, status);
- if (icu_failure(status))
- return {};
- auto end_calendar = adopt_own(*m_formatter->getCalendar()->clone());
- end_calendar->setTime(end, status);
- if (icu_failure(status))
- return {};
- auto formatted = m_range_formatter->formatToValue(*start_calendar, *end_calendar, status);
- if (icu_failure(status))
- return {};
- return formatted;
- }
- icu::Locale& m_locale;
- CalendarPattern m_pattern;
- NonnullOwnPtr<icu::SimpleDateFormat> m_formatter;
- mutable OwnPtr<icu::DateIntervalFormat> m_range_formatter;
- };
- NonnullOwnPtr<DateTimeFormat> DateTimeFormat::create_for_date_and_time_style(
- StringView locale,
- StringView time_zone_identifier,
- Optional<HourCycle> const& hour_cycle,
- Optional<bool> const& hour12,
- Optional<DateTimeStyle> const& date_style,
- Optional<DateTimeStyle> const& time_style)
- {
- UErrorCode status = U_ZERO_ERROR;
- auto locale_data = LocaleData::for_locale(locale);
- VERIFY(locale_data.has_value());
- auto formatter = adopt_own(*verify_cast<icu::SimpleDateFormat>([&]() {
- if (date_style.has_value() && time_style.has_value()) {
- return icu::DateFormat::createDateTimeInstance(
- icu_date_time_style(*date_style), icu_date_time_style(*time_style), locale_data->locale());
- }
- if (date_style.has_value()) {
- return icu::DateFormat::createDateInstance(
- icu_date_time_style(*date_style), locale_data->locale());
- }
- if (time_style.has_value()) {
- return icu::DateFormat::createTimeInstance(
- icu_date_time_style(*time_style), locale_data->locale());
- }
- VERIFY_NOT_REACHED();
- }()));
- icu::UnicodeString pattern;
- formatter->toPattern(pattern);
- auto skeleton = icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status);
- VERIFY(icu_success(status));
- if (apply_hour_cycle_to_skeleton(skeleton, hour_cycle, hour12)) {
- pattern = locale_data->date_time_pattern_generator().getBestPattern(skeleton, UDATPG_MATCH_ALL_FIELDS_LENGTH, status);
- VERIFY(icu_success(status));
- apply_hour_cycle_to_skeleton(pattern, hour_cycle, hour12);
- formatter = adopt_own(*new icu::SimpleDateFormat(pattern, locale_data->locale(), status));
- VERIFY(icu_success(status));
- }
- return adopt_own(*new DateTimeFormatImpl(locale_data->locale(), pattern, time_zone_identifier, move(formatter)));
- }
- NonnullOwnPtr<DateTimeFormat> DateTimeFormat::create_for_pattern_options(
- StringView locale,
- StringView time_zone_identifier,
- CalendarPattern const& options)
- {
- UErrorCode status = U_ZERO_ERROR;
- auto locale_data = LocaleData::for_locale(locale);
- VERIFY(locale_data.has_value());
- auto skeleton = icu_string(options.to_pattern());
- auto pattern = locale_data->date_time_pattern_generator().getBestPattern(skeleton, UDATPG_MATCH_ALL_FIELDS_LENGTH, status);
- VERIFY(icu_success(status));
- apply_hour_cycle_to_skeleton(pattern, options.hour_cycle, {});
- auto formatter = adopt_own(*new icu::SimpleDateFormat(pattern, locale_data->locale(), status));
- VERIFY(icu_success(status));
- return adopt_own(*new DateTimeFormatImpl(locale_data->locale(), pattern, time_zone_identifier, move(formatter)));
- }
- static constexpr Weekday icu_calendar_day_to_weekday(UCalendarDaysOfWeek day)
- {
- switch (day) {
- case UCAL_SUNDAY:
- return Weekday::Sunday;
- case UCAL_MONDAY:
- return Weekday::Monday;
- case UCAL_TUESDAY:
- return Weekday::Tuesday;
- case UCAL_WEDNESDAY:
- return Weekday::Wednesday;
- case UCAL_THURSDAY:
- return Weekday::Thursday;
- case UCAL_FRIDAY:
- return Weekday::Friday;
- case UCAL_SATURDAY:
- return Weekday::Saturday;
- }
- VERIFY_NOT_REACHED();
- }
- WeekInfo week_info_of_locale(StringView locale)
- {
- UErrorCode status = U_ZERO_ERROR;
- auto locale_data = LocaleData::for_locale(locale);
- if (!locale_data.has_value())
- return {};
- auto calendar = adopt_own_if_nonnull(icu::Calendar::createInstance(locale_data->locale(), status));
- if (icu_failure(status))
- return {};
- WeekInfo week_info;
- week_info.minimal_days_in_first_week = calendar->getMinimalDaysInFirstWeek();
- if (auto day = calendar->getFirstDayOfWeek(status); icu_success(status))
- week_info.first_day_of_week = icu_calendar_day_to_weekday(day);
- auto append_if_weekend = [&](auto day) {
- auto type = calendar->getDayOfWeekType(day, status);
- if (icu_failure(status))
- return;
- switch (type) {
- case UCAL_WEEKEND_ONSET:
- case UCAL_WEEKEND_CEASE:
- case UCAL_WEEKEND:
- week_info.weekend_days.append(icu_calendar_day_to_weekday(day));
- break;
- default:
- break;
- }
- };
- append_if_weekend(UCAL_SUNDAY);
- append_if_weekend(UCAL_MONDAY);
- append_if_weekend(UCAL_TUESDAY);
- append_if_weekend(UCAL_WEDNESDAY);
- append_if_weekend(UCAL_THURSDAY);
- append_if_weekend(UCAL_FRIDAY);
- append_if_weekend(UCAL_SATURDAY);
- return week_info;
- }
- }
|