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