V8 API Reference, 7.2.502.16 (for Deno 0.2.4)
intl-objects.cc
1 // Copyright 2013 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/intl-objects.h"
10 
11 #include <algorithm>
12 #include <memory>
13 #include <string>
14 #include <vector>
15 
16 #include "src/api-inl.h"
17 #include "src/global-handles.h"
18 #include "src/heap/factory.h"
19 #include "src/isolate.h"
20 #include "src/objects-inl.h"
21 #include "src/objects/js-collator-inl.h"
22 #include "src/objects/js-date-time-format-inl.h"
23 #include "src/objects/js-number-format-inl.h"
24 #include "src/objects/string.h"
25 #include "src/property-descriptor.h"
26 #include "src/string-case.h"
27 #include "unicode/basictz.h"
28 #include "unicode/brkiter.h"
29 #include "unicode/coll.h"
30 #include "unicode/decimfmt.h"
31 #include "unicode/locid.h"
32 #include "unicode/normalizer2.h"
33 #include "unicode/numfmt.h"
34 #include "unicode/numsys.h"
35 #include "unicode/timezone.h"
36 #include "unicode/ustring.h"
37 #include "unicode/uvernum.h" // U_ICU_VERSION_MAJOR_NUM
38 
39 #define XSTR(s) STR(s)
40 #define STR(s) #s
41 static_assert(
42  V8_MINIMUM_ICU_VERSION <= U_ICU_VERSION_MAJOR_NUM,
43  "v8 is required to build with ICU " XSTR(V8_MINIMUM_ICU_VERSION) " and up");
44 #undef STR
45 #undef XSTR
46 
47 namespace v8 {
48 namespace internal {
49 
50 namespace {
51 inline bool IsASCIIUpper(uint16_t ch) { return ch >= 'A' && ch <= 'Z'; }
52 
53 const uint8_t kToLower[256] = {
54  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
55  0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
56  0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23,
57  0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
58  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,
59  0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
60  0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73,
61  0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
62  0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B,
63  0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
64  0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83,
65  0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
66  0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B,
67  0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
68  0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3,
69  0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
70  0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB,
71  0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xD7,
72  0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3,
73  0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
74  0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB,
75  0xFC, 0xFD, 0xFE, 0xFF,
76 };
77 
78 inline uint16_t ToLatin1Lower(uint16_t ch) {
79  return static_cast<uint16_t>(kToLower[ch]);
80 }
81 
82 inline uint16_t ToASCIIUpper(uint16_t ch) {
83  return ch & ~((ch >= 'a' && ch <= 'z') << 5);
84 }
85 
86 // Does not work for U+00DF (sharp-s), U+00B5 (micron), U+00FF.
87 inline uint16_t ToLatin1Upper(uint16_t ch) {
88  DCHECK(ch != 0xDF && ch != 0xB5 && ch != 0xFF);
89  return ch &
90  ~(((ch >= 'a' && ch <= 'z') || (((ch & 0xE0) == 0xE0) && ch != 0xF7))
91  << 5);
92 }
93 
94 template <typename Char>
95 bool ToUpperFastASCII(const Vector<const Char>& src,
96  Handle<SeqOneByteString> result) {
97  // Do a faster loop for the case where all the characters are ASCII.
98  uint16_t ored = 0;
99  int32_t index = 0;
100  for (auto it = src.begin(); it != src.end(); ++it) {
101  uint16_t ch = static_cast<uint16_t>(*it);
102  ored |= ch;
103  result->SeqOneByteStringSet(index++, ToASCIIUpper(ch));
104  }
105  return !(ored & ~0x7F);
106 }
107 
108 const uint16_t sharp_s = 0xDF;
109 
110 template <typename Char>
111 bool ToUpperOneByte(const Vector<const Char>& src, uint8_t* dest,
112  int* sharp_s_count) {
113  // Still pretty-fast path for the input with non-ASCII Latin-1 characters.
114 
115  // There are two special cases.
116  // 1. U+00B5 and U+00FF are mapped to a character beyond U+00FF.
117  // 2. Lower case sharp-S converts to "SS" (two characters)
118  *sharp_s_count = 0;
119  for (auto it = src.begin(); it != src.end(); ++it) {
120  uint16_t ch = static_cast<uint16_t>(*it);
121  if (V8_UNLIKELY(ch == sharp_s)) {
122  ++(*sharp_s_count);
123  continue;
124  }
125  if (V8_UNLIKELY(ch == 0xB5 || ch == 0xFF)) {
126  // Since this upper-cased character does not fit in an 8-bit string, we
127  // need to take the 16-bit path.
128  return false;
129  }
130  *dest++ = ToLatin1Upper(ch);
131  }
132 
133  return true;
134 }
135 
136 template <typename Char>
137 void ToUpperWithSharpS(const Vector<const Char>& src,
138  Handle<SeqOneByteString> result) {
139  int32_t dest_index = 0;
140  for (auto it = src.begin(); it != src.end(); ++it) {
141  uint16_t ch = static_cast<uint16_t>(*it);
142  if (ch == sharp_s) {
143  result->SeqOneByteStringSet(dest_index++, 'S');
144  result->SeqOneByteStringSet(dest_index++, 'S');
145  } else {
146  result->SeqOneByteStringSet(dest_index++, ToLatin1Upper(ch));
147  }
148  }
149 }
150 
151 inline int FindFirstUpperOrNonAscii(String s, int length) {
152  for (int index = 0; index < length; ++index) {
153  uint16_t ch = s->Get(index);
154  if (V8_UNLIKELY(IsASCIIUpper(ch) || ch & ~0x7F)) {
155  return index;
156  }
157  }
158  return length;
159 }
160 
161 const UChar* GetUCharBufferFromFlat(const String::FlatContent& flat,
162  std::unique_ptr<uc16[]>* dest,
163  int32_t length) {
164  DCHECK(flat.IsFlat());
165  if (flat.IsOneByte()) {
166  if (!*dest) {
167  dest->reset(NewArray<uc16>(length));
168  CopyChars(dest->get(), flat.ToOneByteVector().start(), length);
169  }
170  return reinterpret_cast<const UChar*>(dest->get());
171  } else {
172  return reinterpret_cast<const UChar*>(flat.ToUC16Vector().start());
173  }
174 }
175 
176 template <typename T>
177 MaybeHandle<T> New(Isolate* isolate, Handle<JSFunction> constructor,
178  Handle<Object> locales, Handle<Object> options) {
179  Handle<JSObject> result;
180  ASSIGN_RETURN_ON_EXCEPTION(
181  isolate, result,
182  JSObject::New(constructor, constructor, Handle<AllocationSite>::null()),
183  T);
184  return T::Initialize(isolate, Handle<T>::cast(result), locales, options);
185 }
186 } // namespace
187 
188 const uint8_t* Intl::ToLatin1LowerTable() { return &kToLower[0]; }
189 
190 icu::UnicodeString Intl::ToICUUnicodeString(Isolate* isolate,
191  Handle<String> string) {
192  string = String::Flatten(isolate, string);
193  {
194  DisallowHeapAllocation no_gc;
195  std::unique_ptr<uc16[]> sap;
196  return icu::UnicodeString(GetUCharBufferFromFlat(string->GetFlatContent(),
197  &sap, string->length()),
198  string->length());
199  }
200 }
201 
202 namespace {
203 MaybeHandle<String> LocaleConvertCase(Isolate* isolate, Handle<String> s,
204  bool is_to_upper, const char* lang) {
205  auto case_converter = is_to_upper ? u_strToUpper : u_strToLower;
206  int32_t src_length = s->length();
207  int32_t dest_length = src_length;
208  UErrorCode status;
209  Handle<SeqTwoByteString> result;
210  std::unique_ptr<uc16[]> sap;
211 
212  if (dest_length == 0) return ReadOnlyRoots(isolate).empty_string_handle();
213 
214  // This is not a real loop. It'll be executed only once (no overflow) or
215  // twice (overflow).
216  for (int i = 0; i < 2; ++i) {
217  // Case conversion can increase the string length (e.g. sharp-S => SS) so
218  // that we have to handle RangeError exceptions here.
219  ASSIGN_RETURN_ON_EXCEPTION(
220  isolate, result, isolate->factory()->NewRawTwoByteString(dest_length),
221  String);
222  DisallowHeapAllocation no_gc;
223  DCHECK(s->IsFlat());
224  String::FlatContent flat = s->GetFlatContent();
225  const UChar* src = GetUCharBufferFromFlat(flat, &sap, src_length);
226  status = U_ZERO_ERROR;
227  dest_length = case_converter(reinterpret_cast<UChar*>(result->GetChars()),
228  dest_length, src, src_length, lang, &status);
229  if (status != U_BUFFER_OVERFLOW_ERROR) break;
230  }
231 
232  // In most cases, the output will fill the destination buffer completely
233  // leading to an unterminated string (U_STRING_NOT_TERMINATED_WARNING).
234  // Only in rare cases, it'll be shorter than the destination buffer and
235  // |result| has to be truncated.
236  DCHECK(U_SUCCESS(status));
237  if (V8_LIKELY(status == U_STRING_NOT_TERMINATED_WARNING)) {
238  DCHECK(dest_length == result->length());
239  return result;
240  }
241  DCHECK(dest_length < result->length());
242  return SeqString::Truncate(result, dest_length);
243 }
244 
245 } // namespace
246 
247 // A stripped-down version of ConvertToLower that can only handle flat one-byte
248 // strings and does not allocate. Note that {src} could still be, e.g., a
249 // one-byte sliced string with a two-byte parent string.
250 // Called from TF builtins.
251 String Intl::ConvertOneByteToLower(String src, String dst) {
252  DCHECK_EQ(src->length(), dst->length());
253  DCHECK(src->HasOnlyOneByteChars());
254  DCHECK(src->IsFlat());
255  DCHECK(dst->IsSeqOneByteString());
256 
257  DisallowHeapAllocation no_gc;
258 
259  const int length = src->length();
260  String::FlatContent src_flat = src->GetFlatContent();
261  uint8_t* dst_data = SeqOneByteString::cast(dst)->GetChars();
262 
263  if (src_flat.IsOneByte()) {
264  const uint8_t* src_data = src_flat.ToOneByteVector().start();
265 
266  bool has_changed_character = false;
267  int index_to_first_unprocessed =
268  FastAsciiConvert<true>(reinterpret_cast<char*>(dst_data),
269  reinterpret_cast<const char*>(src_data), length,
270  &has_changed_character);
271 
272  if (index_to_first_unprocessed == length) {
273  return has_changed_character ? dst : src;
274  }
275 
276  // If not ASCII, we keep the result up to index_to_first_unprocessed and
277  // process the rest.
278  for (int index = index_to_first_unprocessed; index < length; ++index) {
279  dst_data[index] = ToLatin1Lower(static_cast<uint16_t>(src_data[index]));
280  }
281  } else {
282  DCHECK(src_flat.IsTwoByte());
283  int index_to_first_unprocessed = FindFirstUpperOrNonAscii(src, length);
284  if (index_to_first_unprocessed == length) return src;
285 
286  const uint16_t* src_data = src_flat.ToUC16Vector().start();
287  CopyChars(dst_data, src_data, index_to_first_unprocessed);
288  for (int index = index_to_first_unprocessed; index < length; ++index) {
289  dst_data[index] = ToLatin1Lower(static_cast<uint16_t>(src_data[index]));
290  }
291  }
292 
293  return dst;
294 }
295 
296 MaybeHandle<String> Intl::ConvertToLower(Isolate* isolate, Handle<String> s) {
297  if (!s->HasOnlyOneByteChars()) {
298  // Use a slower implementation for strings with characters beyond U+00FF.
299  return LocaleConvertCase(isolate, s, false, "");
300  }
301 
302  int length = s->length();
303 
304  // We depend here on the invariant that the length of a Latin1
305  // string is invariant under ToLowerCase, and the result always
306  // fits in the Latin1 range in the *root locale*. It does not hold
307  // for ToUpperCase even in the root locale.
308 
309  // Scan the string for uppercase and non-ASCII characters for strings
310  // shorter than a machine-word without any memory allocation overhead.
311  // TODO(jshin): Apply this to a longer input by breaking FastAsciiConvert()
312  // to two parts, one for scanning the prefix with no change and the other for
313  // handling ASCII-only characters.
314 
315  bool is_short = length < static_cast<int>(sizeof(uintptr_t));
316  if (is_short) {
317  bool is_lower_ascii = FindFirstUpperOrNonAscii(*s, length) == length;
318  if (is_lower_ascii) return s;
319  }
320 
321  Handle<SeqOneByteString> result =
322  isolate->factory()->NewRawOneByteString(length).ToHandleChecked();
323 
324  return Handle<String>(Intl::ConvertOneByteToLower(*s, *result), isolate);
325 }
326 
327 MaybeHandle<String> Intl::ConvertToUpper(Isolate* isolate, Handle<String> s) {
328  int32_t length = s->length();
329  if (s->HasOnlyOneByteChars() && length > 0) {
330  Handle<SeqOneByteString> result =
331  isolate->factory()->NewRawOneByteString(length).ToHandleChecked();
332 
333  DCHECK(s->IsFlat());
334  int sharp_s_count;
335  bool is_result_single_byte;
336  {
337  DisallowHeapAllocation no_gc;
338  String::FlatContent flat = s->GetFlatContent();
339  uint8_t* dest = result->GetChars();
340  if (flat.IsOneByte()) {
341  Vector<const uint8_t> src = flat.ToOneByteVector();
342  bool has_changed_character = false;
343  int index_to_first_unprocessed =
344  FastAsciiConvert<false>(reinterpret_cast<char*>(result->GetChars()),
345  reinterpret_cast<const char*>(src.start()),
346  length, &has_changed_character);
347  if (index_to_first_unprocessed == length) {
348  return has_changed_character ? result : s;
349  }
350  // If not ASCII, we keep the result up to index_to_first_unprocessed and
351  // process the rest.
352  is_result_single_byte =
353  ToUpperOneByte(src.SubVector(index_to_first_unprocessed, length),
354  dest + index_to_first_unprocessed, &sharp_s_count);
355  } else {
356  DCHECK(flat.IsTwoByte());
357  Vector<const uint16_t> src = flat.ToUC16Vector();
358  if (ToUpperFastASCII(src, result)) return result;
359  is_result_single_byte = ToUpperOneByte(src, dest, &sharp_s_count);
360  }
361  }
362 
363  // Go to the full Unicode path if there are characters whose uppercase
364  // is beyond the Latin-1 range (cannot be represented in OneByteString).
365  if (V8_UNLIKELY(!is_result_single_byte)) {
366  return LocaleConvertCase(isolate, s, true, "");
367  }
368 
369  if (sharp_s_count == 0) return result;
370 
371  // We have sharp_s_count sharp-s characters, but the result is still
372  // in the Latin-1 range.
373  ASSIGN_RETURN_ON_EXCEPTION(
374  isolate, result,
375  isolate->factory()->NewRawOneByteString(length + sharp_s_count),
376  String);
377  DisallowHeapAllocation no_gc;
378  String::FlatContent flat = s->GetFlatContent();
379  if (flat.IsOneByte()) {
380  ToUpperWithSharpS(flat.ToOneByteVector(), result);
381  } else {
382  ToUpperWithSharpS(flat.ToUC16Vector(), result);
383  }
384 
385  return result;
386  }
387 
388  return LocaleConvertCase(isolate, s, true, "");
389 }
390 
391 std::string Intl::GetNumberingSystem(const icu::Locale& icu_locale) {
392  // Ugly hack. ICU doesn't expose numbering system in any way, so we have
393  // to assume that for given locale NumberingSystem constructor produces the
394  // same digits as NumberFormat/Calendar would.
395  UErrorCode status = U_ZERO_ERROR;
396  std::unique_ptr<icu::NumberingSystem> numbering_system(
397  icu::NumberingSystem::createInstance(icu_locale, status));
398  if (U_SUCCESS(status)) return numbering_system->getName();
399  return "latn";
400 }
401 
402 icu::Locale Intl::CreateICULocale(const std::string& bcp47_locale) {
403  DisallowHeapAllocation no_gc;
404 
405  // Convert BCP47 into ICU locale format.
406  UErrorCode status = U_ZERO_ERROR;
407  char icu_result[ULOC_FULLNAME_CAPACITY];
408  int parsed_length = 0;
409 
410  // bcp47_locale_str should be a canonicalized language tag, which
411  // means this shouldn't fail.
412  uloc_forLanguageTag(bcp47_locale.c_str(), icu_result, ULOC_FULLNAME_CAPACITY,
413  &parsed_length, &status);
414  CHECK(U_SUCCESS(status));
415 
416  // bcp47_locale is already checked for its structural validity
417  // so that it should be parsed completely.
418  size_t bcp47_length = bcp47_locale.length();
419  CHECK_EQ(bcp47_length, parsed_length);
420 
421  icu::Locale icu_locale(icu_result);
422  if (icu_locale.isBogus()) {
423  FATAL("Failed to create ICU locale, are ICU data files missing?");
424  }
425 
426  return icu_locale;
427 }
428 
429 // static
430 
431 MaybeHandle<String> Intl::ToString(Isolate* isolate,
432  const icu::UnicodeString& string) {
433  return isolate->factory()->NewStringFromTwoByte(Vector<const uint16_t>(
434  reinterpret_cast<const uint16_t*>(string.getBuffer()), string.length()));
435 }
436 
437 MaybeHandle<String> Intl::ToString(Isolate* isolate,
438  const icu::UnicodeString& string,
439  int32_t begin, int32_t end) {
440  return Intl::ToString(isolate, string.tempSubStringBetween(begin, end));
441 }
442 
443 namespace {
444 
445 Handle<JSObject> InnerAddElement(Isolate* isolate, Handle<JSArray> array,
446  int index, Handle<String> field_type_string,
447  Handle<String> value) {
448  // let element = $array[$index] = {
449  // type: $field_type_string,
450  // value: $value
451  // }
452  // return element;
453  Factory* factory = isolate->factory();
454  Handle<JSObject> element = factory->NewJSObject(isolate->object_function());
455  JSObject::AddProperty(isolate, element, factory->type_string(),
456  field_type_string, NONE);
457 
458  JSObject::AddProperty(isolate, element, factory->value_string(), value, NONE);
459  JSObject::AddDataElement(array, index, element, NONE);
460  return element;
461 }
462 
463 } // namespace
464 
465 void Intl::AddElement(Isolate* isolate, Handle<JSArray> array, int index,
466  Handle<String> field_type_string, Handle<String> value) {
467  // Same as $array[$index] = {type: $field_type_string, value: $value};
468  InnerAddElement(isolate, array, index, field_type_string, value);
469 }
470 
471 void Intl::AddElement(Isolate* isolate, Handle<JSArray> array, int index,
472  Handle<String> field_type_string, Handle<String> value,
473  Handle<String> additional_property_name,
474  Handle<String> additional_property_value) {
475  // Same as $array[$index] = {
476  // type: $field_type_string, value: $value,
477  // $additional_property_name: $additional_property_value
478  // }
479  Handle<JSObject> element =
480  InnerAddElement(isolate, array, index, field_type_string, value);
481  JSObject::AddProperty(isolate, element, additional_property_name,
482  additional_property_value, NONE);
483 }
484 
485 namespace {
486 
487 // Build the shortened locale; eg, convert xx_Yyyy_ZZ to xx_ZZ.
488 //
489 // If locale has a script tag then return true and the locale without the
490 // script else return false and an empty string.
491 bool RemoveLocaleScriptTag(const std::string& icu_locale,
492  std::string* locale_less_script) {
493  icu::Locale new_locale = icu::Locale::createCanonical(icu_locale.c_str());
494  const char* icu_script = new_locale.getScript();
495  if (icu_script == nullptr || strlen(icu_script) == 0) {
496  *locale_less_script = std::string();
497  return false;
498  }
499 
500  const char* icu_language = new_locale.getLanguage();
501  const char* icu_country = new_locale.getCountry();
502  icu::Locale short_locale = icu::Locale(icu_language, icu_country);
503  *locale_less_script = short_locale.getName();
504  return true;
505 }
506 
507 } // namespace
508 
509 std::set<std::string> Intl::BuildLocaleSet(
510  const icu::Locale* icu_available_locales, int32_t count) {
511  std::set<std::string> locales;
512  UErrorCode error = U_ZERO_ERROR;
513  char result[ULOC_FULLNAME_CAPACITY];
514 
515  for (int32_t i = 0; i < count; ++i) {
516  const char* icu_name = icu_available_locales[i].getName();
517 
518  error = U_ZERO_ERROR;
519  // No need to force strict BCP47 rules.
520  uloc_toLanguageTag(icu_name, result, ULOC_FULLNAME_CAPACITY, FALSE, &error);
521  if (U_FAILURE(error) || error == U_STRING_NOT_TERMINATED_WARNING) {
522  // This shouldn't happen, but lets not break the user.
523  continue;
524  }
525  std::string locale(result);
526  locales.insert(locale);
527 
528  std::string shortened_locale;
529  if (RemoveLocaleScriptTag(icu_name, &shortened_locale)) {
530  std::replace(shortened_locale.begin(), shortened_locale.end(), '_', '-');
531  locales.insert(shortened_locale);
532  }
533  }
534 
535  return locales;
536 }
537 
538 std::string Intl::DefaultLocale(Isolate* isolate) {
539  if (isolate->default_locale().empty()) {
540  icu::Locale default_locale;
541  // Translate ICU's fallback locale to a well-known locale.
542  if (strcmp(default_locale.getName(), "en_US_POSIX") == 0) {
543  isolate->set_default_locale("en-US");
544  } else {
545  // Set the locale
546  char result[ULOC_FULLNAME_CAPACITY];
547  UErrorCode status = U_ZERO_ERROR;
548  int32_t length =
549  uloc_toLanguageTag(default_locale.getName(), result,
550  ULOC_FULLNAME_CAPACITY, FALSE, &status);
551  isolate->set_default_locale(
552  U_SUCCESS(status) ? std::string(result, length) : "und");
553  }
554  DCHECK(!isolate->default_locale().empty());
555  }
556  return isolate->default_locale();
557 }
558 
559 // See ecma402/#legacy-constructor.
560 MaybeHandle<Object> Intl::LegacyUnwrapReceiver(Isolate* isolate,
561  Handle<JSReceiver> receiver,
562  Handle<JSFunction> constructor,
563  bool has_initialized_slot) {
564  Handle<Object> obj_is_instance_of;
565  ASSIGN_RETURN_ON_EXCEPTION(isolate, obj_is_instance_of,
566  Object::InstanceOf(isolate, receiver, constructor),
567  Object);
568  bool is_instance_of = obj_is_instance_of->BooleanValue(isolate);
569 
570  // 2. If receiver does not have an [[Initialized...]] internal slot
571  // and ? InstanceofOperator(receiver, constructor) is true, then
572  if (!has_initialized_slot && is_instance_of) {
573  // 2. a. Let new_receiver be ? Get(receiver, %Intl%.[[FallbackSymbol]]).
574  Handle<Object> new_receiver;
575  ASSIGN_RETURN_ON_EXCEPTION(
576  isolate, new_receiver,
577  JSReceiver::GetProperty(isolate, receiver,
578  isolate->factory()->intl_fallback_symbol()),
579  Object);
580  return new_receiver;
581  }
582 
583  return receiver;
584 }
585 
586 Maybe<bool> Intl::GetStringOption(Isolate* isolate, Handle<JSReceiver> options,
587  const char* property,
588  std::vector<const char*> values,
589  const char* service,
590  std::unique_ptr<char[]>* result) {
591  Handle<String> property_str =
592  isolate->factory()->NewStringFromAsciiChecked(property);
593 
594  // 1. Let value be ? Get(options, property).
595  Handle<Object> value;
596  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
597  isolate, value,
598  Object::GetPropertyOrElement(isolate, options, property_str),
599  Nothing<bool>());
600 
601  if (value->IsUndefined(isolate)) {
602  return Just(false);
603  }
604 
605  // 2. c. Let value be ? ToString(value).
606  Handle<String> value_str;
607  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
608  isolate, value_str, Object::ToString(isolate, value), Nothing<bool>());
609  std::unique_ptr<char[]> value_cstr = value_str->ToCString();
610 
611  // 2. d. if values is not undefined, then
612  if (values.size() > 0) {
613  // 2. d. i. If values does not contain an element equal to value,
614  // throw a RangeError exception.
615  for (size_t i = 0; i < values.size(); i++) {
616  if (strcmp(values.at(i), value_cstr.get()) == 0) {
617  // 2. e. return value
618  *result = std::move(value_cstr);
619  return Just(true);
620  }
621  }
622 
623  Handle<String> service_str =
624  isolate->factory()->NewStringFromAsciiChecked(service);
625  THROW_NEW_ERROR_RETURN_VALUE(
626  isolate,
627  NewRangeError(MessageTemplate::kValueOutOfRange, value, service_str,
628  property_str),
629  Nothing<bool>());
630  }
631 
632  // 2. e. return value
633  *result = std::move(value_cstr);
634  return Just(true);
635 }
636 
637 V8_WARN_UNUSED_RESULT Maybe<bool> Intl::GetBoolOption(
638  Isolate* isolate, Handle<JSReceiver> options, const char* property,
639  const char* service, bool* result) {
640  Handle<String> property_str =
641  isolate->factory()->NewStringFromAsciiChecked(property);
642 
643  // 1. Let value be ? Get(options, property).
644  Handle<Object> value;
645  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
646  isolate, value,
647  Object::GetPropertyOrElement(isolate, options, property_str),
648  Nothing<bool>());
649 
650  // 2. If value is not undefined, then
651  if (!value->IsUndefined(isolate)) {
652  // 2. b. i. Let value be ToBoolean(value).
653  *result = value->BooleanValue(isolate);
654 
655  // 2. e. return value
656  return Just(true);
657  }
658 
659  return Just(false);
660 }
661 
662 namespace {
663 
664 char AsciiToLower(char c) {
665  if (c < 'A' || c > 'Z') {
666  return c;
667  }
668  return c | (1 << 5);
669 }
670 
671 bool IsLowerAscii(char c) { return c >= 'a' && c < 'z'; }
672 
673 bool IsTwoLetterLanguage(const std::string& locale) {
674  // Two letters, both in range 'a'-'z'...
675  return locale.length() == 2 && IsLowerAscii(locale[0]) &&
676  IsLowerAscii(locale[1]);
677 }
678 
679 bool IsDeprecatedLanguage(const std::string& locale) {
680  // Check if locale is one of the deprecated language tags:
681  return locale == "in" || locale == "iw" || locale == "ji" || locale == "jw";
682 }
683 
684 // Reference:
685 // https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
686 bool IsGrandfatheredTagWithoutPreferredVaule(const std::string& locale) {
687  if (V8_UNLIKELY(locale == "zh-min" || locale == "cel-gaulish")) return true;
688  if (locale.length() > 6 /* i-mingo is 7 chars long */ &&
689  V8_UNLIKELY(locale[0] == 'i' && locale[1] == '-')) {
690  return locale.substr(2) == "default" || locale.substr(2) == "enochian" ||
691  locale.substr(2) == "mingo";
692  }
693  return false;
694 }
695 
696 } // anonymous namespace
697 
698 Maybe<std::string> Intl::CanonicalizeLanguageTag(Isolate* isolate,
699  Handle<Object> locale_in) {
700  Handle<String> locale_str;
701  // This does part of the validity checking spec'ed in CanonicalizeLocaleList:
702  // 7c ii. If Type(kValue) is not String or Object, throw a TypeError
703  // exception.
704  // 7c iii. Let tag be ? ToString(kValue).
705  // 7c iv. If IsStructurallyValidLanguageTag(tag) is false, throw a
706  // RangeError exception.
707 
708  if (locale_in->IsString()) {
709  locale_str = Handle<String>::cast(locale_in);
710  } else if (locale_in->IsJSReceiver()) {
711  ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, locale_str,
712  Object::ToString(isolate, locale_in),
713  Nothing<std::string>());
714  } else {
715  THROW_NEW_ERROR_RETURN_VALUE(isolate,
716  NewTypeError(MessageTemplate::kLanguageID),
717  Nothing<std::string>());
718  }
719  std::string locale(locale_str->ToCString().get());
720 
721  if (locale.length() == 0 ||
722  !String::IsAscii(locale.data(), static_cast<int>(locale.length()))) {
723  THROW_NEW_ERROR_RETURN_VALUE(
724  isolate,
725  NewRangeError(MessageTemplate::kInvalidLanguageTag, locale_str),
726  Nothing<std::string>());
727  }
728 
729  // Optimize for the most common case: a 2-letter language code in the
730  // canonical form/lowercase that is not one of the deprecated codes
731  // (in, iw, ji, jw). Don't check for ~70 of 3-letter deprecated language
732  // codes. Instead, let them be handled by ICU in the slow path. However,
733  // fast-track 'fil' (3-letter canonical code).
734  if ((IsTwoLetterLanguage(locale) && !IsDeprecatedLanguage(locale)) ||
735  locale == "fil") {
736  return Just(locale);
737  }
738 
739  // Because per BCP 47 2.1.1 language tags are case-insensitive, lowercase
740  // the input before any more check.
741  std::transform(locale.begin(), locale.end(), locale.begin(), AsciiToLower);
742 
743  // ICU maps a few grandfathered tags to what looks like a regular language
744  // tag even though IANA language tag registry does not have a preferred
745  // entry map for them. Return them as they're with lowercasing.
746  if (IsGrandfatheredTagWithoutPreferredVaule(locale)) {
747  return Just(locale);
748  }
749 
750  // // ECMA 402 6.2.3
751  // TODO(jshin): uloc_{for,to}TanguageTag can fail even for a structually valid
752  // language tag if it's too long (much longer than 100 chars). Even if we
753  // allocate a longer buffer, ICU will still fail if it's too long. Either
754  // propose to Ecma 402 to put a limit on the locale length or change ICU to
755  // handle long locale names better. See
756  // https://unicode-org.atlassian.net/browse/ICU-13417
757  UErrorCode error = U_ZERO_ERROR;
758  char icu_result[ULOC_FULLNAME_CAPACITY];
759  // uloc_forLanguageTag checks the structrual validity. If the input BCP47
760  // language tag is parsed all the way to the end, it indicates that the input
761  // is structurally valid. Due to a couple of bugs, we can't use it
762  // without Chromium patches or ICU 62 or earlier.
763  int parsed_length;
764  uloc_forLanguageTag(locale.c_str(), icu_result, ULOC_FULLNAME_CAPACITY,
765  &parsed_length, &error);
766  if (U_FAILURE(error) ||
767  static_cast<size_t>(parsed_length) < locale.length() ||
768  error == U_STRING_NOT_TERMINATED_WARNING) {
769  THROW_NEW_ERROR_RETURN_VALUE(
770  isolate,
771  NewRangeError(MessageTemplate::kInvalidLanguageTag, locale_str),
772  Nothing<std::string>());
773  }
774 
775  // Force strict BCP47 rules.
776  char result[ULOC_FULLNAME_CAPACITY];
777  int32_t result_len = uloc_toLanguageTag(icu_result, result,
778  ULOC_FULLNAME_CAPACITY, TRUE, &error);
779 
780  if (U_FAILURE(error)) {
781  THROW_NEW_ERROR_RETURN_VALUE(
782  isolate,
783  NewRangeError(MessageTemplate::kInvalidLanguageTag, locale_str),
784  Nothing<std::string>());
785  }
786 
787  return Just(std::string(result, result_len));
788 }
789 
790 Maybe<std::vector<std::string>> Intl::CanonicalizeLocaleList(
791  Isolate* isolate, Handle<Object> locales, bool only_return_one_result) {
792  // 1. If locales is undefined, then
793  if (locales->IsUndefined(isolate)) {
794  // 1a. Return a new empty List.
795  return Just(std::vector<std::string>());
796  }
797  // 2. Let seen be a new empty List.
798  std::vector<std::string> seen;
799  // 3. If Type(locales) is String, then
800  if (locales->IsString()) {
801  // 3a. Let O be CreateArrayFromList(« locales »).
802  // Instead of creating a one-element array and then iterating over it,
803  // we inline the body of the iteration:
804  std::string canonicalized_tag;
805  if (!CanonicalizeLanguageTag(isolate, locales).To(&canonicalized_tag)) {
806  return Nothing<std::vector<std::string>>();
807  }
808  seen.push_back(canonicalized_tag);
809  return Just(seen);
810  }
811  // 4. Else,
812  // 4a. Let O be ? ToObject(locales).
813  Handle<JSReceiver> o;
814  ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, o,
815  Object::ToObject(isolate, locales),
816  Nothing<std::vector<std::string>>());
817  // 5. Let len be ? ToLength(? Get(O, "length")).
818  Handle<Object> length_obj;
819  ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, length_obj,
820  Object::GetLengthFromArrayLike(isolate, o),
821  Nothing<std::vector<std::string>>());
822  // TODO(jkummerow): Spec violation: strictly speaking, we have to iterate
823  // up to 2^53-1 if {length_obj} says so. Since cases above 2^32 probably
824  // don't happen in practice (and would be very slow if they do), we'll keep
825  // the code simple for now by using a saturating to-uint32 conversion.
826  double raw_length = length_obj->Number();
827  uint32_t len =
828  raw_length >= kMaxUInt32 ? kMaxUInt32 : static_cast<uint32_t>(raw_length);
829  // 6. Let k be 0.
830  // 7. Repeat, while k < len
831  for (uint32_t k = 0; k < len; k++) {
832  // 7a. Let Pk be ToString(k).
833  // 7b. Let kPresent be ? HasProperty(O, Pk).
834  LookupIterator it(isolate, o, k);
835  Maybe<bool> maybe_found = JSReceiver::HasProperty(&it);
836  MAYBE_RETURN(maybe_found, Nothing<std::vector<std::string>>());
837  // 7c. If kPresent is true, then
838  if (!maybe_found.FromJust()) continue;
839  // 7c i. Let kValue be ? Get(O, Pk).
840  Handle<Object> k_value;
841  ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, k_value, Object::GetProperty(&it),
842  Nothing<std::vector<std::string>>());
843  // 7c ii. If Type(kValue) is not String or Object, throw a TypeError
844  // exception.
845  // 7c iii. Let tag be ? ToString(kValue).
846  // 7c iv. If IsStructurallyValidLanguageTag(tag) is false, throw a
847  // RangeError exception.
848  // 7c v. Let canonicalizedTag be CanonicalizeLanguageTag(tag).
849  std::string canonicalized_tag;
850  if (!CanonicalizeLanguageTag(isolate, k_value).To(&canonicalized_tag)) {
851  return Nothing<std::vector<std::string>>();
852  }
853  // 7c vi. If canonicalizedTag is not an element of seen, append
854  // canonicalizedTag as the last element of seen.
855  if (std::find(seen.begin(), seen.end(), canonicalized_tag) == seen.end()) {
856  seen.push_back(canonicalized_tag);
857  }
858  // 7d. Increase k by 1. (See loop header.)
859  // Optimization: some callers only need one result.
860  if (only_return_one_result) return Just(seen);
861  }
862  // 8. Return seen.
863  return Just(seen);
864 }
865 
866 // ecma402 #sup-string.prototype.tolocalelowercase
867 // ecma402 #sup-string.prototype.tolocaleuppercase
868 MaybeHandle<String> Intl::StringLocaleConvertCase(Isolate* isolate,
869  Handle<String> s,
870  bool to_upper,
871  Handle<Object> locales) {
872  std::vector<std::string> requested_locales;
873  if (!CanonicalizeLocaleList(isolate, locales, true).To(&requested_locales)) {
874  return MaybeHandle<String>();
875  }
876  std::string requested_locale = requested_locales.size() == 0
877  ? Intl::DefaultLocale(isolate)
878  : requested_locales[0];
879  size_t dash = requested_locale.find('-');
880  if (dash != std::string::npos) {
881  requested_locale = requested_locale.substr(0, dash);
882  }
883 
884  // Primary language tag can be up to 8 characters long in theory.
885  // https://tools.ietf.org/html/bcp47#section-2.2.1
886  DCHECK_LE(requested_locale.length(), 8);
887  s = String::Flatten(isolate, s);
888 
889  // All the languages requiring special-handling have two-letter codes.
890  // Note that we have to check for '!= 2' here because private-use language
891  // tags (x-foo) or grandfathered irregular tags (e.g. i-enochian) would have
892  // only 'x' or 'i' when they get here.
893  if (V8_UNLIKELY(requested_locale.length() != 2)) {
894  if (to_upper) {
895  return ConvertToUpper(isolate, s);
896  }
897  return ConvertToLower(isolate, s);
898  }
899  // TODO(jshin): Consider adding a fast path for ASCII or Latin-1. The fastpath
900  // in the root locale needs to be adjusted for az, lt and tr because even case
901  // mapping of ASCII range characters are different in those locales.
902  // Greek (el) does not require any adjustment.
903  if (V8_UNLIKELY((requested_locale == "tr") || (requested_locale == "el") ||
904  (requested_locale == "lt") || (requested_locale == "az"))) {
905  return LocaleConvertCase(isolate, s, to_upper, requested_locale.c_str());
906  } else {
907  if (to_upper) {
908  return ConvertToUpper(isolate, s);
909  }
910  return ConvertToLower(isolate, s);
911  }
912 }
913 
914 MaybeHandle<Object> Intl::StringLocaleCompare(Isolate* isolate,
915  Handle<String> string1,
916  Handle<String> string2,
917  Handle<Object> locales,
918  Handle<Object> options) {
919  // We only cache the instance when both locales and options are undefined,
920  // as that is the only case when the specified side-effects of examining
921  // those arguments are unobservable.
922  bool can_cache =
923  locales->IsUndefined(isolate) && options->IsUndefined(isolate);
924  if (can_cache) {
925  // Both locales and options are undefined, check the cache.
926  icu::Collator* cached_icu_collator =
927  static_cast<icu::Collator*>(isolate->get_cached_icu_object(
928  Isolate::ICUObjectCacheType::kDefaultCollator));
929  // We may use the cached icu::Collator for a fast path.
930  if (cached_icu_collator != nullptr) {
931  return Intl::CompareStrings(isolate, *cached_icu_collator, string1,
932  string2);
933  }
934  }
935 
936  Handle<JSFunction> constructor = Handle<JSFunction>(
937  JSFunction::cast(
938  isolate->context()->native_context()->intl_collator_function()),
939  isolate);
940 
941  Handle<JSCollator> collator;
942  ASSIGN_RETURN_ON_EXCEPTION(
943  isolate, collator,
944  New<JSCollator>(isolate, constructor, locales, options), Object);
945  if (can_cache) {
946  isolate->set_icu_object_in_cache(
947  Isolate::ICUObjectCacheType::kDefaultCollator,
948  std::static_pointer_cast<icu::UObject>(
949  collator->icu_collator()->get()));
950  }
951  return Intl::CompareStrings(isolate, *(collator->icu_collator()->raw()),
952  string1, string2);
953 }
954 
955 // ecma402/#sec-collator-comparestrings
956 Handle<Object> Intl::CompareStrings(Isolate* isolate,
957  const icu::Collator& icu_collator,
958  Handle<String> string1,
959  Handle<String> string2) {
960  Factory* factory = isolate->factory();
961 
962  string1 = String::Flatten(isolate, string1);
963  string2 = String::Flatten(isolate, string2);
964 
965  UCollationResult result;
966  UErrorCode status = U_ZERO_ERROR;
967  icu::UnicodeString string_val1 = Intl::ToICUUnicodeString(isolate, string1);
968  icu::UnicodeString string_val2 = Intl::ToICUUnicodeString(isolate, string2);
969  result = icu_collator.compare(string_val1, string_val2, status);
970  DCHECK(U_SUCCESS(status));
971 
972  return factory->NewNumberFromInt(result);
973 }
974 
975 // ecma402/#sup-properties-of-the-number-prototype-object
976 MaybeHandle<String> Intl::NumberToLocaleString(Isolate* isolate,
977  Handle<Object> num,
978  Handle<Object> locales,
979  Handle<Object> options) {
980  Handle<Object> number_obj;
981  ASSIGN_RETURN_ON_EXCEPTION(isolate, number_obj,
982  Object::ToNumber(isolate, num), String);
983 
984  // Spec treats -0 and +0 as 0.
985  double number = number_obj->Number() + 0;
986 
987  // We only cache the instance when both locales and options are undefined,
988  // as that is the only case when the specified side-effects of examining
989  // those arguments are unobservable.
990  bool can_cache =
991  locales->IsUndefined(isolate) && options->IsUndefined(isolate);
992  if (can_cache) {
993  icu::NumberFormat* cached_number_format =
994  static_cast<icu::NumberFormat*>(isolate->get_cached_icu_object(
995  Isolate::ICUObjectCacheType::kDefaultNumberFormat));
996  // We may use the cached icu::NumberFormat for a fast path.
997  if (cached_number_format != nullptr) {
998  return JSNumberFormat::FormatNumber(isolate, *cached_number_format,
999  number);
1000  }
1001  }
1002 
1003  Handle<JSFunction> constructor = Handle<JSFunction>(
1004  JSFunction::cast(
1005  isolate->context()->native_context()->intl_number_format_function()),
1006  isolate);
1007  Handle<JSNumberFormat> number_format;
1008  // 2. Let numberFormat be ? Construct(%NumberFormat%, « locales, options »).
1009  ASSIGN_RETURN_ON_EXCEPTION(
1010  isolate, number_format,
1011  New<JSNumberFormat>(isolate, constructor, locales, options), String);
1012 
1013  if (can_cache) {
1014  isolate->set_icu_object_in_cache(
1015  Isolate::ICUObjectCacheType::kDefaultNumberFormat,
1016  std::static_pointer_cast<icu::UObject>(
1017  number_format->icu_number_format()->get()));
1018  }
1019 
1020  // Return FormatNumber(numberFormat, x).
1021  return JSNumberFormat::FormatNumber(
1022  isolate, *(number_format->icu_number_format()->raw()), number);
1023 }
1024 
1025 namespace {
1026 
1027 // ecma402/#sec-defaultnumberoption
1028 Maybe<int> DefaultNumberOption(Isolate* isolate, Handle<Object> value, int min,
1029  int max, int fallback, Handle<String> property) {
1030  // 2. Else, return fallback.
1031  if (value->IsUndefined()) return Just(fallback);
1032 
1033  // 1. If value is not undefined, then
1034  // a. Let value be ? ToNumber(value).
1035  Handle<Object> value_num;
1036  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
1037  isolate, value_num, Object::ToNumber(isolate, value), Nothing<int>());
1038  DCHECK(value_num->IsNumber());
1039 
1040  // b. If value is NaN or less than minimum or greater than maximum, throw a
1041  // RangeError exception.
1042  if (value_num->IsNaN() || value_num->Number() < min ||
1043  value_num->Number() > max) {
1044  THROW_NEW_ERROR_RETURN_VALUE(
1045  isolate,
1046  NewRangeError(MessageTemplate::kPropertyValueOutOfRange, property),
1047  Nothing<int>());
1048  }
1049 
1050  // The max and min arguments are integers and the above check makes
1051  // sure that we are within the integer range making this double to
1052  // int conversion safe.
1053  //
1054  // c. Return floor(value).
1055  return Just(FastD2I(floor(value_num->Number())));
1056 }
1057 
1058 // ecma402/#sec-getnumberoption
1059 Maybe<int> GetNumberOption(Isolate* isolate, Handle<JSReceiver> options,
1060  Handle<String> property, int min, int max,
1061  int fallback) {
1062  // 1. Let value be ? Get(options, property).
1063  Handle<Object> value;
1064  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
1065  isolate, value, JSReceiver::GetProperty(isolate, options, property),
1066  Nothing<int>());
1067 
1068  // Return ? DefaultNumberOption(value, minimum, maximum, fallback).
1069  return DefaultNumberOption(isolate, value, min, max, fallback, property);
1070 }
1071 
1072 Maybe<int> GetNumberOption(Isolate* isolate, Handle<JSReceiver> options,
1073  const char* property, int min, int max,
1074  int fallback) {
1075  Handle<String> property_str =
1076  isolate->factory()->NewStringFromAsciiChecked(property);
1077  return GetNumberOption(isolate, options, property_str, min, max, fallback);
1078 }
1079 
1080 } // namespace
1081 
1082 Maybe<bool> Intl::SetNumberFormatDigitOptions(Isolate* isolate,
1083  icu::DecimalFormat* number_format,
1084  Handle<JSReceiver> options,
1085  int mnfd_default,
1086  int mxfd_default) {
1087  CHECK_NOT_NULL(number_format);
1088 
1089  // 5. Let mnid be ? GetNumberOption(options, "minimumIntegerDigits,", 1, 21,
1090  // 1).
1091  int mnid;
1092  if (!GetNumberOption(isolate, options, "minimumIntegerDigits", 1, 21, 1)
1093  .To(&mnid)) {
1094  return Nothing<bool>();
1095  }
1096 
1097  // 6. Let mnfd be ? GetNumberOption(options, "minimumFractionDigits", 0, 20,
1098  // mnfdDefault).
1099  int mnfd;
1100  if (!GetNumberOption(isolate, options, "minimumFractionDigits", 0, 20,
1101  mnfd_default)
1102  .To(&mnfd)) {
1103  return Nothing<bool>();
1104  }
1105 
1106  // 7. Let mxfdActualDefault be max( mnfd, mxfdDefault ).
1107  int mxfd_actual_default = std::max(mnfd, mxfd_default);
1108 
1109  // 8. Let mxfd be ? GetNumberOption(options,
1110  // "maximumFractionDigits", mnfd, 20, mxfdActualDefault).
1111  int mxfd;
1112  if (!GetNumberOption(isolate, options, "maximumFractionDigits", mnfd, 20,
1113  mxfd_actual_default)
1114  .To(&mxfd)) {
1115  return Nothing<bool>();
1116  }
1117 
1118  // 9. Let mnsd be ? Get(options, "minimumSignificantDigits").
1119  Handle<Object> mnsd_obj;
1120  Handle<String> mnsd_str =
1121  isolate->factory()->minimumSignificantDigits_string();
1122  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
1123  isolate, mnsd_obj, JSReceiver::GetProperty(isolate, options, mnsd_str),
1124  Nothing<bool>());
1125 
1126  // 10. Let mxsd be ? Get(options, "maximumSignificantDigits").
1127  Handle<Object> mxsd_obj;
1128  Handle<String> mxsd_str =
1129  isolate->factory()->maximumSignificantDigits_string();
1130  ASSIGN_RETURN_ON_EXCEPTION_VALUE(
1131  isolate, mxsd_obj, JSReceiver::GetProperty(isolate, options, mxsd_str),
1132  Nothing<bool>());
1133 
1134  // 11. Set intlObj.[[MinimumIntegerDigits]] to mnid.
1135  number_format->setMinimumIntegerDigits(mnid);
1136 
1137  // 12. Set intlObj.[[MinimumFractionDigits]] to mnfd.
1138  number_format->setMinimumFractionDigits(mnfd);
1139 
1140  // 13. Set intlObj.[[MaximumFractionDigits]] to mxfd.
1141  number_format->setMaximumFractionDigits(mxfd);
1142 
1143  bool significant_digits_used = false;
1144  // 14. If mnsd is not undefined or mxsd is not undefined, then
1145  if (!mnsd_obj->IsUndefined(isolate) || !mxsd_obj->IsUndefined(isolate)) {
1146  // 14. a. Let mnsd be ? DefaultNumberOption(mnsd, 1, 21, 1).
1147  int mnsd;
1148  if (!DefaultNumberOption(isolate, mnsd_obj, 1, 21, 1, mnsd_str).To(&mnsd)) {
1149  return Nothing<bool>();
1150  }
1151 
1152  // 14. b. Let mxsd be ? DefaultNumberOption(mxsd, mnsd, 21, 21).
1153  int mxsd;
1154  if (!DefaultNumberOption(isolate, mxsd_obj, mnsd, 21, 21, mxsd_str)
1155  .To(&mxsd)) {
1156  return Nothing<bool>();
1157  }
1158 
1159  significant_digits_used = true;
1160 
1161  // 14. c. Set intlObj.[[MinimumSignificantDigits]] to mnsd.
1162  number_format->setMinimumSignificantDigits(mnsd);
1163 
1164  // 14. d. Set intlObj.[[MaximumSignificantDigits]] to mxsd.
1165  number_format->setMaximumSignificantDigits(mxsd);
1166  }
1167 
1168  number_format->setSignificantDigitsUsed(significant_digits_used);
1169  number_format->setRoundingMode(icu::DecimalFormat::kRoundHalfUp);
1170  return Just(true);
1171 }
1172 
1173 namespace {
1174 
1175 // ecma402/#sec-bestavailablelocale
1176 std::string BestAvailableLocale(const std::set<std::string>& available_locales,
1177  const std::string& locale) {
1178  // 1. Let candidate be locale.
1179  std::string candidate = locale;
1180 
1181  // 2. Repeat,
1182  while (true) {
1183  // 2.a. If availableLocales contains an element equal to candidate, return
1184  // candidate.
1185  if (available_locales.find(candidate) != available_locales.end()) {
1186  return candidate;
1187  }
1188 
1189  // 2.b. Let pos be the character index of the last occurrence of "-"
1190  // (U+002D) within candidate. If that character does not occur, return
1191  // undefined.
1192  size_t pos = candidate.rfind('-');
1193  if (pos == std::string::npos) {
1194  return std::string();
1195  }
1196 
1197  // 2.c. If pos ≥ 2 and the character "-" occurs at index pos-2 of candidate,
1198  // decrease pos by 2.
1199  if (pos >= 2 && candidate[pos - 2] == '-') {
1200  pos -= 2;
1201  }
1202 
1203  // 2.d. Let candidate be the substring of candidate from position 0,
1204  // inclusive, to position pos, exclusive.
1205  candidate = candidate.substr(0, pos);
1206  }
1207 }
1208 
1209 struct ParsedLocale {
1210  std::string no_extensions_locale;
1211  std::string extension;
1212 };
1213 
1214 // Returns a struct containing a bcp47 tag without unicode extensions
1215 // and the removed unicode extensions.
1216 //
1217 // For example, given 'en-US-u-co-emoji' returns 'en-US' and
1218 // 'u-co-emoji'.
1219 ParsedLocale ParseBCP47Locale(const std::string& locale) {
1220  size_t length = locale.length();
1221  ParsedLocale parsed_locale;
1222 
1223  // Privateuse or grandfathered locales have no extension sequences.
1224  if ((length > 1) && (locale[1] == '-')) {
1225  // Check to make sure that this really is a grandfathered or
1226  // privateuse extension. ICU can sometimes mess up the
1227  // canonicalization.
1228  CHECK(locale[0] == 'x' || locale[0] == 'i');
1229  parsed_locale.no_extensions_locale = locale;
1230  return parsed_locale;
1231  }
1232 
1233  size_t unicode_extension_start = locale.find("-u-");
1234 
1235  // No unicode extensions found.
1236  if (unicode_extension_start == std::string::npos) {
1237  parsed_locale.no_extensions_locale = locale;
1238  return parsed_locale;
1239  }
1240 
1241  size_t private_extension_start = locale.find("-x-");
1242 
1243  // Unicode extensions found within privateuse subtags don't count.
1244  if (private_extension_start != std::string::npos &&
1245  private_extension_start < unicode_extension_start) {
1246  parsed_locale.no_extensions_locale = locale;
1247  return parsed_locale;
1248  }
1249 
1250  const std::string beginning = locale.substr(0, unicode_extension_start);
1251  size_t unicode_extension_end = length;
1252  DCHECK_GT(length, 2);
1253 
1254  // Find the end of the extension production as per the bcp47 grammar
1255  // by looking for '-' followed by 2 chars and then another '-'.
1256  for (size_t i = unicode_extension_start + 1; i < length - 2; i++) {
1257  if (locale[i] != '-') continue;
1258 
1259  if (locale[i + 2] == '-') {
1260  unicode_extension_end = i;
1261  break;
1262  }
1263 
1264  i += 2;
1265  }
1266 
1267  const std::string end = locale.substr(unicode_extension_end);
1268  parsed_locale.no_extensions_locale = beginning + end;
1269  parsed_locale.extension = locale.substr(
1270  unicode_extension_start, unicode_extension_end - unicode_extension_start);
1271  return parsed_locale;
1272 }
1273 
1274 // ecma402/#sec-lookupsupportedlocales
1275 std::vector<std::string> LookupSupportedLocales(
1276  const std::set<std::string>& available_locales,
1277  const std::vector<std::string>& requested_locales) {
1278  // 1. Let subset be a new empty List.
1279  std::vector<std::string> subset;
1280 
1281  // 2. For each element locale of requestedLocales in List order, do
1282  for (const std::string& locale : requested_locales) {
1283  // 2. a. Let noExtensionsLocale be the String value that is locale
1284  // with all Unicode locale extension sequences removed.
1285  std::string no_extension_locale =
1286  ParseBCP47Locale(locale).no_extensions_locale;
1287 
1288  // 2. b. Let availableLocale be
1289  // BestAvailableLocale(availableLocales, noExtensionsLocale).
1290  std::string available_locale =
1291  BestAvailableLocale(available_locales, no_extension_locale);
1292 
1293  // 2. c. If availableLocale is not undefined, append locale to the
1294  // end of subset.
1295  if (!available_locale.empty()) {
1296  subset.push_back(locale);
1297  }
1298  }
1299 
1300  // 3. Return subset.
1301  return subset;
1302 }
1303 
1304 // ECMA 402 9.2.8 BestFitSupportedLocales(availableLocales, requestedLocales)
1305 // https://tc39.github.io/ecma402/#sec-bestfitsupportedlocales
1306 std::vector<std::string> BestFitSupportedLocales(
1307  const std::set<std::string>& available_locales,
1308  const std::vector<std::string>& requested_locales) {
1309  return LookupSupportedLocales(available_locales, requested_locales);
1310 }
1311 
1312 // ecma262 #sec-createarrayfromlist
1313 Handle<JSArray> CreateArrayFromList(Isolate* isolate,
1314  std::vector<std::string> elements,
1315  PropertyAttributes attr) {
1316  Factory* factory = isolate->factory();
1317  // Let array be ! ArrayCreate(0).
1318  Handle<JSArray> array = factory->NewJSArray(0);
1319 
1320  uint32_t length = static_cast<uint32_t>(elements.size());
1321  // 3. Let n be 0.
1322  // 4. For each element e of elements, do
1323  for (uint32_t i = 0; i < length; i++) {
1324  // a. Let status be CreateDataProperty(array, ! ToString(n), e).
1325  const std::string& part = elements[i];
1326  Handle<String> value =
1327  factory->NewStringFromUtf8(CStrVector(part.c_str())).ToHandleChecked();
1328  JSObject::AddDataElement(array, i, value, attr);
1329  }
1330  // 5. Return array.
1331  return array;
1332 }
1333 
1334 // TODO(bstell): should this be moved somewhere where it is reusable?
1335 // Implement steps 5, 6, 7 for ECMA 402 9.2.9 SupportedLocales
1336 // https://tc39.github.io/ecma402/#sec-supportedlocales
1337 MaybeHandle<JSObject> CreateReadOnlyArray(Isolate* isolate,
1338  std::vector<std::string> elements) {
1339  if (elements.size() >= kMaxUInt32) {
1340  THROW_NEW_ERROR(
1341  isolate, NewRangeError(MessageTemplate::kInvalidArrayLength), JSObject);
1342  }
1343 
1344  PropertyAttributes attr =
1345  static_cast<PropertyAttributes>(READ_ONLY | DONT_DELETE);
1346 
1347  // 5. Let subset be CreateArrayFromList(elements).
1348  Handle<JSArray> subset = CreateArrayFromList(isolate, elements, attr);
1349 
1350  // 6. Let keys be subset.[[OwnPropertyKeys]]().
1351 
1352  // 7.a. Let desc be PropertyDescriptor { [[Configurable]]: false,
1353  // [[Writable]]: false }.
1354  PropertyDescriptor desc;
1355  desc.set_writable(false);
1356  desc.set_configurable(false);
1357 
1358  // 7.b. Perform ! DefinePropertyOrThrow(subset, P, desc).
1359  JSArray::ArraySetLength(isolate, subset, &desc, kThrowOnError).ToChecked();
1360  return subset;
1361 }
1362 
1363 // ECMA 402 9.2.9 SupportedLocales(availableLocales, requestedLocales, options)
1364 // https://tc39.github.io/ecma402/#sec-supportedlocales
1365 MaybeHandle<JSObject> SupportedLocales(
1366  Isolate* isolate, const char* method,
1367  const std::set<std::string>& available_locales,
1368  const std::vector<std::string>& requested_locales, Handle<Object> options) {
1369  std::vector<std::string> supported_locales;
1370 
1371  // 2. Else, let matcher be "best fit".
1372  Intl::MatcherOption matcher = Intl::MatcherOption::kBestFit;
1373 
1374  // 1. If options is not undefined, then
1375  if (!options->IsUndefined(isolate)) {
1376  // 1. a. Let options be ? ToObject(options).
1377  Handle<JSReceiver> options_obj;
1378  ASSIGN_RETURN_ON_EXCEPTION(isolate, options_obj,
1379  Object::ToObject(isolate, options), JSObject);
1380 
1381  // 1. b. Let matcher be ? GetOption(options, "localeMatcher", "string",
1382  // « "lookup", "best fit" », "best fit").
1383  Maybe<Intl::MatcherOption> maybe_locale_matcher =
1384  Intl::GetLocaleMatcher(isolate, options_obj, method);
1385  MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSObject>());
1386  matcher = maybe_locale_matcher.FromJust();
1387  }
1388 
1389  // 3. If matcher is "best fit", then
1390  // a. Let supportedLocales be BestFitSupportedLocales(availableLocales,
1391  // requestedLocales).
1392  if (matcher == Intl::MatcherOption::kBestFit) {
1393  supported_locales =
1394  BestFitSupportedLocales(available_locales, requested_locales);
1395  } else {
1396  // 4. Else,
1397  // a. Let supportedLocales be LookupSupportedLocales(availableLocales,
1398  // requestedLocales).
1399  DCHECK_EQ(matcher, Intl::MatcherOption::kLookup);
1400  supported_locales =
1401  LookupSupportedLocales(available_locales, requested_locales);
1402  }
1403 
1404  // TODO(jkummerow): Possibly revisit why the spec has the individual entries
1405  // readonly but the array is not frozen.
1406  // https://github.com/tc39/ecma402/issues/258
1407 
1408  // 5. Let subset be CreateArrayFromList(supportedLocales).
1409  // 6. Let keys be subset.[[OwnPropertyKeys]]().
1410  // 7. For each element P of keys in List order, do
1411  // a. Let desc be PropertyDescriptor { [[Configurable]]: false,
1412  // [[Writable]]: false }.
1413  // b. Perform ! DefinePropertyOrThrow(subset, P, desc).
1414  MaybeHandle<JSObject> subset =
1415  CreateReadOnlyArray(isolate, supported_locales);
1416 
1417  // 8. Return subset.
1418  return subset;
1419 }
1420 } // namespace
1421 
1422 // ecma-402 #sec-intl.getcanonicallocales
1423 MaybeHandle<JSArray> Intl::GetCanonicalLocales(Isolate* isolate,
1424  Handle<Object> locales) {
1425  // 1. Let ll be ? CanonicalizeLocaleList(locales).
1426  Maybe<std::vector<std::string>> maybe_ll =
1427  CanonicalizeLocaleList(isolate, locales, false);
1428  MAYBE_RETURN(maybe_ll, MaybeHandle<JSArray>());
1429 
1430  // 2. Return CreateArrayFromList(ll).
1431  PropertyAttributes attr = static_cast<PropertyAttributes>(NONE);
1432  return CreateArrayFromList(isolate, maybe_ll.FromJust(), attr);
1433 }
1434 
1435 // ECMA 402 Intl.*.supportedLocalesOf
1436 MaybeHandle<JSObject> Intl::SupportedLocalesOf(
1437  Isolate* isolate, const char* method,
1438  const std::set<std::string>& available_locales, Handle<Object> locales,
1439  Handle<Object> options) {
1440  // Let availableLocales be %Collator%.[[AvailableLocales]].
1441 
1442  // Let requestedLocales be ? CanonicalizeLocaleList(locales).
1443  Maybe<std::vector<std::string>> requested_locales =
1444  CanonicalizeLocaleList(isolate, locales, false);
1445  MAYBE_RETURN(requested_locales, MaybeHandle<JSObject>());
1446 
1447  // Return ? SupportedLocales(availableLocales, requestedLocales, options).
1448  return SupportedLocales(isolate, method, available_locales,
1449  requested_locales.FromJust(), options);
1450 }
1451 
1452 namespace {
1453 
1454 std::map<std::string, std::string> LookupAndValidateUnicodeExtensions(
1455  icu::Locale* icu_locale, const std::set<std::string>& relevant_keys) {
1456  std::map<std::string, std::string> extensions;
1457 
1458  UErrorCode status = U_ZERO_ERROR;
1459  std::unique_ptr<icu::StringEnumeration> keywords(
1460  icu_locale->createKeywords(status));
1461  if (U_FAILURE(status)) return extensions;
1462 
1463  if (!keywords) return extensions;
1464  char value[ULOC_FULLNAME_CAPACITY];
1465 
1466  int32_t length;
1467  status = U_ZERO_ERROR;
1468  for (const char* keyword = keywords->next(&length, status);
1469  keyword != nullptr; keyword = keywords->next(&length, status)) {
1470  // Ignore failures in ICU and skip to the next keyword.
1471  //
1472  // This is fine.™
1473  if (U_FAILURE(status)) {
1474  status = U_ZERO_ERROR;
1475  continue;
1476  }
1477 
1478  icu_locale->getKeywordValue(keyword, value, ULOC_FULLNAME_CAPACITY, status);
1479 
1480  // Ignore failures in ICU and skip to the next keyword.
1481  //
1482  // This is fine.™
1483  if (U_FAILURE(status)) {
1484  status = U_ZERO_ERROR;
1485  continue;
1486  }
1487 
1488  const char* bcp47_key = uloc_toUnicodeLocaleKey(keyword);
1489 
1490  if (bcp47_key && (relevant_keys.find(bcp47_key) != relevant_keys.end())) {
1491  const char* bcp47_value = uloc_toUnicodeLocaleType(bcp47_key, value);
1492  extensions.insert(
1493  std::pair<std::string, std::string>(bcp47_key, bcp47_value));
1494  } else {
1495  status = U_ZERO_ERROR;
1496  icu_locale->setKeywordValue(keyword, nullptr, status);
1497  CHECK(U_SUCCESS(status));
1498  }
1499  }
1500 
1501  return extensions;
1502 }
1503 
1504 // ecma402/#sec-lookupmatcher
1505 std::string LookupMatcher(Isolate* isolate,
1506  const std::set<std::string>& available_locales,
1507  const std::vector<std::string>& requested_locales) {
1508  // 1. Let result be a new Record.
1509  std::string result;
1510 
1511  // 2. For each element locale of requestedLocales in List order, do
1512  for (const std::string& locale : requested_locales) {
1513  // 2. a. Let noExtensionsLocale be the String value that is locale
1514  // with all Unicode locale extension sequences removed.
1515  ParsedLocale parsed_locale = ParseBCP47Locale(locale);
1516  std::string no_extensions_locale = parsed_locale.no_extensions_locale;
1517 
1518  // 2. b. Let availableLocale be
1519  // BestAvailableLocale(availableLocales, noExtensionsLocale).
1520  std::string available_locale =
1521  BestAvailableLocale(available_locales, no_extensions_locale);
1522 
1523  // 2. c. If availableLocale is not undefined, append locale to the
1524  // end of subset.
1525  if (!available_locale.empty()) {
1526  // Note: The following steps are not performed here because we
1527  // can use ICU to parse the unicode locale extension sequence
1528  // as part of Intl::ResolveLocale.
1529  //
1530  // There's no need to separate the unicode locale extensions
1531  // right here. Instead just return the available locale with the
1532  // extensions.
1533  //
1534  // 2. c. i. Set result.[[locale]] to availableLocale.
1535  // 2. c. ii. If locale and noExtensionsLocale are not the same
1536  // String value, then
1537  // 2. c. ii. 1. Let extension be the String value consisting of
1538  // the first substring of locale that is a Unicode locale
1539  // extension sequence.
1540  // 2. c. ii. 2. Set result.[[extension]] to extension.
1541  // 2. c. iii. Return result.
1542  return available_locale + parsed_locale.extension;
1543  }
1544  }
1545 
1546  // 3. Let defLocale be DefaultLocale();
1547  // 4. Set result.[[locale]] to defLocale.
1548  // 5. Return result.
1549  return Intl::DefaultLocale(isolate);
1550 }
1551 
1552 } // namespace
1553 
1554 // This function doesn't correspond exactly with the spec. Instead
1555 // we use ICU to do all the string manipulations that the spec
1556 // peforms.
1557 //
1558 // The spec uses this function to normalize values for various
1559 // relevant extension keys (such as disallowing "search" for
1560 // collation). Instead of doing this here, we let the callers of
1561 // this method perform such normalization.
1562 //
1563 // ecma402/#sec-resolvelocale
1564 Intl::ResolvedLocale Intl::ResolveLocale(
1565  Isolate* isolate, const std::set<std::string>& available_locales,
1566  const std::vector<std::string>& requested_locales, MatcherOption matcher,
1567  const std::set<std::string>& relevant_extension_keys) {
1568  std::string locale;
1569  if (matcher == Intl::MatcherOption::kLookup) {
1570  locale = LookupMatcher(isolate, available_locales, requested_locales);
1571  } else if (matcher == Intl::MatcherOption::kBestFit) {
1572  // TODO(intl): Implement better lookup algorithm.
1573  locale = LookupMatcher(isolate, available_locales, requested_locales);
1574  }
1575 
1576  icu::Locale icu_locale = CreateICULocale(locale);
1577  std::map<std::string, std::string> extensions =
1578  LookupAndValidateUnicodeExtensions(&icu_locale, relevant_extension_keys);
1579 
1580  char canonicalized_locale[ULOC_FULLNAME_CAPACITY];
1581  UErrorCode status = U_ZERO_ERROR;
1582  uloc_toLanguageTag(icu_locale.getName(), canonicalized_locale,
1583  ULOC_FULLNAME_CAPACITY, true, &status);
1584  CHECK(U_SUCCESS(status));
1585 
1586  // TODO(gsathya): Remove privateuse subtags from extensions.
1587 
1588  return Intl::ResolvedLocale{canonicalized_locale, icu_locale, extensions};
1589 }
1590 
1591 Managed<icu::UnicodeString>* Intl::SetTextToBreakIterator(
1592  Isolate* isolate, Handle<String> text, icu::BreakIterator* break_iterator) {
1593  icu::UnicodeString* u_text =
1594  (icu::UnicodeString*)(Intl::ToICUUnicodeString(isolate, text).clone());
1595 
1596  Handle<Managed<icu::UnicodeString>> new_u_text =
1597  Managed<icu::UnicodeString>::FromRawPtr(isolate, 0, u_text);
1598 
1599  break_iterator->setText(*u_text);
1600  return *new_u_text;
1601 }
1602 
1603 // ecma262 #sec-string.prototype.normalize
1604 MaybeHandle<String> Intl::Normalize(Isolate* isolate, Handle<String> string,
1605  Handle<Object> form_input) {
1606  const char* form_name;
1607  UNormalization2Mode form_mode;
1608  if (form_input->IsUndefined(isolate)) {
1609  // default is FNC
1610  form_name = "nfc";
1611  form_mode = UNORM2_COMPOSE;
1612  } else {
1613  Handle<String> form;
1614  ASSIGN_RETURN_ON_EXCEPTION(isolate, form,
1615  Object::ToString(isolate, form_input), String);
1616 
1617  if (String::Equals(isolate, form, isolate->factory()->NFC_string())) {
1618  form_name = "nfc";
1619  form_mode = UNORM2_COMPOSE;
1620  } else if (String::Equals(isolate, form,
1621  isolate->factory()->NFD_string())) {
1622  form_name = "nfc";
1623  form_mode = UNORM2_DECOMPOSE;
1624  } else if (String::Equals(isolate, form,
1625  isolate->factory()->NFKC_string())) {
1626  form_name = "nfkc";
1627  form_mode = UNORM2_COMPOSE;
1628  } else if (String::Equals(isolate, form,
1629  isolate->factory()->NFKD_string())) {
1630  form_name = "nfkc";
1631  form_mode = UNORM2_DECOMPOSE;
1632  } else {
1633  Handle<String> valid_forms =
1634  isolate->factory()->NewStringFromStaticChars("NFC, NFD, NFKC, NFKD");
1635  THROW_NEW_ERROR(
1636  isolate,
1637  NewRangeError(MessageTemplate::kNormalizationForm, valid_forms),
1638  String);
1639  }
1640  }
1641 
1642  int length = string->length();
1643  string = String::Flatten(isolate, string);
1644  icu::UnicodeString result;
1645  std::unique_ptr<uc16[]> sap;
1646  UErrorCode status = U_ZERO_ERROR;
1647  icu::UnicodeString input = ToICUUnicodeString(isolate, string);
1648  // Getting a singleton. Should not free it.
1649  const icu::Normalizer2* normalizer =
1650  icu::Normalizer2::getInstance(nullptr, form_name, form_mode, status);
1651  DCHECK(U_SUCCESS(status));
1652  CHECK_NOT_NULL(normalizer);
1653  int32_t normalized_prefix_length =
1654  normalizer->spanQuickCheckYes(input, status);
1655  // Quick return if the input is already normalized.
1656  if (length == normalized_prefix_length) return string;
1657  icu::UnicodeString unnormalized =
1658  input.tempSubString(normalized_prefix_length);
1659  // Read-only alias of the normalized prefix.
1660  result.setTo(false, input.getBuffer(), normalized_prefix_length);
1661  // copy-on-write; normalize the suffix and append to |result|.
1662  normalizer->normalizeSecondAndAppend(result, unnormalized, status);
1663 
1664  if (U_FAILURE(status)) {
1665  THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), String);
1666  }
1667 
1668  return Intl::ToString(isolate, result);
1669 }
1670 
1671 // ICUTimezoneCache calls out to ICU for TimezoneCache
1672 // functionality in a straightforward way.
1674  public:
1675  ICUTimezoneCache() : timezone_(nullptr) { Clear(); }
1676 
1677  ~ICUTimezoneCache() override { Clear(); };
1678 
1679  const char* LocalTimezone(double time_ms) override;
1680 
1681  double DaylightSavingsOffset(double time_ms) override;
1682 
1683  double LocalTimeOffset(double time_ms, bool is_utc) override;
1684 
1685  void Clear() override;
1686 
1687  private:
1688  icu::TimeZone* GetTimeZone();
1689 
1690  bool GetOffsets(double time_ms, bool is_utc, int32_t* raw_offset,
1691  int32_t* dst_offset);
1692 
1693  icu::TimeZone* timezone_;
1694 
1695  std::string timezone_name_;
1696  std::string dst_timezone_name_;
1697 };
1698 
1699 const char* ICUTimezoneCache::LocalTimezone(double time_ms) {
1700  bool is_dst = DaylightSavingsOffset(time_ms) != 0;
1701  std::string* name = is_dst ? &dst_timezone_name_ : &timezone_name_;
1702  if (name->empty()) {
1703  icu::UnicodeString result;
1704  GetTimeZone()->getDisplayName(is_dst, icu::TimeZone::LONG, result);
1705  result += '\0';
1706 
1707  icu::StringByteSink<std::string> byte_sink(name);
1708  result.toUTF8(byte_sink);
1709  }
1710  DCHECK(!name->empty());
1711  return name->c_str();
1712 }
1713 
1714 icu::TimeZone* ICUTimezoneCache::GetTimeZone() {
1715  if (timezone_ == nullptr) {
1716  timezone_ = icu::TimeZone::createDefault();
1717  }
1718  return timezone_;
1719 }
1720 
1721 bool ICUTimezoneCache::GetOffsets(double time_ms, bool is_utc,
1722  int32_t* raw_offset, int32_t* dst_offset) {
1723  UErrorCode status = U_ZERO_ERROR;
1724  // TODO(jshin): ICU TimeZone class handles skipped time differently from
1725  // Ecma 262 (https://github.com/tc39/ecma262/pull/778) and icu::TimeZone
1726  // class does not expose the necessary API. Fixing
1727  // http://bugs.icu-project.org/trac/ticket/13268 would make it easy to
1728  // implement the proposed spec change. A proposed fix for ICU is
1729  // https://chromium-review.googlesource.com/851265 .
1730  // In the meantime, use an internal (still public) API of icu::BasicTimeZone.
1731  // Once it's accepted by the upstream, get rid of cast. Note that casting
1732  // TimeZone to BasicTimeZone is safe because we know that icu::TimeZone used
1733  // here is a BasicTimeZone.
1734  if (is_utc) {
1735  GetTimeZone()->getOffset(time_ms, false, *raw_offset, *dst_offset, status);
1736  } else {
1737  static_cast<const icu::BasicTimeZone*>(GetTimeZone())
1738  ->getOffsetFromLocal(time_ms, icu::BasicTimeZone::kFormer,
1739  icu::BasicTimeZone::kFormer, *raw_offset,
1740  *dst_offset, status);
1741  }
1742 
1743  return U_SUCCESS(status);
1744 }
1745 
1746 double ICUTimezoneCache::DaylightSavingsOffset(double time_ms) {
1747  int32_t raw_offset, dst_offset;
1748  if (!GetOffsets(time_ms, true, &raw_offset, &dst_offset)) return 0;
1749  return dst_offset;
1750 }
1751 
1752 double ICUTimezoneCache::LocalTimeOffset(double time_ms, bool is_utc) {
1753  int32_t raw_offset, dst_offset;
1754  if (!GetOffsets(time_ms, is_utc, &raw_offset, &dst_offset)) return 0;
1755  return raw_offset + dst_offset;
1756 }
1757 
1758 void ICUTimezoneCache::Clear() {
1759  delete timezone_;
1760  timezone_ = nullptr;
1761  timezone_name_.clear();
1762  dst_timezone_name_.clear();
1763 }
1764 
1765 base::TimezoneCache* Intl::CreateTimeZoneCache() {
1766  return FLAG_icu_timezone_data ? new ICUTimezoneCache()
1767  : base::OS::CreateTimezoneCache();
1768 }
1769 
1770 Maybe<Intl::CaseFirst> Intl::GetCaseFirst(Isolate* isolate,
1771  Handle<JSReceiver> options,
1772  const char* method) {
1773  return Intl::GetStringOption<Intl::CaseFirst>(
1774  isolate, options, "caseFirst", method, {"upper", "lower", "false"},
1775  {Intl::CaseFirst::kUpper, Intl::CaseFirst::kLower,
1776  Intl::CaseFirst::kFalse},
1777  Intl::CaseFirst::kUndefined);
1778 }
1779 
1780 Maybe<Intl::HourCycle> Intl::GetHourCycle(Isolate* isolate,
1781  Handle<JSReceiver> options,
1782  const char* method) {
1783  return Intl::GetStringOption<Intl::HourCycle>(
1784  isolate, options, "hourCycle", method, {"h11", "h12", "h23", "h24"},
1785  {Intl::HourCycle::kH11, Intl::HourCycle::kH12, Intl::HourCycle::kH23,
1786  Intl::HourCycle::kH24},
1787  Intl::HourCycle::kUndefined);
1788 }
1789 
1790 Maybe<Intl::MatcherOption> Intl::GetLocaleMatcher(Isolate* isolate,
1791  Handle<JSReceiver> options,
1792  const char* method) {
1793  return Intl::GetStringOption<Intl::MatcherOption>(
1794  isolate, options, "localeMatcher", method, {"best fit", "lookup"},
1795  {Intl::MatcherOption::kLookup, Intl::MatcherOption::kBestFit},
1796  Intl::MatcherOption::kLookup);
1797 }
1798 } // namespace internal
1799 } // namespace v8
Definition: libplatform.h:13