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
35namespace v8 {
36namespace internal {
37
38namespace {
39
40struct 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.
48Maybe<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
101Handle<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
119bool 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}
127bool 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
133bool 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
139bool 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
146bool 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
151bool IsUnicodeScriptSubtag(const std::string& value) {
152  // unicode_script_subtag = alpha{4} ;
153  return IsAlpha(value, 4, 4);
154}
155
156bool 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
161bool 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
166bool 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
171bool IsExtensionSingleton(const std::string& value) {
172  return IsAlphanum(value, 1, 1);
173}
174
175int32_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.
183bool 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
195bool 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.
201bool 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
237namespace {
238Maybe<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
346MaybeHandle<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
385namespace {
386
387MaybeHandle<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
409MaybeHandle<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
444MaybeHandle<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
479template <typename T>
480MaybeHandle<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
507namespace {
508
509MaybeHandle<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
518MaybeHandle<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
524MaybeHandle<JSArray> Intl::AvailableCalendars(Isolate* isolate) {
525  icu::Locale icu_locale("und");
526  return CalendarsForLocale(isolate, icu_locale, false, true);
527}
528
529MaybeHandle<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
537MaybeHandle<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
596MaybeHandle<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
626MaybeHandle<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
666MaybeHandle<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_RTL) {
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
706MaybeHandle<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
731  // Let wi be ! WeekInfoOfLocale(loc).
732  // Let we be ! CreateArrayFromList( wi.[[Weekend]] ).
733  Handle<FixedArray> wi = Handle<FixedArray>::cast(factory->NewFixedArray(2));
734  int32_t length = 0;
735  for (int32_t i = 1; i <= 7; i++) {
736    UCalendarDaysOfWeek day =
737        (i == 7) ? UCAL_SUNDAY : static_cast<UCalendarDaysOfWeek>(i + 1);
738    if (UCAL_WEEKDAY != calendar->getDayOfWeekType(day, status)) {
739      wi->set(length++, Smi::FromInt(i));
740      CHECK_LE(length, 2);
741    }
742  }
743  if (length != 2) {
744    wi = wi->ShrinkOrEmpty(isolate, wi, length);
745  }
746  Handle<JSArray> we = factory->NewJSArrayWithElements(wi);
747
748  if (U_FAILURE(status)) {
749    THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
750                    JSObject);
751  }
752
753  // Let md be the minimal days required in the first week of a month or year,
754  // for calendar purposes, in the locale.
755  int32_t md = calendar->getMinimalDaysInFirstWeek();
756
757  // Perform ! CreateDataPropertyOrThrow(info, "firstDay", fd).
758  CHECK(JSReceiver::CreateDataProperty(
759            isolate, info, factory->firstDay_string(),
760            factory->NewNumberFromInt(fd), Just(kDontThrow))
761            .FromJust());
762
763  // Perform ! CreateDataPropertyOrThrow(info, "weekend", we).
764  CHECK(JSReceiver::CreateDataProperty(isolate, info, factory->weekend_string(),
765                                       we, Just(kDontThrow))
766            .FromJust());
767
768  // Perform ! CreateDataPropertyOrThrow(info, "minimalDays", md).
769  CHECK(JSReceiver::CreateDataProperty(
770            isolate, info, factory->minimalDays_string(),
771            factory->NewNumberFromInt(md), Just(kDontThrow))
772            .FromJust());
773
774  // Return info.
775  return info;
776}
777
778Handle<Object> JSLocale::Language(Isolate* isolate, Handle<JSLocale> locale) {
779  Factory* factory = isolate->factory();
780  const char* language = locale->icu_locale().raw()->getLanguage();
781  if (strlen(language) == 0) return factory->undefined_value();
782  return factory->NewStringFromAsciiChecked(language);
783}
784
785Handle<Object> JSLocale::Script(Isolate* isolate, Handle<JSLocale> locale) {
786  Factory* factory = isolate->factory();
787  const char* script = locale->icu_locale().raw()->getScript();
788  if (strlen(script) == 0) return factory->undefined_value();
789  return factory->NewStringFromAsciiChecked(script);
790}
791
792Handle<Object> JSLocale::Region(Isolate* isolate, Handle<JSLocale> locale) {
793  Factory* factory = isolate->factory();
794  const char* region = locale->icu_locale().raw()->getCountry();
795  if (strlen(region) == 0) return factory->undefined_value();
796  return factory->NewStringFromAsciiChecked(region);
797}
798
799Handle<String> JSLocale::BaseName(Isolate* isolate, Handle<JSLocale> locale) {
800  icu::Locale icu_locale =
801      icu::Locale::createFromName(locale->icu_locale().raw()->getBaseName());
802  std::string base_name = Intl::ToLanguageTag(icu_locale).FromJust();
803  return isolate->factory()->NewStringFromAsciiChecked(base_name.c_str());
804}
805
806Handle<Object> JSLocale::Calendar(Isolate* isolate, Handle<JSLocale> locale) {
807  return UnicodeKeywordValue(isolate, locale, "ca");
808}
809
810Handle<Object> JSLocale::CaseFirst(Isolate* isolate, Handle<JSLocale> locale) {
811  return UnicodeKeywordValue(isolate, locale, "kf");
812}
813
814Handle<Object> JSLocale::Collation(Isolate* isolate, Handle<JSLocale> locale) {
815  return UnicodeKeywordValue(isolate, locale, "co");
816}
817
818Handle<Object> JSLocale::HourCycle(Isolate* isolate, Handle<JSLocale> locale) {
819  return UnicodeKeywordValue(isolate, locale, "hc");
820}
821
822Handle<Object> JSLocale::Numeric(Isolate* isolate, Handle<JSLocale> locale) {
823  Factory* factory = isolate->factory();
824  icu::Locale* icu_locale = locale->icu_locale().raw();
825  UErrorCode status = U_ZERO_ERROR;
826  std::string numeric =
827      icu_locale->getUnicodeKeywordValue<std::string>("kn", status);
828  return (numeric == "true") ? factory->true_value() : factory->false_value();
829}
830
831Handle<Object> JSLocale::NumberingSystem(Isolate* isolate,
832                                         Handle<JSLocale> locale) {
833  return UnicodeKeywordValue(isolate, locale, "nu");
834}
835
836std::string JSLocale::ToString(Handle<JSLocale> locale) {
837  icu::Locale* icu_locale = locale->icu_locale().raw();
838  return Intl::ToLanguageTag(*icu_locale).FromJust();
839}
840
841Handle<String> JSLocale::ToString(Isolate* isolate, Handle<JSLocale> locale) {
842  std::string locale_str = JSLocale::ToString(locale);
843  return isolate->factory()->NewStringFromAsciiChecked(locale_str.c_str());
844}
845
846}  // namespace internal
847}  // namespace v8
848