DateTimeFormat.cpp 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009
  1. /*
  2. * Copyright (c) 2021-2024, Tim Flynn <trflynn89@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/AllOf.h>
  7. #include <AK/Array.h>
  8. #include <AK/GenericLexer.h>
  9. #include <AK/StringBuilder.h>
  10. #include <AK/TypeCasts.h>
  11. #include <LibUnicode/DateTimeFormat.h>
  12. #include <LibUnicode/ICU.h>
  13. #include <LibUnicode/Locale.h>
  14. #include <LibUnicode/NumberFormat.h>
  15. #include <LibUnicode/PartitionRange.h>
  16. #include <stdlib.h>
  17. #include <unicode/calendar.h>
  18. #include <unicode/datefmt.h>
  19. #include <unicode/dtitvfmt.h>
  20. #include <unicode/dtptngen.h>
  21. #include <unicode/gregocal.h>
  22. #include <unicode/smpdtfmt.h>
  23. #include <unicode/timezone.h>
  24. #include <unicode/ucal.h>
  25. namespace Unicode {
  26. DateTimeStyle date_time_style_from_string(StringView style)
  27. {
  28. if (style == "full"sv)
  29. return DateTimeStyle::Full;
  30. if (style == "long"sv)
  31. return DateTimeStyle::Long;
  32. if (style == "medium"sv)
  33. return DateTimeStyle::Medium;
  34. if (style == "short"sv)
  35. return DateTimeStyle::Short;
  36. VERIFY_NOT_REACHED();
  37. }
  38. StringView date_time_style_to_string(DateTimeStyle style)
  39. {
  40. switch (style) {
  41. case DateTimeStyle::Full:
  42. return "full"sv;
  43. case DateTimeStyle::Long:
  44. return "long"sv;
  45. case DateTimeStyle::Medium:
  46. return "medium"sv;
  47. case DateTimeStyle::Short:
  48. return "short"sv;
  49. }
  50. VERIFY_NOT_REACHED();
  51. }
  52. static constexpr icu::DateFormat::EStyle icu_date_time_style(DateTimeStyle style)
  53. {
  54. switch (style) {
  55. case DateTimeStyle::Full:
  56. return icu::DateFormat::EStyle::kFull;
  57. case DateTimeStyle::Long:
  58. return icu::DateFormat::EStyle::kLong;
  59. case DateTimeStyle::Medium:
  60. return icu::DateFormat::EStyle::kMedium;
  61. case DateTimeStyle::Short:
  62. return icu::DateFormat::EStyle::kShort;
  63. }
  64. VERIFY_NOT_REACHED();
  65. }
  66. HourCycle hour_cycle_from_string(StringView hour_cycle)
  67. {
  68. if (hour_cycle == "h11"sv)
  69. return HourCycle::H11;
  70. if (hour_cycle == "h12"sv)
  71. return HourCycle::H12;
  72. if (hour_cycle == "h23"sv)
  73. return HourCycle::H23;
  74. if (hour_cycle == "h24"sv)
  75. return HourCycle::H24;
  76. VERIFY_NOT_REACHED();
  77. }
  78. StringView hour_cycle_to_string(HourCycle hour_cycle)
  79. {
  80. switch (hour_cycle) {
  81. case HourCycle::H11:
  82. return "h11"sv;
  83. case HourCycle::H12:
  84. return "h12"sv;
  85. case HourCycle::H23:
  86. return "h23"sv;
  87. case HourCycle::H24:
  88. return "h24"sv;
  89. }
  90. VERIFY_NOT_REACHED();
  91. }
  92. Optional<HourCycle> default_hour_cycle(StringView locale)
  93. {
  94. UErrorCode status = U_ZERO_ERROR;
  95. auto locale_data = LocaleData::for_locale(locale);
  96. if (!locale_data.has_value())
  97. return {};
  98. auto hour_cycle = locale_data->date_time_pattern_generator().getDefaultHourCycle(status);
  99. if (icu_failure(status))
  100. return {};
  101. switch (hour_cycle) {
  102. case UDAT_HOUR_CYCLE_11:
  103. return HourCycle::H11;
  104. case UDAT_HOUR_CYCLE_12:
  105. return HourCycle::H12;
  106. case UDAT_HOUR_CYCLE_23:
  107. return HourCycle::H23;
  108. case UDAT_HOUR_CYCLE_24:
  109. return HourCycle::H24;
  110. }
  111. VERIFY_NOT_REACHED();
  112. }
  113. static constexpr char icu_hour_cycle(Optional<HourCycle> const& hour_cycle, Optional<bool> const& hour12)
  114. {
  115. if (hour12.has_value())
  116. return *hour12 ? 'h' : 'H';
  117. if (!hour_cycle.has_value())
  118. return 'j';
  119. switch (*hour_cycle) {
  120. case HourCycle::H11:
  121. return 'K';
  122. case HourCycle::H12:
  123. return 'h';
  124. case HourCycle::H23:
  125. return 'H';
  126. case HourCycle::H24:
  127. return 'k';
  128. }
  129. VERIFY_NOT_REACHED();
  130. }
  131. CalendarPatternStyle calendar_pattern_style_from_string(StringView style)
  132. {
  133. if (style == "narrow"sv)
  134. return CalendarPatternStyle::Narrow;
  135. if (style == "short"sv)
  136. return CalendarPatternStyle::Short;
  137. if (style == "long"sv)
  138. return CalendarPatternStyle::Long;
  139. if (style == "numeric"sv)
  140. return CalendarPatternStyle::Numeric;
  141. if (style == "2-digit"sv)
  142. return CalendarPatternStyle::TwoDigit;
  143. if (style == "shortOffset"sv)
  144. return CalendarPatternStyle::ShortOffset;
  145. if (style == "longOffset"sv)
  146. return CalendarPatternStyle::LongOffset;
  147. if (style == "shortGeneric"sv)
  148. return CalendarPatternStyle::ShortGeneric;
  149. if (style == "longGeneric"sv)
  150. return CalendarPatternStyle::LongGeneric;
  151. VERIFY_NOT_REACHED();
  152. }
  153. StringView calendar_pattern_style_to_string(CalendarPatternStyle style)
  154. {
  155. switch (style) {
  156. case CalendarPatternStyle::Narrow:
  157. return "narrow"sv;
  158. case CalendarPatternStyle::Short:
  159. return "short"sv;
  160. case CalendarPatternStyle::Long:
  161. return "long"sv;
  162. case CalendarPatternStyle::Numeric:
  163. return "numeric"sv;
  164. case CalendarPatternStyle::TwoDigit:
  165. return "2-digit"sv;
  166. case CalendarPatternStyle::ShortOffset:
  167. return "shortOffset"sv;
  168. case CalendarPatternStyle::LongOffset:
  169. return "longOffset"sv;
  170. case CalendarPatternStyle::ShortGeneric:
  171. return "shortGeneric"sv;
  172. case CalendarPatternStyle::LongGeneric:
  173. return "longGeneric"sv;
  174. }
  175. VERIFY_NOT_REACHED();
  176. }
  177. // https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
  178. String CalendarPattern::to_pattern() const
  179. {
  180. // What we refer to as Narrow, Short, and Long, TR-35 refers to as Narrow, Abbreviated, and Wide.
  181. StringBuilder builder;
  182. if (era.has_value()) {
  183. switch (*era) {
  184. case CalendarPatternStyle::Narrow:
  185. builder.append("GGGGG"sv);
  186. break;
  187. case CalendarPatternStyle::Short:
  188. builder.append("G"sv);
  189. break;
  190. case CalendarPatternStyle::Long:
  191. builder.append("GGGG"sv);
  192. break;
  193. default:
  194. break;
  195. }
  196. }
  197. if (year.has_value()) {
  198. switch (*year) {
  199. case CalendarPatternStyle::Numeric:
  200. builder.append("y"sv);
  201. break;
  202. case CalendarPatternStyle::TwoDigit:
  203. builder.append("yy"sv);
  204. break;
  205. default:
  206. break;
  207. }
  208. }
  209. if (month.has_value()) {
  210. switch (*month) {
  211. case CalendarPatternStyle::Numeric:
  212. builder.append("M"sv);
  213. break;
  214. case CalendarPatternStyle::TwoDigit:
  215. builder.append("MM"sv);
  216. break;
  217. case CalendarPatternStyle::Narrow:
  218. builder.append("MMMMM"sv);
  219. break;
  220. case CalendarPatternStyle::Short:
  221. builder.append("MMM"sv);
  222. break;
  223. case CalendarPatternStyle::Long:
  224. builder.append("MMMM"sv);
  225. break;
  226. default:
  227. break;
  228. }
  229. }
  230. if (weekday.has_value()) {
  231. switch (*weekday) {
  232. case CalendarPatternStyle::Narrow:
  233. builder.append("EEEEE"sv);
  234. break;
  235. case CalendarPatternStyle::Short:
  236. builder.append("E"sv);
  237. break;
  238. case CalendarPatternStyle::Long:
  239. builder.append("EEEE"sv);
  240. break;
  241. default:
  242. break;
  243. }
  244. }
  245. if (day.has_value()) {
  246. switch (*day) {
  247. case CalendarPatternStyle::Numeric:
  248. builder.append("d"sv);
  249. break;
  250. case CalendarPatternStyle::TwoDigit:
  251. builder.append("dd"sv);
  252. break;
  253. default:
  254. break;
  255. }
  256. }
  257. if (day_period.has_value()) {
  258. switch (*day_period) {
  259. case CalendarPatternStyle::Narrow:
  260. builder.append("BBBBB"sv);
  261. break;
  262. case CalendarPatternStyle::Short:
  263. builder.append("B"sv);
  264. break;
  265. case CalendarPatternStyle::Long:
  266. builder.append("BBBB"sv);
  267. break;
  268. default:
  269. break;
  270. }
  271. }
  272. if (hour.has_value()) {
  273. auto hour_cycle_symbol = icu_hour_cycle(hour_cycle, hour12);
  274. switch (*hour) {
  275. case CalendarPatternStyle::Numeric:
  276. builder.append(hour_cycle_symbol);
  277. break;
  278. case CalendarPatternStyle::TwoDigit:
  279. builder.append_repeated(hour_cycle_symbol, 2);
  280. break;
  281. default:
  282. break;
  283. }
  284. }
  285. if (minute.has_value()) {
  286. switch (*minute) {
  287. case CalendarPatternStyle::Numeric:
  288. builder.append("m"sv);
  289. break;
  290. case CalendarPatternStyle::TwoDigit:
  291. builder.append("mm"sv);
  292. break;
  293. default:
  294. break;
  295. }
  296. }
  297. if (second.has_value()) {
  298. switch (*second) {
  299. case CalendarPatternStyle::Numeric:
  300. builder.append("s"sv);
  301. break;
  302. case CalendarPatternStyle::TwoDigit:
  303. builder.append("ss"sv);
  304. break;
  305. default:
  306. break;
  307. }
  308. }
  309. if (fractional_second_digits.has_value()) {
  310. for (u8 i = 0; i < *fractional_second_digits; ++i)
  311. builder.append("S"sv);
  312. }
  313. if (time_zone_name.has_value()) {
  314. switch (*time_zone_name) {
  315. case CalendarPatternStyle::Short:
  316. builder.append("z"sv);
  317. break;
  318. case CalendarPatternStyle::Long:
  319. builder.append("zzzz"sv);
  320. break;
  321. case CalendarPatternStyle::ShortOffset:
  322. builder.append("O"sv);
  323. break;
  324. case CalendarPatternStyle::LongOffset:
  325. builder.append("OOOO"sv);
  326. break;
  327. case CalendarPatternStyle::ShortGeneric:
  328. builder.append("v"sv);
  329. break;
  330. case CalendarPatternStyle::LongGeneric:
  331. builder.append("vvvv"sv);
  332. break;
  333. default:
  334. break;
  335. }
  336. }
  337. return MUST(builder.to_string());
  338. }
  339. // https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
  340. CalendarPattern CalendarPattern::create_from_pattern(StringView pattern)
  341. {
  342. GenericLexer lexer { pattern };
  343. CalendarPattern format {};
  344. while (!lexer.is_eof()) {
  345. if (lexer.next_is(is_quote)) {
  346. lexer.consume_quoted_string();
  347. continue;
  348. }
  349. auto starting_char = lexer.peek();
  350. auto segment = lexer.consume_while([&](char ch) { return ch == starting_char; });
  351. // Era
  352. if (all_of(segment, is_any_of("G"sv))) {
  353. if (segment.length() <= 3)
  354. format.era = CalendarPatternStyle::Short;
  355. else if (segment.length() == 4)
  356. format.era = CalendarPatternStyle::Long;
  357. else
  358. format.era = CalendarPatternStyle::Narrow;
  359. }
  360. // Year
  361. else if (all_of(segment, is_any_of("yYuUr"sv))) {
  362. if (segment.length() == 2)
  363. format.year = CalendarPatternStyle::TwoDigit;
  364. else
  365. format.year = CalendarPatternStyle::Numeric;
  366. }
  367. // Month
  368. else if (all_of(segment, is_any_of("ML"sv))) {
  369. if (segment.length() == 1)
  370. format.month = CalendarPatternStyle::Numeric;
  371. else if (segment.length() == 2)
  372. format.month = CalendarPatternStyle::TwoDigit;
  373. else if (segment.length() == 3)
  374. format.month = CalendarPatternStyle::Short;
  375. else if (segment.length() == 4)
  376. format.month = CalendarPatternStyle::Long;
  377. else if (segment.length() == 5)
  378. format.month = CalendarPatternStyle::Narrow;
  379. }
  380. // Weekday
  381. else if (all_of(segment, is_any_of("ecE"sv))) {
  382. if (segment.length() == 4)
  383. format.weekday = CalendarPatternStyle::Long;
  384. else if (segment.length() == 5)
  385. format.weekday = CalendarPatternStyle::Narrow;
  386. else
  387. format.weekday = CalendarPatternStyle::Short;
  388. }
  389. // Day
  390. else if (all_of(segment, is_any_of("d"sv))) {
  391. if (segment.length() == 1)
  392. format.day = CalendarPatternStyle::Numeric;
  393. else
  394. format.day = CalendarPatternStyle::TwoDigit;
  395. } else if (all_of(segment, is_any_of("DFg"sv))) {
  396. format.day = CalendarPatternStyle::Numeric;
  397. }
  398. // Day period
  399. else if (all_of(segment, is_any_of("B"sv))) {
  400. if (segment.length() == 4)
  401. format.day_period = CalendarPatternStyle::Long;
  402. else if (segment.length() == 5)
  403. format.day_period = CalendarPatternStyle::Narrow;
  404. else
  405. format.day_period = CalendarPatternStyle::Short;
  406. }
  407. // Hour
  408. else if (all_of(segment, is_any_of("hHKk"sv))) {
  409. switch (starting_char) {
  410. case 'K':
  411. format.hour_cycle = HourCycle::H11;
  412. break;
  413. case 'h':
  414. format.hour_cycle = HourCycle::H12;
  415. break;
  416. case 'H':
  417. format.hour_cycle = HourCycle::H23;
  418. break;
  419. case 'k':
  420. format.hour_cycle = HourCycle::H24;
  421. break;
  422. }
  423. if (segment.length() == 1)
  424. format.hour = CalendarPatternStyle::Numeric;
  425. else
  426. format.hour = CalendarPatternStyle::TwoDigit;
  427. }
  428. // Minute
  429. else if (all_of(segment, is_any_of("m"sv))) {
  430. if (segment.length() == 1)
  431. format.minute = CalendarPatternStyle::Numeric;
  432. else
  433. format.minute = CalendarPatternStyle::TwoDigit;
  434. }
  435. // Second
  436. else if (all_of(segment, is_any_of("s"sv))) {
  437. if (segment.length() == 1)
  438. format.second = CalendarPatternStyle::Numeric;
  439. else
  440. format.second = CalendarPatternStyle::TwoDigit;
  441. } else if (all_of(segment, is_any_of("S"sv))) {
  442. format.fractional_second_digits = static_cast<u8>(segment.length());
  443. }
  444. // Zone
  445. else if (all_of(segment, is_any_of("zV"sv))) {
  446. if (segment.length() < 4)
  447. format.time_zone_name = CalendarPatternStyle::Short;
  448. else
  449. format.time_zone_name = CalendarPatternStyle::Long;
  450. } else if (all_of(segment, is_any_of("ZOXx"sv))) {
  451. if (segment.length() < 4)
  452. format.time_zone_name = CalendarPatternStyle::ShortOffset;
  453. else
  454. format.time_zone_name = CalendarPatternStyle::LongOffset;
  455. } else if (all_of(segment, is_any_of("v"sv))) {
  456. if (segment.length() < 4)
  457. format.time_zone_name = CalendarPatternStyle::ShortGeneric;
  458. else
  459. format.time_zone_name = CalendarPatternStyle::LongGeneric;
  460. }
  461. }
  462. return format;
  463. }
  464. template<typename T, typename GetRegionalValues>
  465. static T find_regional_values_for_locale(StringView locale, GetRegionalValues&& get_regional_values)
  466. {
  467. auto has_value = [](auto const& container) {
  468. if constexpr (requires { container.has_value(); })
  469. return container.has_value();
  470. else
  471. return !container.is_empty();
  472. };
  473. if (auto regional_values = get_regional_values(locale); has_value(regional_values))
  474. return regional_values;
  475. auto return_default_values = [&]() { return get_regional_values("001"sv); };
  476. auto language = parse_unicode_language_id(locale);
  477. if (!language.has_value())
  478. return return_default_values();
  479. if (!language->region.has_value()) {
  480. if (auto maximized = add_likely_subtags(language->to_string()); maximized.has_value())
  481. language = parse_unicode_language_id(*maximized);
  482. }
  483. if (!language.has_value() || !language->region.has_value())
  484. return return_default_values();
  485. if (auto regional_values = get_regional_values(*language->region); has_value(regional_values))
  486. return regional_values;
  487. return return_default_values();
  488. }
  489. // ICU does not contain a field enumeration for "literal" partitions. Define a custom field so that we may provide a
  490. // type for those partitions.
  491. static constexpr i32 LITERAL_FIELD = -1;
  492. static constexpr StringView icu_date_time_format_field_to_string(i32 field)
  493. {
  494. switch (field) {
  495. case LITERAL_FIELD:
  496. return "literal"sv;
  497. case UDAT_ERA_FIELD:
  498. return "era"sv;
  499. case UDAT_YEAR_FIELD:
  500. case UDAT_EXTENDED_YEAR_FIELD:
  501. return "year"sv;
  502. case UDAT_YEAR_NAME_FIELD:
  503. return "yearName"sv;
  504. case UDAT_RELATED_YEAR_FIELD:
  505. return "relatedYear"sv;
  506. case UDAT_MONTH_FIELD:
  507. case UDAT_STANDALONE_MONTH_FIELD:
  508. return "month"sv;
  509. case UDAT_DAY_OF_WEEK_FIELD:
  510. case UDAT_DOW_LOCAL_FIELD:
  511. case UDAT_STANDALONE_DAY_FIELD:
  512. return "weekday"sv;
  513. case UDAT_DATE_FIELD:
  514. return "day"sv;
  515. case UDAT_AM_PM_FIELD:
  516. case UDAT_AM_PM_MIDNIGHT_NOON_FIELD:
  517. case UDAT_FLEXIBLE_DAY_PERIOD_FIELD:
  518. return "dayPeriod"sv;
  519. case UDAT_HOUR_OF_DAY1_FIELD:
  520. case UDAT_HOUR_OF_DAY0_FIELD:
  521. case UDAT_HOUR1_FIELD:
  522. case UDAT_HOUR0_FIELD:
  523. return "hour"sv;
  524. case UDAT_MINUTE_FIELD:
  525. return "minute"sv;
  526. case UDAT_SECOND_FIELD:
  527. return "second"sv;
  528. case UDAT_FRACTIONAL_SECOND_FIELD:
  529. return "fractionalSecond"sv;
  530. case UDAT_TIMEZONE_FIELD:
  531. case UDAT_TIMEZONE_RFC_FIELD:
  532. case UDAT_TIMEZONE_GENERIC_FIELD:
  533. case UDAT_TIMEZONE_SPECIAL_FIELD:
  534. case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD:
  535. case UDAT_TIMEZONE_ISO_FIELD:
  536. case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
  537. return "timeZoneName"sv;
  538. default:
  539. return "unknown"sv;
  540. }
  541. }
  542. static bool apply_hour_cycle_to_skeleton(icu::UnicodeString& skeleton, Optional<HourCycle> const& hour_cycle, Optional<bool> const& hour12)
  543. {
  544. auto hour_cycle_symbol = icu_hour_cycle(hour_cycle, hour12);
  545. if (hour_cycle_symbol == 'j')
  546. return false;
  547. bool changed_hour_cycle = false;
  548. bool inside_quote = false;
  549. for (i32 i = 0; i < skeleton.length(); ++i) {
  550. switch (skeleton[i]) {
  551. case '\'':
  552. inside_quote = !inside_quote;
  553. break;
  554. case 'h':
  555. case 'H':
  556. case 'k':
  557. case 'K':
  558. if (!inside_quote && static_cast<char>(skeleton[i]) != hour_cycle_symbol) {
  559. skeleton.setCharAt(i, hour_cycle_symbol);
  560. changed_hour_cycle = true;
  561. }
  562. break;
  563. default:
  564. break;
  565. }
  566. }
  567. return changed_hour_cycle;
  568. }
  569. static void apply_time_zone_to_formatter(icu::SimpleDateFormat& formatter, icu::Locale const& locale, StringView time_zone_identifier)
  570. {
  571. UErrorCode status = U_ZERO_ERROR;
  572. auto time_zone_data = TimeZoneData::for_time_zone(time_zone_identifier);
  573. auto* calendar = icu::Calendar::createInstance(time_zone_data->time_zone(), locale, status);
  574. VERIFY(icu_success(status));
  575. if (calendar->getDynamicClassID() == icu::GregorianCalendar::getStaticClassID()) {
  576. // https://tc39.es/ecma262/#sec-time-values-and-time-range
  577. // A time value supports a slightly smaller range of -8,640,000,000,000,000 to 8,640,000,000,000,000 milliseconds.
  578. static constexpr double ECMA_262_MINIMUM_TIME = -8.64E15;
  579. auto* gregorian_calendar = static_cast<icu::GregorianCalendar*>(calendar);
  580. gregorian_calendar->setGregorianChange(ECMA_262_MINIMUM_TIME, status);
  581. VERIFY(icu_success(status));
  582. }
  583. formatter.adoptCalendar(calendar);
  584. }
  585. static bool is_formatted_range_actually_a_range(icu::FormattedDateInterval const& formatted)
  586. {
  587. UErrorCode status = U_ZERO_ERROR;
  588. auto result = formatted.toTempString(status);
  589. if (icu_failure(status))
  590. return false;
  591. icu::ConstrainedFieldPosition position;
  592. position.constrainCategory(UFIELD_CATEGORY_DATE_INTERVAL_SPAN);
  593. auto has_range = static_cast<bool>(formatted.nextPosition(position, status));
  594. if (icu_failure(status))
  595. return false;
  596. return has_range;
  597. }
  598. class DateTimeFormatImpl : public DateTimeFormat {
  599. public:
  600. DateTimeFormatImpl(icu::Locale& locale, icu::UnicodeString const& pattern, StringView time_zone_identifier, NonnullOwnPtr<icu::SimpleDateFormat> formatter)
  601. : m_locale(locale)
  602. , m_pattern(CalendarPattern::create_from_pattern(icu_string_to_string(pattern)))
  603. , m_formatter(move(formatter))
  604. {
  605. apply_time_zone_to_formatter(*m_formatter, m_locale, time_zone_identifier);
  606. }
  607. virtual ~DateTimeFormatImpl() override = default;
  608. virtual CalendarPattern const& chosen_pattern() const override { return m_pattern; }
  609. virtual String format(double time) const override
  610. {
  611. auto formatted_time = format_impl(time);
  612. if (!formatted_time.has_value())
  613. return {};
  614. return icu_string_to_string(*formatted_time);
  615. }
  616. virtual Vector<Partition> format_to_parts(double time) const override
  617. {
  618. icu::FieldPositionIterator iterator;
  619. auto formatted_time = format_impl(time, &iterator);
  620. if (!formatted_time.has_value())
  621. return {};
  622. Vector<Partition> result;
  623. auto create_partition = [&](i32 field, i32 begin, i32 end) {
  624. Partition partition;
  625. partition.type = icu_date_time_format_field_to_string(field);
  626. partition.value = icu_string_to_string(formatted_time->tempSubStringBetween(begin, end));
  627. partition.source = "shared"sv;
  628. result.append(move(partition));
  629. };
  630. icu::FieldPosition position;
  631. i32 previous_end_index = 0;
  632. while (static_cast<bool>(iterator.next(position))) {
  633. if (previous_end_index < position.getBeginIndex())
  634. create_partition(LITERAL_FIELD, previous_end_index, position.getBeginIndex());
  635. if (position.getField() >= 0)
  636. create_partition(position.getField(), position.getBeginIndex(), position.getEndIndex());
  637. previous_end_index = position.getEndIndex();
  638. }
  639. if (previous_end_index < formatted_time->length())
  640. create_partition(LITERAL_FIELD, previous_end_index, formatted_time->length());
  641. return result;
  642. }
  643. virtual String format_range(double start, double end) const override
  644. {
  645. UErrorCode status = U_ZERO_ERROR;
  646. auto formatted = format_range_impl(start, end);
  647. if (!formatted.has_value())
  648. return {};
  649. if (!is_formatted_range_actually_a_range(*formatted))
  650. return format(start);
  651. auto formatted_time = formatted->toTempString(status);
  652. if (icu_failure(status))
  653. return {};
  654. normalize_spaces(formatted_time);
  655. return icu_string_to_string(formatted_time);
  656. }
  657. virtual Vector<Partition> format_range_to_parts(double start, double end) const override
  658. {
  659. UErrorCode status = U_ZERO_ERROR;
  660. auto formatted = format_range_impl(start, end);
  661. if (!formatted.has_value())
  662. return {};
  663. if (!is_formatted_range_actually_a_range(*formatted))
  664. return format_to_parts(start);
  665. auto formatted_time = formatted->toTempString(status);
  666. if (icu_failure(status))
  667. return {};
  668. normalize_spaces(formatted_time);
  669. icu::ConstrainedFieldPosition position;
  670. i32 previous_end_index = 0;
  671. Vector<Partition> result;
  672. Optional<PartitionRange> start_range;
  673. Optional<PartitionRange> end_range;
  674. auto create_partition = [&](i32 field, i32 begin, i32 end) {
  675. Partition partition;
  676. partition.type = icu_date_time_format_field_to_string(field);
  677. partition.value = icu_string_to_string(formatted_time.tempSubStringBetween(begin, end));
  678. if (start_range.has_value() && start_range->contains(begin))
  679. partition.source = "startRange"sv;
  680. else if (end_range.has_value() && end_range->contains(begin))
  681. partition.source = "endRange"sv;
  682. else
  683. partition.source = "shared"sv;
  684. result.append(move(partition));
  685. };
  686. while (static_cast<bool>(formatted->nextPosition(position, status)) && icu_success(status)) {
  687. if (previous_end_index < position.getStart())
  688. create_partition(LITERAL_FIELD, previous_end_index, position.getStart());
  689. if (position.getCategory() == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) {
  690. auto& range = position.getField() == 0 ? start_range : end_range;
  691. range = PartitionRange { position.getField(), position.getStart(), position.getLimit() };
  692. } else if (position.getCategory() == UFIELD_CATEGORY_DATE) {
  693. create_partition(position.getField(), position.getStart(), position.getLimit());
  694. }
  695. previous_end_index = position.getLimit();
  696. }
  697. if (previous_end_index < formatted_time.length())
  698. create_partition(LITERAL_FIELD, previous_end_index, formatted_time.length());
  699. return result;
  700. }
  701. private:
  702. Optional<icu::UnicodeString> format_impl(double time, icu::FieldPositionIterator* iterator = nullptr) const
  703. {
  704. UErrorCode status = U_ZERO_ERROR;
  705. icu::UnicodeString formatted_time;
  706. m_formatter->format(time, formatted_time, iterator, status);
  707. if (icu_failure(status))
  708. return {};
  709. normalize_spaces(formatted_time);
  710. return formatted_time;
  711. }
  712. Optional<icu::FormattedDateInterval> format_range_impl(double start, double end) const
  713. {
  714. UErrorCode status = U_ZERO_ERROR;
  715. if (!m_range_formatter) {
  716. icu::UnicodeString pattern;
  717. m_formatter->toPattern(pattern);
  718. auto skeleton = icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status);
  719. if (icu_failure(status))
  720. return {};
  721. auto* formatter = icu::DateIntervalFormat::createInstance(skeleton, m_locale, status);
  722. if (icu_failure(status))
  723. return {};
  724. m_range_formatter = adopt_own(*formatter);
  725. m_range_formatter->setTimeZone(m_formatter->getTimeZone());
  726. }
  727. auto start_calendar = adopt_own(*m_formatter->getCalendar()->clone());
  728. start_calendar->setTime(start, status);
  729. if (icu_failure(status))
  730. return {};
  731. auto end_calendar = adopt_own(*m_formatter->getCalendar()->clone());
  732. end_calendar->setTime(end, status);
  733. if (icu_failure(status))
  734. return {};
  735. auto formatted = m_range_formatter->formatToValue(*start_calendar, *end_calendar, status);
  736. if (icu_failure(status))
  737. return {};
  738. return formatted;
  739. }
  740. // ICU 72 introduced the use of NBSP to separate time fields and day periods. All major browsers have found that
  741. // this significantly breaks web compatibilty, and they all replace these spaces with normal ASCII spaces. See:
  742. //
  743. // https://bugzilla.mozilla.org/show_bug.cgi?id=1806042
  744. // https://bugs.webkit.org/show_bug.cgi?id=252147
  745. // https://issues.chromium.org/issues/40256057
  746. static void normalize_spaces(icu::UnicodeString& string)
  747. {
  748. static char16_t NARROW_NO_BREAK_SPACE = 0x202f;
  749. static char16_t THIN_SPACE = 0x2009;
  750. for (i32 i = 0; i < string.length(); ++i) {
  751. if (string[i] == NARROW_NO_BREAK_SPACE || string[i] == THIN_SPACE)
  752. string.setCharAt(i, ' ');
  753. }
  754. }
  755. icu::Locale& m_locale;
  756. CalendarPattern m_pattern;
  757. NonnullOwnPtr<icu::SimpleDateFormat> m_formatter;
  758. mutable OwnPtr<icu::DateIntervalFormat> m_range_formatter;
  759. };
  760. NonnullOwnPtr<DateTimeFormat> DateTimeFormat::create_for_date_and_time_style(
  761. StringView locale,
  762. StringView time_zone_identifier,
  763. Optional<HourCycle> const& hour_cycle,
  764. Optional<bool> const& hour12,
  765. Optional<DateTimeStyle> const& date_style,
  766. Optional<DateTimeStyle> const& time_style)
  767. {
  768. UErrorCode status = U_ZERO_ERROR;
  769. auto locale_data = LocaleData::for_locale(locale);
  770. VERIFY(locale_data.has_value());
  771. auto formatter = adopt_own(*verify_cast<icu::SimpleDateFormat>([&]() {
  772. if (date_style.has_value() && time_style.has_value()) {
  773. return icu::DateFormat::createDateTimeInstance(
  774. icu_date_time_style(*date_style), icu_date_time_style(*time_style), locale_data->locale());
  775. }
  776. if (date_style.has_value()) {
  777. return icu::DateFormat::createDateInstance(
  778. icu_date_time_style(*date_style), locale_data->locale());
  779. }
  780. if (time_style.has_value()) {
  781. return icu::DateFormat::createTimeInstance(
  782. icu_date_time_style(*time_style), locale_data->locale());
  783. }
  784. VERIFY_NOT_REACHED();
  785. }()));
  786. icu::UnicodeString pattern;
  787. formatter->toPattern(pattern);
  788. auto skeleton = icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status);
  789. VERIFY(icu_success(status));
  790. if (apply_hour_cycle_to_skeleton(skeleton, hour_cycle, hour12)) {
  791. pattern = locale_data->date_time_pattern_generator().getBestPattern(skeleton, UDATPG_MATCH_ALL_FIELDS_LENGTH, status);
  792. VERIFY(icu_success(status));
  793. apply_hour_cycle_to_skeleton(pattern, hour_cycle, hour12);
  794. formatter = adopt_own(*new icu::SimpleDateFormat(pattern, locale_data->locale(), status));
  795. VERIFY(icu_success(status));
  796. }
  797. return adopt_own(*new DateTimeFormatImpl(locale_data->locale(), pattern, time_zone_identifier, move(formatter)));
  798. }
  799. NonnullOwnPtr<DateTimeFormat> DateTimeFormat::create_for_pattern_options(
  800. StringView locale,
  801. StringView time_zone_identifier,
  802. CalendarPattern const& options)
  803. {
  804. UErrorCode status = U_ZERO_ERROR;
  805. auto locale_data = LocaleData::for_locale(locale);
  806. VERIFY(locale_data.has_value());
  807. auto skeleton = icu_string(options.to_pattern());
  808. auto pattern = locale_data->date_time_pattern_generator().getBestPattern(skeleton, UDATPG_MATCH_ALL_FIELDS_LENGTH, status);
  809. VERIFY(icu_success(status));
  810. apply_hour_cycle_to_skeleton(pattern, options.hour_cycle, {});
  811. auto formatter = adopt_own(*new icu::SimpleDateFormat(pattern, locale_data->locale(), status));
  812. VERIFY(icu_success(status));
  813. return adopt_own(*new DateTimeFormatImpl(locale_data->locale(), pattern, time_zone_identifier, move(formatter)));
  814. }
  815. static constexpr Weekday icu_calendar_day_to_weekday(UCalendarDaysOfWeek day)
  816. {
  817. switch (day) {
  818. case UCAL_SUNDAY:
  819. return Weekday::Sunday;
  820. case UCAL_MONDAY:
  821. return Weekday::Monday;
  822. case UCAL_TUESDAY:
  823. return Weekday::Tuesday;
  824. case UCAL_WEDNESDAY:
  825. return Weekday::Wednesday;
  826. case UCAL_THURSDAY:
  827. return Weekday::Thursday;
  828. case UCAL_FRIDAY:
  829. return Weekday::Friday;
  830. case UCAL_SATURDAY:
  831. return Weekday::Saturday;
  832. }
  833. VERIFY_NOT_REACHED();
  834. }
  835. WeekInfo week_info_of_locale(StringView locale)
  836. {
  837. UErrorCode status = U_ZERO_ERROR;
  838. auto locale_data = LocaleData::for_locale(locale);
  839. if (!locale_data.has_value())
  840. return {};
  841. auto calendar = adopt_own_if_nonnull(icu::Calendar::createInstance(locale_data->locale(), status));
  842. if (icu_failure(status))
  843. return {};
  844. WeekInfo week_info;
  845. week_info.minimal_days_in_first_week = calendar->getMinimalDaysInFirstWeek();
  846. if (auto day = calendar->getFirstDayOfWeek(status); icu_success(status))
  847. week_info.first_day_of_week = icu_calendar_day_to_weekday(day);
  848. auto append_if_weekend = [&](auto day) {
  849. auto type = calendar->getDayOfWeekType(day, status);
  850. if (icu_failure(status))
  851. return;
  852. switch (type) {
  853. case UCAL_WEEKEND_ONSET:
  854. case UCAL_WEEKEND_CEASE:
  855. case UCAL_WEEKEND:
  856. week_info.weekend_days.append(icu_calendar_day_to_weekday(day));
  857. break;
  858. default:
  859. break;
  860. }
  861. };
  862. append_if_weekend(UCAL_SUNDAY);
  863. append_if_weekend(UCAL_MONDAY);
  864. append_if_weekend(UCAL_TUESDAY);
  865. append_if_weekend(UCAL_WEDNESDAY);
  866. append_if_weekend(UCAL_THURSDAY);
  867. append_if_weekend(UCAL_FRIDAY);
  868. append_if_weekend(UCAL_SATURDAY);
  869. return week_info;
  870. }
  871. }