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 <algorithm>
12 #include <map>
13 #include <memory>
14 #include <string>
15 #include <utility>
16 #include <vector>
17
18 #include "src/date/date.h"
19 #include "src/execution/isolate.h"
20 #include "src/heap/factory.h"
21 #include "src/objects/intl-objects.h"
22 #include "src/objects/js-date-time-format-inl.h"
23 #include "src/objects/managed-inl.h"
24 #include "src/objects/option-utils.h"
25 #include "unicode/calendar.h"
26 #include "unicode/dtitvfmt.h"
27 #include "unicode/dtptngen.h"
28 #include "unicode/fieldpos.h"
29 #include "unicode/gregocal.h"
30 #include "unicode/smpdtfmt.h"
31 #include "unicode/unistr.h"
32
33 namespace v8 {
34 namespace internal {
35
36 namespace {
37
ToHourCycleString(JSDateTimeFormat::HourCycle hc)38 std::string ToHourCycleString(JSDateTimeFormat::HourCycle hc) {
39 switch (hc) {
40 case JSDateTimeFormat::HourCycle::kH11:
41 return "h11";
42 case JSDateTimeFormat::HourCycle::kH12:
43 return "h12";
44 case JSDateTimeFormat::HourCycle::kH23:
45 return "h23";
46 case JSDateTimeFormat::HourCycle::kH24:
47 return "h24";
48 case JSDateTimeFormat::HourCycle::kUndefined:
49 return "";
50 default:
51 UNREACHABLE();
52 }
53 }
54
ToHourCycle(const std::string & hc)55 JSDateTimeFormat::HourCycle ToHourCycle(const std::string& hc) {
56 if (hc == "h11") return JSDateTimeFormat::HourCycle::kH11;
57 if (hc == "h12") return JSDateTimeFormat::HourCycle::kH12;
58 if (hc == "h23") return JSDateTimeFormat::HourCycle::kH23;
59 if (hc == "h24") return JSDateTimeFormat::HourCycle::kH24;
60 return JSDateTimeFormat::HourCycle::kUndefined;
61 }
62
ToHourCycle(UDateFormatHourCycle hc)63 JSDateTimeFormat::HourCycle ToHourCycle(UDateFormatHourCycle hc) {
64 switch (hc) {
65 case UDAT_HOUR_CYCLE_11:
66 return JSDateTimeFormat::HourCycle::kH11;
67 case UDAT_HOUR_CYCLE_12:
68 return JSDateTimeFormat::HourCycle::kH12;
69 case UDAT_HOUR_CYCLE_23:
70 return JSDateTimeFormat::HourCycle::kH23;
71 case UDAT_HOUR_CYCLE_24:
72 return JSDateTimeFormat::HourCycle::kH24;
73 default:
74 return JSDateTimeFormat::HourCycle::kUndefined;
75 }
76 }
77
GetHourCycle(Isolate * isolate,Handle<JSReceiver> options,const char * method_name)78 Maybe<JSDateTimeFormat::HourCycle> GetHourCycle(Isolate* isolate,
79 Handle<JSReceiver> options,
80 const char* method_name) {
81 return GetStringOption<JSDateTimeFormat::HourCycle>(
82 isolate, options, "hourCycle", method_name, {"h11", "h12", "h23", "h24"},
83 {JSDateTimeFormat::HourCycle::kH11, JSDateTimeFormat::HourCycle::kH12,
84 JSDateTimeFormat::HourCycle::kH23, JSDateTimeFormat::HourCycle::kH24},
85 JSDateTimeFormat::HourCycle::kUndefined);
86 }
87
88 class PatternMap {
89 public:
PatternMap(std::string pattern,std::string value)90 PatternMap(std::string pattern, std::string value)
91 : pattern(std::move(pattern)), value(std::move(value)) {}
92 virtual ~PatternMap() = default;
93 std::string pattern;
94 std::string value;
95 };
96
97 class PatternItem {
98 public:
PatternItem(const std::string property,std::vector<PatternMap> pairs,std::vector<const char * > allowed_values)99 PatternItem(const std::string property, std::vector<PatternMap> pairs,
100 std::vector<const char*> allowed_values)
101 : property(std::move(property)),
102 pairs(std::move(pairs)),
103 allowed_values(allowed_values) {}
104 virtual ~PatternItem() = default;
105
106 const std::string property;
107 // It is important for the pattern in the pairs from longer one to shorter one
108 // if the longer one contains substring of an shorter one.
109 std::vector<PatternMap> pairs;
110 std::vector<const char*> allowed_values;
111 };
112
BuildPatternItems()113 static std::vector<PatternItem> BuildPatternItems() {
114 const std::vector<const char*> kLongShort = {"long", "short"};
115 const std::vector<const char*> kNarrowLongShort = {"narrow", "long", "short"};
116 const std::vector<const char*> k2DigitNumeric = {"2-digit", "numeric"};
117 const std::vector<const char*> kNarrowLongShort2DigitNumeric = {
118 "narrow", "long", "short", "2-digit", "numeric"};
119 std::vector<PatternItem> items = {
120 PatternItem("weekday",
121 {{"EEEEE", "narrow"},
122 {"EEEE", "long"},
123 {"EEE", "short"},
124 {"ccccc", "narrow"},
125 {"cccc", "long"},
126 {"ccc", "short"}},
127 kNarrowLongShort),
128 PatternItem("era",
129 {{"GGGGG", "narrow"}, {"GGGG", "long"}, {"GGG", "short"}},
130 kNarrowLongShort),
131 PatternItem("year", {{"yy", "2-digit"}, {"y", "numeric"}},
132 k2DigitNumeric)};
133 // Sometimes we get L instead of M for month - standalone name.
134 items.push_back(PatternItem("month",
135 {{"MMMMM", "narrow"},
136 {"MMMM", "long"},
137 {"MMM", "short"},
138 {"MM", "2-digit"},
139 {"M", "numeric"},
140 {"LLLLL", "narrow"},
141 {"LLLL", "long"},
142 {"LLL", "short"},
143 {"LL", "2-digit"},
144 {"L", "numeric"}},
145 kNarrowLongShort2DigitNumeric));
146 items.push_back(PatternItem("day", {{"dd", "2-digit"}, {"d", "numeric"}},
147 k2DigitNumeric));
148 items.push_back(PatternItem("dayPeriod",
149 {{"BBBBB", "narrow"},
150 {"bbbbb", "narrow"},
151 {"BBBB", "long"},
152 {"bbbb", "long"},
153 {"B", "short"},
154 {"b", "short"}},
155 kNarrowLongShort));
156 items.push_back(PatternItem("hour",
157 {{"HH", "2-digit"},
158 {"H", "numeric"},
159 {"hh", "2-digit"},
160 {"h", "numeric"},
161 {"kk", "2-digit"},
162 {"k", "numeric"},
163 {"KK", "2-digit"},
164 {"K", "numeric"}},
165 k2DigitNumeric));
166 items.push_back(PatternItem("minute", {{"mm", "2-digit"}, {"m", "numeric"}},
167 k2DigitNumeric));
168 items.push_back(PatternItem("second", {{"ss", "2-digit"}, {"s", "numeric"}},
169 k2DigitNumeric));
170
171 const std::vector<const char*> kTimezone = {"long", "short",
172 "longOffset", "shortOffset",
173 "longGeneric", "shortGeneric"};
174 items.push_back(PatternItem("timeZoneName",
175 {{"zzzz", "long"},
176 {"z", "short"},
177 {"OOOO", "longOffset"},
178 {"O", "shortOffset"},
179 {"vvvv", "longGeneric"},
180 {"v", "shortGeneric"}},
181 kTimezone));
182 return items;
183 }
184
185 class PatternItems {
186 public:
PatternItems()187 PatternItems() : data(BuildPatternItems()) {}
188 virtual ~PatternItems() = default;
Get() const189 const std::vector<PatternItem>& Get() const { return data; }
190
191 private:
192 const std::vector<PatternItem> data;
193 };
194
GetPatternItems()195 static const std::vector<PatternItem>& GetPatternItems() {
196 static base::LazyInstance<PatternItems>::type items =
197 LAZY_INSTANCE_INITIALIZER;
198 return items.Pointer()->Get();
199 }
200
201 class PatternData {
202 public:
PatternData(const std::string property,std::vector<PatternMap> pairs,std::vector<const char * > allowed_values)203 PatternData(const std::string property, std::vector<PatternMap> pairs,
204 std::vector<const char*> allowed_values)
205 : property(std::move(property)), allowed_values(allowed_values) {
206 for (const auto& pair : pairs) {
207 map.insert(std::make_pair(pair.value, pair.pattern));
208 }
209 }
210 virtual ~PatternData() = default;
211
212 const std::string property;
213 std::map<const std::string, const std::string> map;
214 std::vector<const char*> allowed_values;
215 };
216
CreateCommonData(const PatternData & hour_data)217 const std::vector<PatternData> CreateCommonData(const PatternData& hour_data) {
218 std::vector<PatternData> build;
219 for (const PatternItem& item : GetPatternItems()) {
220 if (item.property == "hour") {
221 build.push_back(hour_data);
222 } else {
223 build.push_back(
224 PatternData(item.property, item.pairs, item.allowed_values));
225 }
226 }
227 return build;
228 }
229
CreateData(const char * digit2,const char * numeric)230 const std::vector<PatternData> CreateData(const char* digit2,
231 const char* numeric) {
232 return CreateCommonData(
233 PatternData("hour", {{digit2, "2-digit"}, {numeric, "numeric"}},
234 {"2-digit", "numeric"}));
235 }
236
237 // According to "Date Field Symbol Table" in
238 // http://userguide.icu-project.org/formatparse/datetime
239 // Symbol | Meaning | Example(s)
240 // h hour in am/pm (1~12) h 7
241 // hh 07
242 // H hour in day (0~23) H 0
243 // HH 00
244 // k hour in day (1~24) k 24
245 // kk 24
246 // K hour in am/pm (0~11) K 0
247 // KK 00
248
249 class Pattern {
250 public:
Pattern(const char * d1,const char * d2)251 Pattern(const char* d1, const char* d2) : data(CreateData(d1, d2)) {}
252 virtual ~Pattern() = default;
Get() const253 virtual const std::vector<PatternData>& Get() const { return data; }
254
255 private:
256 std::vector<PatternData> data;
257 };
258
259 #define DEFFINE_TRAIT(name, d1, d2) \
260 struct name { \
261 static void Construct(void* allocated_ptr) { \
262 new (allocated_ptr) Pattern(d1, d2); \
263 } \
264 };
265 DEFFINE_TRAIT(H11Trait, "KK", "K")
266 DEFFINE_TRAIT(H12Trait, "hh", "h")
267 DEFFINE_TRAIT(H23Trait, "HH", "H")
268 DEFFINE_TRAIT(H24Trait, "kk", "k")
269 DEFFINE_TRAIT(HDefaultTrait, "jj", "j")
270 #undef DEFFINE_TRAIT
271
GetPatternData(JSDateTimeFormat::HourCycle hour_cycle)272 const std::vector<PatternData>& GetPatternData(
273 JSDateTimeFormat::HourCycle hour_cycle) {
274 switch (hour_cycle) {
275 case JSDateTimeFormat::HourCycle::kH11: {
276 static base::LazyInstance<Pattern, H11Trait>::type h11 =
277 LAZY_INSTANCE_INITIALIZER;
278 return h11.Pointer()->Get();
279 }
280 case JSDateTimeFormat::HourCycle::kH12: {
281 static base::LazyInstance<Pattern, H12Trait>::type h12 =
282 LAZY_INSTANCE_INITIALIZER;
283 return h12.Pointer()->Get();
284 }
285 case JSDateTimeFormat::HourCycle::kH23: {
286 static base::LazyInstance<Pattern, H23Trait>::type h23 =
287 LAZY_INSTANCE_INITIALIZER;
288 return h23.Pointer()->Get();
289 }
290 case JSDateTimeFormat::HourCycle::kH24: {
291 static base::LazyInstance<Pattern, H24Trait>::type h24 =
292 LAZY_INSTANCE_INITIALIZER;
293 return h24.Pointer()->Get();
294 }
295 case JSDateTimeFormat::HourCycle::kUndefined: {
296 static base::LazyInstance<Pattern, HDefaultTrait>::type hDefault =
297 LAZY_INSTANCE_INITIALIZER;
298 return hDefault.Pointer()->Get();
299 }
300 default:
301 UNREACHABLE();
302 }
303 }
304
GetGMTTzID(const std::string & input)305 std::string GetGMTTzID(const std::string& input) {
306 std::string ret = "Etc/GMT";
307 switch (input.length()) {
308 case 8:
309 if (input[7] == '0') return ret + '0';
310 break;
311 case 9:
312 if ((input[7] == '+' || input[7] == '-') &&
313 base::IsInRange(input[8], '0', '9')) {
314 return ret + input[7] + input[8];
315 }
316 break;
317 case 10:
318 if ((input[7] == '+' || input[7] == '-') && (input[8] == '1') &&
319 base::IsInRange(input[9], '0', '4')) {
320 return ret + input[7] + input[8] + input[9];
321 }
322 break;
323 }
324 return "";
325 }
326
327 // Locale independenty version of isalpha for ascii range. This will return
328 // false if the ch is alpha but not in ascii range.
IsAsciiAlpha(char ch)329 bool IsAsciiAlpha(char ch) {
330 return base::IsInRange(ch, 'A', 'Z') || base::IsInRange(ch, 'a', 'z');
331 }
332
333 // Locale independent toupper for ascii range. This will not return İ (dotted I)
334 // for i under Turkish locale while std::toupper may.
LocaleIndependentAsciiToUpper(char ch)335 char LocaleIndependentAsciiToUpper(char ch) {
336 return (base::IsInRange(ch, 'a', 'z')) ? (ch - 'a' + 'A') : ch;
337 }
338
339 // Locale independent tolower for ascii range.
LocaleIndependentAsciiToLower(char ch)340 char LocaleIndependentAsciiToLower(char ch) {
341 return (base::IsInRange(ch, 'A', 'Z')) ? (ch - 'A' + 'a') : ch;
342 }
343
344 // Returns titlecased location, bueNos_airES -> Buenos_Aires
345 // or ho_cHi_minH -> Ho_Chi_Minh. It is locale-agnostic and only
346 // deals with ASCII only characters.
347 // 'of', 'au' and 'es' are special-cased and lowercased.
348 // ICU's timezone parsing is case sensitive, but ECMAScript is case insensitive
ToTitleCaseTimezoneLocation(const std::string & input)349 std::string ToTitleCaseTimezoneLocation(const std::string& input) {
350 std::string title_cased;
351 int word_length = 0;
352 for (char ch : input) {
353 // Convert first char to upper case, the rest to lower case
354 if (IsAsciiAlpha(ch)) {
355 title_cased += word_length == 0 ? LocaleIndependentAsciiToUpper(ch)
356 : LocaleIndependentAsciiToLower(ch);
357 word_length++;
358 } else if (ch == '_' || ch == '-' || ch == '/') {
359 // Special case Au/Es/Of to be lower case.
360 if (word_length == 2) {
361 size_t pos = title_cased.length() - 2;
362 std::string substr = title_cased.substr(pos, 2);
363 if (substr == "Of" || substr == "Es" || substr == "Au") {
364 title_cased[pos] = LocaleIndependentAsciiToLower(title_cased[pos]);
365 }
366 }
367 title_cased += ch;
368 word_length = 0;
369 } else {
370 // Invalid input
371 return std::string();
372 }
373 }
374
375 return title_cased;
376 }
377
378 class SpecialTimeZoneMap {
379 public:
SpecialTimeZoneMap()380 SpecialTimeZoneMap() {
381 Add("America/Argentina/ComodRivadavia");
382 Add("America/Knox_IN");
383 Add("Antarctica/DumontDUrville");
384 Add("Antarctica/McMurdo");
385 Add("Australia/ACT");
386 Add("Australia/LHI");
387 Add("Australia/NSW");
388 Add("Brazil/DeNoronha");
389 Add("Chile/EasterIsland");
390 Add("GB");
391 Add("GB-Eire");
392 Add("Mexico/BajaNorte");
393 Add("Mexico/BajaSur");
394 Add("NZ");
395 Add("NZ-CHAT");
396 Add("W-SU");
397 }
398
Find(const std::string & id)399 std::string Find(const std::string& id) {
400 auto it = map_.find(id);
401 if (it != map_.end()) {
402 return it->second;
403 }
404 return "";
405 }
406
407 private:
Add(const char * id)408 void Add(const char* id) {
409 std::string upper(id);
410 transform(upper.begin(), upper.end(), upper.begin(),
411 LocaleIndependentAsciiToUpper);
412 map_.insert({upper, id});
413 }
414 std::map<std::string, std::string> map_;
415 };
416
417 } // namespace
418
419 // Return the time zone id which match ICU's expectation of title casing
420 // return empty string when error.
CanonicalizeTimeZoneID(const std::string & input)421 std::string JSDateTimeFormat::CanonicalizeTimeZoneID(const std::string& input) {
422 std::string upper = input;
423 transform(upper.begin(), upper.end(), upper.begin(),
424 LocaleIndependentAsciiToUpper);
425 if (upper.length() == 3) {
426 if (upper == "GMT") return "UTC";
427 // For id such as "CET", return upper case.
428 return upper;
429 } else if (upper.length() == 7 && '0' <= upper[3] && upper[3] <= '9') {
430 // For id such as "CST6CDT", return upper case.
431 return upper;
432 } else if (upper.length() > 3) {
433 if (memcmp(upper.c_str(), "ETC", 3) == 0) {
434 if (upper == "ETC/UTC" || upper == "ETC/GMT" || upper == "ETC/UCT") {
435 return "UTC";
436 }
437 if (strncmp(upper.c_str(), "ETC/GMT", 7) == 0) {
438 return GetGMTTzID(input);
439 }
440 } else if (memcmp(upper.c_str(), "GMT", 3) == 0) {
441 if (upper == "GMT0" || upper == "GMT+0" || upper == "GMT-0") {
442 return "UTC";
443 }
444 } else if (memcmp(upper.c_str(), "US/", 3) == 0) {
445 std::string title = ToTitleCaseTimezoneLocation(input);
446 if (title.length() >= 2) {
447 // Change "Us/" to "US/"
448 title[1] = 'S';
449 }
450 return title;
451 } else if (strncmp(upper.c_str(), "SYSTEMV/", 8) == 0) {
452 upper.replace(0, 8, "SystemV/");
453 return upper;
454 }
455 }
456 // We expect only _, '-' and / beside ASCII letters.
457
458 static base::LazyInstance<SpecialTimeZoneMap>::type special_time_zone_map =
459 LAZY_INSTANCE_INITIALIZER;
460
461 std::string special_case = special_time_zone_map.Pointer()->Find(upper);
462 if (!special_case.empty()) {
463 return special_case;
464 }
465 return ToTitleCaseTimezoneLocation(input);
466 }
467
468 namespace {
DateTimeStyleAsString(Isolate * isolate,JSDateTimeFormat::DateTimeStyle style)469 Handle<String> DateTimeStyleAsString(Isolate* isolate,
470 JSDateTimeFormat::DateTimeStyle style) {
471 switch (style) {
472 case JSDateTimeFormat::DateTimeStyle::kFull:
473 return ReadOnlyRoots(isolate).full_string_handle();
474 case JSDateTimeFormat::DateTimeStyle::kLong:
475 return ReadOnlyRoots(isolate).long_string_handle();
476 case JSDateTimeFormat::DateTimeStyle::kMedium:
477 return ReadOnlyRoots(isolate).medium_string_handle();
478 case JSDateTimeFormat::DateTimeStyle::kShort:
479 return ReadOnlyRoots(isolate).short_string_handle();
480 case JSDateTimeFormat::DateTimeStyle::kUndefined:
481 UNREACHABLE();
482 }
483 }
484
FractionalSecondDigitsFromPattern(const std::string & pattern)485 int FractionalSecondDigitsFromPattern(const std::string& pattern) {
486 int result = 0;
487 for (size_t i = 0; i < pattern.length() && result < 3; i++) {
488 if (pattern[i] == 'S') {
489 result++;
490 }
491 }
492 return result;
493 }
494
495 } // namespace
496
TimeZoneId(Isolate * isolate,const icu::TimeZone & tz)497 Handle<Object> JSDateTimeFormat::TimeZoneId(Isolate* isolate,
498 const icu::TimeZone& tz) {
499 Factory* factory = isolate->factory();
500 icu::UnicodeString time_zone;
501 tz.getID(time_zone);
502 UErrorCode status = U_ZERO_ERROR;
503 icu::UnicodeString canonical_time_zone;
504 icu::TimeZone::getCanonicalID(time_zone, canonical_time_zone, status);
505 Handle<Object> timezone_value;
506 if (U_SUCCESS(status)) {
507 // In CLDR (http://unicode.org/cldr/trac/ticket/9943), Etc/UTC is made
508 // a separate timezone ID from Etc/GMT even though they're still the same
509 // timezone. We have Etc/UTC because 'UTC', 'Etc/Universal',
510 // 'Etc/Zulu' and others are turned to 'Etc/UTC' by ICU. Etc/GMT comes
511 // from Etc/GMT0, Etc/GMT+0, Etc/GMT-0, Etc/Greenwich.
512 // ecma402#sec-canonicalizetimezonename step 3
513 if (canonical_time_zone == UNICODE_STRING_SIMPLE("Etc/UTC") ||
514 canonical_time_zone == UNICODE_STRING_SIMPLE("Etc/GMT")) {
515 timezone_value = factory->UTC_string();
516 } else {
517 ASSIGN_RETURN_ON_EXCEPTION_VALUE(
518 isolate, timezone_value, Intl::ToString(isolate, canonical_time_zone),
519 Handle<Object>());
520 }
521 } else {
522 // Somehow on Windows we will reach here.
523 timezone_value = factory->undefined_value();
524 }
525 return timezone_value;
526 }
527
528 namespace {
GetCalendar(Isolate * isolate,const icu::SimpleDateFormat & simple_date_format,bool is_alt_calendar=false)529 Handle<String> GetCalendar(Isolate* isolate,
530 const icu::SimpleDateFormat& simple_date_format,
531 bool is_alt_calendar = false) {
532 // getType() returns legacy calendar type name instead of LDML/BCP47 calendar
533 // key values. intl.js maps them to BCP47 values for key "ca".
534 // TODO(jshin): Consider doing it here, instead.
535 std::string calendar_str = simple_date_format.getCalendar()->getType();
536
537 // Maps ICU calendar names to LDML/BCP47 types for key 'ca'.
538 // See typeMap section in third_party/icu/source/data/misc/keyTypeData.txt
539 // and
540 // http://www.unicode.org/repos/cldr/tags/latest/common/bcp47/calendar.xml
541 if (calendar_str == "gregorian") {
542 if (is_alt_calendar) {
543 calendar_str = "iso8601";
544 } else {
545 calendar_str = "gregory";
546 }
547 } else if (calendar_str == "ethiopic-amete-alem") {
548 calendar_str = "ethioaa";
549 } else if (calendar_str == "islamic") {
550 if (is_alt_calendar) {
551 calendar_str = "islamic-rgsa";
552 }
553 }
554 return isolate->factory()->NewStringFromAsciiChecked(calendar_str.c_str());
555 }
556
GetTimeZone(Isolate * isolate,const icu::SimpleDateFormat & simple_date_format)557 Handle<Object> GetTimeZone(Isolate* isolate,
558 const icu::SimpleDateFormat& simple_date_format) {
559 return JSDateTimeFormat::TimeZoneId(
560 isolate, simple_date_format.getCalendar()->getTimeZone());
561 }
562 } // namespace
563
Calendar(Isolate * isolate,Handle<JSDateTimeFormat> date_time_format)564 Handle<String> JSDateTimeFormat::Calendar(
565 Isolate* isolate, Handle<JSDateTimeFormat> date_time_format) {
566 return GetCalendar(isolate,
567 *(date_time_format->icu_simple_date_format().raw()),
568 date_time_format->alt_calendar());
569 }
570
TimeZone(Isolate * isolate,Handle<JSDateTimeFormat> date_time_format)571 Handle<Object> JSDateTimeFormat::TimeZone(
572 Isolate* isolate, Handle<JSDateTimeFormat> date_time_format) {
573 return GetTimeZone(isolate,
574 *(date_time_format->icu_simple_date_format().raw()));
575 }
576
577 // ecma402 #sec-intl.datetimeformat.prototype.resolvedoptions
ResolvedOptions(Isolate * isolate,Handle<JSDateTimeFormat> date_time_format)578 MaybeHandle<JSObject> JSDateTimeFormat::ResolvedOptions(
579 Isolate* isolate, Handle<JSDateTimeFormat> date_time_format) {
580 Factory* factory = isolate->factory();
581 // 4. Let options be ! ObjectCreate(%ObjectPrototype%).
582 Handle<JSObject> options = factory->NewJSObject(isolate->object_function());
583
584 Handle<Object> resolved_obj;
585
586 Handle<String> locale = Handle<String>(date_time_format->locale(), isolate);
587 DCHECK(!date_time_format->icu_locale().is_null());
588 DCHECK_NOT_NULL(date_time_format->icu_locale().raw());
589 icu::Locale* icu_locale = date_time_format->icu_locale().raw();
590
591 icu::SimpleDateFormat* icu_simple_date_format =
592 date_time_format->icu_simple_date_format().raw();
593 Handle<Object> timezone =
594 JSDateTimeFormat::TimeZone(isolate, date_time_format);
595
596 // Ugly hack. ICU doesn't expose numbering system in any way, so we have
597 // to assume that for given locale NumberingSystem constructor produces the
598 // same digits as NumberFormat/Calendar would.
599 // Tracked by https://unicode-org.atlassian.net/browse/ICU-13431
600 std::string numbering_system = Intl::GetNumberingSystem(*icu_locale);
601
602 icu::UnicodeString pattern_unicode;
603 icu_simple_date_format->toPattern(pattern_unicode);
604 std::string pattern;
605 pattern_unicode.toUTF8String(pattern);
606
607 // 5. For each row of Table 6, except the header row, in table order, do
608 // Table 6: Resolved Options of DateTimeFormat Instances
609 // Internal Slot Property
610 // [[Locale]] "locale"
611 // [[Calendar]] "calendar"
612 // [[NumberingSystem]] "numberingSystem"
613 // [[TimeZone]] "timeZone"
614 // [[HourCycle]] "hourCycle"
615 // "hour12"
616 // [[Weekday]] "weekday"
617 // [[Era]] "era"
618 // [[Year]] "year"
619 // [[Month]] "month"
620 // [[Day]] "day"
621 // [[Hour]] "hour"
622 // [[Minute]] "minute"
623 // [[Second]] "second"
624 // [[FractionalSecondDigits]] "fractionalSecondDigits"
625 // [[TimeZoneName]] "timeZoneName"
626 Maybe<bool> maybe_create_locale = JSReceiver::CreateDataProperty(
627 isolate, options, factory->locale_string(), locale, Just(kDontThrow));
628 DCHECK(maybe_create_locale.FromJust());
629 USE(maybe_create_locale);
630
631 Handle<String> calendar =
632 JSDateTimeFormat::Calendar(isolate, date_time_format);
633 Maybe<bool> maybe_create_calendar = JSReceiver::CreateDataProperty(
634 isolate, options, factory->calendar_string(), calendar, Just(kDontThrow));
635 DCHECK(maybe_create_calendar.FromJust());
636 USE(maybe_create_calendar);
637
638 if (!numbering_system.empty()) {
639 Maybe<bool> maybe_create_numbering_system = JSReceiver::CreateDataProperty(
640 isolate, options, factory->numberingSystem_string(),
641 factory->NewStringFromAsciiChecked(numbering_system.c_str()),
642 Just(kDontThrow));
643 DCHECK(maybe_create_numbering_system.FromJust());
644 USE(maybe_create_numbering_system);
645 }
646 Maybe<bool> maybe_create_time_zone = JSReceiver::CreateDataProperty(
647 isolate, options, factory->timeZone_string(), timezone, Just(kDontThrow));
648 DCHECK(maybe_create_time_zone.FromJust());
649 USE(maybe_create_time_zone);
650
651 // 5.b.i. Let hc be dtf.[[HourCycle]].
652 HourCycle hc = date_time_format->hour_cycle();
653
654 if (hc != HourCycle::kUndefined) {
655 Maybe<bool> maybe_create_hour_cycle = JSReceiver::CreateDataProperty(
656 isolate, options, factory->hourCycle_string(),
657 date_time_format->HourCycleAsString(), Just(kDontThrow));
658 DCHECK(maybe_create_hour_cycle.FromJust());
659 USE(maybe_create_hour_cycle);
660 switch (hc) {
661 // ii. If hc is "h11" or "h12", let v be true.
662 case HourCycle::kH11:
663 case HourCycle::kH12: {
664 Maybe<bool> maybe_create_hour12 = JSReceiver::CreateDataProperty(
665 isolate, options, factory->hour12_string(), factory->true_value(),
666 Just(kDontThrow));
667 DCHECK(maybe_create_hour12.FromJust());
668 USE(maybe_create_hour12);
669 } break;
670 // iii. Else if, hc is "h23" or "h24", let v be false.
671 case HourCycle::kH23:
672 case HourCycle::kH24: {
673 Maybe<bool> maybe_create_hour12 = JSReceiver::CreateDataProperty(
674 isolate, options, factory->hour12_string(), factory->false_value(),
675 Just(kDontThrow));
676 DCHECK(maybe_create_hour12.FromJust());
677 USE(maybe_create_hour12);
678 } break;
679 // iv. Else, let v be undefined.
680 case HourCycle::kUndefined:
681 break;
682 }
683 }
684
685 // If dateStyle and timeStyle are undefined, then internal slots
686 // listed in "Table 1: Components of date and time formats" will be set
687 // in Step 33.f.iii.1 of InitializeDateTimeFormat
688 if (date_time_format->date_style() == DateTimeStyle::kUndefined &&
689 date_time_format->time_style() == DateTimeStyle::kUndefined) {
690 for (const auto& item : GetPatternItems()) {
691 // fractionalSecondsDigits need to be added before timeZoneName
692 if (item.property == "timeZoneName") {
693 int fsd = FractionalSecondDigitsFromPattern(pattern);
694 if (fsd > 0) {
695 Maybe<bool> maybe_create_fractional_seconds_digits =
696 JSReceiver::CreateDataProperty(
697 isolate, options, factory->fractionalSecondDigits_string(),
698 factory->NewNumberFromInt(fsd), Just(kDontThrow));
699 DCHECK(maybe_create_fractional_seconds_digits.FromJust());
700 USE(maybe_create_fractional_seconds_digits);
701 }
702 }
703 for (const auto& pair : item.pairs) {
704 if (pattern.find(pair.pattern) != std::string::npos) {
705 Maybe<bool> maybe_create_property = JSReceiver::CreateDataProperty(
706 isolate, options,
707 factory->NewStringFromAsciiChecked(item.property.c_str()),
708 factory->NewStringFromAsciiChecked(pair.value.c_str()),
709 Just(kDontThrow));
710 DCHECK(maybe_create_property.FromJust());
711 USE(maybe_create_property);
712 break;
713 }
714 }
715 }
716 }
717
718 // dateStyle
719 if (date_time_format->date_style() != DateTimeStyle::kUndefined) {
720 Maybe<bool> maybe_create_date_style = JSReceiver::CreateDataProperty(
721 isolate, options, factory->dateStyle_string(),
722 DateTimeStyleAsString(isolate, date_time_format->date_style()),
723 Just(kDontThrow));
724 DCHECK(maybe_create_date_style.FromJust());
725 USE(maybe_create_date_style);
726 }
727
728 // timeStyle
729 if (date_time_format->time_style() != DateTimeStyle::kUndefined) {
730 Maybe<bool> maybe_create_time_style = JSReceiver::CreateDataProperty(
731 isolate, options, factory->timeStyle_string(),
732 DateTimeStyleAsString(isolate, date_time_format->time_style()),
733 Just(kDontThrow));
734 DCHECK(maybe_create_time_style.FromJust());
735 USE(maybe_create_time_style);
736 }
737 return options;
738 }
739
740 namespace {
741
742 // ecma402/#sec-formatdatetime
743 // FormatDateTime( dateTimeFormat, x )
FormatDateTime(Isolate * isolate,const icu::SimpleDateFormat & date_format,double x)744 MaybeHandle<String> FormatDateTime(Isolate* isolate,
745 const icu::SimpleDateFormat& date_format,
746 double x) {
747 double date_value = DateCache::TimeClip(x);
748 if (std::isnan(date_value)) {
749 THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
750 String);
751 }
752
753 icu::UnicodeString result;
754 date_format.format(date_value, result);
755
756 // Revert ICU 72 change that introduced U+202F instead of U+0020
757 // to separate time from AM/PM. See https://crbug.com/1414292.
758 result = result.findAndReplace(icu::UnicodeString(0x202f),
759 icu::UnicodeString(0x20));
760
761 return Intl::ToString(isolate, result);
762 }
763
764 } // namespace
765
766 // ecma402/#sec-datetime-format-functions
767 // DateTime Format Functions
DateTimeFormat(Isolate * isolate,Handle<JSDateTimeFormat> date_time_format,Handle<Object> date)768 MaybeHandle<String> JSDateTimeFormat::DateTimeFormat(
769 Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
770 Handle<Object> date) {
771 // 2. Assert: Type(dtf) is Object and dtf has an [[InitializedDateTimeFormat]]
772 // internal slot.
773
774 // 3. If date is not provided or is undefined, then
775 double x;
776 if (date->IsUndefined()) {
777 // 3.a Let x be Call(%Date_now%, undefined).
778 x = JSDate::CurrentTimeValue(isolate);
779 } else {
780 // 4. Else,
781 // a. Let x be ? ToNumber(date).
782 ASSIGN_RETURN_ON_EXCEPTION(isolate, date, Object::ToNumber(isolate, date),
783 String);
784 DCHECK(date->IsNumber());
785 x = date->Number();
786 }
787 // 5. Return FormatDateTime(dtf, x).
788 icu::SimpleDateFormat* format =
789 date_time_format->icu_simple_date_format().raw();
790 return FormatDateTime(isolate, *format, x);
791 }
792
793 namespace {
ConvertToCacheType(JSDateTimeFormat::DefaultsOption type)794 Isolate::ICUObjectCacheType ConvertToCacheType(
795 JSDateTimeFormat::DefaultsOption type) {
796 switch (type) {
797 case JSDateTimeFormat::DefaultsOption::kDate:
798 return Isolate::ICUObjectCacheType::kDefaultSimpleDateFormatForDate;
799 case JSDateTimeFormat::DefaultsOption::kTime:
800 return Isolate::ICUObjectCacheType::kDefaultSimpleDateFormatForTime;
801 case JSDateTimeFormat::DefaultsOption::kAll:
802 return Isolate::ICUObjectCacheType::kDefaultSimpleDateFormat;
803 }
804 }
805 } // namespace
806
ToLocaleDateTime(Isolate * isolate,Handle<Object> date,Handle<Object> locales,Handle<Object> options,RequiredOption required,DefaultsOption defaults,const char * method_name)807 MaybeHandle<String> JSDateTimeFormat::ToLocaleDateTime(
808 Isolate* isolate, Handle<Object> date, Handle<Object> locales,
809 Handle<Object> options, RequiredOption required, DefaultsOption defaults,
810 const char* method_name) {
811 Isolate::ICUObjectCacheType cache_type = ConvertToCacheType(defaults);
812
813 Factory* factory = isolate->factory();
814 // 1. Let x be ? thisTimeValue(this value);
815 if (!date->IsJSDate()) {
816 THROW_NEW_ERROR(isolate,
817 NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
818 factory->Date_string()),
819 String);
820 }
821
822 double const x = Handle<JSDate>::cast(date)->value().Number();
823 // 2. If x is NaN, return "Invalid Date"
824 if (std::isnan(x)) {
825 return factory->Invalid_Date_string();
826 }
827
828 // We only cache the instance when locales is a string/undefined and
829 // options is undefined, as that is the only case when the specified
830 // side-effects of examining those arguments are unobservable.
831 bool can_cache = (locales->IsString() || locales->IsUndefined(isolate)) &&
832 options->IsUndefined(isolate);
833 if (can_cache) {
834 // Both locales and options are undefined, check the cache.
835 icu::SimpleDateFormat* cached_icu_simple_date_format =
836 static_cast<icu::SimpleDateFormat*>(
837 isolate->get_cached_icu_object(cache_type, locales));
838 if (cached_icu_simple_date_format != nullptr) {
839 return FormatDateTime(isolate, *cached_icu_simple_date_format, x);
840 }
841 }
842 // 3. Let options be ? ToDateTimeOptions(options, required, defaults).
843 Handle<JSObject> internal_options;
844 ASSIGN_RETURN_ON_EXCEPTION(
845 isolate, internal_options,
846 ToDateTimeOptions(isolate, options, required, defaults), String);
847
848 // 4. Let dateFormat be ? Construct(%DateTimeFormat%, « locales, options »).
849 Handle<JSFunction> constructor = Handle<JSFunction>(
850 JSFunction::cast(
851 isolate->context().native_context().intl_date_time_format_function()),
852 isolate);
853 Handle<Map> map;
854 ASSIGN_RETURN_ON_EXCEPTION(
855 isolate, map,
856 JSFunction::GetDerivedMap(isolate, constructor, constructor), String);
857 Handle<JSDateTimeFormat> date_time_format;
858 ASSIGN_RETURN_ON_EXCEPTION(
859 isolate, date_time_format,
860 JSDateTimeFormat::New(isolate, map, locales, internal_options,
861 method_name),
862 String);
863
864 if (can_cache) {
865 isolate->set_icu_object_in_cache(
866 cache_type, locales,
867 std::static_pointer_cast<icu::UMemory>(
868 date_time_format->icu_simple_date_format().get()));
869 }
870 // 5. Return FormatDateTime(dateFormat, x).
871 icu::SimpleDateFormat* format =
872 date_time_format->icu_simple_date_format().raw();
873 return FormatDateTime(isolate, *format, x);
874 }
875
876 namespace {
877
IsPropertyUndefined(Isolate * isolate,Handle<JSObject> options,Handle<String> property)878 Maybe<bool> IsPropertyUndefined(Isolate* isolate, Handle<JSObject> options,
879 Handle<String> property) {
880 // i. Let prop be the property name.
881 // ii. Let value be ? Get(options, prop).
882 Handle<Object> value;
883 ASSIGN_RETURN_ON_EXCEPTION_VALUE(
884 isolate, value, Object::GetPropertyOrElement(isolate, options, property),
885 Nothing<bool>());
886 return Just(value->IsUndefined(isolate));
887 }
888
NeedsDefault(Isolate * isolate,Handle<JSObject> options,const std::vector<Handle<String>> & props)889 Maybe<bool> NeedsDefault(Isolate* isolate, Handle<JSObject> options,
890 const std::vector<Handle<String>>& props) {
891 bool needs_default = true;
892 for (const auto& prop : props) {
893 // i. Let prop be the property name.
894 // ii. Let value be ? Get(options, prop)
895 Maybe<bool> maybe_undefined = IsPropertyUndefined(isolate, options, prop);
896 MAYBE_RETURN(maybe_undefined, Nothing<bool>());
897 // iii. If value is not undefined, let needDefaults be false.
898 if (!maybe_undefined.FromJust()) {
899 needs_default = false;
900 }
901 }
902 return Just(needs_default);
903 }
904
CreateDefault(Isolate * isolate,Handle<JSObject> options,const std::vector<std::string> & props)905 Maybe<bool> CreateDefault(Isolate* isolate, Handle<JSObject> options,
906 const std::vector<std::string>& props) {
907 Factory* factory = isolate->factory();
908 // i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric").
909 for (const auto& prop : props) {
910 MAYBE_RETURN(
911 JSReceiver::CreateDataProperty(
912 isolate, options, factory->NewStringFromAsciiChecked(prop.c_str()),
913 factory->numeric_string(), Just(kThrowOnError)),
914 Nothing<bool>());
915 }
916 return Just(true);
917 }
918
919 } // namespace
920
921 // ecma-402/#sec-todatetimeoptions
ToDateTimeOptions(Isolate * isolate,Handle<Object> input_options,RequiredOption required,DefaultsOption defaults)922 MaybeHandle<JSObject> JSDateTimeFormat::ToDateTimeOptions(
923 Isolate* isolate, Handle<Object> input_options, RequiredOption required,
924 DefaultsOption defaults) {
925 Factory* factory = isolate->factory();
926 // 1. If options is undefined, let options be null; otherwise let options be ?
927 // ToObject(options).
928 Handle<JSObject> options;
929 if (input_options->IsUndefined(isolate)) {
930 options = factory->NewJSObjectWithNullProto();
931 } else {
932 Handle<JSReceiver> options_obj;
933 ASSIGN_RETURN_ON_EXCEPTION(isolate, options_obj,
934 Object::ToObject(isolate, input_options),
935 JSObject);
936 // 2. Let options be ObjectCreate(options).
937 ASSIGN_RETURN_ON_EXCEPTION(isolate, options,
938 JSObject::ObjectCreate(isolate, options_obj),
939 JSObject);
940 }
941
942 // 3. Let needDefaults be true.
943 bool needs_default = true;
944
945 // 4. If required is "date" or "any", then
946 if (required == RequiredOption::kAny || required == RequiredOption::kDate) {
947 // a. For each of the property names "weekday", "year", "month",
948 // "day", do
949 std::vector<Handle<String>> list(
950 {factory->weekday_string(), factory->year_string()});
951 list.push_back(factory->month_string());
952 list.push_back(factory->day_string());
953 Maybe<bool> maybe_needs_default = NeedsDefault(isolate, options, list);
954 MAYBE_RETURN(maybe_needs_default, Handle<JSObject>());
955 needs_default = maybe_needs_default.FromJust();
956 }
957
958 // 5. If required is "time" or "any", then
959 if (required == RequiredOption::kAny || required == RequiredOption::kTime) {
960 // a. For each of the property names "dayPeriod", "hour", "minute",
961 // "second", "fractionalSecondDigits", do
962 std::vector<Handle<String>> list;
963 list.push_back(factory->dayPeriod_string());
964 list.push_back(factory->hour_string());
965 list.push_back(factory->minute_string());
966 list.push_back(factory->second_string());
967 list.push_back(factory->fractionalSecondDigits_string());
968 Maybe<bool> maybe_needs_default = NeedsDefault(isolate, options, list);
969 MAYBE_RETURN(maybe_needs_default, Handle<JSObject>());
970 needs_default &= maybe_needs_default.FromJust();
971 }
972
973 // 6. Let dateStyle be ? Get(options, "dateStyle").
974 Maybe<bool> maybe_datestyle_undefined =
975 IsPropertyUndefined(isolate, options, factory->dateStyle_string());
976 MAYBE_RETURN(maybe_datestyle_undefined, Handle<JSObject>());
977 // 7. Let timeStyle be ? Get(options, "timeStyle").
978 Maybe<bool> maybe_timestyle_undefined =
979 IsPropertyUndefined(isolate, options, factory->timeStyle_string());
980 MAYBE_RETURN(maybe_timestyle_undefined, Handle<JSObject>());
981 // 8. If dateStyle is not undefined or timeStyle is not undefined, let
982 // needDefaults be false.
983 if (!maybe_datestyle_undefined.FromJust() ||
984 !maybe_timestyle_undefined.FromJust()) {
985 needs_default = false;
986 }
987 // 9. If required is "date" and timeStyle is not undefined,
988 if (required == RequiredOption::kDate &&
989 !maybe_timestyle_undefined.FromJust()) {
990 // a. Throw a TypeError exception.
991 THROW_NEW_ERROR(
992 isolate,
993 NewTypeError(MessageTemplate::kInvalid,
994 factory->NewStringFromStaticChars("option"),
995 factory->NewStringFromStaticChars("timeStyle")),
996 JSObject);
997 }
998 // 10. If required is "time" and dateStyle is not undefined,
999 if (required == RequiredOption::kTime &&
1000 !maybe_datestyle_undefined.FromJust()) {
1001 // a. Throw a TypeError exception.
1002 THROW_NEW_ERROR(
1003 isolate,
1004 NewTypeError(MessageTemplate::kInvalid,
1005 factory->NewStringFromStaticChars("option"),
1006 factory->NewStringFromStaticChars("dateStyle")),
1007 JSObject);
1008 }
1009
1010 // 11. If needDefaults is true and defaults is either "date" or "all", then
1011 if (needs_default) {
1012 if (defaults == DefaultsOption::kAll || defaults == DefaultsOption::kDate) {
1013 // a. For each of the property names "year", "month", "day", do)
1014 const std::vector<std::string> list({"year", "month", "day"});
1015 MAYBE_RETURN(CreateDefault(isolate, options, list), Handle<JSObject>());
1016 }
1017 // 12. If needDefaults is true and defaults is either "time" or "all", then
1018 if (defaults == DefaultsOption::kAll || defaults == DefaultsOption::kTime) {
1019 // a. For each of the property names "hour", "minute", "second", do
1020 const std::vector<std::string> list({"hour", "minute", "second"});
1021 MAYBE_RETURN(CreateDefault(isolate, options, list), Handle<JSObject>());
1022 }
1023 }
1024 // 13. Return options.
1025 return options;
1026 }
1027
UnwrapDateTimeFormat(Isolate * isolate,Handle<JSReceiver> format_holder)1028 MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::UnwrapDateTimeFormat(
1029 Isolate* isolate, Handle<JSReceiver> format_holder) {
1030 Handle<Context> native_context =
1031 Handle<Context>(isolate->context().native_context(), isolate);
1032 Handle<JSFunction> constructor = Handle<JSFunction>(
1033 JSFunction::cast(native_context->intl_date_time_format_function()),
1034 isolate);
1035 Handle<Object> dtf;
1036 ASSIGN_RETURN_ON_EXCEPTION(
1037 isolate, dtf,
1038 Intl::LegacyUnwrapReceiver(isolate, format_holder, constructor,
1039 format_holder->IsJSDateTimeFormat()),
1040 JSDateTimeFormat);
1041 // 2. If Type(dtf) is not Object or dtf does not have an
1042 // [[InitializedDateTimeFormat]] internal slot, then
1043 if (!dtf->IsJSDateTimeFormat()) {
1044 // a. Throw a TypeError exception.
1045 THROW_NEW_ERROR(isolate,
1046 NewTypeError(MessageTemplate::kIncompatibleMethodReceiver,
1047 isolate->factory()->NewStringFromAsciiChecked(
1048 "UnwrapDateTimeFormat"),
1049 format_holder),
1050 JSDateTimeFormat);
1051 }
1052 // 3. Return dtf.
1053 return Handle<JSDateTimeFormat>::cast(dtf);
1054 }
1055
CreateTimeZone(const char * timezone)1056 std::unique_ptr<icu::TimeZone> JSDateTimeFormat::CreateTimeZone(
1057 const char* timezone) {
1058 // Create time zone as specified by the user. We have to re-create time zone
1059 // since calendar takes ownership.
1060 if (timezone == nullptr) {
1061 // 19.a. Else / Let timeZone be DefaultTimeZone().
1062 return std::unique_ptr<icu::TimeZone>(icu::TimeZone::createDefault());
1063 }
1064 std::string canonicalized = CanonicalizeTimeZoneID(timezone);
1065 if (canonicalized.empty()) return std::unique_ptr<icu::TimeZone>();
1066 std::unique_ptr<icu::TimeZone> tz(
1067 icu::TimeZone::createTimeZone(canonicalized.c_str()));
1068 // 18.b If the result of IsValidTimeZoneName(timeZone) is false, then
1069 // i. Throw a RangeError exception.
1070 if (!Intl::IsValidTimeZoneName(*tz)) return std::unique_ptr<icu::TimeZone>();
1071 return tz;
1072 }
1073
1074 namespace {
1075
1076 class CalendarCache {
1077 public:
CreateCalendar(const icu::Locale & locale,icu::TimeZone * tz)1078 icu::Calendar* CreateCalendar(const icu::Locale& locale, icu::TimeZone* tz) {
1079 icu::UnicodeString tz_id;
1080 tz->getID(tz_id);
1081 std::string key;
1082 tz_id.toUTF8String<std::string>(key);
1083 key += ":";
1084 key += locale.getName();
1085
1086 base::MutexGuard guard(&mutex_);
1087 auto it = map_.find(key);
1088 if (it != map_.end()) {
1089 delete tz;
1090 return it->second->clone();
1091 }
1092 // Create a calendar using locale, and apply time zone to it.
1093 UErrorCode status = U_ZERO_ERROR;
1094 std::unique_ptr<icu::Calendar> calendar(
1095 icu::Calendar::createInstance(tz, locale, status));
1096 DCHECK(U_SUCCESS(status));
1097 DCHECK_NOT_NULL(calendar.get());
1098
1099 if (calendar->getDynamicClassID() ==
1100 icu::GregorianCalendar::getStaticClassID()) {
1101 icu::GregorianCalendar* gc =
1102 static_cast<icu::GregorianCalendar*>(calendar.get());
1103 status = U_ZERO_ERROR;
1104 // The beginning of ECMAScript time, namely -(2**53)
1105 const double start_of_time = -9007199254740992;
1106 gc->setGregorianChange(start_of_time, status);
1107 DCHECK(U_SUCCESS(status));
1108 }
1109
1110 if (map_.size() > 8) { // Cache at most 8 calendars.
1111 map_.clear();
1112 }
1113 map_[key].reset(calendar.release());
1114 return map_[key]->clone();
1115 }
1116
1117 private:
1118 std::map<std::string, std::unique_ptr<icu::Calendar>> map_;
1119 base::Mutex mutex_;
1120 };
1121
CreateCalendar(Isolate * isolate,const icu::Locale & icu_locale,icu::TimeZone * tz)1122 icu::Calendar* CreateCalendar(Isolate* isolate, const icu::Locale& icu_locale,
1123 icu::TimeZone* tz) {
1124 static base::LazyInstance<CalendarCache>::type calendar_cache =
1125 LAZY_INSTANCE_INITIALIZER;
1126 return calendar_cache.Pointer()->CreateCalendar(icu_locale, tz);
1127 }
1128
ReplaceHourCycleInPattern(icu::UnicodeString pattern,JSDateTimeFormat::HourCycle hc)1129 icu::UnicodeString ReplaceHourCycleInPattern(icu::UnicodeString pattern,
1130 JSDateTimeFormat::HourCycle hc) {
1131 char16_t replacement;
1132 switch (hc) {
1133 case JSDateTimeFormat::HourCycle::kUndefined:
1134 return pattern;
1135 case JSDateTimeFormat::HourCycle::kH11:
1136 replacement = 'K';
1137 break;
1138 case JSDateTimeFormat::HourCycle::kH12:
1139 replacement = 'h';
1140 break;
1141 case JSDateTimeFormat::HourCycle::kH23:
1142 replacement = 'H';
1143 break;
1144 case JSDateTimeFormat::HourCycle::kH24:
1145 replacement = 'k';
1146 break;
1147 }
1148 bool replace = true;
1149 icu::UnicodeString result;
1150 char16_t last = u'\0';
1151 for (int32_t i = 0; i < pattern.length(); i++) {
1152 char16_t ch = pattern.charAt(i);
1153 switch (ch) {
1154 case '\'':
1155 replace = !replace;
1156 result.append(ch);
1157 break;
1158 case 'H':
1159 V8_FALLTHROUGH;
1160 case 'h':
1161 V8_FALLTHROUGH;
1162 case 'K':
1163 V8_FALLTHROUGH;
1164 case 'k':
1165 // If the previous field is a day, add a space before the hour.
1166 if (replace && last == u'd') {
1167 result.append(' ');
1168 }
1169 result.append(replace ? replacement : ch);
1170 break;
1171 default:
1172 result.append(ch);
1173 break;
1174 }
1175 last = ch;
1176 }
1177 return result;
1178 }
1179
CreateICUDateFormat(const icu::Locale & icu_locale,const icu::UnicodeString & skeleton,icu::DateTimePatternGenerator * generator,JSDateTimeFormat::HourCycle hc)1180 std::unique_ptr<icu::SimpleDateFormat> CreateICUDateFormat(
1181 const icu::Locale& icu_locale, const icu::UnicodeString& skeleton,
1182 icu::DateTimePatternGenerator* generator, JSDateTimeFormat::HourCycle hc) {
1183 // See https://github.com/tc39/ecma402/issues/225 . The best pattern
1184 // generation needs to be done in the base locale according to the
1185 // current spec however odd it may be. See also crbug.com/826549 .
1186 // This is a temporary work-around to get v8's external behavior to match
1187 // the current spec, but does not follow the spec provisions mentioned
1188 // in the above Ecma 402 issue.
1189 // TODO(jshin): The spec may need to be revised because using the base
1190 // locale for the pattern match is not quite right. Moreover, what to
1191 // do with 'related year' part when 'chinese/dangi' calendar is specified
1192 // has to be discussed. Revisit once the spec is clarified/revised.
1193 icu::UnicodeString pattern;
1194 UErrorCode status = U_ZERO_ERROR;
1195 pattern = generator->getBestPattern(skeleton, UDATPG_MATCH_HOUR_FIELD_LENGTH,
1196 status);
1197 pattern = ReplaceHourCycleInPattern(pattern, hc);
1198 DCHECK(U_SUCCESS(status));
1199
1200 // Make formatter from skeleton. Calendar and numbering system are added
1201 // to the locale as Unicode extension (if they were specified at all).
1202 status = U_ZERO_ERROR;
1203 std::unique_ptr<icu::SimpleDateFormat> date_format(
1204 new icu::SimpleDateFormat(pattern, icu_locale, status));
1205 if (U_FAILURE(status)) return std::unique_ptr<icu::SimpleDateFormat>();
1206
1207 DCHECK_NOT_NULL(date_format.get());
1208 return date_format;
1209 }
1210
1211 class DateFormatCache {
1212 public:
Create(const icu::Locale & icu_locale,const icu::UnicodeString & skeleton,icu::DateTimePatternGenerator * generator,JSDateTimeFormat::HourCycle hc)1213 icu::SimpleDateFormat* Create(const icu::Locale& icu_locale,
1214 const icu::UnicodeString& skeleton,
1215 icu::DateTimePatternGenerator* generator,
1216 JSDateTimeFormat::HourCycle hc) {
1217 std::string key;
1218 skeleton.toUTF8String<std::string>(key);
1219 key += ":";
1220 key += icu_locale.getName();
1221
1222 base::MutexGuard guard(&mutex_);
1223 auto it = map_.find(key);
1224 if (it != map_.end()) {
1225 return static_cast<icu::SimpleDateFormat*>(it->second->clone());
1226 }
1227
1228 if (map_.size() > 8) { // Cache at most 8 DateFormats.
1229 map_.clear();
1230 }
1231 std::unique_ptr<icu::SimpleDateFormat> instance(
1232 CreateICUDateFormat(icu_locale, skeleton, generator, hc));
1233 if (instance.get() == nullptr) return nullptr;
1234 map_[key] = std::move(instance);
1235 return static_cast<icu::SimpleDateFormat*>(map_[key]->clone());
1236 }
1237
1238 private:
1239 std::map<std::string, std::unique_ptr<icu::SimpleDateFormat>> map_;
1240 base::Mutex mutex_;
1241 };
1242
CreateICUDateFormatFromCache(const icu::Locale & icu_locale,const icu::UnicodeString & skeleton,icu::DateTimePatternGenerator * generator,JSDateTimeFormat::HourCycle hc)1243 std::unique_ptr<icu::SimpleDateFormat> CreateICUDateFormatFromCache(
1244 const icu::Locale& icu_locale, const icu::UnicodeString& skeleton,
1245 icu::DateTimePatternGenerator* generator, JSDateTimeFormat::HourCycle hc) {
1246 static base::LazyInstance<DateFormatCache>::type cache =
1247 LAZY_INSTANCE_INITIALIZER;
1248 return std::unique_ptr<icu::SimpleDateFormat>(
1249 cache.Pointer()->Create(icu_locale, skeleton, generator, hc));
1250 }
1251
SkeletonFromDateFormat(const icu::SimpleDateFormat & icu_date_format)1252 icu::UnicodeString SkeletonFromDateFormat(
1253 const icu::SimpleDateFormat& icu_date_format) {
1254 icu::UnicodeString pattern;
1255 pattern = icu_date_format.toPattern(pattern);
1256
1257 UErrorCode status = U_ZERO_ERROR;
1258 icu::UnicodeString skeleton =
1259 icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status);
1260 DCHECK(U_SUCCESS(status));
1261 return skeleton;
1262 }
1263
LazyCreateDateIntervalFormat(Isolate * isolate,Handle<JSDateTimeFormat> date_time_format)1264 icu::DateIntervalFormat* LazyCreateDateIntervalFormat(
1265 Isolate* isolate, Handle<JSDateTimeFormat> date_time_format) {
1266 Managed<icu::DateIntervalFormat> managed_format =
1267 date_time_format->icu_date_interval_format();
1268 if (managed_format.get()) {
1269 return managed_format.raw();
1270 }
1271 icu::SimpleDateFormat* icu_simple_date_format =
1272 date_time_format->icu_simple_date_format().raw();
1273 UErrorCode status = U_ZERO_ERROR;
1274
1275 icu::Locale loc = *(date_time_format->icu_locale().raw());
1276 // We need to pass in the hc to DateIntervalFormat by using Unicode 'hc'
1277 // extension.
1278 std::string hcString = ToHourCycleString(date_time_format->hour_cycle());
1279 if (!hcString.empty()) {
1280 loc.setUnicodeKeywordValue("hc", hcString, status);
1281 }
1282
1283 std::unique_ptr<icu::DateIntervalFormat> date_interval_format(
1284 icu::DateIntervalFormat::createInstance(
1285 SkeletonFromDateFormat(*icu_simple_date_format), loc, status));
1286 if (U_FAILURE(status)) {
1287 return nullptr;
1288 }
1289 date_interval_format->setTimeZone(icu_simple_date_format->getTimeZone());
1290 Handle<Managed<icu::DateIntervalFormat>> managed_interval_format =
1291 Managed<icu::DateIntervalFormat>::FromUniquePtr(
1292 isolate, 0, std::move(date_interval_format));
1293 date_time_format->set_icu_date_interval_format(*managed_interval_format);
1294 return (*managed_interval_format).raw();
1295 }
1296
HourCycleFromPattern(const icu::UnicodeString pattern)1297 JSDateTimeFormat::HourCycle HourCycleFromPattern(
1298 const icu::UnicodeString pattern) {
1299 bool in_quote = false;
1300 for (int32_t i = 0; i < pattern.length(); i++) {
1301 char16_t ch = pattern[i];
1302 switch (ch) {
1303 case '\'':
1304 in_quote = !in_quote;
1305 break;
1306 case 'K':
1307 if (!in_quote) return JSDateTimeFormat::HourCycle::kH11;
1308 break;
1309 case 'h':
1310 if (!in_quote) return JSDateTimeFormat::HourCycle::kH12;
1311 break;
1312 case 'H':
1313 if (!in_quote) return JSDateTimeFormat::HourCycle::kH23;
1314 break;
1315 case 'k':
1316 if (!in_quote) return JSDateTimeFormat::HourCycle::kH24;
1317 break;
1318 }
1319 }
1320 return JSDateTimeFormat::HourCycle::kUndefined;
1321 }
1322
DateTimeStyleToEStyle(JSDateTimeFormat::DateTimeStyle style)1323 icu::DateFormat::EStyle DateTimeStyleToEStyle(
1324 JSDateTimeFormat::DateTimeStyle style) {
1325 switch (style) {
1326 case JSDateTimeFormat::DateTimeStyle::kFull:
1327 return icu::DateFormat::EStyle::kFull;
1328 case JSDateTimeFormat::DateTimeStyle::kLong:
1329 return icu::DateFormat::EStyle::kLong;
1330 case JSDateTimeFormat::DateTimeStyle::kMedium:
1331 return icu::DateFormat::EStyle::kMedium;
1332 case JSDateTimeFormat::DateTimeStyle::kShort:
1333 return icu::DateFormat::EStyle::kShort;
1334 case JSDateTimeFormat::DateTimeStyle::kUndefined:
1335 UNREACHABLE();
1336 }
1337 }
1338
ReplaceSkeleton(const icu::UnicodeString input,JSDateTimeFormat::HourCycle hc)1339 icu::UnicodeString ReplaceSkeleton(const icu::UnicodeString input,
1340 JSDateTimeFormat::HourCycle hc) {
1341 icu::UnicodeString result;
1342 char16_t to;
1343 switch (hc) {
1344 case JSDateTimeFormat::HourCycle::kH11:
1345 to = 'K';
1346 break;
1347 case JSDateTimeFormat::HourCycle::kH12:
1348 to = 'h';
1349 break;
1350 case JSDateTimeFormat::HourCycle::kH23:
1351 to = 'H';
1352 break;
1353 case JSDateTimeFormat::HourCycle::kH24:
1354 to = 'k';
1355 break;
1356 case JSDateTimeFormat::HourCycle::kUndefined:
1357 UNREACHABLE();
1358 }
1359 for (int32_t i = 0; i < input.length(); i++) {
1360 switch (input[i]) {
1361 // We need to skip 'a', 'b', 'B' here due to
1362 // https://unicode-org.atlassian.net/browse/ICU-20437
1363 case 'a':
1364 V8_FALLTHROUGH;
1365 case 'b':
1366 V8_FALLTHROUGH;
1367 case 'B':
1368 // ignore
1369 break;
1370 case 'h':
1371 V8_FALLTHROUGH;
1372 case 'H':
1373 V8_FALLTHROUGH;
1374 case 'K':
1375 V8_FALLTHROUGH;
1376 case 'k':
1377 result += to;
1378 break;
1379 default:
1380 result += input[i];
1381 break;
1382 }
1383 }
1384 return result;
1385 }
1386
DateTimeStylePattern(JSDateTimeFormat::DateTimeStyle date_style,JSDateTimeFormat::DateTimeStyle time_style,icu::Locale & icu_locale,JSDateTimeFormat::HourCycle hc,icu::DateTimePatternGenerator * generator)1387 std::unique_ptr<icu::SimpleDateFormat> DateTimeStylePattern(
1388 JSDateTimeFormat::DateTimeStyle date_style,
1389 JSDateTimeFormat::DateTimeStyle time_style, icu::Locale& icu_locale,
1390 JSDateTimeFormat::HourCycle hc, icu::DateTimePatternGenerator* generator) {
1391 std::unique_ptr<icu::SimpleDateFormat> result;
1392 if (date_style != JSDateTimeFormat::DateTimeStyle::kUndefined) {
1393 if (time_style != JSDateTimeFormat::DateTimeStyle::kUndefined) {
1394 result.reset(reinterpret_cast<icu::SimpleDateFormat*>(
1395 icu::DateFormat::createDateTimeInstance(
1396 DateTimeStyleToEStyle(date_style),
1397 DateTimeStyleToEStyle(time_style), icu_locale)));
1398 } else {
1399 result.reset(reinterpret_cast<icu::SimpleDateFormat*>(
1400 icu::DateFormat::createDateInstance(DateTimeStyleToEStyle(date_style),
1401 icu_locale)));
1402 // For instance without time, we do not need to worry about the hour cycle
1403 // impact so we can return directly.
1404 if (result.get() != nullptr) {
1405 return result;
1406 }
1407 }
1408 } else {
1409 if (time_style != JSDateTimeFormat::DateTimeStyle::kUndefined) {
1410 result.reset(reinterpret_cast<icu::SimpleDateFormat*>(
1411 icu::DateFormat::createTimeInstance(DateTimeStyleToEStyle(time_style),
1412 icu_locale)));
1413 } else {
1414 UNREACHABLE();
1415 }
1416 }
1417
1418 UErrorCode status = U_ZERO_ERROR;
1419 // Somehow we fail to create the instance.
1420 if (result.get() == nullptr) {
1421 // Fallback to the locale without "nu".
1422 if (!icu_locale.getUnicodeKeywordValue<std::string>("nu", status).empty()) {
1423 status = U_ZERO_ERROR;
1424 icu_locale.setUnicodeKeywordValue("nu", nullptr, status);
1425 return DateTimeStylePattern(date_style, time_style, icu_locale, hc,
1426 generator);
1427 }
1428 status = U_ZERO_ERROR;
1429 // Fallback to the locale without "hc".
1430 if (!icu_locale.getUnicodeKeywordValue<std::string>("hc", status).empty()) {
1431 status = U_ZERO_ERROR;
1432 icu_locale.setUnicodeKeywordValue("hc", nullptr, status);
1433 return DateTimeStylePattern(date_style, time_style, icu_locale, hc,
1434 generator);
1435 }
1436 status = U_ZERO_ERROR;
1437 // Fallback to the locale without "ca".
1438 if (!icu_locale.getUnicodeKeywordValue<std::string>("ca", status).empty()) {
1439 status = U_ZERO_ERROR;
1440 icu_locale.setUnicodeKeywordValue("ca", nullptr, status);
1441 return DateTimeStylePattern(date_style, time_style, icu_locale, hc,
1442 generator);
1443 }
1444 return nullptr;
1445 }
1446 icu::UnicodeString pattern;
1447 pattern = result->toPattern(pattern);
1448
1449 status = U_ZERO_ERROR;
1450 icu::UnicodeString skeleton =
1451 icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status);
1452 DCHECK(U_SUCCESS(status));
1453
1454 // If the skeleton match the HourCycle, we just return it.
1455 if (hc == HourCycleFromPattern(pattern)) {
1456 return result;
1457 }
1458
1459 return CreateICUDateFormatFromCache(icu_locale, ReplaceSkeleton(skeleton, hc),
1460 generator, hc);
1461 }
1462
1463 class DateTimePatternGeneratorCache {
1464 public:
1465 // Return a clone copy that the caller have to free.
CreateGenerator(Isolate * isolate,const icu::Locale & locale)1466 icu::DateTimePatternGenerator* CreateGenerator(Isolate* isolate,
1467 const icu::Locale& locale) {
1468 std::string key(locale.getName());
1469 base::MutexGuard guard(&mutex_);
1470 auto it = map_.find(key);
1471 icu::DateTimePatternGenerator* orig;
1472 if (it != map_.end()) {
1473 DCHECK(it->second != nullptr);
1474 orig = it->second.get();
1475 } else {
1476 UErrorCode status = U_ZERO_ERROR;
1477 orig = icu::DateTimePatternGenerator::createInstance(locale, status);
1478 // It may not be an U_MEMORY_ALLOCATION_ERROR.
1479 // Fallback to use "root".
1480 if (U_FAILURE(status)) {
1481 status = U_ZERO_ERROR;
1482 orig = icu::DateTimePatternGenerator::createInstance("root", status);
1483 }
1484 if (U_SUCCESS(status) && orig != nullptr) {
1485 map_[key].reset(orig);
1486 } else {
1487 DCHECK(status == U_MEMORY_ALLOCATION_ERROR);
1488 V8::FatalProcessOutOfMemory(
1489 isolate, "DateTimePatternGeneratorCache::CreateGenerator");
1490 }
1491 }
1492 icu::DateTimePatternGenerator* clone = orig ? orig->clone() : nullptr;
1493 if (clone == nullptr) {
1494 V8::FatalProcessOutOfMemory(
1495 isolate, "DateTimePatternGeneratorCache::CreateGenerator");
1496 }
1497 return clone;
1498 }
1499
1500 private:
1501 std::map<std::string, std::unique_ptr<icu::DateTimePatternGenerator>> map_;
1502 base::Mutex mutex_;
1503 };
1504
1505 } // namespace
1506
1507 enum FormatMatcherOption { kBestFit, kBasic };
1508
1509 // ecma402/#sec-initializedatetimeformat
New(Isolate * isolate,Handle<Map> map,Handle<Object> locales,Handle<Object> input_options,const char * service)1510 MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::New(
1511 Isolate* isolate, Handle<Map> map, Handle<Object> locales,
1512 Handle<Object> input_options, const char* service) {
1513 Factory* factory = isolate->factory();
1514 // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
1515 Maybe<std::vector<std::string>> maybe_requested_locales =
1516 Intl::CanonicalizeLocaleList(isolate, locales);
1517 MAYBE_RETURN(maybe_requested_locales, Handle<JSDateTimeFormat>());
1518 std::vector<std::string> requested_locales =
1519 maybe_requested_locales.FromJust();
1520 // 2. Let options be ? ToDateTimeOptions(options, "any", "date").
1521 Handle<JSObject> options;
1522 ASSIGN_RETURN_ON_EXCEPTION(
1523 isolate, options,
1524 JSDateTimeFormat::ToDateTimeOptions(
1525 isolate, input_options, RequiredOption::kAny, DefaultsOption::kDate),
1526 JSDateTimeFormat);
1527
1528 // 4. Let matcher be ? GetOption(options, "localeMatcher", "string",
1529 // « "lookup", "best fit" », "best fit").
1530 // 5. Set opt.[[localeMatcher]] to matcher.
1531 Maybe<Intl::MatcherOption> maybe_locale_matcher =
1532 Intl::GetLocaleMatcher(isolate, options, service);
1533 MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSDateTimeFormat>());
1534 Intl::MatcherOption locale_matcher = maybe_locale_matcher.FromJust();
1535
1536 std::unique_ptr<char[]> calendar_str = nullptr;
1537 std::unique_ptr<char[]> numbering_system_str = nullptr;
1538 const std::vector<const char*> empty_values = {};
1539 // 6. Let calendar be ? GetOption(options, "calendar",
1540 // "string", undefined, undefined).
1541 Maybe<bool> maybe_calendar = GetStringOption(
1542 isolate, options, "calendar", empty_values, service, &calendar_str);
1543 MAYBE_RETURN(maybe_calendar, MaybeHandle<JSDateTimeFormat>());
1544 if (maybe_calendar.FromJust() && calendar_str != nullptr) {
1545 icu::Locale default_locale;
1546 if (!Intl::IsWellFormedCalendar(calendar_str.get())) {
1547 THROW_NEW_ERROR(
1548 isolate,
1549 NewRangeError(MessageTemplate::kInvalid, factory->calendar_string(),
1550 factory->NewStringFromAsciiChecked(calendar_str.get())),
1551 JSDateTimeFormat);
1552 }
1553 }
1554
1555 // 8. Let numberingSystem be ? GetOption(options, "numberingSystem",
1556 // "string", undefined, undefined).
1557 Maybe<bool> maybe_numberingSystem = Intl::GetNumberingSystem(
1558 isolate, options, service, &numbering_system_str);
1559 MAYBE_RETURN(maybe_numberingSystem, MaybeHandle<JSDateTimeFormat>());
1560
1561 // 6. Let hour12 be ? GetOption(options, "hour12", "boolean", undefined,
1562 // undefined).
1563 bool hour12;
1564 Maybe<bool> maybe_get_hour12 =
1565 GetBoolOption(isolate, options, "hour12", service, &hour12);
1566 MAYBE_RETURN(maybe_get_hour12, Handle<JSDateTimeFormat>());
1567
1568 // 7. Let hourCycle be ? GetOption(options, "hourCycle", "string", « "h11",
1569 // "h12", "h23", "h24" », undefined).
1570 Maybe<HourCycle> maybe_hour_cycle = GetHourCycle(isolate, options, service);
1571 MAYBE_RETURN(maybe_hour_cycle, MaybeHandle<JSDateTimeFormat>());
1572 HourCycle hour_cycle = maybe_hour_cycle.FromJust();
1573
1574 // 8. If hour12 is not undefined, then
1575 if (maybe_get_hour12.FromJust()) {
1576 // a. Let hourCycle be null.
1577 hour_cycle = HourCycle::kUndefined;
1578 }
1579 // 9. Set opt.[[hc]] to hourCycle.
1580
1581 // ecma402/#sec-intl.datetimeformat-internal-slots
1582 // The value of the [[RelevantExtensionKeys]] internal slot is
1583 // « "ca", "nu", "hc" ».
1584 std::set<std::string> relevant_extension_keys = {"nu", "ca", "hc"};
1585
1586 // 10. Let localeData be %DateTimeFormat%.[[LocaleData]].
1587 // 11. Let r be ResolveLocale( %DateTimeFormat%.[[AvailableLocales]],
1588 // requestedLocales, opt, %DateTimeFormat%.[[RelevantExtensionKeys]],
1589 // localeData).
1590 //
1591 Maybe<Intl::ResolvedLocale> maybe_resolve_locale = Intl::ResolveLocale(
1592 isolate, JSDateTimeFormat::GetAvailableLocales(), requested_locales,
1593 locale_matcher, relevant_extension_keys);
1594 if (maybe_resolve_locale.IsNothing()) {
1595 THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
1596 JSDateTimeFormat);
1597 }
1598 Intl::ResolvedLocale r = maybe_resolve_locale.FromJust();
1599
1600 icu::Locale icu_locale = r.icu_locale;
1601 DCHECK(!icu_locale.isBogus());
1602
1603 UErrorCode status = U_ZERO_ERROR;
1604 if (calendar_str != nullptr) {
1605 auto ca_extension_it = r.extensions.find("ca");
1606 if (ca_extension_it != r.extensions.end() &&
1607 ca_extension_it->second != calendar_str.get()) {
1608 icu_locale.setUnicodeKeywordValue("ca", nullptr, status);
1609 DCHECK(U_SUCCESS(status));
1610 }
1611 }
1612 if (numbering_system_str != nullptr) {
1613 auto nu_extension_it = r.extensions.find("nu");
1614 if (nu_extension_it != r.extensions.end() &&
1615 nu_extension_it->second != numbering_system_str.get()) {
1616 icu_locale.setUnicodeKeywordValue("nu", nullptr, status);
1617 DCHECK(U_SUCCESS(status));
1618 }
1619 }
1620
1621 // Need to keep a copy of icu_locale which not changing "ca", "nu", "hc"
1622 // by option.
1623 icu::Locale resolved_locale(icu_locale);
1624
1625 if (calendar_str != nullptr &&
1626 Intl::IsValidCalendar(icu_locale, calendar_str.get())) {
1627 icu_locale.setUnicodeKeywordValue("ca", calendar_str.get(), status);
1628 DCHECK(U_SUCCESS(status));
1629 }
1630 bool alt_calendar =
1631 strstr(icu_locale.getName(), "calendar=iso8601") != nullptr ||
1632 strstr(icu_locale.getName(), "calendar=islamic-rgsa") != nullptr;
1633
1634 if (numbering_system_str != nullptr &&
1635 Intl::IsValidNumberingSystem(numbering_system_str.get())) {
1636 icu_locale.setUnicodeKeywordValue("nu", numbering_system_str.get(), status);
1637 DCHECK(U_SUCCESS(status));
1638 }
1639
1640 static base::LazyInstance<DateTimePatternGeneratorCache>::type
1641 generator_cache = LAZY_INSTANCE_INITIALIZER;
1642
1643 std::unique_ptr<icu::DateTimePatternGenerator> generator(
1644 generator_cache.Pointer()->CreateGenerator(isolate, icu_locale));
1645
1646 // 15.Let hcDefault be dataLocaleData.[[hourCycle]].
1647 HourCycle hc_default = ToHourCycle(generator->getDefaultHourCycle(status));
1648 DCHECK(U_SUCCESS(status));
1649
1650 // 16.Let hc be r.[[hc]].
1651 HourCycle hc = HourCycle::kUndefined;
1652 if (hour_cycle == HourCycle::kUndefined) {
1653 auto hc_extension_it = r.extensions.find("hc");
1654 if (hc_extension_it != r.extensions.end()) {
1655 hc = ToHourCycle(hc_extension_it->second.c_str());
1656 }
1657 } else {
1658 hc = hour_cycle;
1659 }
1660 // 17. If hc is null, then
1661 if (hc == HourCycle::kUndefined) {
1662 // a. Set hc to hcDefault.
1663 hc = hc_default;
1664 }
1665
1666 // 18. If hour12 is not undefined, then
1667 if (maybe_get_hour12.FromJust()) {
1668 // a. If hour12 is true, then
1669 if (hour12) {
1670 // i. If hcDefault is "h11" or "h23", then
1671 if (hc_default == HourCycle::kH11 || hc_default == HourCycle::kH23) {
1672 // 1. Set hc to "h11".
1673 hc = HourCycle::kH11;
1674 // ii. Else,
1675 } else {
1676 // 1. Set hc to "h12".
1677 hc = HourCycle::kH12;
1678 }
1679 // b. Else,
1680 } else {
1681 // ii. If hcDefault is "h11" or "h23", then
1682 if (hc_default == HourCycle::kH11 || hc_default == HourCycle::kH23) {
1683 // 1. Set hc to "h23".
1684 hc = HourCycle::kH23;
1685 // iii. Else,
1686 } else {
1687 // 1. Set hc to "h24".
1688 hc = HourCycle::kH24;
1689 }
1690 }
1691 }
1692
1693 // 17. Let timeZone be ? Get(options, "timeZone").
1694 std::unique_ptr<char[]> timezone = nullptr;
1695 Maybe<bool> maybe_timezone = GetStringOption(
1696 isolate, options, "timeZone", empty_values, service, &timezone);
1697 MAYBE_RETURN(maybe_timezone, Handle<JSDateTimeFormat>());
1698
1699 std::unique_ptr<icu::TimeZone> tz =
1700 JSDateTimeFormat::CreateTimeZone(timezone.get());
1701 if (tz.get() == nullptr) {
1702 THROW_NEW_ERROR(
1703 isolate,
1704 NewRangeError(MessageTemplate::kInvalidTimeZone,
1705 factory->NewStringFromAsciiChecked(timezone.get())),
1706 JSDateTimeFormat);
1707 }
1708
1709 std::unique_ptr<icu::Calendar> calendar(
1710 CreateCalendar(isolate, icu_locale, tz.release()));
1711
1712 // 18.b If the result of IsValidTimeZoneName(timeZone) is false, then
1713 // i. Throw a RangeError exception.
1714 if (calendar.get() == nullptr) {
1715 THROW_NEW_ERROR(
1716 isolate,
1717 NewRangeError(MessageTemplate::kInvalidTimeZone,
1718 factory->NewStringFromAsciiChecked(timezone.get())),
1719 JSDateTimeFormat);
1720 }
1721
1722 DateTimeStyle date_style = DateTimeStyle::kUndefined;
1723 DateTimeStyle time_style = DateTimeStyle::kUndefined;
1724 std::unique_ptr<icu::SimpleDateFormat> icu_date_format;
1725
1726 // 28. For each row of Table 1, except the header row, do
1727 bool has_hour_option = false;
1728 std::string skeleton;
1729 for (const PatternData& item : GetPatternData(hc)) {
1730 // Need to read fractionalSecondDigits before reading the timeZoneName
1731 if (item.property == "timeZoneName") {
1732 // Let _value_ be ? GetNumberOption(options, "fractionalSecondDigits", 1,
1733 // 3, *undefined*). The *undefined* is represented by value 0 here.
1734 Maybe<int> maybe_fsd = GetNumberOption(
1735 isolate, options, factory->fractionalSecondDigits_string(), 1, 3, 0);
1736 MAYBE_RETURN(maybe_fsd, MaybeHandle<JSDateTimeFormat>());
1737 // Convert fractionalSecondDigits to skeleton.
1738 int fsd = maybe_fsd.FromJust();
1739 for (int i = 0; i < fsd; i++) {
1740 skeleton += "S";
1741 }
1742 }
1743 std::unique_ptr<char[]> input;
1744 // i. Let prop be the name given in the Property column of the row.
1745 // ii. Let value be ? GetOption(options, prop, "string", « the strings
1746 // given in the Values column of the row », undefined).
1747 Maybe<bool> maybe_get_option =
1748 GetStringOption(isolate, options, item.property.c_str(),
1749 item.allowed_values, service, &input);
1750 MAYBE_RETURN(maybe_get_option, Handle<JSDateTimeFormat>());
1751 if (maybe_get_option.FromJust()) {
1752 if (item.property == "hour") {
1753 has_hour_option = true;
1754 }
1755 DCHECK_NOT_NULL(input.get());
1756 // iii. Set opt.[[<prop>]] to value.
1757 skeleton += item.map.find(input.get())->second;
1758 }
1759 }
1760
1761 // 29. Let matcher be ? GetOption(options, "formatMatcher", "string", «
1762 // "basic", "best fit" », "best fit").
1763 // We implement only best fit algorithm, but still need to check
1764 // if the formatMatcher values are in range.
1765 // c. Let matcher be ? GetOption(options, "formatMatcher", "string",
1766 // « "basic", "best fit" », "best fit").
1767 Maybe<FormatMatcherOption> maybe_format_matcher =
1768 GetStringOption<FormatMatcherOption>(
1769 isolate, options, "formatMatcher", service, {"best fit", "basic"},
1770 {FormatMatcherOption::kBestFit, FormatMatcherOption::kBasic},
1771 FormatMatcherOption::kBestFit);
1772 MAYBE_RETURN(maybe_format_matcher, MaybeHandle<JSDateTimeFormat>());
1773 // TODO(ftang): uncomment the following line and handle format_matcher.
1774 // FormatMatcherOption format_matcher = maybe_format_matcher.FromJust();
1775
1776 // 32. Let dateStyle be ? GetOption(options, "dateStyle", "string", «
1777 // "full", "long", "medium", "short" », undefined).
1778 Maybe<DateTimeStyle> maybe_date_style = GetStringOption<DateTimeStyle>(
1779 isolate, options, "dateStyle", service,
1780 {"full", "long", "medium", "short"},
1781 {DateTimeStyle::kFull, DateTimeStyle::kLong, DateTimeStyle::kMedium,
1782 DateTimeStyle::kShort},
1783 DateTimeStyle::kUndefined);
1784 MAYBE_RETURN(maybe_date_style, MaybeHandle<JSDateTimeFormat>());
1785 // 33. Set dateTimeFormat.[[DateStyle]] to dateStyle.
1786 date_style = maybe_date_style.FromJust();
1787
1788 // 34. Let timeStyle be ? GetOption(options, "timeStyle", "string", «
1789 // "full", "long", "medium", "short" »).
1790 Maybe<DateTimeStyle> maybe_time_style = GetStringOption<DateTimeStyle>(
1791 isolate, options, "timeStyle", service,
1792 {"full", "long", "medium", "short"},
1793 {DateTimeStyle::kFull, DateTimeStyle::kLong, DateTimeStyle::kMedium,
1794 DateTimeStyle::kShort},
1795 DateTimeStyle::kUndefined);
1796 MAYBE_RETURN(maybe_time_style, MaybeHandle<JSDateTimeFormat>());
1797
1798 // 35. Set dateTimeFormat.[[TimeStyle]] to timeStyle.
1799 time_style = maybe_time_style.FromJust();
1800
1801 // 36. If timeStyle is not undefined, then
1802 HourCycle dateTimeFormatHourCycle = HourCycle::kUndefined;
1803 if (time_style != DateTimeStyle::kUndefined) {
1804 // a. Set dateTimeFormat.[[HourCycle]] to hc.
1805 dateTimeFormatHourCycle = hc;
1806 }
1807
1808 // 37. If dateStyle or timeStyle are not undefined, then
1809 if (date_style != DateTimeStyle::kUndefined ||
1810 time_style != DateTimeStyle::kUndefined) {
1811 // a. For each row in Table 1, except the header row, do
1812 // i. Let prop be the name given in the Property column of the row.
1813 // ii. Let p be opt.[[<prop>]].
1814 // iii. If p is not undefined, then
1815 // 1. Throw a TypeError exception.
1816 if (skeleton.length() > 0) {
1817 std::string prop;
1818 for (const auto& item : GetPatternItems()) {
1819 for (const auto& pair : item.pairs) {
1820 if (skeleton.find(pair.pattern) != std::string::npos) {
1821 prop.assign(item.property);
1822 break;
1823 }
1824 }
1825 if (!prop.empty()) {
1826 break;
1827 }
1828 }
1829 if (prop.empty() && skeleton.find("S") != std::string::npos) {
1830 prop.assign("fractionalSecondDigits");
1831 }
1832 if (!prop.empty()) {
1833 THROW_NEW_ERROR(
1834 isolate,
1835 NewTypeError(MessageTemplate::kCantSetOptionXWhenYIsUsed,
1836 factory->NewStringFromAsciiChecked(prop.c_str()),
1837 date_style != DateTimeStyle::kUndefined
1838 ? factory->dateStyle_string()
1839 : factory->timeStyle_string()),
1840 JSDateTimeFormat);
1841 }
1842 UNREACHABLE();
1843 }
1844 // b. Let pattern be DateTimeStylePattern(dateStyle, timeStyle,
1845 // dataLocaleData, hc).
1846 isolate->CountUsage(
1847 v8::Isolate::UseCounterFeature::kDateTimeFormatDateTimeStyle);
1848
1849 icu_date_format =
1850 DateTimeStylePattern(date_style, time_style, icu_locale,
1851 dateTimeFormatHourCycle, generator.get());
1852 if (icu_date_format.get() == nullptr) {
1853 THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
1854 JSDateTimeFormat);
1855 }
1856 } else {
1857 // e. If dateTimeFormat.[[Hour]] is not undefined, then
1858 if (has_hour_option) {
1859 // v. Set dateTimeFormat.[[HourCycle]] to hc.
1860 dateTimeFormatHourCycle = hc;
1861 } else {
1862 // f. Else,
1863 // Set dateTimeFormat.[[HourCycle]] to undefined.
1864 dateTimeFormatHourCycle = HourCycle::kUndefined;
1865 }
1866 icu::UnicodeString skeleton_ustr(skeleton.c_str());
1867 icu_date_format = CreateICUDateFormatFromCache(
1868 icu_locale, skeleton_ustr, generator.get(), dateTimeFormatHourCycle);
1869 if (icu_date_format.get() == nullptr) {
1870 // Remove extensions and try again.
1871 icu_locale = icu::Locale(icu_locale.getBaseName());
1872 icu_date_format = CreateICUDateFormatFromCache(
1873 icu_locale, skeleton_ustr, generator.get(), dateTimeFormatHourCycle);
1874 if (icu_date_format.get() == nullptr) {
1875 THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
1876 JSDateTimeFormat);
1877 }
1878 }
1879 }
1880
1881 // The creation of Calendar depends on timeZone so we have to put 13 after 17.
1882 // Also icu_date_format is not created until here.
1883 // 13. Set dateTimeFormat.[[Calendar]] to r.[[ca]].
1884 icu_date_format->adoptCalendar(calendar.release());
1885
1886 // 12.1.1 InitializeDateTimeFormat ( dateTimeFormat, locales, options )
1887 //
1888 // Steps 8-9 set opt.[[hc]] to value *other than undefined*
1889 // if "hour12" is set or "hourCycle" is set in the option.
1890 //
1891 // 9.2.6 ResolveLocale (... )
1892 // Step 8.h / 8.i and 8.k
1893 //
1894 // An hour12 option always overrides an hourCycle option.
1895 // Additionally hour12 and hourCycle both clear out any existing Unicode
1896 // extension key in the input locale.
1897 //
1898 // See details in https://github.com/tc39/test262/pull/2035
1899 if (maybe_get_hour12.FromJust() ||
1900 maybe_hour_cycle.FromJust() != HourCycle::kUndefined) {
1901 auto hc_extension_it = r.extensions.find("hc");
1902 if (hc_extension_it != r.extensions.end()) {
1903 if (dateTimeFormatHourCycle !=
1904 ToHourCycle(hc_extension_it->second.c_str())) {
1905 // Remove -hc- if it does not agree with what we used.
1906 status = U_ZERO_ERROR;
1907 resolved_locale.setUnicodeKeywordValue("hc", nullptr, status);
1908 DCHECK(U_SUCCESS(status));
1909 }
1910 }
1911 }
1912
1913 Maybe<std::string> maybe_locale_str = Intl::ToLanguageTag(resolved_locale);
1914 MAYBE_RETURN(maybe_locale_str, MaybeHandle<JSDateTimeFormat>());
1915 Handle<String> locale_str = isolate->factory()->NewStringFromAsciiChecked(
1916 maybe_locale_str.FromJust().c_str());
1917
1918 Handle<Managed<icu::Locale>> managed_locale =
1919 Managed<icu::Locale>::FromRawPtr(isolate, 0, icu_locale.clone());
1920
1921 Handle<Managed<icu::SimpleDateFormat>> managed_format =
1922 Managed<icu::SimpleDateFormat>::FromUniquePtr(isolate, 0,
1923 std::move(icu_date_format));
1924
1925 Handle<Managed<icu::DateIntervalFormat>> managed_interval_format =
1926 Managed<icu::DateIntervalFormat>::FromRawPtr(isolate, 0, nullptr);
1927
1928 // Now all properties are ready, so we can allocate the result object.
1929 Handle<JSDateTimeFormat> date_time_format = Handle<JSDateTimeFormat>::cast(
1930 isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
1931 DisallowGarbageCollection no_gc;
1932 date_time_format->set_flags(0);
1933 if (date_style != DateTimeStyle::kUndefined) {
1934 date_time_format->set_date_style(date_style);
1935 }
1936 if (time_style != DateTimeStyle::kUndefined) {
1937 date_time_format->set_time_style(time_style);
1938 }
1939 date_time_format->set_hour_cycle(dateTimeFormatHourCycle);
1940 date_time_format->set_alt_calendar(alt_calendar);
1941 date_time_format->set_locale(*locale_str);
1942 date_time_format->set_icu_locale(*managed_locale);
1943 date_time_format->set_icu_simple_date_format(*managed_format);
1944 date_time_format->set_icu_date_interval_format(*managed_interval_format);
1945 return date_time_format;
1946 }
1947
1948 namespace {
1949
1950 // The list comes from third_party/icu/source/i18n/unicode/udat.h.
1951 // They're mapped to DateTimeFormat components listed at
1952 // https://tc39.github.io/ecma402/#sec-datetimeformat-abstracts .
IcuDateFieldIdToDateType(int32_t field_id,Isolate * isolate)1953 Handle<String> IcuDateFieldIdToDateType(int32_t field_id, Isolate* isolate) {
1954 switch (field_id) {
1955 case -1:
1956 return isolate->factory()->literal_string();
1957 case UDAT_YEAR_FIELD:
1958 case UDAT_EXTENDED_YEAR_FIELD:
1959 return isolate->factory()->year_string();
1960 case UDAT_YEAR_NAME_FIELD:
1961 return isolate->factory()->yearName_string();
1962 case UDAT_MONTH_FIELD:
1963 case UDAT_STANDALONE_MONTH_FIELD:
1964 return isolate->factory()->month_string();
1965 case UDAT_DATE_FIELD:
1966 return isolate->factory()->day_string();
1967 case UDAT_HOUR_OF_DAY1_FIELD:
1968 case UDAT_HOUR_OF_DAY0_FIELD:
1969 case UDAT_HOUR1_FIELD:
1970 case UDAT_HOUR0_FIELD:
1971 return isolate->factory()->hour_string();
1972 case UDAT_MINUTE_FIELD:
1973 return isolate->factory()->minute_string();
1974 case UDAT_SECOND_FIELD:
1975 return isolate->factory()->second_string();
1976 case UDAT_DAY_OF_WEEK_FIELD:
1977 case UDAT_DOW_LOCAL_FIELD:
1978 case UDAT_STANDALONE_DAY_FIELD:
1979 return isolate->factory()->weekday_string();
1980 case UDAT_AM_PM_FIELD:
1981 case UDAT_AM_PM_MIDNIGHT_NOON_FIELD:
1982 case UDAT_FLEXIBLE_DAY_PERIOD_FIELD:
1983 return isolate->factory()->dayPeriod_string();
1984 case UDAT_TIMEZONE_FIELD:
1985 case UDAT_TIMEZONE_RFC_FIELD:
1986 case UDAT_TIMEZONE_GENERIC_FIELD:
1987 case UDAT_TIMEZONE_SPECIAL_FIELD:
1988 case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD:
1989 case UDAT_TIMEZONE_ISO_FIELD:
1990 case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
1991 return isolate->factory()->timeZoneName_string();
1992 case UDAT_ERA_FIELD:
1993 return isolate->factory()->era_string();
1994 case UDAT_FRACTIONAL_SECOND_FIELD:
1995 return isolate->factory()->fractionalSecond_string();
1996 case UDAT_RELATED_YEAR_FIELD:
1997 return isolate->factory()->relatedYear_string();
1998
1999 case UDAT_QUARTER_FIELD:
2000 case UDAT_STANDALONE_QUARTER_FIELD:
2001 default:
2002 // Other UDAT_*_FIELD's cannot show up because there is no way to specify
2003 // them via options of Intl.DateTimeFormat.
2004 UNREACHABLE();
2005 }
2006 }
2007
2008 } // namespace
2009
FormatToParts(Isolate * isolate,Handle<JSDateTimeFormat> date_time_format,double date_value,bool output_source)2010 MaybeHandle<JSArray> JSDateTimeFormat::FormatToParts(
2011 Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
2012 double date_value, bool output_source) {
2013 Factory* factory = isolate->factory();
2014 icu::SimpleDateFormat* format =
2015 date_time_format->icu_simple_date_format().raw();
2016 DCHECK_NOT_NULL(format);
2017
2018 icu::UnicodeString formatted;
2019 icu::FieldPositionIterator fp_iter;
2020 icu::FieldPosition fp;
2021 UErrorCode status = U_ZERO_ERROR;
2022 format->format(date_value, formatted, &fp_iter, status);
2023 if (U_FAILURE(status)) {
2024 THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), JSArray);
2025 }
2026
2027 Handle<JSArray> result = factory->NewJSArray(0);
2028 int32_t length = formatted.length();
2029 if (length == 0) return result;
2030
2031 int index = 0;
2032 int32_t previous_end_pos = 0;
2033 Handle<String> substring;
2034 while (fp_iter.next(fp)) {
2035 int32_t begin_pos = fp.getBeginIndex();
2036 int32_t end_pos = fp.getEndIndex();
2037
2038 if (previous_end_pos < begin_pos) {
2039 ASSIGN_RETURN_ON_EXCEPTION(
2040 isolate, substring,
2041 Intl::ToString(isolate, formatted, previous_end_pos, begin_pos),
2042 JSArray);
2043 if (output_source) {
2044 Intl::AddElement(isolate, result, index,
2045 IcuDateFieldIdToDateType(-1, isolate), substring,
2046 isolate->factory()->source_string(),
2047 isolate->factory()->shared_string());
2048 } else {
2049 Intl::AddElement(isolate, result, index,
2050 IcuDateFieldIdToDateType(-1, isolate), substring);
2051 }
2052 ++index;
2053 }
2054 ASSIGN_RETURN_ON_EXCEPTION(
2055 isolate, substring,
2056 Intl::ToString(isolate, formatted, begin_pos, end_pos), JSArray);
2057 if (output_source) {
2058 Intl::AddElement(isolate, result, index,
2059 IcuDateFieldIdToDateType(fp.getField(), isolate),
2060 substring, isolate->factory()->source_string(),
2061 isolate->factory()->shared_string());
2062 } else {
2063 Intl::AddElement(isolate, result, index,
2064 IcuDateFieldIdToDateType(fp.getField(), isolate),
2065 substring);
2066 }
2067 previous_end_pos = end_pos;
2068 ++index;
2069 }
2070 if (previous_end_pos < length) {
2071 ASSIGN_RETURN_ON_EXCEPTION(
2072 isolate, substring,
2073 Intl::ToString(isolate, formatted, previous_end_pos, length), JSArray);
2074 if (output_source) {
2075 Intl::AddElement(isolate, result, index,
2076 IcuDateFieldIdToDateType(-1, isolate), substring,
2077 isolate->factory()->source_string(),
2078 isolate->factory()->shared_string());
2079 } else {
2080 Intl::AddElement(isolate, result, index,
2081 IcuDateFieldIdToDateType(-1, isolate), substring);
2082 }
2083 }
2084 JSObject::ValidateElements(*result);
2085 return result;
2086 }
2087
GetAvailableLocales()2088 const std::set<std::string>& JSDateTimeFormat::GetAvailableLocales() {
2089 return Intl::GetAvailableLocalesForDateFormat();
2090 }
2091
HourCycleAsString() const2092 Handle<String> JSDateTimeFormat::HourCycleAsString() const {
2093 switch (hour_cycle()) {
2094 case HourCycle::kUndefined:
2095 return GetReadOnlyRoots().undefined_string_handle();
2096 case HourCycle::kH11:
2097 return GetReadOnlyRoots().h11_string_handle();
2098 case HourCycle::kH12:
2099 return GetReadOnlyRoots().h12_string_handle();
2100 case HourCycle::kH23:
2101 return GetReadOnlyRoots().h23_string_handle();
2102 case HourCycle::kH24:
2103 return GetReadOnlyRoots().h24_string_handle();
2104 default:
2105 UNREACHABLE();
2106 }
2107 }
2108
2109 namespace {
2110
AddPartForFormatRange(Isolate * isolate,Handle<JSArray> array,const icu::UnicodeString & string,int32_t index,int32_t field,int32_t start,int32_t end,const Intl::FormatRangeSourceTracker & tracker)2111 Maybe<bool> AddPartForFormatRange(
2112 Isolate* isolate, Handle<JSArray> array, const icu::UnicodeString& string,
2113 int32_t index, int32_t field, int32_t start, int32_t end,
2114 const Intl::FormatRangeSourceTracker& tracker) {
2115 Handle<String> substring;
2116 ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, substring,
2117 Intl::ToString(isolate, string, start, end),
2118 Nothing<bool>());
2119 Intl::AddElement(isolate, array, index,
2120 IcuDateFieldIdToDateType(field, isolate), substring,
2121 isolate->factory()->source_string(),
2122 Intl::SourceString(isolate, tracker.GetSource(start, end)));
2123 return Just(true);
2124 }
2125
FormattedToString(Isolate * isolate,const icu::FormattedValue & formatted,bool * outputRange)2126 MaybeHandle<String> FormattedToString(Isolate* isolate,
2127 const icu::FormattedValue& formatted,
2128 bool* outputRange) {
2129 UErrorCode status = U_ZERO_ERROR;
2130 icu::UnicodeString result = formatted.toString(status);
2131 if (U_FAILURE(status)) {
2132 THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), String);
2133 }
2134 *outputRange = false;
2135 icu::ConstrainedFieldPosition cfpos;
2136 while (formatted.nextPosition(cfpos, status)) {
2137 if (cfpos.getCategory() == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) {
2138 *outputRange = true;
2139 break;
2140 }
2141 }
2142 return Intl::ToString(isolate, result);
2143 }
2144
2145 // A helper function to convert the FormattedDateInterval to a
2146 // MaybeHandle<JSArray> for the implementation of formatRangeToParts.
FormattedDateIntervalToJSArray(Isolate * isolate,const icu::FormattedValue & formatted,bool * outputRange)2147 MaybeHandle<JSArray> FormattedDateIntervalToJSArray(
2148 Isolate* isolate, const icu::FormattedValue& formatted, bool* outputRange) {
2149 UErrorCode status = U_ZERO_ERROR;
2150 icu::UnicodeString result = formatted.toString(status);
2151
2152 Factory* factory = isolate->factory();
2153 Handle<JSArray> array = factory->NewJSArray(0);
2154 icu::ConstrainedFieldPosition cfpos;
2155 int index = 0;
2156 int32_t previous_end_pos = 0;
2157 Intl::FormatRangeSourceTracker tracker;
2158 *outputRange = false;
2159 while (formatted.nextPosition(cfpos, status)) {
2160 int32_t category = cfpos.getCategory();
2161 int32_t field = cfpos.getField();
2162 int32_t start = cfpos.getStart();
2163 int32_t limit = cfpos.getLimit();
2164
2165 if (category == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) {
2166 DCHECK_LE(field, 2);
2167 *outputRange = true;
2168 tracker.Add(field, start, limit);
2169 } else {
2170 DCHECK(category == UFIELD_CATEGORY_DATE);
2171 if (start > previous_end_pos) {
2172 // Add "literal" from the previous end position to the start if
2173 // necessary.
2174 Maybe<bool> maybe_added =
2175 AddPartForFormatRange(isolate, array, result, index, -1,
2176 previous_end_pos, start, tracker);
2177 MAYBE_RETURN(maybe_added, Handle<JSArray>());
2178 previous_end_pos = start;
2179 index++;
2180 }
2181 Maybe<bool> maybe_added = AddPartForFormatRange(
2182 isolate, array, result, index, field, start, limit, tracker);
2183 MAYBE_RETURN(maybe_added, Handle<JSArray>());
2184 previous_end_pos = limit;
2185 ++index;
2186 }
2187 }
2188 int32_t end = result.length();
2189 // Add "literal" in the end if necessary.
2190 if (end > previous_end_pos) {
2191 Maybe<bool> maybe_added = AddPartForFormatRange(
2192 isolate, array, result, index, -1, previous_end_pos, end, tracker);
2193 MAYBE_RETURN(maybe_added, Handle<JSArray>());
2194 }
2195
2196 if (U_FAILURE(status)) {
2197 THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), JSArray);
2198 }
2199
2200 JSObject::ValidateElements(*array);
2201 return array;
2202 }
2203
2204 // The shared code between formatRange and formatRangeToParts
2205 template <typename T,
2206 MaybeHandle<T> (*F)(Isolate*, const icu::FormattedValue&, bool*)>
FormatRangeCommon(Isolate * isolate,Handle<JSDateTimeFormat> date_time_format,double x,double y,bool * outputRange)2207 MaybeHandle<T> FormatRangeCommon(Isolate* isolate,
2208 Handle<JSDateTimeFormat> date_time_format,
2209 double x, double y, bool* outputRange) {
2210 // Track newer feature formateRange and formatRangeToParts
2211 isolate->CountUsage(v8::Isolate::UseCounterFeature::kDateTimeFormatRange);
2212
2213 // #sec-partitiondatetimerangepattern
2214 // 1. Let x be TimeClip(x).
2215 x = DateCache::TimeClip(x);
2216 // 2. If x is NaN, throw a RangeError exception.
2217 if (std::isnan(x)) {
2218 THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
2219 T);
2220 }
2221 // 3. Let y be TimeClip(y).
2222 y = DateCache::TimeClip(y);
2223 // 4. If y is NaN, throw a RangeError exception.
2224 if (std::isnan(y)) {
2225 THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
2226 T);
2227 }
2228
2229 icu::DateIntervalFormat* format =
2230 LazyCreateDateIntervalFormat(isolate, date_time_format);
2231 if (format == nullptr) {
2232 THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), T);
2233 }
2234
2235 UErrorCode status = U_ZERO_ERROR;
2236
2237 icu::SimpleDateFormat* date_format =
2238 date_time_format->icu_simple_date_format().raw();
2239 const icu::Calendar* calendar = date_format->getCalendar();
2240 std::unique_ptr<icu::Calendar> c1(calendar->clone());
2241 std::unique_ptr<icu::Calendar> c2(calendar->clone());
2242 c1->setTime(x, status);
2243 c2->setTime(y, status);
2244 // We need to format by Calendar because we need the Gregorian change
2245 // adjustment already in the SimpleDateFormat to set the correct value of date
2246 // older than Oct 15, 1582.
2247 icu::FormattedDateInterval formatted =
2248 format->formatToValue(*c1, *c2, status);
2249 if (U_FAILURE(status)) {
2250 THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), T);
2251 }
2252 return F(isolate, formatted, outputRange);
2253 }
2254
2255 } // namespace
2256
FormatRange(Isolate * isolate,Handle<JSDateTimeFormat> date_time_format,double x,double y)2257 MaybeHandle<String> JSDateTimeFormat::FormatRange(
2258 Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x,
2259 double y) {
2260 bool outputRange = true;
2261 MaybeHandle<String> ret = FormatRangeCommon<String, FormattedToString>(
2262 isolate, date_time_format, x, y, &outputRange);
2263 if (outputRange) {
2264 return ret;
2265 }
2266 return FormatDateTime(isolate,
2267 *(date_time_format->icu_simple_date_format().raw()), x);
2268 }
2269
FormatRangeToParts(Isolate * isolate,Handle<JSDateTimeFormat> date_time_format,double x,double y)2270 MaybeHandle<JSArray> JSDateTimeFormat::FormatRangeToParts(
2271 Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x,
2272 double y) {
2273 bool outputRange = true;
2274 MaybeHandle<JSArray> ret =
2275 FormatRangeCommon<JSArray, FormattedDateIntervalToJSArray>(
2276 isolate, date_time_format, x, y, &outputRange);
2277 if (outputRange) {
2278 return ret;
2279 }
2280 return JSDateTimeFormat::FormatToParts(isolate, date_time_format, x, true);
2281 }
2282
2283 } // namespace internal
2284 } // namespace v8
2285