Line data Source code
1 : // Copyright 2018 the V8 project authors. All rights reserved.
2 : // Use of this source code is governed by a BSD-style license that can be
3 : // found in the LICENSE file.
4 :
5 : #ifndef V8_INTL_SUPPORT
6 : #error Internationalization is expected to be enabled.
7 : #endif // V8_INTL_SUPPORT
8 :
9 : #include "src/objects/js-date-time-format.h"
10 :
11 : #include <memory>
12 : #include <string>
13 : #include <vector>
14 :
15 : #include "src/date.h"
16 : #include "src/heap/factory.h"
17 : #include "src/isolate.h"
18 : #include "src/objects/intl-objects.h"
19 : #include "src/objects/js-date-time-format-inl.h"
20 :
21 : #include "unicode/calendar.h"
22 : #include "unicode/dtitvfmt.h"
23 : #include "unicode/dtptngen.h"
24 : #include "unicode/fieldpos.h"
25 : #include "unicode/gregocal.h"
26 : #include "unicode/smpdtfmt.h"
27 : #include "unicode/unistr.h"
28 :
29 : namespace v8 {
30 : namespace internal {
31 :
32 : namespace {
33 :
34 98970 : class PatternMap {
35 : public:
36 11635 : PatternMap(std::string pattern, std::string value)
37 23270 : : pattern(std::move(pattern)), value(std::move(value)) {}
38 67420 : virtual ~PatternMap() = default;
39 : std::string pattern;
40 : std::string value;
41 : };
42 :
43 5310 : class PatternItem {
44 : public:
45 2655 : PatternItem(const std::string property, std::vector<PatternMap> pairs,
46 : std::vector<const char*> allowed_values)
47 : : property(std::move(property)),
48 : pairs(std::move(pairs)),
49 5310 : allowed_values(allowed_values) {}
50 7965 : virtual ~PatternItem() = default;
51 :
52 : const std::string property;
53 : // It is important for the pattern in the pairs from longer one to shorter one
54 : // if the longer one contains substring of an shorter one.
55 : std::vector<PatternMap> pairs;
56 : std::vector<const char*> allowed_values;
57 : };
58 :
59 295 : static const std::vector<PatternItem> BuildPatternItems() {
60 : const std::vector<const char*> kLongShort = {"long", "short"};
61 : const std::vector<const char*> kNarrowLongShort = {"narrow", "long", "short"};
62 : const std::vector<const char*> k2DigitNumeric = {"2-digit", "numeric"};
63 : const std::vector<const char*> kNarrowLongShort2DigitNumeric = {
64 : "narrow", "long", "short", "2-digit", "numeric"};
65 : const std::vector<PatternItem> kPatternItems = {
66 : PatternItem("weekday",
67 : {{"EEEEE", "narrow"},
68 : {"EEEE", "long"},
69 : {"EEE", "short"},
70 : {"ccccc", "narrow"},
71 : {"cccc", "long"},
72 : {"ccc", "short"}},
73 1770 : kNarrowLongShort),
74 : PatternItem("era",
75 : {{"GGGGG", "narrow"}, {"GGGG", "long"}, {"GGG", "short"}},
76 885 : kNarrowLongShort),
77 : PatternItem("year", {{"yy", "2-digit"}, {"y", "numeric"}},
78 590 : k2DigitNumeric),
79 : // Sometimes we get L instead of M for month - standalone name.
80 : PatternItem("month",
81 : {{"MMMMM", "narrow"},
82 : {"MMMM", "long"},
83 : {"MMM", "short"},
84 : {"MM", "2-digit"},
85 : {"M", "numeric"},
86 : {"LLLLL", "narrow"},
87 : {"LLLL", "long"},
88 : {"LLL", "short"},
89 : {"LL", "2-digit"},
90 : {"L", "numeric"}},
91 2950 : kNarrowLongShort2DigitNumeric),
92 590 : PatternItem("day", {{"dd", "2-digit"}, {"d", "numeric"}}, k2DigitNumeric),
93 : PatternItem("hour",
94 : {{"HH", "2-digit"},
95 : {"H", "numeric"},
96 : {"hh", "2-digit"},
97 : {"h", "numeric"},
98 : {"kk", "2-digit"},
99 : {"k", "numeric"},
100 : {"KK", "2-digit"},
101 : {"K", "numeric"}},
102 2360 : k2DigitNumeric),
103 : PatternItem("minute", {{"mm", "2-digit"}, {"m", "numeric"}},
104 590 : k2DigitNumeric),
105 : PatternItem("second", {{"ss", "2-digit"}, {"s", "numeric"}},
106 590 : k2DigitNumeric),
107 : PatternItem("timeZoneName", {{"zzzz", "long"}, {"z", "short"}},
108 35105 : kLongShort)};
109 295 : return kPatternItems;
110 : }
111 :
112 : class PatternItems {
113 : public:
114 295 : PatternItems() : data(BuildPatternItems()) {}
115 0 : virtual ~PatternItems() {}
116 : const std::vector<PatternItem>& Get() const { return data; }
117 :
118 : private:
119 : const std::vector<PatternItem> data;
120 : };
121 :
122 : static const std::vector<PatternItem>& GetPatternItems() {
123 : static base::LazyInstance<PatternItems>::type items =
124 : LAZY_INSTANCE_INITIALIZER;
125 : return items.Pointer()->Get();
126 : }
127 :
128 17280 : class PatternData {
129 : public:
130 3240 : PatternData(const std::string property, std::vector<PatternMap> pairs,
131 : std::vector<const char*> allowed_values)
132 6480 : : property(std::move(property)), allowed_values(allowed_values) {
133 14400 : for (const auto& pair : pairs) {
134 22320 : map.insert(std::make_pair(pair.value, pair.pattern));
135 : }
136 3240 : }
137 17280 : virtual ~PatternData() = default;
138 :
139 : const std::string property;
140 : std::map<const std::string, const std::string> map;
141 : std::vector<const char*> allowed_values;
142 : };
143 :
144 360 : const std::vector<PatternData> CreateCommonData(const PatternData& hour_data) {
145 : std::vector<PatternData> build;
146 3600 : for (const PatternItem& item : GetPatternItems()) {
147 6480 : if (item.property == "hour") {
148 360 : build.push_back(hour_data);
149 : } else {
150 14400 : build.push_back(
151 : PatternData(item.property, item.pairs, item.allowed_values));
152 : }
153 : }
154 360 : return build;
155 : }
156 :
157 360 : const std::vector<PatternData> CreateData(const char* digit2,
158 : const char* numeric) {
159 : return CreateCommonData(
160 : PatternData("hour", {{digit2, "2-digit"}, {numeric, "numeric"}},
161 2880 : {"2-digit", "numeric"}));
162 : }
163 :
164 : // According to "Date Field Symbol Table" in
165 : // http://userguide.icu-project.org/formatparse/datetime
166 : // Symbol | Meaning | Example(s)
167 : // h hour in am/pm (1~12) h 7
168 : // hh 07
169 : // H hour in day (0~23) H 0
170 : // HH 00
171 : // k hour in day (1~24) k 24
172 : // kk 24
173 : // K hour in am/pm (0~11) K 0
174 : // KK 00
175 :
176 : class Pattern {
177 : public:
178 360 : Pattern(const char* d1, const char* d2) : data(CreateData(d1, d2)) {}
179 0 : virtual ~Pattern() {}
180 0 : virtual const std::vector<PatternData>& Get() const { return data; }
181 :
182 : private:
183 : std::vector<PatternData> data;
184 : };
185 :
186 : #define DEFFINE_TRAIT(name, d1, d2) \
187 : struct name { \
188 : static void Construct(void* allocated_ptr) { \
189 : new (allocated_ptr) Pattern(d1, d2); \
190 : } \
191 : };
192 20 : DEFFINE_TRAIT(H11Trait, "KK", "K")
193 265 : DEFFINE_TRAIT(H12Trait, "hh", "h")
194 60 : DEFFINE_TRAIT(H23Trait, "HH", "H")
195 15 : DEFFINE_TRAIT(H24Trait, "kk", "k")
196 0 : DEFFINE_TRAIT(HDefaultTrait, "jj", "j")
197 : #undef DEFFINE_TRAIT
198 :
199 3183 : const std::vector<PatternData>& GetPatternData(Intl::HourCycle hour_cycle) {
200 3183 : switch (hour_cycle) {
201 : case Intl::HourCycle::kH11: {
202 : static base::LazyInstance<Pattern, H11Trait>::type h11 =
203 : LAZY_INSTANCE_INITIALIZER;
204 45 : return h11.Pointer()->Get();
205 : }
206 : case Intl::HourCycle::kH12: {
207 : static base::LazyInstance<Pattern, H12Trait>::type h12 =
208 : LAZY_INSTANCE_INITIALIZER;
209 2823 : return h12.Pointer()->Get();
210 : }
211 : case Intl::HourCycle::kH23: {
212 : static base::LazyInstance<Pattern, H23Trait>::type h23 =
213 : LAZY_INSTANCE_INITIALIZER;
214 279 : return h23.Pointer()->Get();
215 : }
216 : case Intl::HourCycle::kH24: {
217 : static base::LazyInstance<Pattern, H24Trait>::type h24 =
218 : LAZY_INSTANCE_INITIALIZER;
219 36 : return h24.Pointer()->Get();
220 : }
221 : case Intl::HourCycle::kUndefined: {
222 : static base::LazyInstance<Pattern, HDefaultTrait>::type hDefault =
223 : LAZY_INSTANCE_INITIALIZER;
224 0 : return hDefault.Pointer()->Get();
225 : }
226 : default:
227 0 : UNREACHABLE();
228 : }
229 : }
230 :
231 72 : std::string GetGMTTzID(Isolate* isolate, const std::string& input) {
232 72 : std::string ret = "Etc/GMT";
233 72 : switch (input.length()) {
234 : case 8:
235 9 : if (input[7] == '0') return ret + '0';
236 : break;
237 : case 9:
238 72 : if ((input[7] == '+' || input[7] == '-') &&
239 36 : IsInRange(input[8], '0', '9')) {
240 72 : return ret + input[7] + input[8];
241 : }
242 : break;
243 : case 10:
244 54 : if ((input[7] == '+' || input[7] == '-') && (input[8] == '1') &&
245 27 : IsInRange(input[9], '0', '4')) {
246 81 : return ret + input[7] + input[8] + input[9];
247 : }
248 : break;
249 : }
250 0 : return "";
251 : }
252 :
253 : // Locale independenty version of isalpha for ascii range. This will return
254 : // false if the ch is alpha but not in ascii range.
255 : bool IsAsciiAlpha(char ch) {
256 11943 : return IsInRange(ch, 'A', 'Z') || IsInRange(ch, 'a', 'z');
257 : }
258 :
259 : // Locale independent toupper for ascii range. This will not return İ (dotted I)
260 : // for i under Turkish locale while std::toupper may.
261 7623 : char LocaleIndependentAsciiToUpper(char ch) {
262 8640 : return (IsInRange(ch, 'a', 'z')) ? (ch - 'a' + 'A') : ch;
263 : }
264 :
265 : // Locale independent tolower for ascii range.
266 : char LocaleIndependentAsciiToLower(char ch) {
267 4743 : return (IsInRange(ch, 'A', 'Z')) ? (ch - 'A' + 'a') : ch;
268 : }
269 :
270 : // Returns titlecased location, bueNos_airES -> Buenos_Aires
271 : // or ho_cHi_minH -> Ho_Chi_Minh. It is locale-agnostic and only
272 : // deals with ASCII only characters.
273 : // 'of', 'au' and 'es' are special-cased and lowercased.
274 : // Also "Antarctica/DumontDUrville" is special case.
275 : // ICU's timezone parsing is case sensitive, but ECMAScript is case insensitive
276 405 : std::string ToTitleCaseTimezoneLocation(Isolate* isolate,
277 : const std::string& input) {
278 : std::string title_cased;
279 : int word_length = 0;
280 6804 : for (char ch : input) {
281 : // Convert first char to upper case, the rest to lower case
282 6453 : if (IsAsciiAlpha(ch)) {
283 11448 : title_cased += word_length == 0 ? LocaleIndependentAsciiToUpper(ch)
284 : : LocaleIndependentAsciiToLower(ch);
285 5724 : word_length++;
286 729 : } else if (ch == '_' || ch == '-' || ch == '/') {
287 : // Special case Au/Es/Of to be lower case.
288 675 : if (word_length == 2) {
289 45 : size_t pos = title_cased.length() - 2;
290 45 : std::string substr = title_cased.substr(pos, 2);
291 90 : if (substr == "Of" || substr == "Es" || substr == "Au") {
292 72 : title_cased[pos] = LocaleIndependentAsciiToLower(title_cased[pos]);
293 : }
294 : }
295 : title_cased += ch;
296 : word_length = 0;
297 : } else {
298 : // Invalid input
299 : return std::string();
300 : }
301 : }
302 : // Special case
303 351 : if (title_cased == "Antarctica/Dumontdurville") {
304 18 : return "Antarctica/DumontDUrville";
305 : }
306 : return title_cased;
307 : }
308 :
309 : } // namespace
310 :
311 567 : std::string JSDateTimeFormat::CanonicalizeTimeZoneID(Isolate* isolate,
312 : const std::string& input) {
313 : std::string upper = input;
314 : transform(upper.begin(), upper.end(), upper.begin(),
315 : LocaleIndependentAsciiToUpper);
316 2052 : if (upper == "UTC" || upper == "GMT" || upper == "ETC/UTC" ||
317 : upper == "ETC/GMT") {
318 90 : return "UTC";
319 : }
320 : // We expect only _, '-' and / beside ASCII letters.
321 : // All inputs should conform to Area/Location(/Location)*, or Etc/GMT* .
322 : // TODO(jshin): 1. Support 'GB-Eire", 'EST5EDT", "ROK', 'US/*', 'NZ' and many
323 : // other aliases/linked names when moving timezone validation code to C++.
324 : // See crbug.com/364374 and crbug.com/v8/8007 .
325 : // 2. Resolve the difference betwee CLDR/ICU and IANA time zone db.
326 : // See http://unicode.org/cldr/trac/ticket/9892 and crbug.com/645807 .
327 477 : if (strncmp(upper.c_str(), "ETC/GMT", 7) == 0) {
328 72 : return GetGMTTzID(isolate, input);
329 : }
330 405 : return ToTitleCaseTimezoneLocation(isolate, input);
331 : }
332 :
333 : namespace {
334 :
335 36 : Handle<String> DateTimeStyleAsString(Isolate* isolate,
336 : JSDateTimeFormat::DateTimeStyle style) {
337 36 : switch (style) {
338 : case JSDateTimeFormat::DateTimeStyle::kFull:
339 : return ReadOnlyRoots(isolate).full_string_handle();
340 : case JSDateTimeFormat::DateTimeStyle::kLong:
341 : return ReadOnlyRoots(isolate).long_string_handle();
342 : case JSDateTimeFormat::DateTimeStyle::kMedium:
343 : return ReadOnlyRoots(isolate).medium_string_handle();
344 : case JSDateTimeFormat::DateTimeStyle::kShort:
345 : return ReadOnlyRoots(isolate).short_string_handle();
346 : case JSDateTimeFormat::DateTimeStyle::kUndefined:
347 0 : UNREACHABLE();
348 : }
349 0 : }
350 :
351 : } // namespace
352 :
353 : // ecma402 #sec-intl.datetimeformat.prototype.resolvedoptions
354 2529 : MaybeHandle<JSObject> JSDateTimeFormat::ResolvedOptions(
355 : Isolate* isolate, Handle<JSDateTimeFormat> date_time_format) {
356 : Factory* factory = isolate->factory();
357 : // 4. Let options be ! ObjectCreate(%ObjectPrototype%).
358 2529 : Handle<JSObject> options = factory->NewJSObject(isolate->object_function());
359 :
360 : Handle<Object> resolved_obj;
361 :
362 2529 : CHECK(!date_time_format->icu_locale().is_null());
363 2529 : CHECK_NOT_NULL(date_time_format->icu_locale()->raw());
364 : icu::Locale* icu_locale = date_time_format->icu_locale()->raw();
365 2529 : Maybe<std::string> maybe_locale_str = Intl::ToLanguageTag(*icu_locale);
366 2529 : MAYBE_RETURN(maybe_locale_str, MaybeHandle<JSObject>());
367 : std::string locale_str = maybe_locale_str.FromJust();
368 : Handle<String> locale =
369 2529 : factory->NewStringFromAsciiChecked(locale_str.c_str());
370 :
371 : icu::SimpleDateFormat* icu_simple_date_format =
372 : date_time_format->icu_simple_date_format()->raw();
373 : // calendar
374 2529 : const icu::Calendar* calendar = icu_simple_date_format->getCalendar();
375 : // getType() returns legacy calendar type name instead of LDML/BCP47 calendar
376 : // key values. intl.js maps them to BCP47 values for key "ca".
377 : // TODO(jshin): Consider doing it here, instead.
378 2529 : std::string calendar_str = calendar->getType();
379 :
380 : // Maps ICU calendar names to LDML/BCP47 types for key 'ca'.
381 : // See typeMap section in third_party/icu/source/data/misc/keyTypeData.txt
382 : // and
383 : // http://www.unicode.org/repos/cldr/tags/latest/common/bcp47/calendar.xml
384 2529 : if (calendar_str == "gregorian") {
385 : calendar_str = "gregory";
386 612 : } else if (calendar_str == "ethiopic-amete-alem") {
387 : calendar_str = "ethioaa";
388 : }
389 :
390 2529 : const icu::TimeZone& tz = calendar->getTimeZone();
391 2529 : icu::UnicodeString time_zone;
392 : tz.getID(time_zone);
393 2529 : UErrorCode status = U_ZERO_ERROR;
394 2529 : icu::UnicodeString canonical_time_zone;
395 2529 : icu::TimeZone::getCanonicalID(time_zone, canonical_time_zone, status);
396 : Handle<Object> timezone_value;
397 2529 : if (U_SUCCESS(status)) {
398 : // In CLDR (http://unicode.org/cldr/trac/ticket/9943), Etc/UTC is made
399 : // a separate timezone ID from Etc/GMT even though they're still the same
400 : // timezone. We have Etc/UTC because 'UTC', 'Etc/Universal',
401 : // 'Etc/Zulu' and others are turned to 'Etc/UTC' by ICU. Etc/GMT comes
402 : // from Etc/GMT0, Etc/GMT+0, Etc/GMT-0, Etc/Greenwich.
403 : // ecma402#sec-canonicalizetimezonename step 3
404 10026 : if (canonical_time_zone == UNICODE_STRING_SIMPLE("Etc/UTC") ||
405 7407 : canonical_time_zone == UNICODE_STRING_SIMPLE("Etc/GMT")) {
406 126 : timezone_value = factory->UTC_string();
407 : } else {
408 4806 : ASSIGN_RETURN_ON_EXCEPTION(isolate, timezone_value,
409 : Intl::ToString(isolate, canonical_time_zone),
410 : JSObject);
411 : }
412 : } else {
413 : // Somehow on Windows we will reach here.
414 0 : timezone_value = factory->undefined_value();
415 : }
416 :
417 : // Ugly hack. ICU doesn't expose numbering system in any way, so we have
418 : // to assume that for given locale NumberingSystem constructor produces the
419 : // same digits as NumberFormat/Calendar would.
420 : // Tracked by https://unicode-org.atlassian.net/browse/ICU-13431
421 2529 : std::string numbering_system = Intl::GetNumberingSystem(*icu_locale);
422 :
423 2529 : icu::UnicodeString pattern_unicode;
424 2529 : icu_simple_date_format->toPattern(pattern_unicode);
425 : std::string pattern;
426 2529 : pattern_unicode.toUTF8String(pattern);
427 :
428 : // 5. For each row of Table 6, except the header row, in table order, do
429 : // Table 6: Resolved Options of DateTimeFormat Instances
430 : // Internal Slot Property
431 : // [[Locale]] "locale"
432 : // [[Calendar]] "calendar"
433 : // [[NumberingSystem]] "numberingSystem"
434 : // [[TimeZone]] "timeZone"
435 : // [[HourCycle]] "hourCycle"
436 : // "hour12"
437 : // [[Weekday]] "weekday"
438 : // [[Era]] "era"
439 : // [[Year]] "year"
440 : // [[Month]] "month"
441 : // [[Day]] "day"
442 : // [[Hour]] "hour"
443 : // [[Minute]] "minute"
444 : // [[Second]] "second"
445 : // [[TimeZoneName]] "timeZoneName"
446 5058 : CHECK(JSReceiver::CreateDataProperty(isolate, options,
447 : factory->locale_string(), locale,
448 : Just(kDontThrow))
449 : .FromJust());
450 7587 : CHECK(JSReceiver::CreateDataProperty(
451 : isolate, options, factory->calendar_string(),
452 : factory->NewStringFromAsciiChecked(calendar_str.c_str()),
453 : Just(kDontThrow))
454 : .FromJust());
455 2529 : if (!numbering_system.empty()) {
456 7587 : CHECK(JSReceiver::CreateDataProperty(
457 : isolate, options, factory->numberingSystem_string(),
458 : factory->NewStringFromAsciiChecked(numbering_system.c_str()),
459 : Just(kDontThrow))
460 : .FromJust());
461 : }
462 5058 : CHECK(JSReceiver::CreateDataProperty(isolate, options,
463 : factory->timeZone_string(),
464 : timezone_value, Just(kDontThrow))
465 : .FromJust());
466 :
467 : // 5.b.i. Let hc be dtf.[[HourCycle]].
468 : Intl::HourCycle hc = date_time_format->hour_cycle();
469 :
470 2529 : if (hc != Intl::HourCycle::kUndefined) {
471 675 : CHECK(JSReceiver::CreateDataProperty(
472 : isolate, options, factory->hourCycle_string(),
473 : date_time_format->HourCycleAsString(), Just(kDontThrow))
474 : .FromJust());
475 225 : switch (hc) {
476 : // ii. If hc is "h11" or "h12", let v be true.
477 : case Intl::HourCycle::kH11:
478 : case Intl::HourCycle::kH12:
479 324 : CHECK(JSReceiver::CreateDataProperty(
480 : isolate, options, factory->hour12_string(),
481 : factory->true_value(), Just(kDontThrow))
482 : .FromJust());
483 : break;
484 : // iii. Else if, hc is "h23" or "h24", let v be false.
485 : case Intl::HourCycle::kH23:
486 : case Intl::HourCycle::kH24:
487 126 : CHECK(JSReceiver::CreateDataProperty(
488 : isolate, options, factory->hour12_string(),
489 : factory->false_value(), Just(kDontThrow))
490 : .FromJust());
491 : break;
492 : // iv. Else, let v be undefined.
493 : case Intl::HourCycle::kUndefined:
494 : break;
495 : }
496 : }
497 :
498 : // If dateStyle and timeStyle are undefined, then internal slots
499 : // listed in "Table 1: Components of date and time formats" will be set
500 : // in Step 33.f.iii.1 of InitializeDateTimeFormat
501 5040 : if (date_time_format->date_style() == DateTimeStyle::kUndefined &&
502 : date_time_format->time_style() == DateTimeStyle::kUndefined) {
503 25020 : for (const auto& item : GetPatternItems()) {
504 96102 : for (const auto& pair : item.pairs) {
505 80505 : if (pattern.find(pair.pattern) != std::string::npos) {
506 27684 : CHECK(JSReceiver::CreateDataProperty(
507 : isolate, options,
508 : factory->NewStringFromAsciiChecked(item.property.c_str()),
509 : factory->NewStringFromAsciiChecked(pair.value.c_str()),
510 : Just(kDontThrow))
511 : .FromJust());
512 : break;
513 : }
514 : }
515 : }
516 : }
517 :
518 : // dateStyle
519 2529 : if (date_time_format->date_style() != DateTimeStyle::kUndefined) {
520 54 : CHECK(JSReceiver::CreateDataProperty(
521 : isolate, options, factory->dateStyle_string(),
522 : DateTimeStyleAsString(isolate, date_time_format->date_style()),
523 : Just(kDontThrow))
524 : .FromJust());
525 : }
526 :
527 : // timeStyle
528 2529 : if (date_time_format->time_style() != DateTimeStyle::kUndefined) {
529 54 : CHECK(JSReceiver::CreateDataProperty(
530 : isolate, options, factory->timeStyle_string(),
531 : DateTimeStyleAsString(isolate, date_time_format->time_style()),
532 : Just(kDontThrow))
533 : .FromJust());
534 : }
535 :
536 2529 : return options;
537 : }
538 :
539 : namespace {
540 :
541 : // ecma402/#sec-formatdatetime
542 : // FormatDateTime( dateTimeFormat, x )
543 873 : MaybeHandle<String> FormatDateTime(Isolate* isolate,
544 : const icu::SimpleDateFormat& date_format,
545 : double x) {
546 873 : double date_value = DateCache::TimeClip(x);
547 873 : if (std::isnan(date_value)) {
548 108 : THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
549 : String);
550 : }
551 :
552 819 : icu::UnicodeString result;
553 819 : date_format.format(date_value, result);
554 :
555 819 : return Intl::ToString(isolate, result);
556 : }
557 :
558 : } // namespace
559 :
560 : // ecma402/#sec-datetime-format-functions
561 : // DateTime Format Functions
562 576 : MaybeHandle<String> JSDateTimeFormat::DateTimeFormat(
563 : Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
564 : Handle<Object> date) {
565 : // 2. Assert: Type(dtf) is Object and dtf has an [[InitializedDateTimeFormat]]
566 : // internal slot.
567 :
568 : // 3. If date is not provided or is undefined, then
569 : double x;
570 576 : if (date->IsUndefined()) {
571 : // 3.a Let x be Call(%Date_now%, undefined).
572 27 : x = JSDate::CurrentTimeValue(isolate);
573 : } else {
574 : // 4. Else,
575 : // a. Let x be ? ToNumber(date).
576 1098 : ASSIGN_RETURN_ON_EXCEPTION(isolate, date, Object::ToNumber(isolate, date),
577 : String);
578 549 : CHECK(date->IsNumber());
579 : x = date->Number();
580 : }
581 : // 5. Return FormatDateTime(dtf, x).
582 : icu::SimpleDateFormat* format =
583 1152 : date_time_format->icu_simple_date_format()->raw();
584 576 : return FormatDateTime(isolate, *format, x);
585 : }
586 :
587 : namespace {
588 : Isolate::ICUObjectCacheType ConvertToCacheType(
589 : JSDateTimeFormat::DefaultsOption type) {
590 306 : switch (type) {
591 : case JSDateTimeFormat::DefaultsOption::kDate:
592 : return Isolate::ICUObjectCacheType::kDefaultSimpleDateFormatForDate;
593 : case JSDateTimeFormat::DefaultsOption::kTime:
594 : return Isolate::ICUObjectCacheType::kDefaultSimpleDateFormatForTime;
595 : case JSDateTimeFormat::DefaultsOption::kAll:
596 : return Isolate::ICUObjectCacheType::kDefaultSimpleDateFormat;
597 : }
598 : }
599 : } // namespace
600 :
601 306 : MaybeHandle<String> JSDateTimeFormat::ToLocaleDateTime(
602 : Isolate* isolate, Handle<Object> date, Handle<Object> locales,
603 : Handle<Object> options, RequiredOption required, DefaultsOption defaults) {
604 : Isolate::ICUObjectCacheType cache_type = ConvertToCacheType(defaults);
605 :
606 : Factory* factory = isolate->factory();
607 : // 1. Let x be ? thisTimeValue(this value);
608 306 : if (!date->IsJSDate()) {
609 0 : THROW_NEW_ERROR(isolate,
610 : NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
611 : factory->Date_string()),
612 : String);
613 : }
614 :
615 : double const x = Handle<JSDate>::cast(date)->value()->Number();
616 : // 2. If x is NaN, return "Invalid Date"
617 306 : if (std::isnan(x)) {
618 0 : return factory->Invalid_Date_string();
619 : }
620 :
621 : // We only cache the instance when both locales and options are undefined,
622 : // as that is the only case when the specified side-effects of examining
623 : // those arguments are unobservable.
624 : bool can_cache =
625 495 : locales->IsUndefined(isolate) && options->IsUndefined(isolate);
626 306 : if (can_cache) {
627 : // Both locales and options are undefined, check the cache.
628 : icu::SimpleDateFormat* cached_icu_simple_date_format =
629 : static_cast<icu::SimpleDateFormat*>(
630 162 : isolate->get_cached_icu_object(cache_type));
631 162 : if (cached_icu_simple_date_format != nullptr) {
632 102 : return FormatDateTime(isolate, *cached_icu_simple_date_format, x);
633 : }
634 : }
635 : // 3. Let options be ? ToDateTimeOptions(options, required, defaults).
636 : Handle<JSObject> internal_options;
637 408 : ASSIGN_RETURN_ON_EXCEPTION(
638 : isolate, internal_options,
639 : ToDateTimeOptions(isolate, options, required, defaults), String);
640 :
641 : // 4. Let dateFormat be ? Construct(%DateTimeFormat%, « locales, options »).
642 : Handle<JSFunction> constructor = Handle<JSFunction>(
643 : JSFunction::cast(isolate->context()
644 390 : ->native_context()
645 390 : ->intl_date_time_format_function()),
646 : isolate);
647 : Handle<JSObject> obj;
648 390 : ASSIGN_RETURN_ON_EXCEPTION(
649 : isolate, obj,
650 : JSObject::New(constructor, constructor, Handle<AllocationSite>::null()),
651 : String);
652 : Handle<JSDateTimeFormat> date_time_format;
653 390 : ASSIGN_RETURN_ON_EXCEPTION(
654 : isolate, date_time_format,
655 : JSDateTimeFormat::Initialize(isolate, Handle<JSDateTimeFormat>::cast(obj),
656 : locales, internal_options),
657 : String);
658 :
659 195 : if (can_cache) {
660 : isolate->set_icu_object_in_cache(
661 60 : cache_type, std::static_pointer_cast<icu::UObject>(
662 60 : date_time_format->icu_simple_date_format()->get()));
663 : }
664 : // 5. Return FormatDateTime(dateFormat, x).
665 : icu::SimpleDateFormat* format =
666 : date_time_format->icu_simple_date_format()->raw();
667 195 : return FormatDateTime(isolate, *format, x);
668 : }
669 :
670 : namespace {
671 :
672 27745 : Maybe<bool> IsPropertyUndefined(Isolate* isolate, Handle<JSObject> options,
673 : const char* property) {
674 : Factory* factory = isolate->factory();
675 : // i. Let prop be the property name.
676 : // ii. Let value be ? Get(options, prop).
677 : Handle<Object> value;
678 83235 : ASSIGN_RETURN_ON_EXCEPTION_VALUE(
679 : isolate, value,
680 : Object::GetPropertyOrElement(
681 : isolate, options, factory->NewStringFromAsciiChecked(property)),
682 : Nothing<bool>());
683 : return Just(value->IsUndefined(isolate));
684 : }
685 :
686 7922 : Maybe<bool> NeedsDefault(Isolate* isolate, Handle<JSObject> options,
687 : const std::vector<std::string>& props) {
688 : bool needs_default = true;
689 35667 : for (const auto& prop : props) {
690 : // i. Let prop be the property name.
691 : // ii. Let value be ? Get(options, prop)
692 : Maybe<bool> maybe_undefined =
693 27745 : IsPropertyUndefined(isolate, options, prop.c_str());
694 27745 : MAYBE_RETURN(maybe_undefined, Nothing<bool>());
695 : // iii. If value is not undefined, let needDefaults be false.
696 27745 : if (!maybe_undefined.FromJust()) {
697 : needs_default = false;
698 : }
699 : }
700 : return Just(needs_default);
701 : }
702 :
703 3194 : Maybe<bool> CreateDefault(Isolate* isolate, Handle<JSObject> options,
704 : const std::vector<std::string>& props) {
705 : Factory* factory = isolate->factory();
706 : // i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric").
707 12776 : for (const auto& prop : props) {
708 28746 : MAYBE_RETURN(
709 : JSReceiver::CreateDataProperty(
710 : isolate, options, factory->NewStringFromAsciiChecked(prop.c_str()),
711 : factory->numeric_string(), Just(kThrowOnError)),
712 : Nothing<bool>());
713 : }
714 : return Just(true);
715 : }
716 :
717 : } // namespace
718 :
719 : // ecma-402/#sec-todatetimeoptions
720 4035 : MaybeHandle<JSObject> JSDateTimeFormat::ToDateTimeOptions(
721 : Isolate* isolate, Handle<Object> input_options, RequiredOption required,
722 : DefaultsOption defaults) {
723 : Factory* factory = isolate->factory();
724 : // 1. If options is undefined, let options be null; otherwise let options be ?
725 : // ToObject(options).
726 : Handle<JSObject> options;
727 4035 : if (input_options->IsUndefined(isolate)) {
728 1950 : options = factory->NewJSObjectWithNullProto();
729 : } else {
730 : Handle<JSReceiver> options_obj;
731 4170 : ASSIGN_RETURN_ON_EXCEPTION(isolate, options_obj,
732 : Object::ToObject(isolate, input_options),
733 : JSObject);
734 : // 2. Let options be ObjectCreate(options).
735 4152 : ASSIGN_RETURN_ON_EXCEPTION(isolate, options,
736 : JSObject::ObjectCreate(isolate, options_obj),
737 : JSObject);
738 : }
739 :
740 : // 3. Let needDefaults be true.
741 : bool needs_default = true;
742 :
743 : // 4. If required is "date" or "any", then
744 4026 : if (required == RequiredOption::kAny || required == RequiredOption::kDate) {
745 : // a. For each of the property names "weekday", "year", "month", "day", do
746 11937 : const std::vector<std::string> list({"weekday", "year", "month", "day"});
747 3979 : Maybe<bool> maybe_needs_default = NeedsDefault(isolate, options, list);
748 3979 : MAYBE_RETURN(maybe_needs_default, Handle<JSObject>());
749 : needs_default = maybe_needs_default.FromJust();
750 : }
751 :
752 : // 5. If required is "time" or "any", then
753 4026 : if (required == RequiredOption::kAny || required == RequiredOption::kTime) {
754 : // a. For each of the property names "hour", "minute", "second", do
755 11829 : const std::vector<std::string> list({"hour", "minute", "second"});
756 3943 : Maybe<bool> maybe_needs_default = NeedsDefault(isolate, options, list);
757 3943 : MAYBE_RETURN(maybe_needs_default, Handle<JSObject>());
758 : needs_default &= maybe_needs_default.FromJust();
759 : }
760 :
761 : // 6. If needDefaults is true and defaults is either "date" or "all", then
762 4026 : if (needs_default) {
763 3138 : if (defaults == DefaultsOption::kAll || defaults == DefaultsOption::kDate) {
764 : // a. For each of the property names "year", "month", "day", do)
765 9300 : const std::vector<std::string> list({"year", "month", "day"});
766 6200 : MAYBE_RETURN(CreateDefault(isolate, options, list), Handle<JSObject>());
767 : }
768 : // 7. If needDefaults is true and defaults is either "time" or "all", then
769 3138 : if (defaults == DefaultsOption::kAll || defaults == DefaultsOption::kTime) {
770 : // a. For each of the property names "hour", "minute", "second", do
771 282 : const std::vector<std::string> list({"hour", "minute", "second"});
772 188 : MAYBE_RETURN(CreateDefault(isolate, options, list), Handle<JSObject>());
773 : }
774 : }
775 : // 8. Return options.
776 4026 : return options;
777 : }
778 :
779 3168 : MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::UnwrapDateTimeFormat(
780 : Isolate* isolate, Handle<JSReceiver> format_holder) {
781 : Handle<Context> native_context =
782 : Handle<Context>(isolate->context()->native_context(), isolate);
783 : Handle<JSFunction> constructor = Handle<JSFunction>(
784 6336 : JSFunction::cast(native_context->intl_date_time_format_function()),
785 : isolate);
786 : Handle<Object> dtf;
787 6336 : ASSIGN_RETURN_ON_EXCEPTION(
788 : isolate, dtf,
789 : Intl::LegacyUnwrapReceiver(isolate, format_holder, constructor,
790 : format_holder->IsJSDateTimeFormat()),
791 : JSDateTimeFormat);
792 : // 2. If Type(dtf) is not Object or dtf does not have an
793 : // [[InitializedDateTimeFormat]] internal slot, then
794 3168 : if (!dtf->IsJSDateTimeFormat()) {
795 : // a. Throw a TypeError exception.
796 135 : THROW_NEW_ERROR(isolate,
797 : NewTypeError(MessageTemplate::kIncompatibleMethodReceiver,
798 : isolate->factory()->NewStringFromAsciiChecked(
799 : "UnwrapDateTimeFormat"),
800 : format_holder),
801 : JSDateTimeFormat);
802 : }
803 : // 3. Return dtf.
804 3123 : return Handle<JSDateTimeFormat>::cast(dtf);
805 : }
806 :
807 : namespace {
808 :
809 : // ecma-402/#sec-isvalidtimezonename
810 513 : bool IsValidTimeZoneName(const icu::TimeZone& tz) {
811 513 : UErrorCode status = U_ZERO_ERROR;
812 513 : icu::UnicodeString id;
813 : tz.getID(id);
814 513 : icu::UnicodeString canonical;
815 513 : icu::TimeZone::getCanonicalID(id, canonical, status);
816 1026 : return U_SUCCESS(status) &&
817 1539 : canonical != icu::UnicodeString("Etc/Unknown", -1, US_INV);
818 : }
819 :
820 3813 : std::unique_ptr<icu::TimeZone> CreateTimeZone(Isolate* isolate,
821 : const char* timezone) {
822 : // Create time zone as specified by the user. We have to re-create time zone
823 : // since calendar takes ownership.
824 3813 : if (timezone == nullptr) {
825 : // 19.a. Else / Let timeZone be DefaultTimeZone().
826 3246 : return std::unique_ptr<icu::TimeZone>(icu::TimeZone::createDefault());
827 : }
828 : std::string canonicalized =
829 1134 : JSDateTimeFormat::CanonicalizeTimeZoneID(isolate, timezone);
830 567 : if (canonicalized.empty()) return std::unique_ptr<icu::TimeZone>();
831 : std::unique_ptr<icu::TimeZone> tz(
832 513 : icu::TimeZone::createTimeZone(canonicalized.c_str()));
833 : // 18.b If the result of IsValidTimeZoneName(timeZone) is false, then
834 : // i. Throw a RangeError exception.
835 513 : if (!IsValidTimeZoneName(*tz)) return std::unique_ptr<icu::TimeZone>();
836 : return tz;
837 : }
838 :
839 310 : class CalendarCache {
840 : public:
841 3669 : icu::Calendar* CreateCalendar(const icu::Locale& locale, icu::TimeZone* tz) {
842 3669 : icu::UnicodeString tz_id;
843 : tz->getID(tz_id);
844 : std::string key;
845 3669 : tz_id.toUTF8String<std::string>(key);
846 : key += ":";
847 : key += locale.getName();
848 :
849 3669 : base::MutexGuard guard(&mutex_);
850 : auto it = map_.find(key);
851 3669 : if (it != map_.end()) {
852 2124 : delete tz;
853 2124 : return it->second->clone();
854 : }
855 : // Create a calendar using locale, and apply time zone to it.
856 1545 : UErrorCode status = U_ZERO_ERROR;
857 : std::unique_ptr<icu::Calendar> calendar(
858 1545 : icu::Calendar::createInstance(tz, locale, status));
859 1545 : CHECK(U_SUCCESS(status));
860 1545 : CHECK_NOT_NULL(calendar.get());
861 :
862 3090 : if (calendar->getDynamicClassID() ==
863 1545 : icu::GregorianCalendar::getStaticClassID()) {
864 : icu::GregorianCalendar* gc =
865 : static_cast<icu::GregorianCalendar*>(calendar.get());
866 1090 : UErrorCode status = U_ZERO_ERROR;
867 : // The beginning of ECMAScript time, namely -(2**53)
868 : const double start_of_time = -9007199254740992;
869 1090 : gc->setGregorianChange(start_of_time, status);
870 : DCHECK(U_SUCCESS(status));
871 : }
872 :
873 1545 : if (map_.size() > 8) { // Cache at most 8 calendars.
874 : map_.clear();
875 : }
876 1545 : map_[key].reset(calendar.release());
877 3090 : return map_[key]->clone();
878 : }
879 :
880 : private:
881 : std::map<std::string, std::unique_ptr<icu::Calendar>> map_;
882 : base::Mutex mutex_;
883 : };
884 :
885 3669 : icu::Calendar* CreateCalendar(Isolate* isolate, const icu::Locale& icu_locale,
886 : icu::TimeZone* tz) {
887 : static base::LazyInstance<CalendarCache>::type calendar_cache =
888 : LAZY_INSTANCE_INITIALIZER;
889 3669 : return calendar_cache.Pointer()->CreateCalendar(icu_locale, tz);
890 : }
891 :
892 1678 : std::unique_ptr<icu::SimpleDateFormat> CreateICUDateFormat(
893 : const icu::Locale& icu_locale, const icu::UnicodeString& skeleton,
894 : icu::DateTimePatternGenerator& generator) {
895 : // See https://github.com/tc39/ecma402/issues/225 . The best pattern
896 : // generation needs to be done in the base locale according to the
897 : // current spec however odd it may be. See also crbug.com/826549 .
898 : // This is a temporary work-around to get v8's external behavior to match
899 : // the current spec, but does not follow the spec provisions mentioned
900 : // in the above Ecma 402 issue.
901 : // TODO(jshin): The spec may need to be revised because using the base
902 : // locale for the pattern match is not quite right. Moreover, what to
903 : // do with 'related year' part when 'chinese/dangi' calendar is specified
904 : // has to be discussed. Revisit once the spec is clarified/revised.
905 1678 : icu::UnicodeString pattern;
906 1678 : UErrorCode status = U_ZERO_ERROR;
907 3356 : pattern = generator.getBestPattern(skeleton, UDATPG_MATCH_HOUR_FIELD_LENGTH,
908 1678 : status);
909 1678 : CHECK(U_SUCCESS(status));
910 :
911 : // Make formatter from skeleton. Calendar and numbering system are added
912 : // to the locale as Unicode extension (if they were specified at all).
913 1678 : status = U_ZERO_ERROR;
914 : std::unique_ptr<icu::SimpleDateFormat> date_format(
915 1678 : new icu::SimpleDateFormat(pattern, icu_locale, status));
916 1678 : if (U_FAILURE(status)) return std::unique_ptr<icu::SimpleDateFormat>();
917 :
918 1669 : CHECK_NOT_NULL(date_format.get());
919 : return date_format;
920 : }
921 :
922 295 : class DateFormatCache {
923 : public:
924 3192 : icu::SimpleDateFormat* Create(const icu::Locale& icu_locale,
925 : const icu::UnicodeString& skeleton,
926 : icu::DateTimePatternGenerator& generator) {
927 : std::string key;
928 3192 : skeleton.toUTF8String<std::string>(key);
929 : key += ":";
930 : key += icu_locale.getName();
931 :
932 3192 : base::MutexGuard guard(&mutex_);
933 : auto it = map_.find(key);
934 3192 : if (it != map_.end()) {
935 1514 : return static_cast<icu::SimpleDateFormat*>(it->second->clone());
936 : }
937 :
938 1678 : if (map_.size() > 8) { // Cache at most 8 DateFormats.
939 : map_.clear();
940 : }
941 : std::unique_ptr<icu::SimpleDateFormat> instance(
942 1678 : CreateICUDateFormat(icu_locale, skeleton, generator));
943 1678 : if (instance.get() == nullptr) return nullptr;
944 1669 : map_[key] = std::move(instance);
945 3338 : return static_cast<icu::SimpleDateFormat*>(map_[key]->clone());
946 : }
947 :
948 : private:
949 : std::map<std::string, std::unique_ptr<icu::SimpleDateFormat>> map_;
950 : base::Mutex mutex_;
951 : };
952 :
953 3192 : std::unique_ptr<icu::SimpleDateFormat> CreateICUDateFormatFromCache(
954 : const icu::Locale& icu_locale, const icu::UnicodeString& skeleton,
955 : icu::DateTimePatternGenerator& generator) {
956 : static base::LazyInstance<DateFormatCache>::type cache =
957 : LAZY_INSTANCE_INITIALIZER;
958 : return std::unique_ptr<icu::SimpleDateFormat>(
959 6384 : cache.Pointer()->Create(icu_locale, skeleton, generator));
960 : }
961 :
962 27 : std::unique_ptr<icu::DateIntervalFormat> CreateICUDateIntervalFormat(
963 : const icu::Locale& icu_locale, const icu::UnicodeString& skeleton) {
964 27 : UErrorCode status = U_ZERO_ERROR;
965 : std::unique_ptr<icu::DateIntervalFormat> date_interval_format(
966 27 : icu::DateIntervalFormat::createInstance(skeleton, icu_locale, status));
967 27 : if (U_FAILURE(status)) return std::unique_ptr<icu::DateIntervalFormat>();
968 27 : CHECK_NOT_NULL(date_interval_format.get());
969 : return date_interval_format;
970 : }
971 :
972 3885 : Intl::HourCycle HourCycleFromPattern(const icu::UnicodeString pattern) {
973 : bool in_quote = false;
974 8763 : for (int32_t i = 0; i < pattern.length(); i++) {
975 : char16_t ch = pattern[i];
976 6324 : switch (ch) {
977 : case '\'':
978 180 : in_quote = !in_quote;
979 180 : break;
980 : case 'K':
981 0 : if (!in_quote) return Intl::HourCycle::kH11;
982 : break;
983 : case 'h':
984 3615 : if (!in_quote) return Intl::HourCycle::kH12;
985 : break;
986 : case 'H':
987 270 : if (!in_quote) return Intl::HourCycle::kH23;
988 : break;
989 : case 'k':
990 0 : if (!in_quote) return Intl::HourCycle::kH24;
991 : break;
992 : }
993 : }
994 : return Intl::HourCycle::kUndefined;
995 : }
996 :
997 432 : icu::DateFormat::EStyle DateTimeStyleToEStyle(
998 : JSDateTimeFormat::DateTimeStyle style) {
999 432 : switch (style) {
1000 : case JSDateTimeFormat::DateTimeStyle::kFull:
1001 : return icu::DateFormat::EStyle::kFull;
1002 : case JSDateTimeFormat::DateTimeStyle::kLong:
1003 90 : return icu::DateFormat::EStyle::kLong;
1004 : case JSDateTimeFormat::DateTimeStyle::kMedium:
1005 90 : return icu::DateFormat::EStyle::kMedium;
1006 : case JSDateTimeFormat::DateTimeStyle::kShort:
1007 90 : return icu::DateFormat::EStyle::kShort;
1008 : case JSDateTimeFormat::DateTimeStyle::kUndefined:
1009 0 : UNREACHABLE();
1010 : }
1011 0 : }
1012 :
1013 0 : icu::UnicodeString ReplaceSkeleton(const icu::UnicodeString input,
1014 : Intl::HourCycle hc) {
1015 : icu::UnicodeString result;
1016 : char16_t to;
1017 0 : switch (hc) {
1018 : case Intl::HourCycle::kH11:
1019 : to = 'K';
1020 0 : break;
1021 : case Intl::HourCycle::kH12:
1022 : to = 'h';
1023 0 : break;
1024 : case Intl::HourCycle::kH23:
1025 : to = 'H';
1026 0 : break;
1027 : case Intl::HourCycle::kH24:
1028 : to = 'k';
1029 0 : break;
1030 : case Intl::HourCycle::kUndefined:
1031 0 : UNREACHABLE();
1032 : }
1033 0 : for (int32_t i = 0; i < input.length(); i++) {
1034 : switch (input[i]) {
1035 : // We need to skip 'a', 'b', 'B' here due to
1036 : // https://unicode-org.atlassian.net/browse/ICU-20437
1037 : case 'a':
1038 : V8_FALLTHROUGH;
1039 : case 'b':
1040 : V8_FALLTHROUGH;
1041 : case 'B':
1042 : // ignore
1043 : break;
1044 : case 'h':
1045 : V8_FALLTHROUGH;
1046 : case 'H':
1047 : V8_FALLTHROUGH;
1048 : case 'K':
1049 : V8_FALLTHROUGH;
1050 : case 'k':
1051 : result += to;
1052 : break;
1053 : default:
1054 : result += input[i];
1055 : break;
1056 : }
1057 : }
1058 0 : return result;
1059 : }
1060 :
1061 270 : std::unique_ptr<icu::SimpleDateFormat> DateTimeStylePattern(
1062 : JSDateTimeFormat::DateTimeStyle date_style,
1063 : JSDateTimeFormat::DateTimeStyle time_style, const icu::Locale& icu_locale,
1064 : Intl::HourCycle hc, icu::DateTimePatternGenerator& generator) {
1065 : std::unique_ptr<icu::SimpleDateFormat> result;
1066 270 : if (date_style != JSDateTimeFormat::DateTimeStyle::kUndefined) {
1067 216 : if (time_style != JSDateTimeFormat::DateTimeStyle::kUndefined) {
1068 : result.reset(reinterpret_cast<icu::SimpleDateFormat*>(
1069 162 : icu::DateFormat::createDateTimeInstance(
1070 : DateTimeStyleToEStyle(date_style),
1071 : DateTimeStyleToEStyle(time_style), icu_locale)));
1072 : } else {
1073 : result.reset(reinterpret_cast<icu::SimpleDateFormat*>(
1074 54 : icu::DateFormat::createDateInstance(DateTimeStyleToEStyle(date_style),
1075 : icu_locale)));
1076 : // For instance without time, we do not need to worry about the hour cycle
1077 : // impact so we can return directly.
1078 : return result;
1079 : }
1080 : } else {
1081 54 : if (time_style != JSDateTimeFormat::DateTimeStyle::kUndefined) {
1082 : result.reset(reinterpret_cast<icu::SimpleDateFormat*>(
1083 54 : icu::DateFormat::createTimeInstance(DateTimeStyleToEStyle(time_style),
1084 : icu_locale)));
1085 : } else {
1086 0 : UNREACHABLE();
1087 : }
1088 : }
1089 216 : icu::UnicodeString pattern;
1090 216 : pattern = result->toPattern(pattern);
1091 :
1092 216 : UErrorCode status = U_ZERO_ERROR;
1093 : icu::UnicodeString skeleton =
1094 432 : icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status);
1095 216 : CHECK(U_SUCCESS(status));
1096 :
1097 : // If the skeleton match the HourCycle, we just return it.
1098 216 : if (hc == HourCycleFromPattern(pattern)) {
1099 : return result;
1100 : }
1101 :
1102 0 : return CreateICUDateFormatFromCache(icu_locale, ReplaceSkeleton(skeleton, hc),
1103 0 : generator);
1104 : }
1105 :
1106 0 : icu::UnicodeString SkeletonFromDateFormat(
1107 : const icu::SimpleDateFormat& icu_date_format) {
1108 0 : icu::UnicodeString pattern;
1109 0 : pattern = icu_date_format.toPattern(pattern);
1110 :
1111 0 : UErrorCode status = U_ZERO_ERROR;
1112 : icu::UnicodeString skeleton =
1113 0 : icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status);
1114 0 : CHECK(U_SUCCESS(status));
1115 0 : return skeleton;
1116 : }
1117 :
1118 310 : class DateTimePatternGeneratorCache {
1119 : public:
1120 : // Return a clone copy that the caller have to free.
1121 3669 : icu::DateTimePatternGenerator* CreateGenerator(const icu::Locale& locale) {
1122 3669 : std::string key(locale.getBaseName());
1123 3669 : base::MutexGuard guard(&mutex_);
1124 : auto it = map_.find(key);
1125 3669 : if (it != map_.end()) {
1126 3254 : return it->second->clone();
1127 : }
1128 415 : UErrorCode status = U_ZERO_ERROR;
1129 830 : map_[key].reset(icu::DateTimePatternGenerator::createInstance(
1130 : icu::Locale(key.c_str()), status));
1131 415 : CHECK(U_SUCCESS(status));
1132 830 : return map_[key]->clone();
1133 : }
1134 :
1135 : private:
1136 : std::map<std::string, std::unique_ptr<icu::DateTimePatternGenerator>> map_;
1137 : base::Mutex mutex_;
1138 : };
1139 :
1140 : } // namespace
1141 :
1142 : enum FormatMatcherOption { kBestFit, kBasic };
1143 :
1144 : // ecma402/#sec-initializedatetimeformat
1145 3840 : MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::Initialize(
1146 : Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
1147 : Handle<Object> locales, Handle<Object> input_options) {
1148 : date_time_format->set_flags(0);
1149 : // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
1150 : Maybe<std::vector<std::string>> maybe_requested_locales =
1151 3840 : Intl::CanonicalizeLocaleList(isolate, locales);
1152 3840 : MAYBE_RETURN(maybe_requested_locales, Handle<JSDateTimeFormat>());
1153 : std::vector<std::string> requested_locales =
1154 3831 : maybe_requested_locales.FromJust();
1155 : // 2. Let options be ? ToDateTimeOptions(options, "any", "date").
1156 : Handle<JSObject> options;
1157 7662 : ASSIGN_RETURN_ON_EXCEPTION(
1158 : isolate, options,
1159 : JSDateTimeFormat::ToDateTimeOptions(
1160 : isolate, input_options, RequiredOption::kAny, DefaultsOption::kDate),
1161 : JSDateTimeFormat);
1162 :
1163 : // 4. Let matcher be ? GetOption(options, "localeMatcher", "string",
1164 : // « "lookup", "best fit" », "best fit").
1165 : // 5. Set opt.[[localeMatcher]] to matcher.
1166 : Maybe<Intl::MatcherOption> maybe_locale_matcher =
1167 3831 : Intl::GetLocaleMatcher(isolate, options, "Intl.DateTimeFormat");
1168 3831 : MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSDateTimeFormat>());
1169 : Intl::MatcherOption locale_matcher = maybe_locale_matcher.FromJust();
1170 :
1171 : // 6. Let hour12 be ? GetOption(options, "hour12", "boolean", undefined,
1172 : // undefined).
1173 : bool hour12;
1174 : Maybe<bool> maybe_get_hour12 = Intl::GetBoolOption(
1175 3831 : isolate, options, "hour12", "Intl.DateTimeFormat", &hour12);
1176 3831 : MAYBE_RETURN(maybe_get_hour12, Handle<JSDateTimeFormat>());
1177 :
1178 : // 7. Let hourCycle be ? GetOption(options, "hourCycle", "string", « "h11",
1179 : // "h12", "h23", "h24" », undefined).
1180 : Maybe<Intl::HourCycle> maybe_hour_cycle =
1181 3831 : Intl::GetHourCycle(isolate, options, "Intl.DateTimeFormat");
1182 3831 : MAYBE_RETURN(maybe_hour_cycle, MaybeHandle<JSDateTimeFormat>());
1183 : Intl::HourCycle hour_cycle = maybe_hour_cycle.FromJust();
1184 :
1185 : // 8. If hour12 is not undefined, then
1186 3831 : if (maybe_get_hour12.FromJust()) {
1187 : // a. Let hourCycle be null.
1188 : hour_cycle = Intl::HourCycle::kUndefined;
1189 : }
1190 : // 9. Set opt.[[hc]] to hourCycle.
1191 :
1192 : // ecma402/#sec-intl.datetimeformat-internal-slots
1193 : // The value of the [[RelevantExtensionKeys]] internal slot is
1194 : // « "ca", "nu", "hc" ».
1195 7662 : std::set<std::string> relevant_extension_keys = {"nu", "ca", "hc"};
1196 :
1197 : // 10. Let localeData be %DateTimeFormat%.[[LocaleData]].
1198 : // 11. Let r be ResolveLocale( %DateTimeFormat%.[[AvailableLocales]],
1199 : // requestedLocales, opt, %DateTimeFormat%.[[RelevantExtensionKeys]],
1200 : // localeData).
1201 : //
1202 : Intl::ResolvedLocale r = Intl::ResolveLocale(
1203 : isolate, JSDateTimeFormat::GetAvailableLocales(), requested_locales,
1204 7662 : locale_matcher, relevant_extension_keys);
1205 :
1206 7662 : icu::Locale icu_locale = r.icu_locale;
1207 : DCHECK(!icu_locale.isBogus());
1208 :
1209 : // 17. Let timeZone be ? Get(options, "timeZone").
1210 : const std::vector<const char*> empty_values;
1211 : std::unique_ptr<char[]> timezone = nullptr;
1212 : Maybe<bool> maybe_timezone =
1213 : Intl::GetStringOption(isolate, options, "timeZone", empty_values,
1214 11493 : "Intl.DateTimeFormat", &timezone);
1215 3831 : MAYBE_RETURN(maybe_timezone, Handle<JSDateTimeFormat>());
1216 :
1217 3813 : std::unique_ptr<icu::TimeZone> tz = CreateTimeZone(isolate, timezone.get());
1218 3813 : if (tz.get() == nullptr) {
1219 432 : THROW_NEW_ERROR(isolate,
1220 : NewRangeError(MessageTemplate::kInvalidTimeZone,
1221 : isolate->factory()->NewStringFromAsciiChecked(
1222 : timezone.get())),
1223 : JSDateTimeFormat);
1224 : }
1225 :
1226 : std::unique_ptr<icu::Calendar> calendar(
1227 3669 : CreateCalendar(isolate, icu_locale, tz.release()));
1228 :
1229 : // 18.b If the result of IsValidTimeZoneName(timeZone) is false, then
1230 : // i. Throw a RangeError exception.
1231 3669 : if (calendar.get() == nullptr) {
1232 0 : THROW_NEW_ERROR(isolate,
1233 : NewRangeError(MessageTemplate::kInvalidTimeZone,
1234 : isolate->factory()->NewStringFromAsciiChecked(
1235 : timezone.get())),
1236 : JSDateTimeFormat);
1237 : }
1238 :
1239 : static base::LazyInstance<DateTimePatternGeneratorCache>::type
1240 : generator_cache = LAZY_INSTANCE_INITIALIZER;
1241 :
1242 : std::unique_ptr<icu::DateTimePatternGenerator> generator(
1243 3669 : generator_cache.Pointer()->CreateGenerator(icu_locale));
1244 :
1245 : // 15.Let hcDefault be dataLocaleData.[[hourCycle]].
1246 3669 : UErrorCode status = U_ZERO_ERROR;
1247 7338 : icu::UnicodeString hour_pattern = generator->getBestPattern("jjmm", status);
1248 3669 : CHECK(U_SUCCESS(status));
1249 3669 : Intl::HourCycle hc_default = HourCycleFromPattern(hour_pattern);
1250 :
1251 : // 16.Let hc be r.[[hc]].
1252 : Intl::HourCycle hc = Intl::HourCycle::kUndefined;
1253 3669 : if (hour_cycle == Intl::HourCycle::kUndefined) {
1254 7338 : auto hc_extension_it = r.extensions.find("hc");
1255 3669 : if (hc_extension_it != r.extensions.end()) {
1256 216 : hc = Intl::ToHourCycle(hc_extension_it->second.c_str());
1257 : }
1258 : } else {
1259 : hc = hour_cycle;
1260 : }
1261 : // 17. If hc is null, then
1262 3669 : if (hc == Intl::HourCycle::kUndefined) {
1263 : // a. Set hc to hcDefault.
1264 : hc = hc_default;
1265 : }
1266 :
1267 : // 18. If hour12 is not undefined, then
1268 3669 : if (maybe_get_hour12.FromJust()) {
1269 : // a. If hour12 is true, then
1270 36 : if (hour12) {
1271 : // i. If hcDefault is "h11" or "h23", then
1272 36 : if (hc_default == Intl::HourCycle::kH11 ||
1273 18 : hc_default == Intl::HourCycle::kH23) {
1274 : // 1. Set hc to "h11".
1275 : hc = Intl::HourCycle::kH11;
1276 : // ii. Else,
1277 : } else {
1278 : // 1. Set hc to "h12".
1279 : hc = Intl::HourCycle::kH12;
1280 : }
1281 : // b. Else,
1282 : } else {
1283 : // ii. If hcDefault is "h11" or "h23", then
1284 36 : if (hc_default == Intl::HourCycle::kH11 ||
1285 18 : hc_default == Intl::HourCycle::kH23) {
1286 : // 1. Set hc to "h23".
1287 : hc = Intl::HourCycle::kH23;
1288 : // iii. Else,
1289 : } else {
1290 : // 1. Set hc to "h24".
1291 : hc = Intl::HourCycle::kH24;
1292 : }
1293 : }
1294 : }
1295 3669 : date_time_format->set_hour_cycle(hc);
1296 :
1297 : DateTimeStyle date_style = DateTimeStyle::kUndefined;
1298 : DateTimeStyle time_style = DateTimeStyle::kUndefined;
1299 : std::unique_ptr<icu::SimpleDateFormat> icu_date_format;
1300 : std::unique_ptr<icu::DateIntervalFormat> icu_date_interval_format;
1301 :
1302 3669 : if (FLAG_harmony_intl_datetime_style) {
1303 : // 28. Let dateStyle be ? GetOption(options, "dateStyle", "string", «
1304 : // "full", "long", "medium", "short" », undefined).
1305 : Maybe<DateTimeStyle> maybe_date_style =
1306 : Intl::GetStringOption<DateTimeStyle>(
1307 : isolate, options, "dateStyle", "Intl.DateTimeFormat",
1308 : {"full", "long", "medium", "short"},
1309 : {DateTimeStyle::kFull, DateTimeStyle::kLong, DateTimeStyle::kMedium,
1310 : DateTimeStyle::kShort},
1311 1593 : DateTimeStyle::kUndefined);
1312 531 : MAYBE_RETURN(maybe_date_style, MaybeHandle<JSDateTimeFormat>());
1313 : // 29. If dateStyle is not undefined, set dateTimeFormat.[[DateStyle]] to
1314 : // dateStyle.
1315 : date_style = maybe_date_style.FromJust();
1316 405 : if (date_style != DateTimeStyle::kUndefined) {
1317 288 : date_time_format->set_date_style(date_style);
1318 : }
1319 :
1320 : // 30. Let timeStyle be ? GetOption(options, "timeStyle", "string", «
1321 : // "full", "long", "medium", "short" »).
1322 : Maybe<DateTimeStyle> maybe_time_style =
1323 : Intl::GetStringOption<DateTimeStyle>(
1324 : isolate, options, "timeStyle", "Intl.DateTimeFormat",
1325 : {"full", "long", "medium", "short"},
1326 : {DateTimeStyle::kFull, DateTimeStyle::kLong, DateTimeStyle::kMedium,
1327 : DateTimeStyle::kShort},
1328 1215 : DateTimeStyle::kUndefined);
1329 405 : MAYBE_RETURN(maybe_time_style, MaybeHandle<JSDateTimeFormat>());
1330 :
1331 : // 31. If timeStyle is not undefined, set dateTimeFormat.[[TimeStyle]] to
1332 : // timeStyle.
1333 : time_style = maybe_time_style.FromJust();
1334 315 : if (time_style != DateTimeStyle::kUndefined) {
1335 216 : date_time_format->set_time_style(time_style);
1336 : }
1337 :
1338 : // 32. If dateStyle or timeStyle are not undefined, then
1339 315 : if (date_style != DateTimeStyle::kUndefined ||
1340 : time_style != DateTimeStyle::kUndefined) {
1341 540 : icu_date_format = DateTimeStylePattern(date_style, time_style, icu_locale,
1342 : hc, *generator);
1343 270 : if (FLAG_harmony_intl_date_format_range) {
1344 0 : icu_date_interval_format = CreateICUDateIntervalFormat(
1345 0 : icu_locale, SkeletonFromDateFormat(*icu_date_format));
1346 : }
1347 : }
1348 : }
1349 : // 33. Else,
1350 3453 : if (icu_date_format.get() == nullptr) {
1351 : bool has_hour_option = false;
1352 : // b. For each row of Table 5, except the header row, do
1353 : std::string skeleton;
1354 3183 : for (const PatternData& item : GetPatternData(hc)) {
1355 28647 : std::unique_ptr<char[]> input;
1356 : // i. Let prop be the name given in the Property column of the row.
1357 : // ii. Let value be ? GetOption(options, prop, "string", « the strings
1358 : // given in the Values column of the row », undefined).
1359 : Maybe<bool> maybe_get_option = Intl::GetStringOption(
1360 : isolate, options, item.property.c_str(), item.allowed_values,
1361 85941 : "Intl.DateTimeFormat", &input);
1362 28647 : MAYBE_RETURN(maybe_get_option, Handle<JSDateTimeFormat>());
1363 28647 : if (maybe_get_option.FromJust()) {
1364 18174 : if (item.property == "hour") {
1365 : has_hour_option = true;
1366 : }
1367 : DCHECK_NOT_NULL(input.get());
1368 : // iii. Set opt.[[<prop>]] to value.
1369 18174 : skeleton += item.map.find(input.get())->second;
1370 : }
1371 : }
1372 :
1373 : enum FormatMatcherOption { kBestFit, kBasic };
1374 : // We implement only best fit algorithm, but still need to check
1375 : // if the formatMatcher values are in range.
1376 : // c. Let matcher be ? GetOption(options, "formatMatcher", "string",
1377 : // « "basic", "best fit" », "best fit").
1378 : Maybe<FormatMatcherOption> maybe_format_matcher =
1379 : Intl::GetStringOption<FormatMatcherOption>(
1380 : isolate, options, "formatMatcher", "Intl.DateTimeFormat",
1381 : {"best fit", "basic"},
1382 : {FormatMatcherOption::kBestFit, FormatMatcherOption::kBasic},
1383 9549 : FormatMatcherOption::kBestFit);
1384 3183 : MAYBE_RETURN(maybe_format_matcher, MaybeHandle<JSDateTimeFormat>());
1385 : // TODO(ftang): uncomment the following line and handle format_matcher.
1386 : // FormatMatcherOption format_matcher = maybe_format_matcher.FromJust();
1387 :
1388 6366 : icu::UnicodeString skeleton_ustr(skeleton.c_str());
1389 : icu_date_format =
1390 6366 : CreateICUDateFormatFromCache(icu_locale, skeleton_ustr, *generator);
1391 3183 : if (icu_date_format.get() == nullptr) {
1392 : // Remove extensions and try again.
1393 9 : icu_locale = icu::Locale(icu_locale.getBaseName());
1394 : icu_date_format =
1395 18 : CreateICUDateFormatFromCache(icu_locale, skeleton_ustr, *generator);
1396 9 : if (icu_date_format.get() == nullptr) {
1397 0 : FATAL("Failed to create ICU date format, are ICU data files missing?");
1398 : }
1399 : }
1400 3183 : if (FLAG_harmony_intl_date_format_range) {
1401 : icu_date_interval_format =
1402 54 : CreateICUDateIntervalFormat(icu_locale, skeleton_ustr);
1403 : }
1404 :
1405 : // g. If dateTimeFormat.[[Hour]] is not undefined, then
1406 3183 : if (!has_hour_option) {
1407 : // h. Else, i. Set dateTimeFormat.[[HourCycle]] to undefined.
1408 2747 : date_time_format->set_hour_cycle(Intl::HourCycle::kUndefined);
1409 : }
1410 : }
1411 :
1412 : // The creation of Calendar depends on timeZone so we have to put 13 after 17.
1413 : // Also icu_date_format is not created until here.
1414 : // 13. Set dateTimeFormat.[[Calendar]] to r.[[ca]].
1415 3453 : icu_date_format->adoptCalendar(calendar.release());
1416 :
1417 : // 12.1.1 InitializeDateTimeFormat ( dateTimeFormat, locales, options )
1418 : //
1419 : // Steps 8-9 set opt.[[hc]] to value *other than undefined*
1420 : // if "hour12" is set or "hourCycle" is set in the option.
1421 : //
1422 : // 9.2.6 ResolveLocale (... )
1423 : // Step 8.h / 8.i and 8.k
1424 : //
1425 : // An hour12 option always overrides an hourCycle option.
1426 : // Additionally hour12 and hourCycle both clear out any existing Unicode
1427 : // extension key in the input locale.
1428 : //
1429 : // See details in https://github.com/tc39/test262/pull/2035
1430 6870 : if (maybe_get_hour12.FromJust() ||
1431 : maybe_hour_cycle.FromJust() != Intl::HourCycle::kUndefined) {
1432 72 : auto hc_extension_it = r.extensions.find("hc");
1433 36 : if (hc_extension_it != r.extensions.end()) {
1434 0 : if (date_time_format->hour_cycle() !=
1435 0 : Intl::ToHourCycle(hc_extension_it->second.c_str())) {
1436 : // Remove -hc- if it does not agree with what we used.
1437 0 : UErrorCode status = U_ZERO_ERROR;
1438 0 : icu_locale.setUnicodeKeywordValue("hc", nullptr, status);
1439 0 : CHECK(U_SUCCESS(status));
1440 : }
1441 : }
1442 : }
1443 :
1444 : Handle<Managed<icu::Locale>> managed_locale =
1445 3453 : Managed<icu::Locale>::FromRawPtr(isolate, 0, icu_locale.clone());
1446 :
1447 3453 : date_time_format->set_icu_locale(*managed_locale);
1448 : Handle<Managed<icu::SimpleDateFormat>> managed_format =
1449 : Managed<icu::SimpleDateFormat>::FromUniquePtr(isolate, 0,
1450 6906 : std::move(icu_date_format));
1451 3453 : date_time_format->set_icu_simple_date_format(*managed_format);
1452 3453 : if (FLAG_harmony_intl_date_format_range) {
1453 : Handle<Managed<icu::DateIntervalFormat>> managed_interval_format =
1454 : Managed<icu::DateIntervalFormat>::FromUniquePtr(
1455 54 : isolate, 0, std::move(icu_date_interval_format));
1456 27 : date_time_format->set_icu_date_interval_format(*managed_interval_format);
1457 : }
1458 :
1459 3453 : return date_time_format;
1460 : }
1461 :
1462 : namespace {
1463 :
1464 : // The list comes from third_party/icu/source/i18n/unicode/udat.h.
1465 : // They're mapped to DateTimeFormat components listed at
1466 : // https://tc39.github.io/ecma402/#sec-datetimeformat-abstracts .
1467 387 : Handle<String> IcuDateFieldIdToDateType(int32_t field_id, Isolate* isolate) {
1468 387 : switch (field_id) {
1469 : case -1:
1470 : return isolate->factory()->literal_string();
1471 : case UDAT_YEAR_FIELD:
1472 : case UDAT_EXTENDED_YEAR_FIELD:
1473 : case UDAT_YEAR_NAME_FIELD:
1474 : return isolate->factory()->year_string();
1475 : case UDAT_MONTH_FIELD:
1476 : case UDAT_STANDALONE_MONTH_FIELD:
1477 : return isolate->factory()->month_string();
1478 : case UDAT_DATE_FIELD:
1479 : return isolate->factory()->day_string();
1480 : case UDAT_HOUR_OF_DAY1_FIELD:
1481 : case UDAT_HOUR_OF_DAY0_FIELD:
1482 : case UDAT_HOUR1_FIELD:
1483 : case UDAT_HOUR0_FIELD:
1484 : return isolate->factory()->hour_string();
1485 : case UDAT_MINUTE_FIELD:
1486 : return isolate->factory()->minute_string();
1487 : case UDAT_SECOND_FIELD:
1488 : return isolate->factory()->second_string();
1489 : case UDAT_DAY_OF_WEEK_FIELD:
1490 : case UDAT_DOW_LOCAL_FIELD:
1491 : case UDAT_STANDALONE_DAY_FIELD:
1492 : return isolate->factory()->weekday_string();
1493 : case UDAT_AM_PM_FIELD:
1494 : return isolate->factory()->dayPeriod_string();
1495 : case UDAT_TIMEZONE_FIELD:
1496 : case UDAT_TIMEZONE_RFC_FIELD:
1497 : case UDAT_TIMEZONE_GENERIC_FIELD:
1498 : case UDAT_TIMEZONE_SPECIAL_FIELD:
1499 : case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD:
1500 : case UDAT_TIMEZONE_ISO_FIELD:
1501 : case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
1502 : return isolate->factory()->timeZoneName_string();
1503 : case UDAT_ERA_FIELD:
1504 : return isolate->factory()->era_string();
1505 : default:
1506 : // Other UDAT_*_FIELD's cannot show up because there is no way to specify
1507 : // them via options of Intl.DateTimeFormat.
1508 0 : UNREACHABLE();
1509 : // To prevent MSVC from issuing C4715 warning.
1510 : return Handle<String>();
1511 : }
1512 : }
1513 :
1514 : } // namespace
1515 :
1516 153 : MaybeHandle<JSArray> JSDateTimeFormat::FormatToParts(
1517 : Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
1518 : double date_value) {
1519 : Factory* factory = isolate->factory();
1520 : icu::SimpleDateFormat* format =
1521 : date_time_format->icu_simple_date_format()->raw();
1522 153 : CHECK_NOT_NULL(format);
1523 :
1524 153 : icu::UnicodeString formatted;
1525 306 : icu::FieldPositionIterator fp_iter;
1526 153 : icu::FieldPosition fp;
1527 153 : UErrorCode status = U_ZERO_ERROR;
1528 153 : format->format(date_value, formatted, &fp_iter, status);
1529 153 : if (U_FAILURE(status)) {
1530 0 : THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), JSArray);
1531 : }
1532 :
1533 : Handle<JSArray> result = factory->NewJSArray(0);
1534 : int32_t length = formatted.length();
1535 153 : if (length == 0) return result;
1536 :
1537 : int index = 0;
1538 : int32_t previous_end_pos = 0;
1539 : Handle<String> substring;
1540 927 : while (fp_iter.next(fp)) {
1541 : int32_t begin_pos = fp.getBeginIndex();
1542 : int32_t end_pos = fp.getEndIndex();
1543 :
1544 387 : if (previous_end_pos < begin_pos) {
1545 432 : ASSIGN_RETURN_ON_EXCEPTION(
1546 : isolate, substring,
1547 : Intl::ToString(isolate, formatted, previous_end_pos, begin_pos),
1548 : JSArray);
1549 : Intl::AddElement(isolate, result, index,
1550 216 : IcuDateFieldIdToDateType(-1, isolate), substring);
1551 216 : ++index;
1552 : }
1553 774 : ASSIGN_RETURN_ON_EXCEPTION(
1554 : isolate, substring,
1555 : Intl::ToString(isolate, formatted, begin_pos, end_pos), JSArray);
1556 387 : Intl::AddElement(isolate, result, index,
1557 : IcuDateFieldIdToDateType(fp.getField(), isolate),
1558 387 : substring);
1559 : previous_end_pos = end_pos;
1560 387 : ++index;
1561 : }
1562 153 : if (previous_end_pos < length) {
1563 0 : ASSIGN_RETURN_ON_EXCEPTION(
1564 : isolate, substring,
1565 : Intl::ToString(isolate, formatted, previous_end_pos, length), JSArray);
1566 : Intl::AddElement(isolate, result, index,
1567 0 : IcuDateFieldIdToDateType(-1, isolate), substring);
1568 : }
1569 153 : JSObject::ValidateElements(*result);
1570 153 : return result;
1571 : }
1572 :
1573 131 : const std::set<std::string>& JSDateTimeFormat::GetAvailableLocales() {
1574 3962 : return Intl::GetAvailableLocalesForDateFormat();
1575 : }
1576 :
1577 225 : Handle<String> JSDateTimeFormat::HourCycleAsString() const {
1578 225 : switch (hour_cycle()) {
1579 : case Intl::HourCycle::kUndefined:
1580 : return GetReadOnlyRoots().undefined_string_handle();
1581 : case Intl::HourCycle::kH11:
1582 : return GetReadOnlyRoots().h11_string_handle();
1583 : case Intl::HourCycle::kH12:
1584 : return GetReadOnlyRoots().h12_string_handle();
1585 : case Intl::HourCycle::kH23:
1586 : return GetReadOnlyRoots().h23_string_handle();
1587 : case Intl::HourCycle::kH24:
1588 : return GetReadOnlyRoots().h24_string_handle();
1589 : default:
1590 0 : UNREACHABLE();
1591 : }
1592 : }
1593 :
1594 135 : MaybeHandle<String> JSDateTimeFormat::FormatRange(
1595 : Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x,
1596 : double y) {
1597 : // TODO(ftang): Merge the following with FormatRangeToParts after
1598 : // the landing of ICU64 to make it cleaner.
1599 :
1600 : // #sec-partitiondatetimerangepattern
1601 : // 1. Let x be TimeClip(x).
1602 135 : x = DateCache::TimeClip(x);
1603 : // 2. If x is NaN, throw a RangeError exception.
1604 135 : if (std::isnan(x)) {
1605 18 : THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
1606 : String);
1607 : }
1608 : // 3. Let y be TimeClip(y).
1609 126 : y = DateCache::TimeClip(y);
1610 : // 4. If y is NaN, throw a RangeError exception.
1611 126 : if (std::isnan(y)) {
1612 18 : THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
1613 : String);
1614 : }
1615 :
1616 : icu::DateIntervalFormat* date_interval_format =
1617 : date_time_format->icu_date_interval_format()->raw();
1618 117 : CHECK_NOT_NULL(date_interval_format);
1619 234 : icu::DateInterval interval(x, y);
1620 :
1621 117 : icu::UnicodeString result;
1622 117 : icu::FieldPosition fpos;
1623 117 : UErrorCode status = U_ZERO_ERROR;
1624 117 : date_interval_format->format(&interval, result, fpos, status);
1625 117 : CHECK(U_SUCCESS(status));
1626 :
1627 117 : return Intl::ToString(isolate, result);
1628 : }
1629 :
1630 27 : MaybeHandle<JSArray> JSDateTimeFormat::FormatRangeToParts(
1631 : Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x,
1632 : double y) {
1633 : // TODO(ftang): Merge the following with FormatRangeToParts after
1634 : // the landing of ICU64 to make it cleaner.
1635 :
1636 : // #sec-partitiondatetimerangepattern
1637 : // 1. Let x be TimeClip(x).
1638 27 : x = DateCache::TimeClip(x);
1639 : // 2. If x is NaN, throw a RangeError exception.
1640 27 : if (std::isnan(x)) {
1641 18 : THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
1642 : JSArray);
1643 : }
1644 : // 3. Let y be TimeClip(y).
1645 18 : y = DateCache::TimeClip(y);
1646 : // 4. If y is NaN, throw a RangeError exception.
1647 18 : if (std::isnan(y)) {
1648 18 : THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
1649 : JSArray);
1650 : }
1651 :
1652 : icu::DateIntervalFormat* date_interval_format =
1653 18 : date_time_format->icu_date_interval_format()->raw();
1654 9 : CHECK_NOT_NULL(date_interval_format);
1655 : Factory* factory = isolate->factory();
1656 : Handle<JSArray> result = factory->NewJSArray(0);
1657 :
1658 : // TODO(ftang) To be implemented after ICU64 landed that support
1659 : // DateIntervalFormat::formatToValue() and FormattedDateInterval.
1660 :
1661 9 : JSObject::ValidateElements(*result);
1662 9 : return result;
1663 : }
1664 :
1665 : } // namespace internal
1666 121996 : } // namespace v8
|