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-locale.h"
10 
11 #include <map>
12 #include <memory>
13 #include <string>
14 #include <vector>
15 
16 #include "src/api/api.h"
17 #include "src/execution/isolate.h"
18 #include "src/heap/factory.h"
19 #include "src/objects/intl-objects.h"
20 #include "src/objects/js-locale-inl.h"
21 #include "src/objects/managed-inl.h"
22 #include "src/objects/objects-inl.h"
23 #include "src/objects/option-utils.h"
24 #include "unicode/calendar.h"
25 #include "unicode/char16ptr.h"
26 #include "unicode/coll.h"
27 #include "unicode/dtptngen.h"
28 #include "unicode/localebuilder.h"
29 #include "unicode/locid.h"
30 #include "unicode/ucal.h"
31 #include "unicode/uloc.h"
32 #include "unicode/ulocdata.h"
33 #include "unicode/unistr.h"
34 
35 namespace v8 {
36 namespace internal {
37 
38 namespace {
39 
40 struct OptionData {
41   const char* name;
42   const char* key;
43   const std::vector<const char*>* possible_values;
44   bool is_bool_value;
45 };
46 
47 // Inserts tags from options into locale string.
InsertOptionsIntoLocale(Isolate * isolate,Handle<JSReceiver> options,icu::LocaleBuilder * builder)48 Maybe<bool> InsertOptionsIntoLocale(Isolate* isolate,
49                                     Handle<JSReceiver> options,
50                                     icu::LocaleBuilder* builder) {
51   DCHECK(isolate);
52 
53   const std::vector<const char*> hour_cycle_values = {"h11", "h12", "h23",
54                                                       "h24"};
55   const std::vector<const char*> case_first_values = {"upper", "lower",
56                                                       "false"};
57   const std::vector<const char*> empty_values = {};
58   const std::array<OptionData, 6> kOptionToUnicodeTagMap = {
59       {{"calendar", "ca", &empty_values, false},
60        {"collation", "co", &empty_values, false},
61        {"hourCycle", "hc", &hour_cycle_values, false},
62        {"caseFirst", "kf", &case_first_values, false},
63        {"numeric", "kn", &empty_values, true},
64        {"numberingSystem", "nu", &empty_values, false}}};
65 
66   // TODO(cira): Pass in values as per the spec to make this to be
67   // spec compliant.
68 
69   for (const auto& option_to_bcp47 : kOptionToUnicodeTagMap) {
70     std::unique_ptr<char[]> value_str = nullptr;
71     bool value_bool = false;
72     Maybe<bool> maybe_found =
73         option_to_bcp47.is_bool_value
74             ? GetBoolOption(isolate, options, option_to_bcp47.name, "locale",
75                             &value_bool)
76             : GetStringOption(isolate, options, option_to_bcp47.name,
77                               *(option_to_bcp47.possible_values), "locale",
78                               &value_str);
79     MAYBE_RETURN(maybe_found, Nothing<bool>());
80 
81     // TODO(cira): Use fallback value if value is not found to make
82     // this spec compliant.
83     if (!maybe_found.FromJust()) continue;
84 
85     if (option_to_bcp47.is_bool_value) {
86       value_str = value_bool ? isolate->factory()->true_string()->ToCString()
87                              : isolate->factory()->false_string()->ToCString();
88     }
89     DCHECK_NOT_NULL(value_str.get());
90 
91     // Overwrite existing, or insert new key-value to the locale string.
92     if (!uloc_toLegacyType(uloc_toLegacyKey(option_to_bcp47.key),
93                            value_str.get())) {
94       return Just(false);
95     }
96     builder->setUnicodeLocaleKeyword(option_to_bcp47.key, value_str.get());
97   }
98   return Just(true);
99 }
100 
UnicodeKeywordValue(Isolate * isolate,Handle<JSLocale> locale,const char * key)101 Handle<Object> UnicodeKeywordValue(Isolate* isolate, Handle<JSLocale> locale,
102                                    const char* key) {
103   icu::Locale* icu_locale = locale->icu_locale().raw();
104   UErrorCode status = U_ZERO_ERROR;
105   std::string value =
106       icu_locale->getUnicodeKeywordValue<std::string>(key, status);
107   if (status == U_ILLEGAL_ARGUMENT_ERROR || value == "") {
108     return isolate->factory()->undefined_value();
109   }
110   if (value == "yes") {
111     value = "true";
112   }
113   if (value == "true" && strcmp(key, "kf") == 0) {
114     return isolate->factory()->NewStringFromStaticChars("");
115   }
116   return isolate->factory()->NewStringFromAsciiChecked(value.c_str());
117 }
118 
IsCheckRange(const std::string & str,size_t min,size_t max,bool (range_check_func)(char))119 bool IsCheckRange(const std::string& str, size_t min, size_t max,
120                   bool(range_check_func)(char)) {
121   if (!base::IsInRange(str.length(), min, max)) return false;
122   for (size_t i = 0; i < str.length(); i++) {
123     if (!range_check_func(str[i])) return false;
124   }
125   return true;
126 }
IsAlpha(const std::string & str,size_t min,size_t max)127 bool IsAlpha(const std::string& str, size_t min, size_t max) {
128   return IsCheckRange(str, min, max, [](char c) -> bool {
129     return base::IsInRange(c, 'a', 'z') || base::IsInRange(c, 'A', 'Z');
130   });
131 }
132 
IsDigit(const std::string & str,size_t min,size_t max)133 bool IsDigit(const std::string& str, size_t min, size_t max) {
134   return IsCheckRange(str, min, max, [](char c) -> bool {
135     return base::IsInRange(c, '0', '9');
136   });
137 }
138 
IsAlphanum(const std::string & str,size_t min,size_t max)139 bool IsAlphanum(const std::string& str, size_t min, size_t max) {
140   return IsCheckRange(str, min, max, [](char c) -> bool {
141     return base::IsInRange(c, 'a', 'z') || base::IsInRange(c, 'A', 'Z') ||
142            base::IsInRange(c, '0', '9');
143   });
144 }
145 
IsUnicodeLanguageSubtag(const std::string & value)146 bool IsUnicodeLanguageSubtag(const std::string& value) {
147   // unicode_language_subtag = alpha{2,3} | alpha{5,8};
148   return IsAlpha(value, 2, 3) || IsAlpha(value, 5, 8);
149 }
150 
IsUnicodeScriptSubtag(const std::string & value)151 bool IsUnicodeScriptSubtag(const std::string& value) {
152   // unicode_script_subtag = alpha{4} ;
153   return IsAlpha(value, 4, 4);
154 }
155 
IsUnicodeRegionSubtag(const std::string & value)156 bool IsUnicodeRegionSubtag(const std::string& value) {
157   // unicode_region_subtag = (alpha{2} | digit{3});
158   return IsAlpha(value, 2, 2) || IsDigit(value, 3, 3);
159 }
160 
IsDigitAlphanum3(const std::string & value)161 bool IsDigitAlphanum3(const std::string& value) {
162   return value.length() == 4 && base::IsInRange(value[0], '0', '9') &&
163          IsAlphanum(value.substr(1), 3, 3);
164 }
165 
IsUnicodeVariantSubtag(const std::string & value)166 bool IsUnicodeVariantSubtag(const std::string& value) {
167   // unicode_variant_subtag = (alphanum{5,8} | digit alphanum{3}) ;
168   return IsAlphanum(value, 5, 8) || IsDigitAlphanum3(value);
169 }
170 
IsExtensionSingleton(const std::string & value)171 bool IsExtensionSingleton(const std::string& value) {
172   return IsAlphanum(value, 1, 1);
173 }
174 
weekdayFromEDaysOfWeek(icu::Calendar::EDaysOfWeek eDaysOfWeek)175 int32_t weekdayFromEDaysOfWeek(icu::Calendar::EDaysOfWeek eDaysOfWeek) {
176   return (eDaysOfWeek == icu::Calendar::SUNDAY) ? 7 : eDaysOfWeek - 1;
177 }
178 
179 }  // namespace
180 
181 // Implemented as iteration instead of recursion to avoid stack overflow for
182 // very long input strings.
Is38AlphaNumList(const std::string & in)183 bool JSLocale::Is38AlphaNumList(const std::string& in) {
184   std::string value = in;
185   while (true) {
186     std::size_t found_dash = value.find("-");
187     if (found_dash == std::string::npos) {
188       return IsAlphanum(value, 3, 8);
189     }
190     if (!IsAlphanum(value.substr(0, found_dash), 3, 8)) return false;
191     value = value.substr(found_dash + 1);
192   }
193 }
194 
Is3Alpha(const std::string & value)195 bool JSLocale::Is3Alpha(const std::string& value) {
196   return IsAlpha(value, 3, 3);
197 }
198 
199 // TODO(ftang) Replace the following check w/ icu::LocaleBuilder
200 // once ICU64 land in March 2019.
StartsWithUnicodeLanguageId(const std::string & value)201 bool JSLocale::StartsWithUnicodeLanguageId(const std::string& value) {
202   // unicode_language_id =
203   // unicode_language_subtag (sep unicode_script_subtag)?
204   //   (sep unicode_region_subtag)? (sep unicode_variant_subtag)* ;
205   std::vector<std::string> tokens;
206   std::string token;
207   std::istringstream token_stream(value);
208   while (std::getline(token_stream, token, '-')) {
209     tokens.push_back(token);
210   }
211   if (tokens.size() == 0) return false;
212 
213   // length >= 1
214   if (!IsUnicodeLanguageSubtag(tokens[0])) return false;
215 
216   if (tokens.size() == 1) return true;
217 
218   // length >= 2
219   if (IsExtensionSingleton(tokens[1])) return true;
220 
221   size_t index = 1;
222   if (IsUnicodeScriptSubtag(tokens[index])) {
223     index++;
224     if (index == tokens.size()) return true;
225   }
226   if (IsUnicodeRegionSubtag(tokens[index])) {
227     index++;
228   }
229   while (index < tokens.size()) {
230     if (IsExtensionSingleton(tokens[index])) return true;
231     if (!IsUnicodeVariantSubtag(tokens[index])) return false;
232     index++;
233   }
234   return true;
235 }
236 
237 namespace {
ApplyOptionsToTag(Isolate * isolate,Handle<String> tag,Handle<JSReceiver> options,icu::LocaleBuilder * builder)238 Maybe<bool> ApplyOptionsToTag(Isolate* isolate, Handle<String> tag,
239                               Handle<JSReceiver> options,
240                               icu::LocaleBuilder* builder) {
241   v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
242   if (tag->length() == 0) {
243     THROW_NEW_ERROR_RETURN_VALUE(
244         isolate, NewRangeError(MessageTemplate::kLocaleNotEmpty),
245         Nothing<bool>());
246   }
247 
248   v8::String::Utf8Value bcp47_tag(v8_isolate, v8::Utils::ToLocal(tag));
249   builder->setLanguageTag({*bcp47_tag, bcp47_tag.length()});
250   DCHECK_LT(0, bcp47_tag.length());
251   DCHECK_NOT_NULL(*bcp47_tag);
252   // 2. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError
253   // exception.
254   if (!JSLocale::StartsWithUnicodeLanguageId(*bcp47_tag)) {
255     return Just(false);
256   }
257   UErrorCode status = U_ZERO_ERROR;
258   icu::Locale canonicalized = builder->build(status);
259   canonicalized.canonicalize(status);
260   if (U_FAILURE(status)) {
261     return Just(false);
262   }
263   builder->setLocale(canonicalized);
264 
265   // 3. Let language be ? GetOption(options, "language", "string", undefined,
266   // undefined).
267   const std::vector<const char*> empty_values = {};
268   std::unique_ptr<char[]> language_str = nullptr;
269   Maybe<bool> maybe_language =
270       GetStringOption(isolate, options, "language", empty_values,
271                       "ApplyOptionsToTag", &language_str);
272   MAYBE_RETURN(maybe_language, Nothing<bool>());
273   // 4. If language is not undefined, then
274   if (maybe_language.FromJust()) {
275     builder->setLanguage(language_str.get());
276     builder->build(status);
277     // a. If language does not match the unicode_language_subtag production,
278     //    throw a RangeError exception.
279     if (U_FAILURE(status) || language_str[0] == '\0' ||
280         IsAlpha(language_str.get(), 4, 4)) {
281       return Just(false);
282     }
283   }
284   // 5. Let script be ? GetOption(options, "script", "string", undefined,
285   // undefined).
286   std::unique_ptr<char[]> script_str = nullptr;
287   Maybe<bool> maybe_script =
288       GetStringOption(isolate, options, "script", empty_values,
289                       "ApplyOptionsToTag", &script_str);
290   MAYBE_RETURN(maybe_script, Nothing<bool>());
291   // 6. If script is not undefined, then
292   if (maybe_script.FromJust()) {
293     builder->setScript(script_str.get());
294     builder->build(status);
295     // a. If script does not match the unicode_script_subtag production, throw
296     //    a RangeError exception.
297     if (U_FAILURE(status) || script_str[0] == '\0') {
298       return Just(false);
299     }
300   }
301   // 7. Let region be ? GetOption(options, "region", "string", undefined,
302   // undefined).
303   std::unique_ptr<char[]> region_str = nullptr;
304   Maybe<bool> maybe_region =
305       GetStringOption(isolate, options, "region", empty_values,
306                       "ApplyOptionsToTag", &region_str);
307   MAYBE_RETURN(maybe_region, Nothing<bool>());
308   // 8. If region is not undefined, then
309   if (maybe_region.FromJust()) {
310     // a. If region does not match the region production, throw a RangeError
311     // exception.
312     builder->setRegion(region_str.get());
313     builder->build(status);
314     if (U_FAILURE(status) || region_str[0] == '\0') {
315       return Just(false);
316     }
317   }
318 
319   // 9. Set tag to CanonicalizeLanguageTag(tag).
320   // 10.  If language is not undefined,
321   // a. Assert: tag matches the unicode_locale_id production.
322   // b. Set tag to tag with the substring corresponding to the
323   //    unicode_language_subtag production replaced by the string language.
324   // 11. If script is not undefined, then
325   // a. If tag does not contain a unicode_script_subtag production, then
326   //   i. Set tag to the concatenation of the unicode_language_subtag
327   //      production of tag, "-", script, and the rest of tag.
328   // b. Else,
329   //   i. Set tag to tag with the substring corresponding to the
330   //      unicode_script_subtag production replaced by the string script.
331   // 12. If region is not undefined, then
332   // a. If tag does not contain a unicode_region_subtag production, then
333   //   i. Set tag to the concatenation of the unicode_language_subtag
334   //      production of tag, the substring corresponding to the  "-"
335   //      unicode_script_subtag production if present, "-", region, and
336   //      the rest of tag.
337   // b. Else,
338   // i. Set tag to tag with the substring corresponding to the
339   //    unicode_region_subtag production replaced by the string region.
340   // 13.  Return CanonicalizeLanguageTag(tag).
341   return Just(true);
342 }
343 
344 }  // namespace
345 
New(Isolate * isolate,Handle<Map> map,Handle<String> locale_str,Handle<JSReceiver> options)346 MaybeHandle<JSLocale> JSLocale::New(Isolate* isolate, Handle<Map> map,
347                                     Handle<String> locale_str,
348                                     Handle<JSReceiver> options) {
349   icu::LocaleBuilder builder;
350   Maybe<bool> maybe_apply =
351       ApplyOptionsToTag(isolate, locale_str, options, &builder);
352   MAYBE_RETURN(maybe_apply, MaybeHandle<JSLocale>());
353   if (!maybe_apply.FromJust()) {
354     THROW_NEW_ERROR(isolate,
355                     NewRangeError(MessageTemplate::kLocaleBadParameters),
356                     JSLocale);
357   }
358 
359   Maybe<bool> maybe_insert =
360       InsertOptionsIntoLocale(isolate, options, &builder);
361   MAYBE_RETURN(maybe_insert, MaybeHandle<JSLocale>());
362   UErrorCode status = U_ZERO_ERROR;
363   icu::Locale icu_locale = builder.build(status);
364 
365   icu_locale.canonicalize(status);
366 
367   if (!maybe_insert.FromJust() || U_FAILURE(status)) {
368     THROW_NEW_ERROR(isolate,
369                     NewRangeError(MessageTemplate::kLocaleBadParameters),
370                     JSLocale);
371   }
372 
373   // 31. Set locale.[[Locale]] to r.[[locale]].
374   Handle<Managed<icu::Locale>> managed_locale =
375       Managed<icu::Locale>::FromRawPtr(isolate, 0, icu_locale.clone());
376 
377   // Now all properties are ready, so we can allocate the result object.
378   Handle<JSLocale> locale = Handle<JSLocale>::cast(
379       isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
380   DisallowGarbageCollection no_gc;
381   locale->set_icu_locale(*managed_locale);
382   return locale;
383 }
384 
385 namespace {
386 
Construct(Isolate * isolate,const icu::Locale & icu_locale)387 MaybeHandle<JSLocale> Construct(Isolate* isolate,
388                                 const icu::Locale& icu_locale) {
389   Handle<Managed<icu::Locale>> managed_locale =
390       Managed<icu::Locale>::FromRawPtr(isolate, 0, icu_locale.clone());
391 
392   Handle<JSFunction> constructor(
393       isolate->native_context()->intl_locale_function(), isolate);
394 
395   Handle<Map> map;
396   ASSIGN_RETURN_ON_EXCEPTION(
397       isolate, map,
398       JSFunction::GetDerivedMap(isolate, constructor, constructor), JSLocale);
399 
400   Handle<JSLocale> locale = Handle<JSLocale>::cast(
401       isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
402   DisallowGarbageCollection no_gc;
403   locale->set_icu_locale(*managed_locale);
404   return locale;
405 }
406 
407 }  // namespace
408 
Maximize(Isolate * isolate,Handle<JSLocale> locale)409 MaybeHandle<JSLocale> JSLocale::Maximize(Isolate* isolate,
410                                          Handle<JSLocale> locale) {
411   // ICU has limitation on the length of the locale while addLikelySubtags
412   // is called. Work around the issue by only perform addLikelySubtags
413   // on the base locale and merge the extension if needed.
414   icu::Locale source(*(locale->icu_locale().raw()));
415   icu::Locale result = icu::Locale::createFromName(source.getBaseName());
416   UErrorCode status = U_ZERO_ERROR;
417   result.addLikelySubtags(status);
418   if (strlen(source.getBaseName()) != strlen(result.getBaseName())) {
419     // Base name is changed
420     if (strlen(source.getBaseName()) != strlen(source.getName())) {
421       // the source has extensions, get the extensions from the source.
422       result = icu::LocaleBuilder()
423                    .setLocale(source)
424                    .setLanguage(result.getLanguage())
425                    .setRegion(result.getCountry())
426                    .setScript(result.getScript())
427                    .setVariant(result.getVariant())
428                    .build(status);
429     }
430   } else {
431     // Base name is not changed
432     result = source;
433   }
434   if (U_FAILURE(status) || result.isBogus()) {
435     // Due to https://unicode-org.atlassian.net/browse/ICU-21639
436     // Valid but super long locale will fail. Just throw here for now.
437     THROW_NEW_ERROR(isolate,
438                     NewRangeError(MessageTemplate::kLocaleBadParameters),
439                     JSLocale);
440   }
441   return Construct(isolate, result);
442 }
443 
Minimize(Isolate * isolate,Handle<JSLocale> locale)444 MaybeHandle<JSLocale> JSLocale::Minimize(Isolate* isolate,
445                                          Handle<JSLocale> locale) {
446   // ICU has limitation on the length of the locale while minimizeSubtags
447   // is called. Work around the issue by only perform addLikelySubtags
448   // on the base locale and merge the extension if needed.
449   icu::Locale source(*(locale->icu_locale().raw()));
450   icu::Locale result = icu::Locale::createFromName(source.getBaseName());
451   UErrorCode status = U_ZERO_ERROR;
452   result.minimizeSubtags(status);
453   if (strlen(source.getBaseName()) != strlen(result.getBaseName())) {
454     // Base name is changed
455     if (strlen(source.getBaseName()) != strlen(source.getName())) {
456       // the source has extensions, get the extensions from the source.
457       result = icu::LocaleBuilder()
458                    .setLocale(source)
459                    .setLanguage(result.getLanguage())
460                    .setRegion(result.getCountry())
461                    .setScript(result.getScript())
462                    .setVariant(result.getVariant())
463                    .build(status);
464     }
465   } else {
466     // Base name is not changed
467     result = source;
468   }
469   if (U_FAILURE(status) || result.isBogus()) {
470     // Due to https://unicode-org.atlassian.net/browse/ICU-21639
471     // Valid but super long locale will fail. Just throw here for now.
472     THROW_NEW_ERROR(isolate,
473                     NewRangeError(MessageTemplate::kLocaleBadParameters),
474                     JSLocale);
475   }
476   return Construct(isolate, result);
477 }
478 
479 template <typename T>
GetKeywordValuesFromLocale(Isolate * isolate,const char * key,const char * unicode_key,const icu::Locale & locale,bool (* removes)(const char *),bool commonly_used,bool sort)480 MaybeHandle<JSArray> GetKeywordValuesFromLocale(Isolate* isolate,
481                                                 const char* key,
482                                                 const char* unicode_key,
483                                                 const icu::Locale& locale,
484                                                 bool (*removes)(const char*),
485                                                 bool commonly_used, bool sort) {
486   Factory* factory = isolate->factory();
487   UErrorCode status = U_ZERO_ERROR;
488   std::string ext =
489       locale.getUnicodeKeywordValue<std::string>(unicode_key, status);
490   if (!ext.empty()) {
491     Handle<FixedArray> fixed_array = factory->NewFixedArray(1);
492     Handle<String> str = factory->NewStringFromAsciiChecked(ext.c_str());
493     fixed_array->set(0, *str);
494     return factory->NewJSArrayWithElements(fixed_array);
495   }
496   status = U_ZERO_ERROR;
497   std::unique_ptr<icu::StringEnumeration> enumeration(
498       T::getKeywordValuesForLocale(key, locale, commonly_used, status));
499   if (U_FAILURE(status)) {
500     THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
501                     JSArray);
502   }
503   return Intl::ToJSArray(isolate, unicode_key, enumeration.get(), removes,
504                          sort);
505 }
506 
507 namespace {
508 
CalendarsForLocale(Isolate * isolate,const icu::Locale & icu_locale,bool commonly_used,bool sort)509 MaybeHandle<JSArray> CalendarsForLocale(Isolate* isolate,
510                                         const icu::Locale& icu_locale,
511                                         bool commonly_used, bool sort) {
512   return GetKeywordValuesFromLocale<icu::Calendar>(
513       isolate, "calendar", "ca", icu_locale, nullptr, commonly_used, sort);
514 }
515 
516 }  // namespace
517 
Calendars(Isolate * isolate,Handle<JSLocale> locale)518 MaybeHandle<JSArray> JSLocale::Calendars(Isolate* isolate,
519                                          Handle<JSLocale> locale) {
520   icu::Locale icu_locale(*(locale->icu_locale().raw()));
521   return CalendarsForLocale(isolate, icu_locale, true, false);
522 }
523 
AvailableCalendars(Isolate * isolate)524 MaybeHandle<JSArray> Intl::AvailableCalendars(Isolate* isolate) {
525   icu::Locale icu_locale("und");
526   return CalendarsForLocale(isolate, icu_locale, false, true);
527 }
528 
Collations(Isolate * isolate,Handle<JSLocale> locale)529 MaybeHandle<JSArray> JSLocale::Collations(Isolate* isolate,
530                                           Handle<JSLocale> locale) {
531   icu::Locale icu_locale(*(locale->icu_locale().raw()));
532   return GetKeywordValuesFromLocale<icu::Collator>(
533       isolate, "collations", "co", icu_locale, Intl::RemoveCollation, true,
534       false);
535 }
536 
HourCycles(Isolate * isolate,Handle<JSLocale> locale)537 MaybeHandle<JSArray> JSLocale::HourCycles(Isolate* isolate,
538                                           Handle<JSLocale> locale) {
539   // Let preferred be loc.[[HourCycle]].
540   // Let locale be loc.[[Locale]].
541   icu::Locale icu_locale(*(locale->icu_locale().raw()));
542   Factory* factory = isolate->factory();
543 
544   // Assert: locale matches the unicode_locale_id production.
545 
546   // Let list be a List of 1 or more hour cycle identifiers, which must be
547   // String values indicating either the 12-hour format ("h11", "h12") or the
548   // 24-hour format ("h23", "h24"), sorted in descending preference of those in
549   // common use in the locale for date and time formatting.
550 
551   // Return CreateArrayFromListAndPreferred( list, preferred ).
552   Handle<FixedArray> fixed_array = factory->NewFixedArray(1);
553   UErrorCode status = U_ZERO_ERROR;
554   std::string ext =
555       icu_locale.getUnicodeKeywordValue<std::string>("hc", status);
556   if (!ext.empty()) {
557     Handle<String> str = factory->NewStringFromAsciiChecked(ext.c_str());
558     fixed_array->set(0, *str);
559     return factory->NewJSArrayWithElements(fixed_array);
560   }
561   status = U_ZERO_ERROR;
562   std::unique_ptr<icu::DateTimePatternGenerator> generator(
563       icu::DateTimePatternGenerator::createInstance(icu_locale, status));
564   if (U_FAILURE(status)) {
565     THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
566                     JSArray);
567   }
568 
569   UDateFormatHourCycle hc = generator->getDefaultHourCycle(status);
570   if (U_FAILURE(status)) {
571     THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
572                     JSArray);
573   }
574   Handle<String> hour_cycle;
575 
576   switch (hc) {
577     case UDAT_HOUR_CYCLE_11:
578       hour_cycle = factory->h11_string();
579       break;
580     case UDAT_HOUR_CYCLE_12:
581       hour_cycle = factory->h12_string();
582       break;
583     case UDAT_HOUR_CYCLE_23:
584       hour_cycle = factory->h23_string();
585       break;
586     case UDAT_HOUR_CYCLE_24:
587       hour_cycle = factory->h24_string();
588       break;
589     default:
590       break;
591   }
592   fixed_array->set(0, *hour_cycle);
593   return factory->NewJSArrayWithElements(fixed_array);
594 }
595 
NumberingSystems(Isolate * isolate,Handle<JSLocale> locale)596 MaybeHandle<JSArray> JSLocale::NumberingSystems(Isolate* isolate,
597                                                 Handle<JSLocale> locale) {
598   // Let preferred be loc.[[NumberingSystem]].
599 
600   // Let locale be loc.[[Locale]].
601   icu::Locale icu_locale(*(locale->icu_locale().raw()));
602   Factory* factory = isolate->factory();
603 
604   // Assert: locale matches the unicode_locale_id production.
605 
606   // Let list be a List of 1 or more numbering system identifiers, which must be
607   // String values conforming to the type sequence from UTS 35 Unicode Locale
608   // Identifier, section 3.2, sorted in descending preference of those in common
609   // use in the locale for formatting numeric values.
610 
611   // Return CreateArrayFromListAndPreferred( list, preferred ).
612   UErrorCode status = U_ZERO_ERROR;
613   Handle<FixedArray> fixed_array = factory->NewFixedArray(1);
614   std::string numbering_system =
615       icu_locale.getUnicodeKeywordValue<std::string>("nu", status);
616   if (numbering_system.empty()) {
617     numbering_system = Intl::GetNumberingSystem(icu_locale);
618   }
619   Handle<String> str =
620       factory->NewStringFromAsciiChecked(numbering_system.c_str());
621 
622   fixed_array->set(0, *str);
623   return factory->NewJSArrayWithElements(fixed_array);
624 }
625 
TimeZones(Isolate * isolate,Handle<JSLocale> locale)626 MaybeHandle<Object> JSLocale::TimeZones(Isolate* isolate,
627                                         Handle<JSLocale> locale) {
628   // Let loc be the this value.
629 
630   // Perform ? RequireInternalSlot(loc, [[InitializedLocale]])
631 
632   // Let locale be loc.[[Locale]].
633   icu::Locale icu_locale(*(locale->icu_locale().raw()));
634   Factory* factory = isolate->factory();
635 
636   // If the unicode_language_id production of locale does not contain the
637   // ["-" unicode_region_subtag] sequence, return undefined.
638   const char* region = icu_locale.getCountry();
639   if (region == nullptr || strlen(region) == 0) {
640     return factory->undefined_value();
641   }
642 
643   // Return TimeZonesOfLocale(loc).
644 
645   // Let locale be loc.[[Locale]].
646 
647   // Assert: locale matches the unicode_locale_id production.
648 
649   // Let region be the substring of locale corresponding to the
650   // unicode_region_subtag production of the unicode_language_id.
651 
652   // Let list be a List of 1 or more time zone identifiers, which must be String
653   // values indicating a Zone or Link name of the IANA Time Zone Database,
654   // sorted in descending preference of those in common use in region.
655   UErrorCode status = U_ZERO_ERROR;
656   std::unique_ptr<icu::StringEnumeration> enumeration(
657       icu::TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL,
658                                                  region, nullptr, status));
659   if (U_FAILURE(status)) {
660     THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
661                     JSArray);
662   }
663   return Intl::ToJSArray(isolate, nullptr, enumeration.get(), nullptr, true);
664 }
665 
TextInfo(Isolate * isolate,Handle<JSLocale> locale)666 MaybeHandle<JSObject> JSLocale::TextInfo(Isolate* isolate,
667                                          Handle<JSLocale> locale) {
668   // Let loc be the this value.
669 
670   // Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
671 
672   // Let locale be loc.[[Locale]].
673 
674   // Assert: locale matches the unicode_locale_id production.
675 
676   Factory* factory = isolate->factory();
677   // Let info be ! ObjectCreate(%Object.prototype%).
678   Handle<JSObject> info = factory->NewJSObject(isolate->object_function());
679 
680   // Let dir be "ltr".
681   Handle<String> dir = factory->ltr_string();
682 
683   // If the default general ordering of characters (characterOrder) within a
684   // line in the locale is right-to-left, then
685   UErrorCode status = U_ZERO_ERROR;
686   ULayoutType orientation = uloc_getCharacterOrientation(
687       (locale->icu_locale().raw())->getName(), &status);
688   if (U_FAILURE(status)) {
689     THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
690                     JSObject);
691   }
692   if (orientation == ULOC_LAYOUT_LTR) {
693     // Let dir be "rtl".
694     dir = factory->rtl_string();
695   }
696 
697   // Perform ! CreateDataPropertyOrThrow(info, "direction", dir).
698   CHECK(JSReceiver::CreateDataProperty(
699             isolate, info, factory->direction_string(), dir, Just(kDontThrow))
700             .FromJust());
701 
702   // Return info.
703   return info;
704 }
705 
WeekInfo(Isolate * isolate,Handle<JSLocale> locale)706 MaybeHandle<JSObject> JSLocale::WeekInfo(Isolate* isolate,
707                                          Handle<JSLocale> locale) {
708   // Let loc be the this value.
709 
710   // Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).
711 
712   // Let locale be loc.[[Locale]].
713 
714   // Assert: locale matches the unicode_locale_id production.
715   Factory* factory = isolate->factory();
716 
717   // Let info be ! ObjectCreate(%Object.prototype%).
718   Handle<JSObject> info = factory->NewJSObject(isolate->object_function());
719   UErrorCode status = U_ZERO_ERROR;
720   std::unique_ptr<icu::Calendar> calendar(
721       icu::Calendar::createInstance(*(locale->icu_locale().raw()), status));
722   if (U_FAILURE(status)) {
723     THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
724                     JSObject);
725   }
726 
727   // Let fd be the weekday value indicating which day of the week is considered
728   // the 'first' day, for calendar purposes, in the locale.
729   int32_t fd = weekdayFromEDaysOfWeek(calendar->getFirstDayOfWeek());
730   bool thursday_is_weekend =
731       (UCAL_WEEKDAY != calendar->getDayOfWeekType(UCAL_THURSDAY, status));
732   bool friday_is_weekend =
733       (UCAL_WEEKDAY != calendar->getDayOfWeekType(UCAL_FRIDAY, status));
734   bool saturday_is_weekend =
735       (UCAL_WEEKDAY != calendar->getDayOfWeekType(UCAL_SATURDAY, status));
736   bool sunday_is_weekend =
737       (UCAL_WEEKDAY != calendar->getDayOfWeekType(UCAL_SUNDAY, status));
738   if (U_FAILURE(status)) {
739     THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
740                     JSObject);
741   }
742 
743   // Let ws be the weekday value indicating which day of the week is considered
744   // the starting day of the 'weekend', for calendar purposes, in the locale.
745   int32_t ws = thursday_is_weekend ? 4 : (friday_is_weekend ? 5 : 6);
746 
747   // Let we be the weekday value indicating which day of the week is considered
748   // the ending day of the 'weekend', for calendar purposes, in the locale.
749   int32_t we = sunday_is_weekend ? 7 : (saturday_is_weekend ? 6 : 5);
750 
751   // Let md be the minimal days required in the first week of a month or year,
752   // for calendar purposes, in the locale.
753   int32_t md = calendar->getMinimalDaysInFirstWeek();
754 
755   // Perform ! CreateDataPropertyOrThrow(info, "firstDay", fd).
756   CHECK(JSReceiver::CreateDataProperty(
757             isolate, info, factory->firstDay_string(),
758             factory->NewNumberFromInt(fd), Just(kDontThrow))
759             .FromJust());
760 
761   // Perform ! CreateDataPropertyOrThrow(info, "weekendStart", ws).
762   CHECK(JSReceiver::CreateDataProperty(
763             isolate, info, factory->weekendStart_string(),
764             factory->NewNumberFromInt(ws), Just(kDontThrow))
765             .FromJust());
766 
767   // Perform ! CreateDataPropertyOrThrow(info, "weekendEnd", we).
768   CHECK(JSReceiver::CreateDataProperty(
769             isolate, info, factory->weekendEnd_string(),
770             factory->NewNumberFromInt(we), Just(kDontThrow))
771             .FromJust());
772 
773   // Perform ! CreateDataPropertyOrThrow(info, "minimalDays", md).
774   CHECK(JSReceiver::CreateDataProperty(
775             isolate, info, factory->minimalDays_string(),
776             factory->NewNumberFromInt(md), Just(kDontThrow))
777             .FromJust());
778 
779   // Return info.
780   return info;
781 }
782 
Language(Isolate * isolate,Handle<JSLocale> locale)783 Handle<Object> JSLocale::Language(Isolate* isolate, Handle<JSLocale> locale) {
784   Factory* factory = isolate->factory();
785   const char* language = locale->icu_locale().raw()->getLanguage();
786   if (strlen(language) == 0) return factory->undefined_value();
787   return factory->NewStringFromAsciiChecked(language);
788 }
789 
Script(Isolate * isolate,Handle<JSLocale> locale)790 Handle<Object> JSLocale::Script(Isolate* isolate, Handle<JSLocale> locale) {
791   Factory* factory = isolate->factory();
792   const char* script = locale->icu_locale().raw()->getScript();
793   if (strlen(script) == 0) return factory->undefined_value();
794   return factory->NewStringFromAsciiChecked(script);
795 }
796 
Region(Isolate * isolate,Handle<JSLocale> locale)797 Handle<Object> JSLocale::Region(Isolate* isolate, Handle<JSLocale> locale) {
798   Factory* factory = isolate->factory();
799   const char* region = locale->icu_locale().raw()->getCountry();
800   if (strlen(region) == 0) return factory->undefined_value();
801   return factory->NewStringFromAsciiChecked(region);
802 }
803 
BaseName(Isolate * isolate,Handle<JSLocale> locale)804 Handle<String> JSLocale::BaseName(Isolate* isolate, Handle<JSLocale> locale) {
805   icu::Locale icu_locale =
806       icu::Locale::createFromName(locale->icu_locale().raw()->getBaseName());
807   std::string base_name = Intl::ToLanguageTag(icu_locale).FromJust();
808   return isolate->factory()->NewStringFromAsciiChecked(base_name.c_str());
809 }
810 
Calendar(Isolate * isolate,Handle<JSLocale> locale)811 Handle<Object> JSLocale::Calendar(Isolate* isolate, Handle<JSLocale> locale) {
812   return UnicodeKeywordValue(isolate, locale, "ca");
813 }
814 
CaseFirst(Isolate * isolate,Handle<JSLocale> locale)815 Handle<Object> JSLocale::CaseFirst(Isolate* isolate, Handle<JSLocale> locale) {
816   return UnicodeKeywordValue(isolate, locale, "kf");
817 }
818 
Collation(Isolate * isolate,Handle<JSLocale> locale)819 Handle<Object> JSLocale::Collation(Isolate* isolate, Handle<JSLocale> locale) {
820   return UnicodeKeywordValue(isolate, locale, "co");
821 }
822 
HourCycle(Isolate * isolate,Handle<JSLocale> locale)823 Handle<Object> JSLocale::HourCycle(Isolate* isolate, Handle<JSLocale> locale) {
824   return UnicodeKeywordValue(isolate, locale, "hc");
825 }
826 
Numeric(Isolate * isolate,Handle<JSLocale> locale)827 Handle<Object> JSLocale::Numeric(Isolate* isolate, Handle<JSLocale> locale) {
828   Factory* factory = isolate->factory();
829   icu::Locale* icu_locale = locale->icu_locale().raw();
830   UErrorCode status = U_ZERO_ERROR;
831   std::string numeric =
832       icu_locale->getUnicodeKeywordValue<std::string>("kn", status);
833   return (numeric == "true") ? factory->true_value() : factory->false_value();
834 }
835 
NumberingSystem(Isolate * isolate,Handle<JSLocale> locale)836 Handle<Object> JSLocale::NumberingSystem(Isolate* isolate,
837                                          Handle<JSLocale> locale) {
838   return UnicodeKeywordValue(isolate, locale, "nu");
839 }
840 
ToString(Handle<JSLocale> locale)841 std::string JSLocale::ToString(Handle<JSLocale> locale) {
842   icu::Locale* icu_locale = locale->icu_locale().raw();
843   return Intl::ToLanguageTag(*icu_locale).FromJust();
844 }
845 
ToString(Isolate * isolate,Handle<JSLocale> locale)846 Handle<String> JSLocale::ToString(Isolate* isolate, Handle<JSLocale> locale) {
847   std::string locale_str = JSLocale::ToString(locale);
848   return isolate->factory()->NewStringFromAsciiChecked(locale_str.c_str());
849 }
850 
851 }  // namespace internal
852 }  // namespace v8
853