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