DateTimeFormat.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. /*
  2. * Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Array.h>
  7. #include <AK/StringBuilder.h>
  8. #include <LibLocale/DateTimeFormat.h>
  9. #include <LibLocale/Locale.h>
  10. #include <LibLocale/NumberFormat.h>
  11. #include <stdlib.h>
  12. namespace Locale {
  13. HourCycle hour_cycle_from_string(StringView hour_cycle)
  14. {
  15. if (hour_cycle == "h11"sv)
  16. return HourCycle::H11;
  17. if (hour_cycle == "h12"sv)
  18. return HourCycle::H12;
  19. if (hour_cycle == "h23"sv)
  20. return HourCycle::H23;
  21. if (hour_cycle == "h24"sv)
  22. return HourCycle::H24;
  23. VERIFY_NOT_REACHED();
  24. }
  25. StringView hour_cycle_to_string(HourCycle hour_cycle)
  26. {
  27. switch (hour_cycle) {
  28. case HourCycle::H11:
  29. return "h11"sv;
  30. case HourCycle::H12:
  31. return "h12"sv;
  32. case HourCycle::H23:
  33. return "h23"sv;
  34. case HourCycle::H24:
  35. return "h24"sv;
  36. default:
  37. VERIFY_NOT_REACHED();
  38. }
  39. }
  40. CalendarPatternStyle calendar_pattern_style_from_string(StringView style)
  41. {
  42. if (style == "narrow"sv)
  43. return CalendarPatternStyle::Narrow;
  44. if (style == "short"sv)
  45. return CalendarPatternStyle::Short;
  46. if (style == "long"sv)
  47. return CalendarPatternStyle::Long;
  48. if (style == "numeric"sv)
  49. return CalendarPatternStyle::Numeric;
  50. if (style == "2-digit"sv)
  51. return CalendarPatternStyle::TwoDigit;
  52. if (style == "shortOffset"sv)
  53. return CalendarPatternStyle::ShortOffset;
  54. if (style == "longOffset"sv)
  55. return CalendarPatternStyle::LongOffset;
  56. if (style == "shortGeneric"sv)
  57. return CalendarPatternStyle::ShortGeneric;
  58. if (style == "longGeneric"sv)
  59. return CalendarPatternStyle::LongGeneric;
  60. VERIFY_NOT_REACHED();
  61. }
  62. StringView calendar_pattern_style_to_string(CalendarPatternStyle style)
  63. {
  64. switch (style) {
  65. case CalendarPatternStyle::Narrow:
  66. return "narrow"sv;
  67. case CalendarPatternStyle::Short:
  68. return "short"sv;
  69. case CalendarPatternStyle::Long:
  70. return "long"sv;
  71. case CalendarPatternStyle::Numeric:
  72. return "numeric"sv;
  73. case CalendarPatternStyle::TwoDigit:
  74. return "2-digit"sv;
  75. case CalendarPatternStyle::ShortOffset:
  76. return "shortOffset"sv;
  77. case CalendarPatternStyle::LongOffset:
  78. return "longOffset"sv;
  79. case CalendarPatternStyle::ShortGeneric:
  80. return "shortGeneric"sv;
  81. case CalendarPatternStyle::LongGeneric:
  82. return "longGeneric"sv;
  83. default:
  84. VERIFY_NOT_REACHED();
  85. }
  86. }
  87. Optional<HourCycleRegion> __attribute__((weak)) hour_cycle_region_from_string(StringView) { return {}; }
  88. ErrorOr<Vector<HourCycle>> __attribute__((weak)) get_regional_hour_cycles(StringView) { return Vector<HourCycle> {}; }
  89. template<typename T, FallibleFunction<StringView> GetRegionalValues>
  90. static ErrorOr<T> find_regional_values_for_locale(StringView locale, GetRegionalValues&& get_regional_values)
  91. {
  92. auto has_value = [](auto const& container) {
  93. if constexpr (requires { container.has_value(); })
  94. return container.has_value();
  95. else
  96. return !container.is_empty();
  97. };
  98. if (auto regional_values = TRY(get_regional_values(locale)); has_value(regional_values))
  99. return regional_values;
  100. auto return_default_values = [&]() { return get_regional_values("001"sv); };
  101. auto language = TRY(parse_unicode_language_id(locale));
  102. if (!language.has_value())
  103. return return_default_values();
  104. if (!language->region.has_value())
  105. language = TRY(add_likely_subtags(*language));
  106. if (!language.has_value() || !language->region.has_value())
  107. return return_default_values();
  108. if (auto regional_values = TRY(get_regional_values(*language->region)); has_value(regional_values))
  109. return regional_values;
  110. return return_default_values();
  111. }
  112. template<typename T, typename GetRegionalValues>
  113. static ErrorOr<T> find_regional_values_for_locale(StringView locale, GetRegionalValues&& get_regional_values)
  114. {
  115. return find_regional_values_for_locale<T>(locale, [&](auto region) -> ErrorOr<T> { return get_regional_values(region); });
  116. }
  117. // https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
  118. ErrorOr<Vector<HourCycle>> get_locale_hour_cycles(StringView locale)
  119. {
  120. return find_regional_values_for_locale<Vector<HourCycle>>(locale, get_regional_hour_cycles);
  121. }
  122. ErrorOr<Optional<HourCycle>> get_default_regional_hour_cycle(StringView locale)
  123. {
  124. if (auto hour_cycles = TRY(get_locale_hour_cycles(locale)); !hour_cycles.is_empty())
  125. return hour_cycles.first();
  126. return OptionalNone {};
  127. }
  128. Optional<MinimumDaysRegion> __attribute__((weak)) minimum_days_region_from_string(StringView) { return {}; }
  129. Optional<u8> __attribute__((weak)) get_regional_minimum_days(StringView) { return {}; }
  130. ErrorOr<Optional<u8>> get_locale_minimum_days(StringView locale)
  131. {
  132. return find_regional_values_for_locale<Optional<u8>>(locale, get_regional_minimum_days);
  133. }
  134. Optional<FirstDayRegion> __attribute__((weak)) first_day_region_from_string(StringView) { return {}; }
  135. Optional<Weekday> __attribute__((weak)) get_regional_first_day(StringView) { return {}; }
  136. ErrorOr<Optional<Weekday>> get_locale_first_day(StringView locale)
  137. {
  138. return find_regional_values_for_locale<Optional<Weekday>>(locale, get_regional_first_day);
  139. }
  140. Optional<WeekendStartRegion> __attribute__((weak)) weekend_start_region_from_string(StringView) { return {}; }
  141. Optional<Weekday> __attribute__((weak)) get_regional_weekend_start(StringView) { return {}; }
  142. ErrorOr<Optional<Weekday>> get_locale_weekend_start(StringView locale)
  143. {
  144. return find_regional_values_for_locale<Optional<Weekday>>(locale, get_regional_weekend_start);
  145. }
  146. Optional<WeekendEndRegion> __attribute__((weak)) weekend_end_region_from_string(StringView) { return {}; }
  147. Optional<Weekday> __attribute__((weak)) get_regional_weekend_end(StringView) { return {}; }
  148. ErrorOr<Optional<Weekday>> get_locale_weekend_end(StringView locale)
  149. {
  150. return find_regional_values_for_locale<Optional<Weekday>>(locale, get_regional_weekend_end);
  151. }
  152. ErrorOr<String> combine_skeletons(StringView first, StringView second)
  153. {
  154. // https://unicode.org/reports/tr35/tr35-dates.html#availableFormats_appendItems
  155. constexpr auto field_order = Array {
  156. "G"sv, // Era
  157. "yYuUr"sv, // Year
  158. "ML"sv, // Month
  159. "dDFg"sv, // Day
  160. "Eec"sv, // Weekday
  161. "abB"sv, // Period
  162. "hHKk"sv, // Hour
  163. "m"sv, // Minute
  164. "sSA"sv, // Second
  165. "zZOvVXx"sv, // Zone
  166. };
  167. StringBuilder builder;
  168. auto append_from_skeleton = [&](auto skeleton, auto ch) -> ErrorOr<bool> {
  169. auto first_index = skeleton.find(ch);
  170. if (!first_index.has_value())
  171. return false;
  172. auto last_index = skeleton.find_last(ch);
  173. TRY(builder.try_append(skeleton.substring_view(*first_index, *last_index - *first_index + 1)));
  174. return true;
  175. };
  176. for (auto fields : field_order) {
  177. for (auto ch : fields) {
  178. if (TRY(append_from_skeleton(first, ch)))
  179. break;
  180. if (TRY(append_from_skeleton(second, ch)))
  181. break;
  182. }
  183. }
  184. return builder.to_string();
  185. }
  186. ErrorOr<Optional<CalendarFormat>> __attribute__((weak)) get_calendar_date_format(StringView, StringView) { return OptionalNone {}; }
  187. ErrorOr<Optional<CalendarFormat>> __attribute__((weak)) get_calendar_time_format(StringView, StringView) { return OptionalNone {}; }
  188. ErrorOr<Optional<CalendarFormat>> __attribute__((weak)) get_calendar_date_time_format(StringView, StringView) { return OptionalNone {}; }
  189. ErrorOr<Optional<CalendarFormat>> get_calendar_format(StringView locale, StringView calendar, CalendarFormatType type)
  190. {
  191. switch (type) {
  192. case CalendarFormatType::Date:
  193. return get_calendar_date_format(locale, calendar);
  194. case CalendarFormatType::Time:
  195. return get_calendar_time_format(locale, calendar);
  196. case CalendarFormatType::DateTime:
  197. return get_calendar_date_time_format(locale, calendar);
  198. default:
  199. VERIFY_NOT_REACHED();
  200. }
  201. }
  202. ErrorOr<Vector<CalendarPattern>> __attribute__((weak)) get_calendar_available_formats(StringView, StringView) { return Vector<CalendarPattern> {}; }
  203. ErrorOr<Optional<CalendarRangePattern>> __attribute__((weak)) get_calendar_default_range_format(StringView, StringView) { return OptionalNone {}; }
  204. ErrorOr<Vector<CalendarRangePattern>> __attribute__((weak)) get_calendar_range_formats(StringView, StringView, StringView) { return Vector<CalendarRangePattern> {}; }
  205. ErrorOr<Vector<CalendarRangePattern>> __attribute__((weak)) get_calendar_range12_formats(StringView, StringView, StringView) { return Vector<CalendarRangePattern> {}; }
  206. ErrorOr<Optional<StringView>> __attribute__((weak)) get_calendar_era_symbol(StringView, StringView, CalendarPatternStyle, Era) { return OptionalNone {}; }
  207. ErrorOr<Optional<StringView>> __attribute__((weak)) get_calendar_month_symbol(StringView, StringView, CalendarPatternStyle, Month) { return OptionalNone {}; }
  208. ErrorOr<Optional<StringView>> __attribute__((weak)) get_calendar_weekday_symbol(StringView, StringView, CalendarPatternStyle, Weekday) { return OptionalNone {}; }
  209. ErrorOr<Optional<StringView>> __attribute__((weak)) get_calendar_day_period_symbol(StringView, StringView, CalendarPatternStyle, DayPeriod) { return OptionalNone {}; }
  210. ErrorOr<Optional<StringView>> __attribute__((weak)) get_calendar_day_period_symbol_for_hour(StringView, StringView, CalendarPatternStyle, u8) { return OptionalNone {}; }
  211. Optional<StringView> __attribute__((weak)) get_time_zone_name(StringView, StringView, CalendarPatternStyle, TimeZone::InDST) { return {}; }
  212. Optional<TimeZoneFormat> __attribute__((weak)) get_time_zone_format(StringView) { return {}; }
  213. static ErrorOr<Optional<String>> format_time_zone_offset(StringView locale, CalendarPatternStyle style, i64 offset_seconds)
  214. {
  215. auto formats = get_time_zone_format(locale);
  216. if (!formats.has_value())
  217. return OptionalNone {};
  218. auto number_system = TRY(get_preferred_keyword_value_for_locale(locale, "nu"sv));
  219. if (!number_system.has_value())
  220. return OptionalNone {};
  221. if (offset_seconds == 0)
  222. return String::from_utf8(formats->gmt_zero_format);
  223. auto sign = offset_seconds > 0 ? formats->symbol_ahead_sign : formats->symbol_behind_sign;
  224. auto separator = offset_seconds > 0 ? formats->symbol_ahead_separator : formats->symbol_behind_separator;
  225. offset_seconds = llabs(offset_seconds);
  226. auto offset_hours = offset_seconds / 3'600;
  227. offset_seconds %= 3'600;
  228. auto offset_minutes = offset_seconds / 60;
  229. offset_seconds %= 60;
  230. StringBuilder builder;
  231. TRY(builder.try_append(sign));
  232. switch (style) {
  233. // The long format always uses 2-digit hours field and minutes field, with optional 2-digit seconds field.
  234. case CalendarPatternStyle::LongOffset:
  235. TRY(builder.try_appendff("{:02}{}{:02}", offset_hours, separator, offset_minutes));
  236. if (offset_seconds > 0)
  237. TRY(builder.try_appendff("{}{:02}", separator, offset_seconds));
  238. break;
  239. // The short format is intended for the shortest representation and uses hour fields without leading zero, with optional 2-digit minutes and seconds fields.
  240. case CalendarPatternStyle::ShortOffset:
  241. TRY(builder.try_appendff("{}", offset_hours));
  242. if (offset_minutes > 0) {
  243. TRY(builder.try_appendff("{}{:02}", separator, offset_minutes));
  244. if (offset_seconds > 0)
  245. TRY(builder.try_appendff("{}{:02}", separator, offset_seconds));
  246. }
  247. break;
  248. default:
  249. VERIFY_NOT_REACHED();
  250. }
  251. // The digits used for hours, minutes and seconds fields in this format are the locale's default decimal digits.
  252. auto result = TRY(replace_digits_for_number_system(*number_system, TRY(builder.to_string())));
  253. return TRY(String::from_utf8(formats->gmt_format)).replace("{0}"sv, result, ReplaceMode::FirstOnly);
  254. }
  255. // https://unicode.org/reports/tr35/tr35-dates.html#Time_Zone_Format_Terminology
  256. ErrorOr<String> format_time_zone(StringView locale, StringView time_zone, CalendarPatternStyle style, AK::Time time)
  257. {
  258. auto offset = TimeZone::get_time_zone_offset(time_zone, time);
  259. if (!offset.has_value())
  260. return String::from_utf8(time_zone);
  261. switch (style) {
  262. case CalendarPatternStyle::Short:
  263. case CalendarPatternStyle::Long:
  264. case CalendarPatternStyle::ShortGeneric:
  265. case CalendarPatternStyle::LongGeneric:
  266. if (auto name = get_time_zone_name(locale, time_zone, style, offset->in_dst); name.has_value())
  267. return String::from_utf8(*name);
  268. break;
  269. case CalendarPatternStyle::ShortOffset:
  270. case CalendarPatternStyle::LongOffset:
  271. if (auto formatted_offset = TRY(format_time_zone_offset(locale, style, offset->seconds)); formatted_offset.has_value())
  272. return formatted_offset.release_value();
  273. return String::from_utf8(time_zone);
  274. default:
  275. VERIFY_NOT_REACHED();
  276. }
  277. // If more styles are added, consult the following table to ensure always falling back to GMT offset is still correct:
  278. // https://unicode.org/reports/tr35/tr35-dates.html#dfst-zone
  279. switch (style) {
  280. case CalendarPatternStyle::Short:
  281. case CalendarPatternStyle::ShortGeneric:
  282. return format_time_zone(locale, time_zone, CalendarPatternStyle::ShortOffset, time);
  283. case CalendarPatternStyle::Long:
  284. case CalendarPatternStyle::LongGeneric:
  285. return format_time_zone(locale, time_zone, CalendarPatternStyle::LongOffset, time);
  286. default:
  287. VERIFY_NOT_REACHED();
  288. }
  289. }
  290. }