DateTimeFormat.cpp 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194
  1. /*
  2. * Copyright (c) 2021-2023, Tim Flynn <trflynn89@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/Find.h>
  7. #include <AK/IterationDecision.h>
  8. #include <AK/NumericLimits.h>
  9. #include <AK/StringBuilder.h>
  10. #include <AK/Utf16View.h>
  11. #include <LibJS/Runtime/AbstractOperations.h>
  12. #include <LibJS/Runtime/Array.h>
  13. #include <LibJS/Runtime/Date.h>
  14. #include <LibJS/Runtime/Intl/DateTimeFormat.h>
  15. #include <LibJS/Runtime/Intl/NumberFormat.h>
  16. #include <LibJS/Runtime/Intl/NumberFormatConstructor.h>
  17. #include <LibJS/Runtime/NativeFunction.h>
  18. #include <LibJS/Runtime/Temporal/TimeZone.h>
  19. #include <LibJS/Runtime/Utf16String.h>
  20. #include <LibLocale/Locale.h>
  21. #include <LibLocale/NumberFormat.h>
  22. #include <math.h>
  23. namespace JS::Intl {
  24. static Crypto::SignedBigInteger const s_one_million_bigint { 1'000'000 };
  25. // 11 DateTimeFormat Objects, https://tc39.es/ecma402/#datetimeformat-objects
  26. DateTimeFormat::DateTimeFormat(Object& prototype)
  27. : Object(ConstructWithPrototypeTag::Tag, prototype)
  28. {
  29. }
  30. void DateTimeFormat::visit_edges(Cell::Visitor& visitor)
  31. {
  32. Base::visit_edges(visitor);
  33. if (m_bound_format)
  34. visitor.visit(m_bound_format);
  35. }
  36. DateTimeFormat::Style DateTimeFormat::style_from_string(StringView style)
  37. {
  38. if (style == "full"sv)
  39. return Style::Full;
  40. if (style == "long"sv)
  41. return Style::Long;
  42. if (style == "medium"sv)
  43. return Style::Medium;
  44. if (style == "short"sv)
  45. return Style::Short;
  46. VERIFY_NOT_REACHED();
  47. }
  48. StringView DateTimeFormat::style_to_string(Style style)
  49. {
  50. switch (style) {
  51. case Style::Full:
  52. return "full"sv;
  53. case Style::Long:
  54. return "long"sv;
  55. case Style::Medium:
  56. return "medium"sv;
  57. case Style::Short:
  58. return "short"sv;
  59. default:
  60. VERIFY_NOT_REACHED();
  61. }
  62. }
  63. // 11.5.1 DateTimeStyleFormat ( dateStyle, timeStyle, styles ), https://tc39.es/ecma402/#sec-date-time-style-format
  64. Optional<::Locale::CalendarPattern> date_time_style_format(StringView data_locale, DateTimeFormat& date_time_format)
  65. {
  66. ::Locale::CalendarPattern time_format {};
  67. ::Locale::CalendarPattern date_format {};
  68. auto get_pattern = [&](auto type, auto style) -> Optional<::Locale::CalendarPattern> {
  69. auto formats = ::Locale::get_calendar_format(data_locale, date_time_format.calendar(), type);
  70. if (formats.has_value()) {
  71. switch (style) {
  72. case DateTimeFormat::Style::Full:
  73. return formats->full_format;
  74. case DateTimeFormat::Style::Long:
  75. return formats->long_format;
  76. case DateTimeFormat::Style::Medium:
  77. return formats->medium_format;
  78. case DateTimeFormat::Style::Short:
  79. return formats->short_format;
  80. }
  81. }
  82. return {};
  83. };
  84. // 1. If timeStyle is not undefined, then
  85. if (date_time_format.has_time_style()) {
  86. // a. Assert: timeStyle is one of "full", "long", "medium", or "short".
  87. // b. Let timeFormat be styles.[[TimeFormat]].[[<timeStyle>]].
  88. auto pattern = get_pattern(::Locale::CalendarFormatType::Time, date_time_format.time_style());
  89. if (!pattern.has_value())
  90. return {};
  91. time_format = pattern.release_value();
  92. }
  93. // 2. If dateStyle is not undefined, then
  94. if (date_time_format.has_date_style()) {
  95. // a. Assert: dateStyle is one of "full", "long", "medium", or "short".
  96. // b. Let dateFormat be styles.[[DateFormat]].[[<dateStyle>]].
  97. auto pattern = get_pattern(::Locale::CalendarFormatType::Date, date_time_format.date_style());
  98. if (!pattern.has_value())
  99. return {};
  100. date_format = pattern.release_value();
  101. }
  102. // 3. If dateStyle is not undefined and timeStyle is not undefined, then
  103. if (date_time_format.has_date_style() && date_time_format.has_time_style()) {
  104. // a. Let format be a new Record.
  105. ::Locale::CalendarPattern format {};
  106. // b. Add to format all fields from dateFormat except [[pattern]] and [[rangePatterns]].
  107. format.for_each_calendar_field_zipped_with(date_format, [](auto& format_field, auto const& date_format_field, auto) {
  108. format_field = date_format_field;
  109. });
  110. // c. Add to format all fields from timeFormat except [[pattern]], [[rangePatterns]], [[pattern12]], and [[rangePatterns12]], if present.
  111. format.for_each_calendar_field_zipped_with(time_format, [](auto& format_field, auto const& time_format_field, auto) {
  112. if (time_format_field.has_value())
  113. format_field = time_format_field;
  114. });
  115. // d. Let connector be styles.[[DateTimeFormat]].[[<dateStyle>]].
  116. auto connector = get_pattern(::Locale::CalendarFormatType::DateTime, date_time_format.date_style());
  117. if (!connector.has_value())
  118. return {};
  119. // e. Let pattern be the string connector with the substring "{0}" replaced with timeFormat.[[pattern]] and the substring "{1}" replaced with dateFormat.[[pattern]].
  120. auto pattern = MUST(connector->pattern.replace("{0}"sv, time_format.pattern, ReplaceMode::FirstOnly));
  121. pattern = MUST(pattern.replace("{1}"sv, date_format.pattern, ReplaceMode::FirstOnly));
  122. // f. Set format.[[pattern]] to pattern.
  123. format.pattern = move(pattern);
  124. // g. If timeFormat has a [[pattern12]] field, then
  125. if (time_format.pattern12.has_value()) {
  126. // i. Let pattern12 be the string connector with the substring "{0}" replaced with timeFormat.[[pattern12]] and the substring "{1}" replaced with dateFormat.[[pattern]].
  127. auto pattern12 = MUST(connector->pattern.replace("{0}"sv, *time_format.pattern12, ReplaceMode::FirstOnly));
  128. pattern12 = MUST(pattern12.replace("{1}"sv, date_format.pattern, ReplaceMode::FirstOnly));
  129. // ii. Set format.[[pattern12]] to pattern12.
  130. format.pattern12 = move(pattern12);
  131. }
  132. // NOTE: Our implementation of steps h-j differ from the spec. LibUnicode does not attach range patterns to the
  133. // format pattern; rather, lookups for range patterns are performed separately based on the format pattern's
  134. // skeleton. So we form a new skeleton here and defer the range pattern lookups.
  135. format.skeleton = ::Locale::combine_skeletons(date_format.skeleton, time_format.skeleton);
  136. // k. Return format.
  137. return format;
  138. }
  139. // 4. If timeStyle is not undefined, then
  140. if (date_time_format.has_time_style()) {
  141. // a. Return timeFormat.
  142. return time_format;
  143. }
  144. // 5. Assert: dateStyle is not undefined.
  145. VERIFY(date_time_format.has_date_style());
  146. // 6. Return dateFormat.
  147. return date_format;
  148. }
  149. // 11.5.2 BasicFormatMatcher ( options, formats ), https://tc39.es/ecma402/#sec-basicformatmatcher
  150. Optional<::Locale::CalendarPattern> basic_format_matcher(::Locale::CalendarPattern const& options, Vector<::Locale::CalendarPattern> formats)
  151. {
  152. // 1. Let removalPenalty be 120.
  153. constexpr int removal_penalty = 120;
  154. // 2. Let additionPenalty be 20.
  155. constexpr int addition_penalty = 20;
  156. // 3. Let longLessPenalty be 8.
  157. constexpr int long_less_penalty = 8;
  158. // 4. Let longMorePenalty be 6.
  159. constexpr int long_more_penalty = 6;
  160. // 5. Let shortLessPenalty be 6.
  161. constexpr int short_less_penalty = 6;
  162. // 6. Let shortMorePenalty be 3.
  163. constexpr int short_more_penalty = 3;
  164. // 7. Let offsetPenalty be 1.
  165. constexpr int offset_penalty = 1;
  166. // 8. Let bestScore be -Infinity.
  167. int best_score = NumericLimits<int>::min();
  168. // 9. Let bestFormat be undefined.
  169. Optional<::Locale::CalendarPattern> best_format;
  170. // 10. Assert: Type(formats) is List.
  171. // 11. For each element format of formats, do
  172. for (auto& format : formats) {
  173. // a. Let score be 0.
  174. int score = 0;
  175. // b. For each property name property shown in Table 6, do
  176. format.for_each_calendar_field_zipped_with(options, [&](auto const& format_prop, auto const& options_prop, auto type) {
  177. using ValueType = typename RemoveReference<decltype(options_prop)>::ValueType;
  178. // i. If options has a field [[<property>]], let optionsProp be options.[[<property>]]; else let optionsProp be undefined.
  179. // ii. If format has a field [[<property>]], let formatProp be format.[[<property>]]; else let formatProp be undefined.
  180. // iii. If optionsProp is undefined and formatProp is not undefined, decrease score by additionPenalty.
  181. if (!options_prop.has_value() && format_prop.has_value()) {
  182. score -= addition_penalty;
  183. }
  184. // iv. Else if optionsProp is not undefined and formatProp is undefined, decrease score by removalPenalty.
  185. else if (options_prop.has_value() && !format_prop.has_value()) {
  186. score -= removal_penalty;
  187. }
  188. // v. Else if property is "timeZoneName", then
  189. else if (type == ::Locale::CalendarPattern::Field::TimeZoneName) {
  190. // This is needed to avoid a compile error. Although we only enter this branch for TimeZoneName,
  191. // the lambda we are in will be generated with property types other than CalendarPatternStyle.
  192. auto compare_prop = [](auto prop, auto test) { return prop == static_cast<ValueType>(test); };
  193. // 1. If optionsProp is "short" or "shortGeneric", then
  194. if (compare_prop(options_prop, ::Locale::CalendarPatternStyle::Short) || compare_prop(options_prop, ::Locale::CalendarPatternStyle::ShortGeneric)) {
  195. // a. If formatProp is "shortOffset", decrease score by offsetPenalty.
  196. if (compare_prop(format_prop, ::Locale::CalendarPatternStyle::ShortOffset))
  197. score -= offset_penalty;
  198. // b. Else if formatProp is "longOffset", decrease score by (offsetPenalty + shortMorePenalty).
  199. else if (compare_prop(format_prop, ::Locale::CalendarPatternStyle::LongOffset))
  200. score -= offset_penalty + short_more_penalty;
  201. // c. Else if optionsProp is "short" and formatProp is "long", decrease score by shortMorePenalty.
  202. else if (compare_prop(options_prop, ::Locale::CalendarPatternStyle::Short) || compare_prop(format_prop, ::Locale::CalendarPatternStyle::Long))
  203. score -= short_more_penalty;
  204. // d. Else if optionsProp is "shortGeneric" and formatProp is "longGeneric", decrease score by shortMorePenalty.
  205. else if (compare_prop(options_prop, ::Locale::CalendarPatternStyle::ShortGeneric) || compare_prop(format_prop, ::Locale::CalendarPatternStyle::LongGeneric))
  206. score -= short_more_penalty;
  207. // e. Else if optionsProp ≠ formatProp, decrease score by removalPenalty.
  208. else if (options_prop != format_prop)
  209. score -= removal_penalty;
  210. }
  211. // 2. Else if optionsProp is "shortOffset" and formatProp is "longOffset", decrease score by shortMorePenalty.
  212. else if (compare_prop(options_prop, ::Locale::CalendarPatternStyle::ShortOffset) || compare_prop(format_prop, ::Locale::CalendarPatternStyle::LongOffset)) {
  213. score -= short_more_penalty;
  214. }
  215. // 3. Else if optionsProp is "long" or "longGeneric", then
  216. else if (compare_prop(options_prop, ::Locale::CalendarPatternStyle::Long) || compare_prop(options_prop, ::Locale::CalendarPatternStyle::LongGeneric)) {
  217. // a. If formatProp is "longOffset", decrease score by offsetPenalty.
  218. if (compare_prop(format_prop, ::Locale::CalendarPatternStyle::LongOffset))
  219. score -= offset_penalty;
  220. // b. Else if formatProp is "shortOffset", decrease score by (offsetPenalty + longLessPenalty).
  221. else if (compare_prop(format_prop, ::Locale::CalendarPatternStyle::ShortOffset))
  222. score -= offset_penalty + long_less_penalty;
  223. // c. Else if optionsProp is "long" and formatProp is "short", decrease score by longLessPenalty.
  224. else if (compare_prop(options_prop, ::Locale::CalendarPatternStyle::Long) || compare_prop(format_prop, ::Locale::CalendarPatternStyle::Short))
  225. score -= long_less_penalty;
  226. // d. Else if optionsProp is "longGeneric" and formatProp is "shortGeneric", decrease score by longLessPenalty.
  227. else if (compare_prop(options_prop, ::Locale::CalendarPatternStyle::LongGeneric) || compare_prop(format_prop, ::Locale::CalendarPatternStyle::ShortGeneric))
  228. score -= long_less_penalty;
  229. // e. Else if optionsProp ≠ formatProp, decrease score by removalPenalty.
  230. else if (options_prop != format_prop)
  231. score -= removal_penalty;
  232. }
  233. // 4. Else if optionsProp is "longOffset" and formatProp is "shortOffset", decrease score by longLessPenalty.
  234. else if (compare_prop(options_prop, ::Locale::CalendarPatternStyle::LongOffset) || compare_prop(format_prop, ::Locale::CalendarPatternStyle::ShortOffset)) {
  235. score -= long_less_penalty;
  236. }
  237. // 5. Else if optionsProp ≠ formatProp, decrease score by removalPenalty.
  238. else if (options_prop != format_prop) {
  239. score -= removal_penalty;
  240. }
  241. }
  242. // vi. Else if optionsProp ≠ formatProp, then
  243. else if (options_prop != format_prop) {
  244. using ValuesType = Conditional<IsIntegral<ValueType>, AK::Array<u8, 3>, AK::Array<::Locale::CalendarPatternStyle, 5>>;
  245. ValuesType values {};
  246. // 1. If property is "fractionalSecondDigits", then
  247. if constexpr (IsIntegral<ValueType>) {
  248. // a. Let values be « 1𝔽, 2𝔽, 3𝔽 ».
  249. values = { 1, 2, 3 };
  250. }
  251. // 2. Else,
  252. else {
  253. // a. Let values be « "2-digit", "numeric", "narrow", "short", "long" ».
  254. values = {
  255. ::Locale::CalendarPatternStyle::TwoDigit,
  256. ::Locale::CalendarPatternStyle::Numeric,
  257. ::Locale::CalendarPatternStyle::Narrow,
  258. ::Locale::CalendarPatternStyle::Short,
  259. ::Locale::CalendarPatternStyle::Long,
  260. };
  261. }
  262. // 3. Let optionsPropIndex be the index of optionsProp within values.
  263. auto options_prop_index = static_cast<int>(find_index(values.begin(), values.end(), *options_prop));
  264. // 4. Let formatPropIndex be the index of formatProp within values.
  265. auto format_prop_index = static_cast<int>(find_index(values.begin(), values.end(), *format_prop));
  266. // 5. Let delta be max(min(formatPropIndex - optionsPropIndex, 2), -2).
  267. int delta = max(min(format_prop_index - options_prop_index, 2), -2);
  268. // 6. If delta = 2, decrease score by longMorePenalty.
  269. if (delta == 2)
  270. score -= long_more_penalty;
  271. // 7. Else if delta = 1, decrease score by shortMorePenalty.
  272. else if (delta == 1)
  273. score -= short_more_penalty;
  274. // 8. Else if delta = -1, decrease score by shortLessPenalty.
  275. else if (delta == -1)
  276. score -= short_less_penalty;
  277. // 9. Else if delta = -2, decrease score by longLessPenalty.
  278. else if (delta == -2)
  279. score -= long_less_penalty;
  280. }
  281. });
  282. // c. If score > bestScore, then
  283. if (score > best_score) {
  284. // i. Let bestScore be score.
  285. best_score = score;
  286. // ii. Let bestFormat be format.
  287. best_format = format;
  288. }
  289. }
  290. if (!best_format.has_value())
  291. return {};
  292. // Non-standard, if the user provided options that differ from the best format's options, keep
  293. // the user's options. This is expected by TR-35:
  294. //
  295. // It is not necessary to supply dateFormatItems with skeletons for every field length; fields
  296. // in the skeleton and pattern are expected to be expanded in parallel to handle a request.
  297. // https://unicode.org/reports/tr35/tr35-dates.html#Matching_Skeletons
  298. //
  299. // Rather than generating an prohibitively large amount of nearly-duplicate patterns, which only
  300. // differ by field length, we expand the field lengths here.
  301. best_format->for_each_calendar_field_zipped_with(options, [&](auto& best_format_field, auto const& option_field, auto field_type) {
  302. switch (field_type) {
  303. case ::Locale::CalendarPattern::Field::FractionalSecondDigits:
  304. if ((best_format_field.has_value() || best_format->second.has_value()) && option_field.has_value())
  305. best_format_field = option_field;
  306. break;
  307. case ::Locale::CalendarPattern::Field::Hour:
  308. case ::Locale::CalendarPattern::Field::Minute:
  309. case ::Locale::CalendarPattern::Field::Second:
  310. break;
  311. default:
  312. if (best_format_field.has_value() && option_field.has_value())
  313. best_format_field = option_field;
  314. break;
  315. }
  316. });
  317. // 12. Return bestFormat.
  318. return best_format;
  319. }
  320. // 11.5.3 BestFitFormatMatcher ( options, formats ), https://tc39.es/ecma402/#sec-bestfitformatmatcher
  321. Optional<::Locale::CalendarPattern> best_fit_format_matcher(::Locale::CalendarPattern const& options, Vector<::Locale::CalendarPattern> formats)
  322. {
  323. // When the BestFitFormatMatcher abstract operation is called with two arguments options and formats, it performs
  324. // implementation dependent steps, which should return a set of component representations that a typical user of
  325. // the selected locale would perceive as at least as good as the one returned by BasicFormatMatcher.
  326. return basic_format_matcher(options, move(formats));
  327. }
  328. struct StyleAndValue {
  329. StringView name {};
  330. ::Locale::CalendarPatternStyle style {};
  331. i32 value { 0 };
  332. };
  333. static Optional<StyleAndValue> find_calendar_field(StringView name, ::Locale::CalendarPattern const& options, ::Locale::CalendarPattern const* range_options, LocalTime const& local_time)
  334. {
  335. auto make_style_and_value = [](auto name, auto style, auto fallback_style, auto value) {
  336. if (style.has_value())
  337. return StyleAndValue { name, *style, static_cast<i32>(value) };
  338. return StyleAndValue { name, fallback_style, static_cast<i32>(value) };
  339. };
  340. constexpr auto weekday = "weekday"sv;
  341. constexpr auto era = "era"sv;
  342. constexpr auto year = "year"sv;
  343. constexpr auto month = "month"sv;
  344. constexpr auto day = "day"sv;
  345. constexpr auto hour = "hour"sv;
  346. constexpr auto minute = "minute"sv;
  347. constexpr auto second = "second"sv;
  348. Optional<::Locale::CalendarPatternStyle> empty;
  349. if (name == weekday)
  350. return make_style_and_value(weekday, range_options ? range_options->weekday : empty, *options.weekday, local_time.weekday);
  351. if (name == era)
  352. return make_style_and_value(era, range_options ? range_options->era : empty, *options.era, local_time.era);
  353. if (name == year)
  354. return make_style_and_value(year, range_options ? range_options->year : empty, *options.year, local_time.year);
  355. if (name == month)
  356. return make_style_and_value(month, range_options ? range_options->month : empty, *options.month, local_time.month);
  357. if (name == day)
  358. return make_style_and_value(day, range_options ? range_options->day : empty, *options.day, local_time.day);
  359. if (name == hour)
  360. return make_style_and_value(hour, range_options ? range_options->hour : empty, *options.hour, local_time.hour);
  361. if (name == minute)
  362. return make_style_and_value(minute, range_options ? range_options->minute : empty, *options.minute, local_time.minute);
  363. if (name == second)
  364. return make_style_and_value(second, range_options ? range_options->second : empty, *options.second, local_time.second);
  365. return {};
  366. }
  367. static Optional<StringView> resolve_day_period(StringView locale, StringView calendar, ::Locale::CalendarPatternStyle style, ReadonlySpan<PatternPartition> pattern_parts, LocalTime local_time)
  368. {
  369. // Use the "noon" day period if the locale has it, but only if the time is either exactly 12:00.00 or would be displayed as such.
  370. if (local_time.hour == 12) {
  371. auto it = find_if(pattern_parts.begin(), pattern_parts.end(), [&](auto const& part) {
  372. if (part.type == "minute"sv && local_time.minute != 0)
  373. return true;
  374. if (part.type == "second"sv && local_time.second != 0)
  375. return true;
  376. if (part.type == "fractionalSecondDigits"sv && local_time.millisecond != 0)
  377. return true;
  378. return false;
  379. });
  380. if (it == pattern_parts.end()) {
  381. auto noon_symbol = ::Locale::get_calendar_day_period_symbol(locale, calendar, style, ::Locale::DayPeriod::Noon);
  382. if (noon_symbol.has_value())
  383. return *noon_symbol;
  384. }
  385. }
  386. return ::Locale::get_calendar_day_period_symbol_for_hour(locale, calendar, style, local_time.hour);
  387. }
  388. // 11.5.5 FormatDateTimePattern ( dateTimeFormat, patternParts, x, rangeFormatOptions ), https://tc39.es/ecma402/#sec-formatdatetimepattern
  389. ThrowCompletionOr<Vector<PatternPartition>> format_date_time_pattern(VM& vm, DateTimeFormat& date_time_format, Vector<PatternPartition> pattern_parts, double time, ::Locale::CalendarPattern const* range_format_options)
  390. {
  391. auto& realm = *vm.current_realm();
  392. // 1. Let x be TimeClip(x).
  393. time = time_clip(time);
  394. // 2. If x is NaN, throw a RangeError exception.
  395. if (isnan(time))
  396. return vm.throw_completion<RangeError>(ErrorType::IntlInvalidTime);
  397. // 3. Let locale be dateTimeFormat.[[Locale]].
  398. auto const& locale = date_time_format.locale();
  399. auto const& data_locale = date_time_format.data_locale();
  400. auto construct_number_format = [&](auto& options) -> ThrowCompletionOr<NumberFormat*> {
  401. auto number_format = TRY(construct(vm, realm.intrinsics().intl_number_format_constructor(), PrimitiveString::create(vm, locale), options));
  402. return static_cast<NumberFormat*>(number_format.ptr());
  403. };
  404. // 4. Let nfOptions be OrdinaryObjectCreate(null).
  405. auto number_format_options = Object::create(realm, nullptr);
  406. // 5. Perform ! CreateDataPropertyOrThrow(nfOptions, "useGrouping", false).
  407. MUST(number_format_options->create_data_property_or_throw(vm.names.useGrouping, Value(false)));
  408. // 6. Let nf be ? Construct(%NumberFormat%, « locale, nfOptions »).
  409. auto* number_format = TRY(construct_number_format(number_format_options));
  410. // 7. Let nf2Options be OrdinaryObjectCreate(null).
  411. auto number_format_options2 = Object::create(realm, nullptr);
  412. // 8. Perform ! CreateDataPropertyOrThrow(nf2Options, "minimumIntegerDigits", 2).
  413. MUST(number_format_options2->create_data_property_or_throw(vm.names.minimumIntegerDigits, Value(2)));
  414. // 9. Perform ! CreateDataPropertyOrThrow(nf2Options, "useGrouping", false).
  415. MUST(number_format_options2->create_data_property_or_throw(vm.names.useGrouping, Value(false)));
  416. // 10. Let nf2 be ? Construct(%NumberFormat%, « locale, nf2Options »).
  417. auto* number_format2 = TRY(construct_number_format(number_format_options2));
  418. // 11. Let fractionalSecondDigits be dateTimeFormat.[[FractionalSecondDigits]].
  419. Optional<u8> fractional_second_digits;
  420. NumberFormat* number_format3 = nullptr;
  421. // 12. If fractionalSecondDigits is not undefined, then
  422. if (date_time_format.has_fractional_second_digits()) {
  423. fractional_second_digits = date_time_format.fractional_second_digits();
  424. // a. Let nf3Options be OrdinaryObjectCreate(null).
  425. auto number_format_options3 = Object::create(realm, nullptr);
  426. // b. Perform ! CreateDataPropertyOrThrow(nf3Options, "minimumIntegerDigits", fractionalSecondDigits).
  427. MUST(number_format_options3->create_data_property_or_throw(vm.names.minimumIntegerDigits, Value(*fractional_second_digits)));
  428. // c. Perform ! CreateDataPropertyOrThrow(nf3Options, "useGrouping", false).
  429. MUST(number_format_options3->create_data_property_or_throw(vm.names.useGrouping, Value(false)));
  430. // d. Let nf3 be ? Construct(%NumberFormat%, « locale, nf3Options »).
  431. number_format3 = TRY(construct_number_format(number_format_options3));
  432. }
  433. // 13. Let tm be ToLocalTime(ℤ(ℝ(x) × 10^6), dateTimeFormat.[[Calendar]], dateTimeFormat.[[TimeZone]]).
  434. auto time_bigint = Crypto::SignedBigInteger { time }.multiplied_by(s_one_million_bigint);
  435. auto local_time = TRY(to_local_time(vm, time_bigint, date_time_format.calendar(), date_time_format.time_zone()));
  436. // 14. Let result be a new empty List.
  437. Vector<PatternPartition> result;
  438. // 15. For each Record { [[Type]], [[Value]] } patternPart in patternParts, do
  439. for (auto& pattern_part : pattern_parts) {
  440. // a. Let p be patternPart.[[Type]].
  441. auto part = pattern_part.type;
  442. // b. If p is "literal", then
  443. if (part == "literal"sv) {
  444. // i. Append a new Record { [[Type]]: "literal", [[Value]]: patternPart.[[Value]] } as the last element of the list result.
  445. result.append({ "literal"sv, move(pattern_part.value) });
  446. }
  447. // c. Else if p is equal to "fractionalSecondDigits", then
  448. else if (part == "fractionalSecondDigits"sv) {
  449. // i. Let v be tm.[[Millisecond]].
  450. auto value = local_time.millisecond;
  451. // ii. Let v be floor(v × 10^(fractionalSecondDigits - 3)).
  452. value = floor(value * pow(10, static_cast<int>(*fractional_second_digits) - 3));
  453. // iii. Let fv be FormatNumeric(nf3, v).
  454. auto formatted_value = format_numeric(vm, *number_format3, Value(value));
  455. // iv. Append a new Record { [[Type]]: "fractionalSecond", [[Value]]: fv } as the last element of result.
  456. result.append({ "fractionalSecond"sv, move(formatted_value) });
  457. }
  458. // d. Else if p is equal to "dayPeriod", then
  459. else if (part == "dayPeriod"sv) {
  460. String formatted_value;
  461. // i. Let f be the value of dateTimeFormat's internal slot whose name is the Internal Slot column of the matching row.
  462. auto style = date_time_format.day_period();
  463. // ii. Let fv be a String value representing the day period of tm in the form given by f; the String value depends upon the implementation and the effective locale of dateTimeFormat.
  464. auto symbol = resolve_day_period(data_locale, date_time_format.calendar(), style, pattern_parts, local_time);
  465. if (symbol.has_value())
  466. formatted_value = MUST(String::from_utf8(*symbol));
  467. // iii. Append a new Record { [[Type]]: p, [[Value]]: fv } as the last element of the list result.
  468. result.append({ "dayPeriod"sv, move(formatted_value) });
  469. }
  470. // e. Else if p is equal to "timeZoneName", then
  471. else if (part == "timeZoneName"sv) {
  472. // i. Let f be dateTimeFormat.[[TimeZoneName]].
  473. auto style = date_time_format.time_zone_name();
  474. // ii. Let v be dateTimeFormat.[[TimeZone]].
  475. auto const& value = date_time_format.time_zone();
  476. // iii. Let fv be a String value representing v in the form given by f; the String value depends upon the implementation and the effective locale of dateTimeFormat.
  477. // The String value may also depend on the value of the [[InDST]] field of tm if f is "short", "long", "shortOffset", or "longOffset".
  478. // If the implementation does not have a localized representation of f, then use the String value of v itself.
  479. auto formatted_value = ::Locale::format_time_zone(data_locale, value, style, local_time.time_since_epoch());
  480. // iv. Append a new Record { [[Type]]: p, [[Value]]: fv } as the last element of the list result.
  481. result.append({ "timeZoneName"sv, move(formatted_value) });
  482. }
  483. // f. Else if p matches a Property column of the row in Table 6, then
  484. else if (auto style_and_value = find_calendar_field(part, date_time_format, range_format_options, local_time); style_and_value.has_value()) {
  485. String formatted_value;
  486. // i. If rangeFormatOptions is not undefined, let f be the value of rangeFormatOptions's field whose name matches p.
  487. // ii. Else, let f be the value of dateTimeFormat's internal slot whose name is the Internal Slot column of the matching row.
  488. // NOTE: find_calendar_field handles resolving rangeFormatOptions and dateTimeFormat fields.
  489. auto style = style_and_value->style;
  490. // iii. Let v be the value of tm's field whose name is the Internal Slot column of the matching row.
  491. auto value = style_and_value->value;
  492. // iv. If p is "year" and v ≤ 0, let v be 1 - v.
  493. if ((part == "year"sv) && (value <= 0))
  494. value = 1 - value;
  495. // v. If p is "month", increase v by 1.
  496. if (part == "month"sv)
  497. ++value;
  498. if (part == "hour"sv) {
  499. auto hour_cycle = date_time_format.hour_cycle();
  500. // vi. If p is "hour" and dateTimeFormat.[[HourCycle]] is "h11" or "h12", then
  501. if ((hour_cycle == ::Locale::HourCycle::H11) || (hour_cycle == ::Locale::HourCycle::H12)) {
  502. // 1. Let v be v modulo 12.
  503. value = value % 12;
  504. // 2. If v is 0 and dateTimeFormat.[[HourCycle]] is "h12", let v be 12.
  505. if ((value == 0) && (hour_cycle == ::Locale::HourCycle::H12))
  506. value = 12;
  507. }
  508. // vii. If p is "hour" and dateTimeFormat.[[HourCycle]] is "h24", then
  509. if (hour_cycle == ::Locale::HourCycle::H24) {
  510. // 1. If v is 0, let v be 24.
  511. if (value == 0)
  512. value = 24;
  513. }
  514. }
  515. switch (style) {
  516. // viii. If f is "numeric", then
  517. case ::Locale::CalendarPatternStyle::Numeric:
  518. // 1. Let fv be FormatNumeric(nf, v).
  519. formatted_value = format_numeric(vm, *number_format, Value(value));
  520. break;
  521. // ix. Else if f is "2-digit", then
  522. case ::Locale::CalendarPatternStyle::TwoDigit:
  523. // 1. Let fv be FormatNumeric(nf2, v).
  524. formatted_value = format_numeric(vm, *number_format2, Value(value));
  525. // 2. If the "length" property of fv is greater than 2, let fv be the substring of fv containing the last two characters.
  526. // NOTE: The first length check here isn't enough, but lets us avoid UTF-16 transcoding when the formatted value is ASCII.
  527. if (formatted_value.bytes_as_string_view().length() > 2) {
  528. auto utf16_formatted_value = Utf16String::create(formatted_value);
  529. if (utf16_formatted_value.length_in_code_units() > 2)
  530. formatted_value = MUST(utf16_formatted_value.substring_view(utf16_formatted_value.length_in_code_units() - 2).to_utf8());
  531. }
  532. break;
  533. // x. Else if f is "narrow", "short", or "long", then let fv be a String value representing v in the form given by f; the String value depends upon the implementation and the effective locale and calendar of dateTimeFormat.
  534. // If p is "month" and rangeFormatOptions is undefined, then the String value may also depend on whether dateTimeFormat.[[Day]] is undefined.
  535. // If p is "month" and rangeFormatOptions is not undefined, then the String value may also depend on whether rangeFormatOptions.[[day]] is undefined.
  536. // If p is "era" and rangeFormatOptions is undefined, then the String value may also depend on whether dateTimeFormat.[[Era]] is undefined.
  537. // If p is "era" and rangeFormatOptions is not undefined, then the String value may also depend on whether rangeFormatOptions.[[era]] is undefined.
  538. // If the implementation does not have a localized representation of f, then use the String value of v itself.
  539. case ::Locale::CalendarPatternStyle::Narrow:
  540. case ::Locale::CalendarPatternStyle::Short:
  541. case ::Locale::CalendarPatternStyle::Long: {
  542. Optional<StringView> symbol;
  543. if (part == "era"sv)
  544. symbol = ::Locale::get_calendar_era_symbol(data_locale, date_time_format.calendar(), style, static_cast<::Locale::Era>(value));
  545. else if (part == "month"sv)
  546. symbol = ::Locale::get_calendar_month_symbol(data_locale, date_time_format.calendar(), style, static_cast<::Locale::Month>(value - 1));
  547. else if (part == "weekday"sv)
  548. symbol = ::Locale::get_calendar_weekday_symbol(data_locale, date_time_format.calendar(), style, static_cast<::Locale::Weekday>(value));
  549. if (symbol.has_value())
  550. formatted_value = MUST(String::from_utf8(*symbol));
  551. else
  552. formatted_value = MUST(String::number(value));
  553. break;
  554. }
  555. default:
  556. VERIFY_NOT_REACHED();
  557. }
  558. // xi. Append a new Record { [[Type]]: p, [[Value]]: fv } as the last element of the list result.
  559. result.append({ style_and_value->name, move(formatted_value) });
  560. }
  561. // g. Else if p is equal to "ampm", then
  562. else if (part == "ampm"sv) {
  563. String formatted_value;
  564. // i. Let v be tm.[[Hour]].
  565. auto value = local_time.hour;
  566. // ii. If v is greater than 11, then
  567. if (value > 11) {
  568. // 1. Let fv be an implementation and locale dependent String value representing "post meridiem".
  569. auto symbol = ::Locale::get_calendar_day_period_symbol(data_locale, date_time_format.calendar(), ::Locale::CalendarPatternStyle::Short, ::Locale::DayPeriod::PM);
  570. formatted_value = MUST(String::from_utf8(symbol.value_or("PM"sv)));
  571. }
  572. // iii. Else,
  573. else {
  574. // 1. Let fv be an implementation and locale dependent String value representing "ante meridiem".
  575. auto symbol = ::Locale::get_calendar_day_period_symbol(data_locale, date_time_format.calendar(), ::Locale::CalendarPatternStyle::Short, ::Locale::DayPeriod::AM);
  576. formatted_value = MUST(String::from_utf8(symbol.value_or("AM"sv)));
  577. }
  578. // iv. Append a new Record { [[Type]]: "dayPeriod", [[Value]]: fv } as the last element of the list result.
  579. result.append({ "dayPeriod"sv, move(formatted_value) });
  580. }
  581. // h. Else if p is equal to "relatedYear", then
  582. else if (part == "relatedYear"sv) {
  583. // i. Let v be tm.[[RelatedYear]].
  584. // ii. Let fv be FormatNumeric(nf, v).
  585. // iii. Append a new Record { [[Type]]: "relatedYear", [[Value]]: fv } as the last element of the list result.
  586. // FIXME: Implement this when relatedYear is supported.
  587. }
  588. // i. Else if p is equal to "yearName", then
  589. else if (part == "yearName"sv) {
  590. // i. Let v be tm.[[YearName]].
  591. // ii. Let fv be an implementation and locale dependent String value representing v.
  592. // iii. Append a new Record { [[Type]]: "yearName", [[Value]]: fv } as the last element of the list result.
  593. // FIXME: Implement this when yearName is supported.
  594. }
  595. // Non-standard, TR-35 requires the decimal separator before injected {fractionalSecondDigits} partitions
  596. // to adhere to the selected locale. This depends on other generated data, so it is deferred to here.
  597. else if (part == "decimal"sv) {
  598. auto decimal_symbol = ::Locale::get_number_system_symbol(data_locale, date_time_format.numbering_system(), ::Locale::NumericSymbol::Decimal).value_or("."sv);
  599. result.append({ "literal"sv, MUST(String::from_utf8(decimal_symbol)) });
  600. }
  601. // j. Else,
  602. else {
  603. // i. Let unknown be an implementation-, locale-, and numbering system-dependent String based on x and p.
  604. // ii. Append a new Record { [[Type]]: "unknown", [[Value]]: unknown } as the last element of result.
  605. // LibUnicode doesn't generate any "unknown" patterns.
  606. VERIFY_NOT_REACHED();
  607. }
  608. }
  609. // 16. Return result.
  610. return result;
  611. }
  612. // 11.5.6 PartitionDateTimePattern ( dateTimeFormat, x ), https://tc39.es/ecma402/#sec-partitiondatetimepattern
  613. ThrowCompletionOr<Vector<PatternPartition>> partition_date_time_pattern(VM& vm, DateTimeFormat& date_time_format, double time)
  614. {
  615. // 1. Let patternParts be PartitionPattern(dateTimeFormat.[[Pattern]]).
  616. auto pattern_parts = partition_pattern(date_time_format.pattern());
  617. // 2. Let result be ? FormatDateTimePattern(dateTimeFormat, patternParts, x, undefined).
  618. auto result = TRY(format_date_time_pattern(vm, date_time_format, move(pattern_parts), time, nullptr));
  619. // 3. Return result.
  620. return result;
  621. }
  622. // 11.5.7 FormatDateTime ( dateTimeFormat, x ), https://tc39.es/ecma402/#sec-formatdatetime
  623. ThrowCompletionOr<String> format_date_time(VM& vm, DateTimeFormat& date_time_format, double time)
  624. {
  625. // 1. Let parts be ? PartitionDateTimePattern(dateTimeFormat, x).
  626. auto parts = TRY(partition_date_time_pattern(vm, date_time_format, time));
  627. // 2. Let result be the empty String.
  628. StringBuilder result;
  629. // 3. For each Record { [[Type]], [[Value]] } part in parts, do
  630. for (auto& part : parts) {
  631. // a. Set result to the string-concatenation of result and part.[[Value]].
  632. result.append(part.value);
  633. }
  634. // 4. Return result.
  635. return MUST(result.to_string());
  636. }
  637. // 11.5.8 FormatDateTimeToParts ( dateTimeFormat, x ), https://tc39.es/ecma402/#sec-formatdatetimetoparts
  638. ThrowCompletionOr<NonnullGCPtr<Array>> format_date_time_to_parts(VM& vm, DateTimeFormat& date_time_format, double time)
  639. {
  640. auto& realm = *vm.current_realm();
  641. // 1. Let parts be ? PartitionDateTimePattern(dateTimeFormat, x).
  642. auto parts = TRY(partition_date_time_pattern(vm, date_time_format, time));
  643. // 2. Let result be ! ArrayCreate(0).
  644. auto result = MUST(Array::create(realm, 0));
  645. // 3. Let n be 0.
  646. size_t n = 0;
  647. // 4. For each Record { [[Type]], [[Value]] } part in parts, do
  648. for (auto& part : parts) {
  649. // a. Let O be OrdinaryObjectCreate(%Object.prototype%).
  650. auto object = Object::create(realm, realm.intrinsics().object_prototype());
  651. // b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]).
  652. MUST(object->create_data_property_or_throw(vm.names.type, PrimitiveString::create(vm, part.type)));
  653. // c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]).
  654. MUST(object->create_data_property_or_throw(vm.names.value, PrimitiveString::create(vm, move(part.value))));
  655. // d. Perform ! CreateDataProperty(result, ! ToString(n), O).
  656. MUST(result->create_data_property_or_throw(n, object));
  657. // e. Increment n by 1.
  658. ++n;
  659. }
  660. // 5. Return result.
  661. return result;
  662. }
  663. template<typename Callback>
  664. void for_each_range_pattern_field(LocalTime const& time1, LocalTime const& time2, Callback&& callback)
  665. {
  666. // Table 4: Range pattern fields, https://tc39.es/ecma402/#table-datetimeformat-rangepatternfields
  667. if (callback(static_cast<u8>(time1.era), static_cast<u8>(time2.era), ::Locale::CalendarRangePattern::Field::Era) == IterationDecision::Break)
  668. return;
  669. if (callback(time1.year, time2.year, ::Locale::CalendarRangePattern::Field::Year) == IterationDecision::Break)
  670. return;
  671. if (callback(time1.month, time2.month, ::Locale::CalendarRangePattern::Field::Month) == IterationDecision::Break)
  672. return;
  673. if (callback(time1.day, time2.day, ::Locale::CalendarRangePattern::Field::Day) == IterationDecision::Break)
  674. return;
  675. if (callback(time1.hour, time2.hour, ::Locale::CalendarRangePattern::Field::AmPm) == IterationDecision::Break)
  676. return;
  677. if (callback(time1.hour, time2.hour, ::Locale::CalendarRangePattern::Field::DayPeriod) == IterationDecision::Break)
  678. return;
  679. if (callback(time1.hour, time2.hour, ::Locale::CalendarRangePattern::Field::Hour) == IterationDecision::Break)
  680. return;
  681. if (callback(time1.minute, time2.minute, ::Locale::CalendarRangePattern::Field::Minute) == IterationDecision::Break)
  682. return;
  683. if (callback(time1.second, time2.second, ::Locale::CalendarRangePattern::Field::Second) == IterationDecision::Break)
  684. return;
  685. if (callback(time1.millisecond, time2.millisecond, ::Locale::CalendarRangePattern::Field::FractionalSecondDigits) == IterationDecision::Break)
  686. return;
  687. }
  688. template<typename Callback>
  689. static ThrowCompletionOr<void> for_each_range_pattern_with_source(::Locale::CalendarRangePattern& pattern, Callback&& callback)
  690. {
  691. TRY(callback(pattern.start_range, "startRange"sv));
  692. TRY(callback(pattern.separator, "shared"sv));
  693. TRY(callback(pattern.end_range, "endRange"sv));
  694. return {};
  695. }
  696. // 11.5.9 PartitionDateTimeRangePattern ( dateTimeFormat, x, y ), https://tc39.es/ecma402/#sec-partitiondatetimerangepattern
  697. ThrowCompletionOr<Vector<PatternPartitionWithSource>> partition_date_time_range_pattern(VM& vm, DateTimeFormat& date_time_format, double start, double end)
  698. {
  699. // 1. Let x be TimeClip(x).
  700. start = time_clip(start);
  701. // 2. If x is NaN, throw a RangeError exception.
  702. if (isnan(start))
  703. return vm.throw_completion<RangeError>(ErrorType::IntlInvalidTime);
  704. // 3. Let y be TimeClip(y).
  705. end = time_clip(end);
  706. // 4. If y is NaN, throw a RangeError exception.
  707. if (isnan(end))
  708. return vm.throw_completion<RangeError>(ErrorType::IntlInvalidTime);
  709. // 5. Let tm1 be ToLocalTime(ℤ(ℝ(x) × 10^6), dateTimeFormat.[[Calendar]], dateTimeFormat.[[TimeZone]]).
  710. auto start_bigint = Crypto::SignedBigInteger { start }.multiplied_by(s_one_million_bigint);
  711. auto start_local_time = TRY(to_local_time(vm, start_bigint, date_time_format.calendar(), date_time_format.time_zone()));
  712. // 6. Let tm2 be ToLocalTime(ℤ(ℝ(y) × 10^6), dateTimeFormat.[[Calendar]], dateTimeFormat.[[TimeZone]]).
  713. auto end_bigint = Crypto::SignedBigInteger { end }.multiplied_by(s_one_million_bigint);
  714. auto end_local_time = TRY(to_local_time(vm, end_bigint, date_time_format.calendar(), date_time_format.time_zone()));
  715. // 7. Let rangePatterns be dateTimeFormat.[[RangePatterns]].
  716. auto range_patterns = date_time_format.range_patterns();
  717. // 8. Let rangePattern be undefined.
  718. Optional<::Locale::CalendarRangePattern> range_pattern;
  719. // 9. Let dateFieldsPracticallyEqual be true.
  720. bool date_fields_practically_equal = true;
  721. // 10. Let patternContainsLargerDateField be false.
  722. bool pattern_contains_larger_date_field = false;
  723. // 11. While dateFieldsPracticallyEqual is true and patternContainsLargerDateField is false, repeat for each row of Table 4 in order, except the header row:
  724. for_each_range_pattern_field(start_local_time, end_local_time, [&](auto start_value, auto end_value, auto field_name) {
  725. // a. Let fieldName be the name given in the Range Pattern Field column of the row.
  726. // b. If rangePatterns has a field [[<fieldName>]], let rp be rangePatterns.[[<fieldName>]]; else let rp be undefined.
  727. Optional<::Locale::CalendarRangePattern> pattern;
  728. for (auto const& range : range_patterns) {
  729. if (range.field == field_name) {
  730. pattern = range;
  731. break;
  732. }
  733. }
  734. // c. If rangePattern is not undefined and rp is undefined, then
  735. if (range_pattern.has_value() && !pattern.has_value()) {
  736. // i. Set patternContainsLargerDateField to true.
  737. pattern_contains_larger_date_field = true;
  738. }
  739. // d. Else,
  740. else {
  741. // i. Let rangePattern be rp.
  742. range_pattern = pattern;
  743. switch (field_name) {
  744. // ii. If fieldName is equal to [[AmPm]], then
  745. case ::Locale::CalendarRangePattern::Field::AmPm: {
  746. // 1. Let v1 be tm1.[[Hour]].
  747. // 2. Let v2 be tm2.[[Hour]].
  748. // 3. If v1 is greater than 11 and v2 less or equal than 11, or v1 is less or equal than 11 and v2 is greater than 11, then
  749. if ((start_value > 11 && end_value <= 11) || (start_value <= 11 && end_value > 11)) {
  750. // a. Set dateFieldsPracticallyEqual to false.
  751. date_fields_practically_equal = false;
  752. }
  753. break;
  754. }
  755. // iii. Else if fieldName is equal to [[DayPeriod]], then
  756. case ::Locale::CalendarRangePattern::Field::DayPeriod: {
  757. // 1. Let v1 be a String value representing the day period of tm1; the String value depends upon the implementation and the effective locale of dateTimeFormat.
  758. auto start_period = ::Locale::get_calendar_day_period_symbol_for_hour(date_time_format.data_locale(), date_time_format.calendar(), ::Locale::CalendarPatternStyle::Short, start_value);
  759. // 2. Let v2 be a String value representing the day period of tm2; the String value depends upon the implementation and the effective locale of dateTimeFormat.
  760. auto end_period = ::Locale::get_calendar_day_period_symbol_for_hour(date_time_format.data_locale(), date_time_format.calendar(), ::Locale::CalendarPatternStyle::Short, end_value);
  761. // 3. If v1 is not equal to v2, then
  762. if (start_period != end_period) {
  763. // a. Set dateFieldsPracticallyEqual to false.
  764. date_fields_practically_equal = false;
  765. }
  766. break;
  767. }
  768. // iv. Else if fieldName is equal to [[FractionalSecondDigits]], then
  769. case ::Locale::CalendarRangePattern::Field::FractionalSecondDigits: {
  770. // 1. Let fractionalSecondDigits be dateTimeFormat.[[FractionalSecondDigits]].
  771. Optional<u8> fractional_second_digits;
  772. if (date_time_format.has_fractional_second_digits())
  773. fractional_second_digits = date_time_format.fractional_second_digits();
  774. // 2. If fractionalSecondDigits is undefined, then
  775. if (!fractional_second_digits.has_value()) {
  776. // a. Set fractionalSecondDigits to 3.
  777. fractional_second_digits = 3;
  778. }
  779. // 3. Let v1 be tm1.[[Millisecond]].
  780. // 4. Let v2 be tm2.[[Millisecond]].
  781. // 5. Let v1 be floor(v1 × 10( fractionalSecondDigits - 3 )).
  782. start_value = floor(start_value * pow(10, static_cast<int>(*fractional_second_digits) - 3));
  783. // 6. Let v2 be floor(v2 × 10( fractionalSecondDigits - 3 )).
  784. end_value = floor(end_value * pow(10, static_cast<int>(*fractional_second_digits) - 3));
  785. // 7. If v1 is not equal to v2, then
  786. if (start_value != end_value) {
  787. // a. Set dateFieldsPracticallyEqual to false.
  788. date_fields_practically_equal = false;
  789. }
  790. break;
  791. }
  792. // v. Else,
  793. default: {
  794. // 1. Let v1 be tm1.[[<fieldName>]].
  795. // 2. Let v2 be tm2.[[<fieldName>]].
  796. // 3. If v1 is not equal to v2, then
  797. if (start_value != end_value) {
  798. // a. Set dateFieldsPracticallyEqual to false.
  799. date_fields_practically_equal = false;
  800. }
  801. break;
  802. }
  803. }
  804. }
  805. if (date_fields_practically_equal && !pattern_contains_larger_date_field)
  806. return IterationDecision::Continue;
  807. return IterationDecision::Break;
  808. });
  809. // 12. If dateFieldsPracticallyEqual is true, then
  810. if (date_fields_practically_equal) {
  811. // a. Let pattern be dateTimeFormat.[[Pattern]].
  812. auto const& pattern = date_time_format.pattern();
  813. // b. Let patternParts be PartitionPattern(pattern).
  814. auto pattern_parts = partition_pattern(pattern);
  815. // c. Let result be ? FormatDateTimePattern(dateTimeFormat, patternParts, x, undefined).
  816. auto raw_result = TRY(format_date_time_pattern(vm, date_time_format, move(pattern_parts), start, nullptr));
  817. auto result = PatternPartitionWithSource::create_from_parent_list(move(raw_result));
  818. // d. For each Record { [[Type]], [[Value]] } r in result, do
  819. for (auto& part : result) {
  820. // i. Set r.[[Source]] to "shared".
  821. part.source = "shared"sv;
  822. }
  823. // e. Return result.
  824. return result;
  825. }
  826. // 13. Let result be a new empty List.
  827. Vector<PatternPartitionWithSource> result;
  828. // 14. If rangePattern is undefined, then
  829. if (!range_pattern.has_value()) {
  830. // a. Let rangePattern be rangePatterns.[[Default]].
  831. range_pattern = ::Locale::get_calendar_default_range_format(date_time_format.data_locale(), date_time_format.calendar());
  832. // Non-standard, range_pattern will be empty if Unicode data generation is disabled.
  833. if (!range_pattern.has_value())
  834. return result;
  835. // Non-standard, LibUnicode leaves the CLDR's {0} and {1} partitions in the default patterns
  836. // to be replaced at runtime with the DateTimeFormat object's pattern.
  837. auto const& pattern = date_time_format.pattern();
  838. if (range_pattern->start_range.contains("{0}"sv)) {
  839. range_pattern->start_range = MUST(range_pattern->start_range.replace("{0}"sv, pattern, ReplaceMode::FirstOnly));
  840. range_pattern->end_range = MUST(range_pattern->end_range.replace("{1}"sv, pattern, ReplaceMode::FirstOnly));
  841. } else {
  842. range_pattern->start_range = MUST(range_pattern->start_range.replace("{1}"sv, pattern, ReplaceMode::FirstOnly));
  843. range_pattern->end_range = MUST(range_pattern->end_range.replace("{0}"sv, pattern, ReplaceMode::FirstOnly));
  844. }
  845. // FIXME: The above is not sufficient. For example, if the start date is days before the end date, and only the timeStyle
  846. // option is provided, the resulting range will not include the differing dates. We will likely need to implement
  847. // step 3 here: https://unicode.org/reports/tr35/tr35-dates.html#intervalFormats
  848. }
  849. // 15. For each Record { [[Pattern]], [[Source]] } rangePatternPart in rangePattern.[[PatternParts]], do
  850. TRY(for_each_range_pattern_with_source(*range_pattern, [&](auto const& pattern, auto source) -> ThrowCompletionOr<void> {
  851. // a. Let pattern be rangePatternPart.[[Pattern]].
  852. // b. Let source be rangePatternPart.[[Source]].
  853. // c. If source is "startRange" or "shared", then
  854. // i. Let z be x.
  855. // d. Else,
  856. // i. Let z be y.
  857. auto time = ((source == "startRange") || (source == "shared")) ? start : end;
  858. // e. Let patternParts be PartitionPattern(pattern).
  859. auto pattern_parts = partition_pattern(pattern);
  860. // f. Let partResult be ? FormatDateTimePattern(dateTimeFormat, patternParts, z, rangePattern).
  861. auto raw_part_result = TRY(format_date_time_pattern(vm, date_time_format, move(pattern_parts), time, &range_pattern.value()));
  862. auto part_result = PatternPartitionWithSource::create_from_parent_list(move(raw_part_result));
  863. // g. For each Record { [[Type]], [[Value]] } r in partResult, do
  864. for (auto& part : part_result) {
  865. // i. Set r.[[Source]] to source.
  866. part.source = source;
  867. }
  868. // h. Add all elements in partResult to result in order.
  869. result.extend(move(part_result));
  870. return {};
  871. }));
  872. // 16. Return result.
  873. return result;
  874. }
  875. // 11.5.10 FormatDateTimeRange ( dateTimeFormat, x, y ), https://tc39.es/ecma402/#sec-formatdatetimerange
  876. ThrowCompletionOr<String> format_date_time_range(VM& vm, DateTimeFormat& date_time_format, double start, double end)
  877. {
  878. // 1. Let parts be ? PartitionDateTimeRangePattern(dateTimeFormat, x, y).
  879. auto parts = TRY(partition_date_time_range_pattern(vm, date_time_format, start, end));
  880. // 2. Let result be the empty String.
  881. StringBuilder result;
  882. // 3. For each Record { [[Type]], [[Value]], [[Source]] } part in parts, do
  883. for (auto& part : parts) {
  884. // a. Set result to the string-concatenation of result and part.[[Value]].
  885. result.append(part.value);
  886. }
  887. // 4. Return result.
  888. return MUST(result.to_string());
  889. }
  890. // 11.5.11 FormatDateTimeRangeToParts ( dateTimeFormat, x, y ), https://tc39.es/ecma402/#sec-formatdatetimerangetoparts
  891. ThrowCompletionOr<NonnullGCPtr<Array>> format_date_time_range_to_parts(VM& vm, DateTimeFormat& date_time_format, double start, double end)
  892. {
  893. auto& realm = *vm.current_realm();
  894. // 1. Let parts be ? PartitionDateTimeRangePattern(dateTimeFormat, x, y).
  895. auto parts = TRY(partition_date_time_range_pattern(vm, date_time_format, start, end));
  896. // 2. Let result be ! ArrayCreate(0).
  897. auto result = MUST(Array::create(realm, 0));
  898. // 3. Let n be 0.
  899. size_t n = 0;
  900. // 4. For each Record { [[Type]], [[Value]], [[Source]] } part in parts, do
  901. for (auto& part : parts) {
  902. // a. Let O be OrdinaryObjectCreate(%ObjectPrototype%).
  903. auto object = Object::create(realm, realm.intrinsics().object_prototype());
  904. // b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]).
  905. MUST(object->create_data_property_or_throw(vm.names.type, PrimitiveString::create(vm, part.type)));
  906. // c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]).
  907. MUST(object->create_data_property_or_throw(vm.names.value, PrimitiveString::create(vm, move(part.value))));
  908. // d. Perform ! CreateDataPropertyOrThrow(O, "source", part.[[Source]]).
  909. MUST(object->create_data_property_or_throw(vm.names.source, PrimitiveString::create(vm, part.source)));
  910. // e. Perform ! CreateDataProperty(result, ! ToString(n), O).
  911. MUST(result->create_data_property_or_throw(n, object));
  912. // f. Increment n by 1.
  913. ++n;
  914. }
  915. // 5. Return result.
  916. return result;
  917. }
  918. // 11.5.12 ToLocalTime ( epochNs, calendar, timeZoneIdentifier ), https://tc39.es/ecma402/#sec-tolocaltime
  919. ThrowCompletionOr<LocalTime> to_local_time(VM& vm, Crypto::SignedBigInteger const& epoch_ns, StringView calendar, StringView time_zone_identifier)
  920. {
  921. double offset_ns { 0 };
  922. // 1. If IsTimeZoneOffsetString(timeZoneIdentifier) is true, then
  923. if (is_time_zone_offset_string(time_zone_identifier)) {
  924. // a. Let offsetNs be ParseTimeZoneOffsetString(timeZoneIdentifier).
  925. offset_ns = parse_time_zone_offset_string(time_zone_identifier);
  926. }
  927. // 2. Else,
  928. else {
  929. // a. Assert: IsValidTimeZoneName(timeZoneIdentifier) is true.
  930. VERIFY(Temporal::is_available_time_zone_name(time_zone_identifier));
  931. // b. Let offsetNs be GetNamedTimeZoneOffsetNanoseconds(timeZoneIdentifier, epochNs).
  932. offset_ns = get_named_time_zone_offset_nanoseconds(time_zone_identifier, epoch_ns);
  933. }
  934. // NOTE: Unlike the spec, we still perform the below computations with BigInts until we are ready
  935. // to divide the number by 10^6. The spec expects an MV here. If we try to use i64, we will
  936. // overflow; if we try to use a double, we lose quite a bit of accuracy.
  937. // 3. Let tz be ℝ(epochNs) + offsetNs.
  938. auto zoned_time_ns = epoch_ns.plus(Crypto::SignedBigInteger { offset_ns });
  939. // 4. If calendar is "gregory", then
  940. if (calendar == "gregory"sv) {
  941. auto zoned_time_ms = zoned_time_ns.divided_by(s_one_million_bigint).quotient;
  942. auto zoned_time = floor(zoned_time_ms.to_double(Crypto::UnsignedBigInteger::RoundingMode::ECMAScriptNumberValueFor));
  943. auto year = year_from_time(zoned_time);
  944. // a. Return a record with fields calculated from tz according to Table 8.
  945. return LocalTime {
  946. // WeekDay(𝔽(floor(tz / 10^6)))
  947. .weekday = week_day(zoned_time),
  948. // Let year be YearFromTime(𝔽(floor(tz / 10^6))). If year < 1𝔽, return "BC", else return "AD".
  949. .era = year < 1 ? ::Locale::Era::BC : ::Locale::Era::AD,
  950. // YearFromTime(𝔽(floor(tz / 10^6)))
  951. .year = year,
  952. // undefined.
  953. .related_year = js_undefined(),
  954. // undefined.
  955. .year_name = js_undefined(),
  956. // MonthFromTime(𝔽(floor(tz / 10^6)))
  957. .month = month_from_time(zoned_time),
  958. // DateFromTime(𝔽(floor(tz / 10^6)))
  959. .day = date_from_time(zoned_time),
  960. // HourFromTime(𝔽(floor(tz / 10^6)))
  961. .hour = hour_from_time(zoned_time),
  962. // MinFromTime(𝔽(floor(tz / 10^6)))
  963. .minute = min_from_time(zoned_time),
  964. // SecFromTime(𝔽(floor(tz / 10^6)))
  965. .second = sec_from_time(zoned_time),
  966. // msFromTime(𝔽(floor(tz / 10^6)))
  967. .millisecond = ms_from_time(zoned_time),
  968. };
  969. }
  970. // 5. Else,
  971. // a. Return a record with the fields of Column 1 of Table 8 calculated from tz for the given calendar. The calculations should use best available information about the specified calendar.
  972. // FIXME: Implement this when non-Gregorian calendars are supported by LibUnicode.
  973. return vm.throw_completion<InternalError>(ErrorType::NotImplemented, "Non-Gregorian calendars"sv);
  974. }
  975. }