• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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