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