LCOV - code coverage report
Current view: top level - src/objects - js-date-time-format.cc (source / functions) Hit Total Coverage
Test: app.info Lines: 324 343 94.5 %
Date: 2019-01-20 Functions: 37 40 92.5 %

          Line data    Source code
       1             : // Copyright 2018 the V8 project authors. All rights reserved.
       2             : // Use of this source code is governed by a BSD-style license that can be
       3             : // found in the LICENSE file.
       4             : 
       5             : #ifndef V8_INTL_SUPPORT
       6             : #error Internationalization is expected to be enabled.
       7             : #endif  // V8_INTL_SUPPORT
       8             : 
       9             : #include "src/objects/js-date-time-format.h"
      10             : 
      11             : #include <memory>
      12             : #include <string>
      13             : #include <vector>
      14             : 
      15             : #include "src/date.h"
      16             : #include "src/heap/factory.h"
      17             : #include "src/isolate.h"
      18             : #include "src/objects/intl-objects.h"
      19             : #include "src/objects/js-date-time-format-inl.h"
      20             : 
      21             : #include "unicode/calendar.h"
      22             : #include "unicode/dtptngen.h"
      23             : #include "unicode/gregocal.h"
      24             : #include "unicode/smpdtfmt.h"
      25             : #include "unicode/unistr.h"
      26             : 
      27             : namespace v8 {
      28             : namespace internal {
      29             : 
      30             : namespace {
      31             : 
      32     1737252 : class PatternMap {
      33             :  public:
      34             :   PatternMap(std::string pattern, std::string value)
      35      668556 :       : pattern(std::move(pattern)), value(std::move(value)) {}
      36     4811616 :   virtual ~PatternMap() = default;
      37             :   std::string pattern;
      38             :   std::string value;
      39             : };
      40             : 
      41      155412 : class PatternItem {
      42             :  public:
      43      155412 :   PatternItem(const std::string property, std::vector<PatternMap> pairs,
      44             :               std::vector<const char*> allowed_values)
      45             :       : property(std::move(property)),
      46             :         pairs(std::move(pairs)),
      47      310824 :         allowed_values(allowed_values) {}
      48      932472 :   virtual ~PatternItem() = default;
      49             : 
      50             :   const std::string property;
      51             :   // It is important for the pattern in the pairs from longer one to shorter one
      52             :   // if the longer one contains substring of an shorter one.
      53             :   std::vector<PatternMap> pairs;
      54             :   std::vector<const char*> allowed_values;
      55             : };
      56             : 
      57       17268 : const std::vector<PatternItem> GetPatternItems() {
      58             :   const std::vector<const char*> kLongShort = {"long", "short"};
      59             :   const std::vector<const char*> kNarrowLongShort = {"narrow", "long", "short"};
      60             :   const std::vector<const char*> k2DigitNumeric = {"2-digit", "numeric"};
      61             :   const std::vector<const char*> kNarrowLongShort2DigitNumeric = {
      62             :       "narrow", "long", "short", "2-digit", "numeric"};
      63             :   const std::vector<PatternItem> kPatternItems = {
      64             :       PatternItem("weekday",
      65             :                   {{"EEEEE", "narrow"},
      66             :                    {"EEEE", "long"},
      67             :                    {"EEE", "short"},
      68             :                    {"ccccc", "narrow"},
      69             :                    {"cccc", "long"},
      70             :                    {"ccc", "short"}},
      71      103608 :                   kNarrowLongShort),
      72             :       PatternItem("era",
      73             :                   {{"GGGGG", "narrow"}, {"GGGG", "long"}, {"GGG", "short"}},
      74       51804 :                   kNarrowLongShort),
      75             :       PatternItem("year", {{"yy", "2-digit"}, {"y", "numeric"}},
      76       34536 :                   k2DigitNumeric),
      77             :       // Sometimes we get L instead of M for month - standalone name.
      78             :       PatternItem("month",
      79             :                   {{"MMMMM", "narrow"},
      80             :                    {"MMMM", "long"},
      81             :                    {"MMM", "short"},
      82             :                    {"MM", "2-digit"},
      83             :                    {"M", "numeric"},
      84             :                    {"LLLLL", "narrow"},
      85             :                    {"LLLL", "long"},
      86             :                    {"LLL", "short"},
      87             :                    {"LL", "2-digit"},
      88             :                    {"L", "numeric"}},
      89      172680 :                   kNarrowLongShort2DigitNumeric),
      90       34536 :       PatternItem("day", {{"dd", "2-digit"}, {"d", "numeric"}}, k2DigitNumeric),
      91             :       PatternItem("hour",
      92             :                   {{"HH", "2-digit"},
      93             :                    {"H", "numeric"},
      94             :                    {"hh", "2-digit"},
      95             :                    {"h", "numeric"},
      96             :                    {"kk", "2-digit"},
      97             :                    {"k", "numeric"},
      98             :                    {"KK", "2-digit"},
      99             :                    {"K", "numeric"}},
     100      138144 :                   k2DigitNumeric),
     101             :       PatternItem("minute", {{"mm", "2-digit"}, {"m", "numeric"}},
     102       34536 :                   k2DigitNumeric),
     103             :       PatternItem("second", {{"ss", "2-digit"}, {"s", "numeric"}},
     104       34536 :                   k2DigitNumeric),
     105             :       PatternItem("timeZoneName", {{"zzzz", "long"}, {"z", "short"}},
     106     2538396 :                   kLongShort)};
     107       17268 :   return kPatternItems;
     108             : }
     109             : 
     110      764712 : class PatternData {
     111             :  public:
     112      133380 :   PatternData(const std::string property, std::vector<PatternMap> pairs,
     113             :               std::vector<const char*> allowed_values)
     114      266760 :       : property(std::move(property)), allowed_values(allowed_values) {
     115      726180 :     for (const auto& pair : pairs) {
     116      918840 :       map.insert(std::make_pair(pair.value, pair.pattern));
     117             :     }
     118      133380 :   }
     119     1031472 :   virtual ~PatternData() = default;
     120             : 
     121             :   const std::string property;
     122             :   std::map<const std::string, const std::string> map;
     123             :   std::vector<const char*> allowed_values;
     124             : };
     125             : 
     126       14820 : const std::vector<PatternData> CreateCommonData(const PatternData& hour_data) {
     127             :   std::vector<PatternData> build;
     128      163020 :   for (const PatternItem& item : GetPatternItems()) {
     129      266760 :     if (item.property == "hour") {
     130       14820 :       build.push_back(hour_data);
     131             :     } else {
     132             :       build.push_back(
     133      474240 :           PatternData(item.property, item.pairs, item.allowed_values));
     134             :     }
     135       14820 :   }
     136       14820 :   return build;
     137             : }
     138             : 
     139       14820 : const std::vector<PatternData> CreateData(const char* digit2,
     140             :                                           const char* numeric) {
     141             :   return CreateCommonData(
     142             :       PatternData("hour", {{digit2, "2-digit"}, {numeric, "numeric"}},
     143      133380 :                   {"2-digit", "numeric"}));
     144             : }
     145             : 
     146             : // According to "Date Field Symbol Table" in
     147             : // http://userguide.icu-project.org/formatparse/datetime
     148             : // Symbol | Meaning              | Example(s)
     149             : //   h      hour in am/pm (1~12)    h    7
     150             : //                                  hh   07
     151             : //   H      hour in day (0~23)      H    0
     152             : //                                  HH   00
     153             : //   k      hour in day (1~24)      k    24
     154             : //                                  kk   24
     155             : //   K      hour in am/pm (0~11)    K    0
     156             : //                                  KK   00
     157        2964 : const std::vector<PatternData> GetPatternData(Intl::HourCycle hour_cycle) {
     158        2964 :   const std::vector<PatternData> data = CreateData("jj", "j");
     159        5928 :   const std::vector<PatternData> data_h11 = CreateData("KK", "K");
     160        5928 :   const std::vector<PatternData> data_h12 = CreateData("hh", "h");
     161        5928 :   const std::vector<PatternData> data_h23 = CreateData("HH", "H");
     162        5928 :   const std::vector<PatternData> data_h24 = CreateData("kk", "k");
     163        2964 :   switch (hour_cycle) {
     164             :     case Intl::HourCycle::kH11:
     165          27 :       return data_h11;
     166             :     case Intl::HourCycle::kH12:
     167          36 :       return data_h12;
     168             :     case Intl::HourCycle::kH23:
     169          27 :       return data_h23;
     170             :     case Intl::HourCycle::kH24:
     171          27 :       return data_h24;
     172             :     case Intl::HourCycle::kUndefined:
     173        2847 :       return data;
     174             :     default:
     175           0 :       UNREACHABLE();
     176        2964 :   }
     177             : }
     178             : 
     179          72 : std::string GetGMTTzID(Isolate* isolate, const std::string& input) {
     180          72 :   std::string ret = "Etc/GMT";
     181          72 :   switch (input.length()) {
     182             :     case 8:
     183           9 :       if (input[7] == '0') return ret + '0';
     184             :       break;
     185             :     case 9:
     186          72 :       if ((input[7] == '+' || input[7] == '-') &&
     187          36 :           IsInRange(input[8], '0', '9')) {
     188          72 :         return ret + input[7] + input[8];
     189             :       }
     190             :       break;
     191             :     case 10:
     192          54 :       if ((input[7] == '+' || input[7] == '-') && (input[8] == '1') &&
     193          27 :           IsInRange(input[9], '0', '4')) {
     194          81 :         return ret + input[7] + input[8] + input[9];
     195             :       }
     196             :       break;
     197             :   }
     198           0 :   return "";
     199             : }
     200             : 
     201             : // Locale independenty version of isalpha for ascii range. This will return
     202             : // false if the ch is alpha but not in ascii range.
     203             : bool IsAsciiAlpha(char ch) {
     204       11079 :   return IsInRange(ch, 'A', 'Z') || IsInRange(ch, 'a', 'z');
     205             : }
     206             : 
     207             : // Locale independent toupper for ascii range. This will not return İ (dotted I)
     208             : // for i under Turkish locale while std::toupper may.
     209        7092 : char LocaleIndependentAsciiToUpper(char ch) {
     210        8073 :   return (IsInRange(ch, 'a', 'z')) ? (ch - 'a' + 'A') : ch;
     211             : }
     212             : 
     213             : // Locale independent tolower for ascii range.
     214             : char LocaleIndependentAsciiToLower(char ch) {
     215        4347 :   return (IsInRange(ch, 'A', 'Z')) ? (ch - 'A' + 'a') : ch;
     216             : }
     217             : 
     218             : // Returns titlecased location, bueNos_airES -> Buenos_Aires
     219             : // or ho_cHi_minH -> Ho_Chi_Minh. It is locale-agnostic and only
     220             : // deals with ASCII only characters.
     221             : // 'of', 'au' and 'es' are special-cased and lowercased.
     222             : // ICU's timezone parsing is case sensitive, but ECMAScript is case insensitive
     223         387 : std::string ToTitleCaseTimezoneLocation(Isolate* isolate,
     224             :                                         const std::string& input) {
     225             :   std::string title_cased;
     226             :   int word_length = 0;
     227       12339 :   for (char ch : input) {
     228             :     // Convert first char to upper case, the rest to lower case
     229        6003 :     if (IsAsciiAlpha(ch)) {
     230       10584 :       title_cased += word_length == 0 ? LocaleIndependentAsciiToUpper(ch)
     231             :                                       : LocaleIndependentAsciiToLower(ch);
     232        5292 :       word_length++;
     233         711 :     } else if (ch == '_' || ch == '-' || ch == '/') {
     234             :       // Special case Au/Es/Of to be lower case.
     235         657 :       if (word_length == 2) {
     236          45 :         size_t pos = title_cased.length() - 2;
     237          45 :         std::string substr = title_cased.substr(pos, 2);
     238          90 :         if (substr == "Of" || substr == "Es" || substr == "Au") {
     239          72 :           title_cased[pos] = LocaleIndependentAsciiToLower(title_cased[pos]);
     240             :         }
     241             :       }
     242             :       title_cased += ch;
     243             :       word_length = 0;
     244             :     } else {
     245             :       // Invalid input
     246             :       return std::string();
     247             :     }
     248             :   }
     249             :   return title_cased;
     250             : }
     251             : 
     252             : }  // namespace
     253             : 
     254         522 : std::string JSDateTimeFormat::CanonicalizeTimeZoneID(Isolate* isolate,
     255             :                                                      const std::string& input) {
     256         522 :   std::string upper = input;
     257             :   transform(upper.begin(), upper.end(), upper.begin(),
     258         522 :             LocaleIndependentAsciiToUpper);
     259        1953 :   if (upper == "UTC" || upper == "GMT" || upper == "ETC/UTC" ||
     260             :       upper == "ETC/GMT") {
     261          63 :     return "UTC";
     262             :   }
     263             :   // We expect only _, '-' and / beside ASCII letters.
     264             :   // All inputs should conform to Area/Location(/Location)*, or Etc/GMT* .
     265             :   // TODO(jshin): 1. Support 'GB-Eire", 'EST5EDT", "ROK', 'US/*', 'NZ' and many
     266             :   // other aliases/linked names when moving timezone validation code to C++.
     267             :   // See crbug.com/364374 and crbug.com/v8/8007 .
     268             :   // 2. Resolve the difference betwee CLDR/ICU and IANA time zone db.
     269             :   // See http://unicode.org/cldr/trac/ticket/9892 and crbug.com/645807 .
     270         459 :   if (strncmp(upper.c_str(), "ETC/GMT", 7) == 0) {
     271          72 :     return GetGMTTzID(isolate, input);
     272             :   }
     273         387 :   return ToTitleCaseTimezoneLocation(isolate, input);
     274             : }
     275             : 
     276             : // ecma402 #sec-intl.datetimeformat.prototype.resolvedoptions
     277        2448 : MaybeHandle<JSObject> JSDateTimeFormat::ResolvedOptions(
     278             :     Isolate* isolate, Handle<JSDateTimeFormat> date_time_format) {
     279             :   Factory* factory = isolate->factory();
     280             :   // 4. Let options be ! ObjectCreate(%ObjectPrototype%).
     281        2448 :   Handle<JSObject> options = factory->NewJSObject(isolate->object_function());
     282             : 
     283             :   Handle<Object> resolved_obj;
     284             : 
     285        2448 :   CHECK(!date_time_format->icu_locale().is_null());
     286        4896 :   CHECK_NOT_NULL(date_time_format->icu_locale()->raw());
     287        4896 :   icu::Locale* icu_locale = date_time_format->icu_locale()->raw();
     288        2448 :   Maybe<std::string> maybe_locale_str = Intl::ToLanguageTag(*icu_locale);
     289        2448 :   MAYBE_RETURN(maybe_locale_str, MaybeHandle<JSObject>());
     290             :   std::string locale_str = maybe_locale_str.FromJust();
     291             :   Handle<String> locale =
     292        2448 :       factory->NewStringFromAsciiChecked(locale_str.c_str());
     293             : 
     294             :   icu::SimpleDateFormat* icu_simple_date_format =
     295        4896 :       date_time_format->icu_simple_date_format()->raw();
     296             :   // calendar
     297        2448 :   const icu::Calendar* calendar = icu_simple_date_format->getCalendar();
     298             :   // getType() returns legacy calendar type name instead of LDML/BCP47 calendar
     299             :   // key values. intl.js maps them to BCP47 values for key "ca".
     300             :   // TODO(jshin): Consider doing it here, instead.
     301        2448 :   std::string calendar_str = calendar->getType();
     302             : 
     303             :   // Maps ICU calendar names to LDML/BCP47 types for key 'ca'.
     304             :   // See typeMap section in third_party/icu/source/data/misc/keyTypeData.txt
     305             :   // and
     306             :   // http://www.unicode.org/repos/cldr/tags/latest/common/bcp47/calendar.xml
     307        2448 :   if (calendar_str == "gregorian") {
     308             :     calendar_str = "gregory";
     309         612 :   } else if (calendar_str == "ethiopic-amete-alem") {
     310             :     calendar_str = "ethioaa";
     311             :   }
     312             : 
     313        2448 :   const icu::TimeZone& tz = calendar->getTimeZone();
     314        2448 :   icu::UnicodeString time_zone;
     315             :   tz.getID(time_zone);
     316        2448 :   UErrorCode status = U_ZERO_ERROR;
     317        2448 :   icu::UnicodeString canonical_time_zone;
     318        2448 :   icu::TimeZone::getCanonicalID(time_zone, canonical_time_zone, status);
     319             :   Handle<Object> timezone_value;
     320        2448 :   if (U_SUCCESS(status)) {
     321             :     // In CLDR (http://unicode.org/cldr/trac/ticket/9943), Etc/UTC is made
     322             :     // a separate timezone ID from Etc/GMT even though they're still the same
     323             :     // timezone. We have Etc/UTC because 'UTC', 'Etc/Universal',
     324             :     // 'Etc/Zulu' and others are turned to 'Etc/UTC' by ICU. Etc/GMT comes
     325             :     // from Etc/GMT0, Etc/GMT+0, Etc/GMT-0, Etc/Greenwich.
     326             :     // ecma402#sec-canonicalizetimezonename step 3
     327        9729 :     if (canonical_time_zone == UNICODE_STRING_SIMPLE("Etc/UTC") ||
     328        7218 :         canonical_time_zone == UNICODE_STRING_SIMPLE("Etc/GMT")) {
     329          99 :       timezone_value = factory->UTC_string();
     330             :     } else {
     331        4698 :       ASSIGN_RETURN_ON_EXCEPTION(isolate, timezone_value,
     332             :                                  Intl::ToString(isolate, canonical_time_zone),
     333             :                                  JSObject);
     334             :     }
     335             :   } else {
     336             :     // Somehow on Windows we will reach here.
     337           0 :     timezone_value = factory->undefined_value();
     338             :   }
     339             : 
     340             :   // Ugly hack. ICU doesn't expose numbering system in any way, so we have
     341             :   // to assume that for given locale NumberingSystem constructor produces the
     342             :   // same digits as NumberFormat/Calendar would.
     343             :   // Tracked by https://unicode-org.atlassian.net/browse/ICU-13431
     344        2448 :   std::string numbering_system = Intl::GetNumberingSystem(*icu_locale);
     345             : 
     346        2448 :   icu::UnicodeString pattern_unicode;
     347        2448 :   icu_simple_date_format->toPattern(pattern_unicode);
     348             :   std::string pattern;
     349        2448 :   pattern_unicode.toUTF8String(pattern);
     350             : 
     351             :   // 5. For each row of Table 6, except the header row, in table order, do
     352             :   // Table 6: Resolved Options of DateTimeFormat Instances
     353             :   //  Internal Slot          Property
     354             :   //    [[Locale]]           "locale"
     355             :   //    [[Calendar]]         "calendar"
     356             :   //    [[NumberingSystem]]  "numberingSystem"
     357             :   //    [[TimeZone]]         "timeZone"
     358             :   //    [[HourCycle]]        "hourCycle"
     359             :   //                         "hour12"
     360             :   //    [[Weekday]]          "weekday"
     361             :   //    [[Era]]              "era"
     362             :   //    [[Year]]             "year"
     363             :   //    [[Month]]            "month"
     364             :   //    [[Day]]              "day"
     365             :   //    [[Hour]]             "hour"
     366             :   //    [[Minute]]           "minute"
     367             :   //    [[Second]]           "second"
     368             :   //    [[TimeZoneName]]     "timeZoneName"
     369        4896 :   CHECK(JSReceiver::CreateDataProperty(
     370             :             isolate, options, factory->locale_string(), locale, kDontThrow)
     371             :             .FromJust());
     372        7344 :   CHECK(JSReceiver::CreateDataProperty(
     373             :             isolate, options, factory->calendar_string(),
     374             :             factory->NewStringFromAsciiChecked(calendar_str.c_str()),
     375             :             kDontThrow)
     376             :             .FromJust());
     377        2448 :   if (!numbering_system.empty()) {
     378        7344 :     CHECK(JSReceiver::CreateDataProperty(
     379             :               isolate, options, factory->numberingSystem_string(),
     380             :               factory->NewStringFromAsciiChecked(numbering_system.c_str()),
     381             :               kDontThrow)
     382             :               .FromJust());
     383             :   }
     384        4896 :   CHECK(JSReceiver::CreateDataProperty(isolate, options,
     385             :                                        factory->timeZone_string(),
     386             :                                        timezone_value, kDontThrow)
     387             :             .FromJust());
     388             : 
     389             :   // 5.b.i. Let hc be dtf.[[HourCycle]].
     390             :   Intl::HourCycle hc = date_time_format->hour_cycle();
     391             : 
     392        2448 :   if (hc != Intl::HourCycle::kUndefined) {
     393         135 :     CHECK(JSReceiver::CreateDataProperty(
     394             :               isolate, options, factory->hourCycle_string(),
     395             :               date_time_format->HourCycleAsString(), kDontThrow)
     396             :               .FromJust());
     397          45 :     switch (hc) {
     398             :       //  ii. If hc is "h11" or "h12", let v be true.
     399             :       case Intl::HourCycle::kH11:
     400             :       case Intl::HourCycle::kH12:
     401          36 :         CHECK(JSReceiver::CreateDataProperty(isolate, options,
     402             :                                              factory->hour12_string(),
     403             :                                              factory->true_value(), kDontThrow)
     404             :                   .FromJust());
     405             :         break;
     406             :       // iii. Else if, hc is "h23" or "h24", let v be false.
     407             :       case Intl::HourCycle::kH23:
     408             :       case Intl::HourCycle::kH24:
     409          54 :         CHECK(JSReceiver::CreateDataProperty(isolate, options,
     410             :                                              factory->hour12_string(),
     411             :                                              factory->false_value(), kDontThrow)
     412             :                   .FromJust());
     413             :         break;
     414             :       // iv. Else, let v be undefined.
     415             :       case Intl::HourCycle::kUndefined:
     416             :         break;
     417             :     }
     418             :   }
     419             : 
     420       26928 :   for (const auto& item : GetPatternItems()) {
     421      115848 :     for (const auto& pair : item.pairs) {
     422       78849 :       if (pattern.find(pair.pattern) != std::string::npos) {
     423       28260 :         CHECK(JSReceiver::CreateDataProperty(
     424             :                   isolate, options,
     425             :                   factory->NewStringFromAsciiChecked(item.property.c_str()),
     426             :                   factory->NewStringFromAsciiChecked(pair.value.c_str()),
     427             :                   kDontThrow)
     428             :                   .FromJust());
     429             :         break;
     430             :       }
     431             :     }
     432        2448 :   }
     433             : 
     434        2448 :   return options;
     435             : }
     436             : 
     437             : namespace {
     438             : 
     439             : // ecma402/#sec-formatdatetime
     440             : // FormatDateTime( dateTimeFormat, x )
     441         711 : MaybeHandle<String> FormatDateTime(Isolate* isolate,
     442             :                                    const icu::SimpleDateFormat& date_format,
     443             :                                    double x) {
     444         711 :   double date_value = DateCache::TimeClip(x);
     445         711 :   if (std::isnan(date_value)) {
     446          54 :     THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
     447             :                     String);
     448             :   }
     449             : 
     450             :   icu::UnicodeString result;
     451         657 :   date_format.format(date_value, result);
     452             : 
     453         657 :   return Intl::ToString(isolate, result);
     454             : }
     455             : 
     456             : }  // namespace
     457             : 
     458             : // ecma402/#sec-datetime-format-functions
     459             : // DateTime Format Functions
     460         540 : MaybeHandle<String> JSDateTimeFormat::DateTimeFormat(
     461             :     Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
     462             :     Handle<Object> date) {
     463             :   // 2. Assert: Type(dtf) is Object and dtf has an [[InitializedDateTimeFormat]]
     464             :   // internal slot.
     465             : 
     466             :   // 3. If date is not provided or is undefined, then
     467             :   double x;
     468        1080 :   if (date->IsUndefined()) {
     469             :     // 3.a Let x be Call(%Date_now%, undefined).
     470          27 :     x = JSDate::CurrentTimeValue(isolate);
     471             :   } else {
     472             :     // 4. Else,
     473             :     //    a. Let x be ? ToNumber(date).
     474        1026 :     ASSIGN_RETURN_ON_EXCEPTION(isolate, date, Object::ToNumber(isolate, date),
     475             :                                String);
     476        1026 :     CHECK(date->IsNumber());
     477         513 :     x = date->Number();
     478             :   }
     479             :   // 5. Return FormatDateTime(dtf, x).
     480             :   icu::SimpleDateFormat* format =
     481        1080 :       date_time_format->icu_simple_date_format()->raw();
     482         540 :   return FormatDateTime(isolate, *format, x);
     483             : }
     484             : 
     485             : namespace {
     486             : Isolate::ICUObjectCacheType ConvertToCacheType(
     487             :     JSDateTimeFormat::DefaultsOption type) {
     488         180 :   switch (type) {
     489             :     case JSDateTimeFormat::DefaultsOption::kDate:
     490             :       return Isolate::ICUObjectCacheType::kDefaultSimpleDateFormatForDate;
     491             :     case JSDateTimeFormat::DefaultsOption::kTime:
     492             :       return Isolate::ICUObjectCacheType::kDefaultSimpleDateFormatForTime;
     493             :     case JSDateTimeFormat::DefaultsOption::kAll:
     494             :       return Isolate::ICUObjectCacheType::kDefaultSimpleDateFormat;
     495             :   }
     496             : }
     497             : }  // namespace
     498             : 
     499         180 : MaybeHandle<String> JSDateTimeFormat::ToLocaleDateTime(
     500             :     Isolate* isolate, Handle<Object> date, Handle<Object> locales,
     501             :     Handle<Object> options, RequiredOption required, DefaultsOption defaults) {
     502             :   Isolate::ICUObjectCacheType cache_type = ConvertToCacheType(defaults);
     503             : 
     504             :   Factory* factory = isolate->factory();
     505             :   // 1. Let x be ? thisTimeValue(this value);
     506         360 :   if (!date->IsJSDate()) {
     507           0 :     THROW_NEW_ERROR(isolate,
     508             :                     NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
     509             :                                  factory->Date_string()),
     510             :                     String);
     511             :   }
     512             : 
     513         360 :   double const x = Handle<JSDate>::cast(date)->value()->Number();
     514             :   // 2. If x is NaN, return "Invalid Date"
     515         180 :   if (std::isnan(x)) {
     516           0 :     return factory->Invalid_Date_string();
     517             :   }
     518             : 
     519             :   // We only cache the instance when both locales and options are undefined,
     520             :   // as that is the only case when the specified side-effects of examining
     521             :   // those arguments are unobservable.
     522             :   bool can_cache =
     523         522 :       locales->IsUndefined(isolate) && options->IsUndefined(isolate);
     524         180 :   if (can_cache) {
     525             :     // Both locales and options are undefined, check the cache.
     526             :     icu::SimpleDateFormat* cached_icu_simple_date_format =
     527             :         static_cast<icu::SimpleDateFormat*>(
     528          54 :             isolate->get_cached_icu_object(cache_type));
     529          54 :     if (cached_icu_simple_date_format != nullptr) {
     530          24 :       return FormatDateTime(isolate, *cached_icu_simple_date_format, x);
     531             :     }
     532             :   }
     533             :   // 3. Let options be ? ToDateTimeOptions(options, required, defaults).
     534             :   Handle<JSObject> internal_options;
     535         312 :   ASSIGN_RETURN_ON_EXCEPTION(
     536             :       isolate, internal_options,
     537             :       ToDateTimeOptions(isolate, options, required, defaults), String);
     538             : 
     539             :   // 4. Let dateFormat be ? Construct(%DateTimeFormat%, « locales, options »).
     540             :   Handle<JSFunction> constructor = Handle<JSFunction>(
     541             :       JSFunction::cast(isolate->context()
     542         294 :                            ->native_context()
     543         294 :                            ->intl_date_time_format_function()),
     544         294 :       isolate);
     545             :   Handle<JSObject> obj;
     546         294 :   ASSIGN_RETURN_ON_EXCEPTION(
     547             :       isolate, obj,
     548             :       JSObject::New(constructor, constructor, Handle<AllocationSite>::null()),
     549             :       String);
     550             :   Handle<JSDateTimeFormat> date_time_format;
     551         294 :   ASSIGN_RETURN_ON_EXCEPTION(
     552             :       isolate, date_time_format,
     553             :       JSDateTimeFormat::Initialize(isolate, Handle<JSDateTimeFormat>::cast(obj),
     554             :                                    locales, internal_options),
     555             :       String);
     556             : 
     557         147 :   if (can_cache) {
     558             :     isolate->set_icu_object_in_cache(
     559             :         cache_type, std::static_pointer_cast<icu::UObject>(
     560         120 :                         date_time_format->icu_simple_date_format()->get()));
     561             :   }
     562             :   // 5. Return FormatDateTime(dateFormat, x).
     563             :   icu::SimpleDateFormat* format =
     564         294 :       date_time_format->icu_simple_date_format()->raw();
     565         147 :   return FormatDateTime(isolate, *format, x);
     566             : }
     567             : 
     568             : namespace {
     569             : 
     570       22544 : Maybe<bool> IsPropertyUndefined(Isolate* isolate, Handle<JSObject> options,
     571             :                                 const char* property) {
     572             :   Factory* factory = isolate->factory();
     573             :   // i. Let prop be the property name.
     574             :   // ii. Let value be ? Get(options, prop).
     575             :   Handle<Object> value;
     576       67632 :   ASSIGN_RETURN_ON_EXCEPTION_VALUE(
     577             :       isolate, value,
     578             :       Object::GetPropertyOrElement(
     579             :           isolate, options, factory->NewStringFromAsciiChecked(property)),
     580             :       Nothing<bool>());
     581       45088 :   return Just(value->IsUndefined(isolate));
     582             : }
     583             : 
     584        6436 : Maybe<bool> NeedsDefault(Isolate* isolate, Handle<JSObject> options,
     585             :                          const std::vector<std::string>& props) {
     586             :   bool needs_default = true;
     587       35416 :   for (const auto& prop : props) {
     588             :     //  i. Let prop be the property name.
     589             :     // ii. Let value be ? Get(options, prop)
     590             :     Maybe<bool> maybe_undefined =
     591       22544 :         IsPropertyUndefined(isolate, options, prop.c_str());
     592       22544 :     MAYBE_RETURN(maybe_undefined, Nothing<bool>());
     593             :     // iii. If value is not undefined, let needDefaults be false.
     594       22544 :     if (!maybe_undefined.FromJust()) {
     595             :       needs_default = false;
     596             :     }
     597             :   }
     598             :   return Just(needs_default);
     599             : }
     600             : 
     601        2659 : Maybe<bool> CreateDefault(Isolate* isolate, Handle<JSObject> options,
     602             :                           const std::vector<std::string>& props) {
     603             :   Factory* factory = isolate->factory();
     604             :   // i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric").
     605       13295 :   for (const auto& prop : props) {
     606       15954 :     MAYBE_RETURN(
     607             :         JSReceiver::CreateDataProperty(
     608             :             isolate, options, factory->NewStringFromAsciiChecked(prop.c_str()),
     609             :             factory->numeric_string(), kThrowOnError),
     610             :         Nothing<bool>());
     611             :   }
     612             :   return Just(true);
     613             : }
     614             : 
     615             : }  // namespace
     616             : 
     617             : // ecma-402/#sec-todatetimeoptions
     618        3282 : MaybeHandle<JSObject> JSDateTimeFormat::ToDateTimeOptions(
     619             :     Isolate* isolate, Handle<Object> input_options, RequiredOption required,
     620             :     DefaultsOption defaults) {
     621             :   Factory* factory = isolate->factory();
     622             :   // 1. If options is undefined, let options be null; otherwise let options be ?
     623             :   //    ToObject(options).
     624             :   Handle<JSObject> options;
     625        6564 :   if (input_options->IsUndefined(isolate)) {
     626        1965 :     options = factory->NewJSObjectWithNullProto();
     627             :   } else {
     628             :     Handle<JSReceiver> options_obj;
     629        2634 :     ASSIGN_RETURN_ON_EXCEPTION(isolate, options_obj,
     630             :                                Object::ToObject(isolate, input_options),
     631             :                                JSObject);
     632             :     // 2. Let options be ObjectCreate(options).
     633        2616 :     ASSIGN_RETURN_ON_EXCEPTION(isolate, options,
     634             :                                JSObject::ObjectCreate(isolate, options_obj),
     635             :                                JSObject);
     636             :   }
     637             : 
     638             :   // 3. Let needDefaults be true.
     639             :   bool needs_default = true;
     640             : 
     641             :   // 4. If required is "date" or "any", then
     642        3273 :   if (required == RequiredOption::kAny || required == RequiredOption::kDate) {
     643             :     // a. For each of the property names "weekday", "year", "month", "day", do
     644        6472 :     const std::vector<std::string> list({"weekday", "year", "month", "day"});
     645        3236 :     Maybe<bool> maybe_needs_default = NeedsDefault(isolate, options, list);
     646        3236 :     MAYBE_RETURN(maybe_needs_default, Handle<JSObject>());
     647        3236 :     needs_default = maybe_needs_default.FromJust();
     648             :   }
     649             : 
     650             :   // 5. If required is "time" or "any", then
     651        3273 :   if (required == RequiredOption::kAny || required == RequiredOption::kTime) {
     652             :     // a. For each of the property names "hour", "minute", "second", do
     653        6400 :     const std::vector<std::string> list({"hour", "minute", "second"});
     654        3200 :     Maybe<bool> maybe_needs_default = NeedsDefault(isolate, options, list);
     655        3200 :     MAYBE_RETURN(maybe_needs_default, Handle<JSObject>());
     656        3200 :     needs_default &= maybe_needs_default.FromJust();
     657             :   }
     658             : 
     659             :   // 6. If needDefaults is true and defaults is either "date" or "all", then
     660        3273 :   if (needs_default) {
     661        2631 :     if (defaults == DefaultsOption::kAll || defaults == DefaultsOption::kDate) {
     662             :       // a. For each of the property names "year", "month", "day", do)
     663        5206 :       const std::vector<std::string> list({"year", "month", "day"});
     664        2603 :       MAYBE_RETURN(CreateDefault(isolate, options, list), Handle<JSObject>());
     665             :     }
     666             :     // 7. If needDefaults is true and defaults is either "time" or "all", then
     667        2631 :     if (defaults == DefaultsOption::kAll || defaults == DefaultsOption::kTime) {
     668             :       // a. For each of the property names "hour", "minute", "second", do
     669         112 :       const std::vector<std::string> list({"hour", "minute", "second"});
     670          56 :       MAYBE_RETURN(CreateDefault(isolate, options, list), Handle<JSObject>());
     671             :     }
     672             :   }
     673             :   // 8. Return options.
     674        3273 :   return options;
     675             : }
     676             : 
     677        3051 : MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::UnwrapDateTimeFormat(
     678             :     Isolate* isolate, Handle<JSReceiver> format_holder) {
     679             :   Handle<Context> native_context =
     680        6102 :       Handle<Context>(isolate->context()->native_context(), isolate);
     681             :   Handle<JSFunction> constructor = Handle<JSFunction>(
     682        6102 :       JSFunction::cast(native_context->intl_date_time_format_function()),
     683        6102 :       isolate);
     684             :   Handle<Object> dtf;
     685        9153 :   ASSIGN_RETURN_ON_EXCEPTION(
     686             :       isolate, dtf,
     687             :       Intl::LegacyUnwrapReceiver(isolate, format_holder, constructor,
     688             :                                  format_holder->IsJSDateTimeFormat()),
     689             :       JSDateTimeFormat);
     690             :   // 2. If Type(dtf) is not Object or dtf does not have an
     691             :   //    [[InitializedDateTimeFormat]] internal slot, then
     692        6102 :   if (!dtf->IsJSDateTimeFormat()) {
     693             :     // a. Throw a TypeError exception.
     694          90 :     THROW_NEW_ERROR(isolate,
     695             :                     NewTypeError(MessageTemplate::kIncompatibleMethodReceiver,
     696             :                                  isolate->factory()->NewStringFromAsciiChecked(
     697             :                                      "UnwrapDateTimeFormat"),
     698             :                                  format_holder),
     699             :                     JSDateTimeFormat);
     700             :   }
     701             :   // 3. Return dtf.
     702        3006 :   return Handle<JSDateTimeFormat>::cast(dtf);
     703             : }
     704             : 
     705             : namespace {
     706             : 
     707             : // ecma-402/#sec-isvalidtimezonename
     708         468 : bool IsValidTimeZoneName(const icu::TimeZone& tz) {
     709         468 :   UErrorCode status = U_ZERO_ERROR;
     710             :   icu::UnicodeString id;
     711             :   tz.getID(id);
     712         468 :   icu::UnicodeString canonical;
     713         468 :   icu::TimeZone::getCanonicalID(id, canonical, status);
     714         936 :   return U_SUCCESS(status) &&
     715        1404 :          canonical != icu::UnicodeString("Etc/Unknown", -1, US_INV);
     716             : }
     717             : 
     718        3108 : std::unique_ptr<icu::TimeZone> CreateTimeZone(Isolate* isolate,
     719             :                                               const char* timezone) {
     720             :   // Create time zone as specified by the user. We have to re-create time zone
     721             :   // since calendar takes ownership.
     722        3108 :   if (timezone == nullptr) {
     723             :     // 19.a. Else / Let timeZone be DefaultTimeZone().
     724        2586 :     return std::unique_ptr<icu::TimeZone>(icu::TimeZone::createDefault());
     725             :   }
     726             :   std::string canonicalized =
     727        1044 :       JSDateTimeFormat::CanonicalizeTimeZoneID(isolate, timezone);
     728         522 :   if (canonicalized.empty()) return std::unique_ptr<icu::TimeZone>();
     729             :   std::unique_ptr<icu::TimeZone> tz(
     730         468 :       icu::TimeZone::createTimeZone(canonicalized.c_str()));
     731             :   // 18.b If the result of IsValidTimeZoneName(timeZone) is false, then
     732             :   // i. Throw a RangeError exception.
     733         468 :   if (!IsValidTimeZoneName(*tz)) return std::unique_ptr<icu::TimeZone>();
     734             :   return tz;
     735             : }
     736             : 
     737        3108 : std::unique_ptr<icu::Calendar> CreateCalendar(Isolate* isolate,
     738             :                                               const icu::Locale& icu_locale,
     739             :                                               const char* timezone) {
     740        3108 :   std::unique_ptr<icu::TimeZone> tz = CreateTimeZone(isolate, timezone);
     741        3108 :   if (tz.get() == nullptr) return std::unique_ptr<icu::Calendar>();
     742             : 
     743             :   // Create a calendar using locale, and apply time zone to it.
     744        2964 :   UErrorCode status = U_ZERO_ERROR;
     745             :   std::unique_ptr<icu::Calendar> calendar(
     746        2964 :       icu::Calendar::createInstance(tz.release(), icu_locale, status));
     747        5928 :   CHECK(U_SUCCESS(status));
     748        2964 :   CHECK_NOT_NULL(calendar.get());
     749             : 
     750        5928 :   if (calendar->getDynamicClassID() ==
     751        2964 :       icu::GregorianCalendar::getStaticClassID()) {
     752             :     icu::GregorianCalendar* gc =
     753             :         static_cast<icu::GregorianCalendar*>(calendar.get());
     754        2235 :     UErrorCode status = U_ZERO_ERROR;
     755             :     // The beginning of ECMAScript time, namely -(2**53)
     756             :     const double start_of_time = -9007199254740992;
     757        2235 :     gc->setGregorianChange(start_of_time, status);
     758             :     DCHECK(U_SUCCESS(status));
     759             :   }
     760             :   return calendar;
     761             : }
     762             : 
     763        2964 : std::unique_ptr<icu::SimpleDateFormat> CreateICUDateFormat(
     764             :     Isolate* isolate, const icu::Locale& icu_locale,
     765             :     const std::string& skeleton) {
     766             :   // See https://github.com/tc39/ecma402/issues/225 . The best pattern
     767             :   // generation needs to be done in the base locale according to the
     768             :   // current spec however odd it may be. See also crbug.com/826549 .
     769             :   // This is a temporary work-around to get v8's external behavior to match
     770             :   // the current spec, but does not follow the spec provisions mentioned
     771             :   // in the above Ecma 402 issue.
     772             :   // TODO(jshin): The spec may need to be revised because using the base
     773             :   // locale for the pattern match is not quite right. Moreover, what to
     774             :   // do with 'related year' part when 'chinese/dangi' calendar is specified
     775             :   // has to be discussed. Revisit once the spec is clarified/revised.
     776        2964 :   icu::Locale no_extension_locale(icu_locale.getBaseName());
     777        2964 :   UErrorCode status = U_ZERO_ERROR;
     778             :   std::unique_ptr<icu::DateTimePatternGenerator> generator(
     779             :       icu::DateTimePatternGenerator::createInstance(no_extension_locale,
     780        2964 :                                                     status));
     781        2964 :   icu::UnicodeString pattern;
     782        2964 :   if (U_SUCCESS(status)) {
     783        5928 :     pattern =
     784             :         generator->getBestPattern(icu::UnicodeString(skeleton.c_str()), status);
     785             :   }
     786             : 
     787             :   // Make formatter from skeleton. Calendar and numbering system are added
     788             :   // to the locale as Unicode extension (if they were specified at all).
     789        2964 :   status = U_ZERO_ERROR;
     790             :   std::unique_ptr<icu::SimpleDateFormat> date_format(
     791        2964 :       new icu::SimpleDateFormat(pattern, icu_locale, status));
     792        2964 :   if (U_FAILURE(status)) return std::unique_ptr<icu::SimpleDateFormat>();
     793             : 
     794        2964 :   CHECK_NOT_NULL(date_format.get());
     795        2964 :   return date_format;
     796             : }
     797             : 
     798         209 : Intl::HourCycle HourCycleDefault(icu::SimpleDateFormat* date_format) {
     799             :   icu::UnicodeString pattern;
     800         209 :   date_format->toPattern(pattern);
     801         209 :   if (pattern.indexOf('K') >= 0) {
     802             :     return Intl::HourCycle::kH11;
     803         200 :   } else if (pattern.indexOf('h') >= 0) {
     804             :     return Intl::HourCycle::kH12;
     805          63 :   } else if (pattern.indexOf('H') >= 0) {
     806             :     return Intl::HourCycle::kH23;
     807           0 :   } else if (pattern.indexOf('k') >= 0) {
     808             :     return Intl::HourCycle::kH24;
     809             :   }
     810           0 :   return Intl::HourCycle::kUndefined;
     811             : }
     812             : 
     813             : }  // namespace
     814             : 
     815             : enum FormatMatcherOption { kBestFit, kBasic };
     816             : 
     817             : // ecma402/#sec-initializedatetimeformat
     818        3135 : MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::Initialize(
     819             :     Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
     820             :     Handle<Object> locales, Handle<Object> input_options) {
     821             :   date_time_format->set_flags(0);
     822             :   // 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
     823             :   Maybe<std::vector<std::string>> maybe_requested_locales =
     824        3135 :       Intl::CanonicalizeLocaleList(isolate, locales);
     825        3135 :   MAYBE_RETURN(maybe_requested_locales, Handle<JSDateTimeFormat>());
     826             :   std::vector<std::string> requested_locales =
     827        3126 :       maybe_requested_locales.FromJust();
     828             :   // 2. Let options be ? ToDateTimeOptions(options, "any", "date").
     829             :   Handle<JSObject> options;
     830        6252 :   ASSIGN_RETURN_ON_EXCEPTION(
     831             :       isolate, options,
     832             :       JSDateTimeFormat::ToDateTimeOptions(
     833             :           isolate, input_options, RequiredOption::kAny, DefaultsOption::kDate),
     834             :       JSDateTimeFormat);
     835             : 
     836             :   // 4. Let matcher be ? GetOption(options, "localeMatcher", "string",
     837             :   // « "lookup", "best fit" », "best fit").
     838             :   // 5. Set opt.[[localeMatcher]] to matcher.
     839             :   Maybe<Intl::MatcherOption> maybe_locale_matcher =
     840        3126 :       Intl::GetLocaleMatcher(isolate, options, "Intl.DateTimeFormat");
     841        3126 :   MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSDateTimeFormat>());
     842             :   Intl::MatcherOption locale_matcher = maybe_locale_matcher.FromJust();
     843             : 
     844             :   // 6. Let hour12 be ? GetOption(options, "hour12", "boolean", undefined,
     845             :   // undefined).
     846             :   bool hour12;
     847             :   Maybe<bool> maybe_get_hour12 = Intl::GetBoolOption(
     848        3126 :       isolate, options, "hour12", "Intl.DateTimeFormat", &hour12);
     849        3126 :   MAYBE_RETURN(maybe_get_hour12, Handle<JSDateTimeFormat>());
     850             : 
     851             :   // 7. Let hourCycle be ? GetOption(options, "hourCycle", "string", « "h11",
     852             :   // "h12", "h23", "h24" », undefined).
     853             :   Maybe<Intl::HourCycle> maybe_hour_cycle =
     854        3126 :       Intl::GetHourCycle(isolate, options, "Intl.DateTimeFormat");
     855        3126 :   MAYBE_RETURN(maybe_hour_cycle, MaybeHandle<JSDateTimeFormat>());
     856             :   Intl::HourCycle hour_cycle = maybe_hour_cycle.FromJust();
     857             : 
     858             :   // 8. If hour12 is not undefined, then
     859        3126 :   if (maybe_get_hour12.FromJust()) {
     860             :     // a. Let hourCycle be null.
     861             :     hour_cycle = Intl::HourCycle::kUndefined;
     862             :   }
     863             :   // 9. Set opt.[[hc]] to hourCycle.
     864             : 
     865             :   // ecma402/#sec-intl.datetimeformat-internal-slots
     866             :   // The value of the [[RelevantExtensionKeys]] internal slot is
     867             :   // « "ca", "nu", "hc" ».
     868        6252 :   std::set<std::string> relevant_extension_keys = {"nu", "ca", "hc"};
     869             : 
     870             :   // 10. Let localeData be %DateTimeFormat%.[[LocaleData]].
     871             :   // 11. Let r be ResolveLocale( %DateTimeFormat%.[[AvailableLocales]],
     872             :   //     requestedLocales, opt, %DateTimeFormat%.[[RelevantExtensionKeys]],
     873             :   //     localeData).
     874             :   //
     875             :   Intl::ResolvedLocale r = Intl::ResolveLocale(
     876             :       isolate, JSDateTimeFormat::GetAvailableLocales(), requested_locales,
     877        9378 :       locale_matcher, relevant_extension_keys);
     878             : 
     879        6252 :   icu::Locale icu_locale = r.icu_locale;
     880             :   DCHECK(!icu_locale.isBogus());
     881             : 
     882        3126 :   if (!maybe_get_hour12.FromJust() &&
     883             :       hour_cycle == Intl::HourCycle::kUndefined) {
     884        6234 :     auto hc_extension_it = r.extensions.find("hc");
     885        3117 :     if (hc_extension_it != r.extensions.end()) {
     886         216 :       hour_cycle = Intl::ToHourCycle(hc_extension_it->second.c_str());
     887             :     }
     888             :   }
     889             : 
     890             :   // 17. Let timeZone be ? Get(options, "timeZone").
     891             :   const std::vector<const char*> empty_values;
     892             :   std::unique_ptr<char[]> timezone = nullptr;
     893             :   Maybe<bool> maybe_timezone =
     894             :       Intl::GetStringOption(isolate, options, "timeZone", empty_values,
     895        9378 :                             "Intl.DateTimeFormat", &timezone);
     896        3126 :   MAYBE_RETURN(maybe_timezone, Handle<JSDateTimeFormat>());
     897             : 
     898             :   std::unique_ptr<icu::Calendar> calendar(
     899        3108 :       CreateCalendar(isolate, icu_locale, timezone.get()));
     900             : 
     901             :   // 18.b If the result of IsValidTimeZoneName(timeZone) is false, then
     902             :   // i. Throw a RangeError exception.
     903        3108 :   if (calendar.get() == nullptr) {
     904         288 :     THROW_NEW_ERROR(isolate,
     905             :                     NewRangeError(MessageTemplate::kInvalidTimeZone,
     906             :                                   isolate->factory()->NewStringFromAsciiChecked(
     907             :                                       timezone.get())),
     908             :                     JSDateTimeFormat);
     909             :   }
     910             : 
     911             :   // 29.  If dateTimeFormat.[[Hour]] is not undefined, then
     912        2964 :   if (hour_cycle == Intl::HourCycle::kUndefined) {
     913             :     // d. If hour12 is not undefined, then
     914        2856 :     if (maybe_get_hour12.FromJust()) {
     915             :       // i. If hour12 is true, then
     916           9 :       if (hour12) {
     917             :         hour_cycle = Intl::HourCycle::kH12;
     918             :       } else {  // ii. Else,
     919             :         hour_cycle = Intl::HourCycle::kH23;
     920             :       }
     921             :     }
     922             :   }
     923             : 
     924             :   bool has_hour_option = false;
     925             :   // 22. For each row of Table 5, except the header row, do
     926             :   std::string skeleton;
     927       32604 :   for (const PatternData& item : GetPatternData(hour_cycle)) {
     928       26676 :     std::unique_ptr<char[]> input;
     929             :     // a. Let prop be the name given in the Property column of the row.
     930             :     // b. Let value be ? GetOption(options, prop, "string", « the strings given
     931             :     // in the Values column of the row », undefined).
     932             :     Maybe<bool> maybe_get_option = Intl::GetStringOption(
     933             :         isolate, options, item.property.c_str(), item.allowed_values,
     934       80028 :         "Intl.DateTimeFormat", &input);
     935       26676 :     MAYBE_RETURN(maybe_get_option, Handle<JSDateTimeFormat>());
     936       26676 :     if (maybe_get_option.FromJust()) {
     937       17376 :       if (item.property == "hour") {
     938             :         has_hour_option = true;
     939             :       }
     940             :       DCHECK_NOT_NULL(input.get());
     941             :       // c. Set opt.[[<prop>]] to value.
     942       26064 :       skeleton += item.map.find(input.get())->second;
     943             :     }
     944        2964 :   }
     945             : 
     946             :   enum FormatMatcherOption { kBestFit, kBasic };
     947             :   // We implement only best fit algorithm, but still need to check
     948             :   // if the formatMatcher values are in range.
     949             :   // 25. Let matcher be ? GetOption(options, "formatMatcher", "string",
     950             :   //     «  "basic", "best fit" », "best fit").
     951             :   Maybe<FormatMatcherOption> maybe_format_matcher =
     952             :       Intl::GetStringOption<FormatMatcherOption>(
     953             :           isolate, options, "formatMatcher", "Intl.DateTimeFormat",
     954             :           {"best fit", "basic"},
     955             :           {FormatMatcherOption::kBestFit, FormatMatcherOption::kBasic},
     956        8892 :           FormatMatcherOption::kBestFit);
     957        2964 :   MAYBE_RETURN(maybe_format_matcher, MaybeHandle<JSDateTimeFormat>());
     958             :   // TODO(ftang): uncomment the following line and handle format_matcher.
     959             :   // FormatMatcherOption format_matcher = maybe_format_matcher.FromJust();
     960             : 
     961             :   std::unique_ptr<icu::SimpleDateFormat> date_format(
     962        2964 :       CreateICUDateFormat(isolate, icu_locale, skeleton));
     963        2964 :   if (date_format.get() == nullptr) {
     964             :     // Remove extensions and try again.
     965           0 :     icu_locale = icu::Locale(icu_locale.getBaseName());
     966           0 :     date_format = CreateICUDateFormat(isolate, icu_locale, skeleton);
     967           0 :     if (date_format.get() == nullptr) {
     968           0 :       FATAL("Failed to create ICU date format, are ICU data files missing?");
     969             :     }
     970             :   }
     971             : 
     972             :   // The creation of Calendar depends on timeZone so we have to put 13 after 17.
     973             :   // Also date_format is not created until here.
     974             :   // 13. Set dateTimeFormat.[[Calendar]] to r.[[ca]].
     975        5928 :   date_format->adoptCalendar(calendar.release());
     976             : 
     977             :   // 29. If dateTimeFormat.[[Hour]] is not undefined, then
     978        2964 :   if (has_hour_option) {
     979             :     // a. Let hcDefault be dataLocaleData.[[hourCycle]].
     980         209 :     Intl::HourCycle hc_default = HourCycleDefault(date_format.get());
     981             :     // b. Let hc be dateTimeFormat.[[HourCycle]].
     982             :     Intl::HourCycle hc = hour_cycle;
     983             :     // c. If hc is null, then
     984         209 :     if (hc == Intl::HourCycle::kUndefined) {
     985             :       // i. Set hc to hcDefault.
     986             :       hc = hc_default;
     987             :     }
     988             :     // e.  Set dateTimeFormat.[[HourCycle]] to hc.
     989         209 :     date_time_format->set_hour_cycle(hc);
     990             :     // 30. Else
     991             :   } else {
     992             :     // a. Set dateTimeFormat.[[HourCycle]] to undefined.
     993        2755 :     date_time_format->set_hour_cycle(Intl::HourCycle::kUndefined);
     994             :   }
     995             :   Handle<Managed<icu::Locale>> managed_locale =
     996        2964 :       Managed<icu::Locale>::FromRawPtr(isolate, 0, icu_locale.clone());
     997             : 
     998        2964 :   date_time_format->set_icu_locale(*managed_locale);
     999             :   Handle<Managed<icu::SimpleDateFormat>> managed_format =
    1000             :       Managed<icu::SimpleDateFormat>::FromUniquePtr(isolate, 0,
    1001        5928 :                                                     std::move(date_format));
    1002        2964 :   date_time_format->set_icu_simple_date_format(*managed_format);
    1003             : 
    1004        2964 :   return date_time_format;
    1005             : }
    1006             : 
    1007             : namespace {
    1008             : 
    1009             : // The list comes from third_party/icu/source/i18n/unicode/udat.h.
    1010             : // They're mapped to DateTimeFormat components listed at
    1011             : // https://tc39.github.io/ecma402/#sec-datetimeformat-abstracts .
    1012         603 : Handle<String> IcuDateFieldIdToDateType(int32_t field_id, Isolate* isolate) {
    1013         603 :   switch (field_id) {
    1014             :     case -1:
    1015             :       return isolate->factory()->literal_string();
    1016             :     case UDAT_YEAR_FIELD:
    1017             :     case UDAT_EXTENDED_YEAR_FIELD:
    1018             :     case UDAT_YEAR_NAME_FIELD:
    1019             :       return isolate->factory()->year_string();
    1020             :     case UDAT_MONTH_FIELD:
    1021             :     case UDAT_STANDALONE_MONTH_FIELD:
    1022             :       return isolate->factory()->month_string();
    1023             :     case UDAT_DATE_FIELD:
    1024             :       return isolate->factory()->day_string();
    1025             :     case UDAT_HOUR_OF_DAY1_FIELD:
    1026             :     case UDAT_HOUR_OF_DAY0_FIELD:
    1027             :     case UDAT_HOUR1_FIELD:
    1028             :     case UDAT_HOUR0_FIELD:
    1029             :       return isolate->factory()->hour_string();
    1030             :     case UDAT_MINUTE_FIELD:
    1031             :       return isolate->factory()->minute_string();
    1032             :     case UDAT_SECOND_FIELD:
    1033             :       return isolate->factory()->second_string();
    1034             :     case UDAT_DAY_OF_WEEK_FIELD:
    1035             :     case UDAT_DOW_LOCAL_FIELD:
    1036             :     case UDAT_STANDALONE_DAY_FIELD:
    1037             :       return isolate->factory()->weekday_string();
    1038             :     case UDAT_AM_PM_FIELD:
    1039             :       return isolate->factory()->dayPeriod_string();
    1040             :     case UDAT_TIMEZONE_FIELD:
    1041             :     case UDAT_TIMEZONE_RFC_FIELD:
    1042             :     case UDAT_TIMEZONE_GENERIC_FIELD:
    1043             :     case UDAT_TIMEZONE_SPECIAL_FIELD:
    1044             :     case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD:
    1045             :     case UDAT_TIMEZONE_ISO_FIELD:
    1046             :     case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
    1047             :       return isolate->factory()->timeZoneName_string();
    1048             :     case UDAT_ERA_FIELD:
    1049             :       return isolate->factory()->era_string();
    1050             :     default:
    1051             :       // Other UDAT_*_FIELD's cannot show up because there is no way to specify
    1052             :       // them via options of Intl.DateTimeFormat.
    1053           0 :       UNREACHABLE();
    1054             :       // To prevent MSVC from issuing C4715 warning.
    1055             :       return Handle<String>();
    1056             :   }
    1057             : }
    1058             : 
    1059             : }  // namespace
    1060             : 
    1061         153 : MaybeHandle<Object> JSDateTimeFormat::FormatToParts(
    1062             :     Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
    1063             :     double date_value) {
    1064             :   Factory* factory = isolate->factory();
    1065             :   icu::SimpleDateFormat* format =
    1066         306 :       date_time_format->icu_simple_date_format()->raw();
    1067         153 :   CHECK_NOT_NULL(format);
    1068             : 
    1069             :   icu::UnicodeString formatted;
    1070         306 :   icu::FieldPositionIterator fp_iter;
    1071         153 :   icu::FieldPosition fp;
    1072         153 :   UErrorCode status = U_ZERO_ERROR;
    1073         153 :   format->format(date_value, formatted, &fp_iter, status);
    1074         153 :   if (U_FAILURE(status)) {
    1075           0 :     THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), Object);
    1076             :   }
    1077             : 
    1078         153 :   Handle<JSArray> result = factory->NewJSArray(0);
    1079             :   int32_t length = formatted.length();
    1080         153 :   if (length == 0) return result;
    1081             : 
    1082             :   int index = 0;
    1083             :   int32_t previous_end_pos = 0;
    1084             :   Handle<String> substring;
    1085         540 :   while (fp_iter.next(fp)) {
    1086         387 :     int32_t begin_pos = fp.getBeginIndex();
    1087         387 :     int32_t end_pos = fp.getEndIndex();
    1088             : 
    1089         387 :     if (previous_end_pos < begin_pos) {
    1090         432 :       ASSIGN_RETURN_ON_EXCEPTION(
    1091             :           isolate, substring,
    1092             :           Intl::ToString(isolate, formatted, previous_end_pos, begin_pos),
    1093             :           Object);
    1094             :       Intl::AddElement(isolate, result, index,
    1095         216 :                        IcuDateFieldIdToDateType(-1, isolate), substring);
    1096         216 :       ++index;
    1097             :     }
    1098         774 :     ASSIGN_RETURN_ON_EXCEPTION(
    1099             :         isolate, substring,
    1100             :         Intl::ToString(isolate, formatted, begin_pos, end_pos), Object);
    1101             :     Intl::AddElement(isolate, result, index,
    1102             :                      IcuDateFieldIdToDateType(fp.getField(), isolate),
    1103         387 :                      substring);
    1104             :     previous_end_pos = end_pos;
    1105         387 :     ++index;
    1106             :   }
    1107         153 :   if (previous_end_pos < length) {
    1108           0 :     ASSIGN_RETURN_ON_EXCEPTION(
    1109             :         isolate, substring,
    1110             :         Intl::ToString(isolate, formatted, previous_end_pos, length), Object);
    1111             :     Intl::AddElement(isolate, result, index,
    1112           0 :                      IcuDateFieldIdToDateType(-1, isolate), substring);
    1113             :   }
    1114         153 :   JSObject::ValidateElements(*result);
    1115         306 :   return result;
    1116             : }
    1117             : 
    1118        3257 : std::set<std::string> JSDateTimeFormat::GetAvailableLocales() {
    1119        3257 :   int32_t num_locales = 0;
    1120             :   const icu::Locale* icu_available_locales =
    1121        3257 :       icu::DateFormat::getAvailableLocales(num_locales);
    1122        3257 :   return Intl::BuildLocaleSet(icu_available_locales, num_locales);
    1123             : }
    1124             : 
    1125          45 : Handle<String> JSDateTimeFormat::HourCycleAsString() const {
    1126          45 :   switch (hour_cycle()) {
    1127             :     case Intl::HourCycle::kUndefined:
    1128           0 :       return GetReadOnlyRoots().undefined_string_handle();
    1129             :     case Intl::HourCycle::kH11:
    1130           0 :       return GetReadOnlyRoots().h11_string_handle();
    1131             :     case Intl::HourCycle::kH12:
    1132          36 :       return GetReadOnlyRoots().h12_string_handle();
    1133             :     case Intl::HourCycle::kH23:
    1134          54 :       return GetReadOnlyRoots().h23_string_handle();
    1135             :     case Intl::HourCycle::kH24:
    1136           0 :       return GetReadOnlyRoots().h24_string_handle();
    1137             :     default:
    1138           0 :       UNREACHABLE();
    1139             :   }
    1140             : }
    1141             : 
    1142             : }  // namespace internal
    1143      183867 : }  // namespace v8

Generated by: LCOV version 1.10