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