DateTimeFormat.cpp 11 KB

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