js-locale.cc 30.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef V8_INTL_SUPPORT
#error Internationalization is expected to be enabled.
#endif  // V8_INTL_SUPPORT

#include "src/objects/js-locale.h"

#include <map>
#include <memory>
#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 27
#include "unicode/coll.h"
#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 34 35 36 37 38
#include "unicode/unistr.h"

namespace v8 {
namespace internal {

namespace {
39 40 41 42 43 44 45 46

struct OptionData {
  const char* name;
  const char* key;
  const std::vector<const char*>* possible_values;
  bool is_bool_value;
};

47 48 49
// Inserts tags from options into locale string.
Maybe<bool> InsertOptionsIntoLocale(Isolate* isolate,
                                    Handle<JSReceiver> options,
50
                                    icu::LocaleBuilder* builder) {
Frank Tang's avatar
Frank Tang committed
51
  DCHECK(isolate);
52

53 54 55 56 57 58
  const std::vector<const char*> hour_cycle_values = {"h11", "h12", "h23",
                                                      "h24"};
  const std::vector<const char*> case_first_values = {"upper", "lower",
                                                      "false"};
  const std::vector<const char*> empty_values = {};
  const std::array<OptionData, 6> kOptionToUnicodeTagMap = {
59 60 61 62 63 64
      {{"calendar", "ca", &empty_values, false},
       {"collation", "co", &empty_values, false},
       {"hourCycle", "hc", &hour_cycle_values, false},
       {"caseFirst", "kf", &case_first_values, false},
       {"numeric", "kn", &empty_values, true},
       {"numberingSystem", "nu", &empty_values, false}}};
65

66 67
  // TODO(cira): Pass in values as per the spec to make this to be
  // spec compliant.
68

69
  for (const auto& option_to_bcp47 : kOptionToUnicodeTagMap) {
70
    std::unique_ptr<char[]> value_str = nullptr;
71 72 73
    bool value_bool = false;
    Maybe<bool> maybe_found =
        option_to_bcp47.is_bool_value
74 75 76 77 78
            ? GetBoolOption(isolate, options, option_to_bcp47.name, "locale",
                            &value_bool)
            : GetStringOption(isolate, options, option_to_bcp47.name,
                              *(option_to_bcp47.possible_values), "locale",
                              &value_str);
79
    MAYBE_RETURN(maybe_found, Nothing<bool>());
80

81 82 83
    // TODO(cira): Use fallback value if value is not found to make
    // this spec compliant.
    if (!maybe_found.FromJust()) continue;
84 85 86 87 88

    if (option_to_bcp47.is_bool_value) {
      value_str = value_bool ? isolate->factory()->true_string()->ToCString()
                             : isolate->factory()->false_string()->ToCString();
    }
89
    DCHECK_NOT_NULL(value_str.get());
90 91

    // Overwrite existing, or insert new key-value to the locale string.
92 93
    if (!uloc_toLegacyType(uloc_toLegacyKey(option_to_bcp47.key),
                           value_str.get())) {
94 95
      return Just(false);
    }
96
    builder->setUnicodeLocaleKeyword(option_to_bcp47.key, value_str.get());
97 98 99 100
  }
  return Just(true);
}

101 102
Handle<Object> UnicodeKeywordValue(Isolate* isolate, Handle<JSLocale> locale,
                                   const char* key) {
103
  icu::Locale* icu_locale = locale->icu_locale().raw();
104
  UErrorCode status = U_ZERO_ERROR;
105 106
  std::string value =
      icu_locale->getUnicodeKeywordValue<std::string>(key, status);
107
  if (status == U_ILLEGAL_ARGUMENT_ERROR || value == "") {
108
    return isolate->factory()->undefined_value();
109
  }
110 111 112
  if (value == "yes") {
    value = "true";
  }
113 114 115
  if (value == "true" && strcmp(key, "kf") == 0) {
    return isolate->factory()->NewStringFromStaticChars("");
  }
116
  return isolate->factory()->NewStringFromAsciiChecked(value.c_str());
117
}
118

119 120
bool IsCheckRange(const std::string& str, size_t min, size_t max,
                  bool(range_check_func)(char)) {
121
  if (!base::IsInRange(str.length(), min, max)) return false;
122 123 124 125 126 127 128
  for (size_t i = 0; i < str.length(); i++) {
    if (!range_check_func(str[i])) return false;
  }
  return true;
}
bool IsAlpha(const std::string& str, size_t min, size_t max) {
  return IsCheckRange(str, min, max, [](char c) -> bool {
129
    return base::IsInRange(c, 'a', 'z') || base::IsInRange(c, 'A', 'Z');
130 131 132 133
  });
}

bool IsDigit(const std::string& str, size_t min, size_t max) {
134 135 136
  return IsCheckRange(str, min, max, [](char c) -> bool {
    return base::IsInRange(c, '0', '9');
  });
137 138
}

139 140
bool IsAlphanum(const std::string& str, size_t min, size_t max) {
  return IsCheckRange(str, min, max, [](char c) -> bool {
141 142
    return base::IsInRange(c, 'a', 'z') || base::IsInRange(c, 'A', 'Z') ||
           base::IsInRange(c, '0', '9');
143 144 145 146 147 148
  });
}

bool IsUnicodeLanguageSubtag(const std::string& value) {
  // unicode_language_subtag = alpha{2,3} | alpha{5,8};
  return IsAlpha(value, 2, 3) || IsAlpha(value, 5, 8);
149 150
}

151 152
bool IsUnicodeScriptSubtag(const std::string& value) {
  // unicode_script_subtag = alpha{4} ;
153 154 155
  return IsAlpha(value, 4, 4);
}

156 157
bool IsUnicodeRegionSubtag(const std::string& value) {
  // unicode_region_subtag = (alpha{2} | digit{3});
158 159 160
  return IsAlpha(value, 2, 2) || IsDigit(value, 3, 3);
}

161
bool IsDigitAlphanum3(const std::string& value) {
162
  return value.length() == 4 && base::IsInRange(value[0], '0', '9') &&
163 164 165 166 167 168 169 170 171 172 173
         IsAlphanum(value.substr(1), 3, 3);
}

bool IsUnicodeVariantSubtag(const std::string& value) {
  // unicode_variant_subtag = (alphanum{5,8} | digit alphanum{3}) ;
  return IsAlphanum(value, 5, 8) || IsDigitAlphanum3(value);
}

bool IsExtensionSingleton(const std::string& value) {
  return IsAlphanum(value, 1, 1);
}
174 175 176 177 178

int32_t weekdayFromEDaysOfWeek(icu::Calendar::EDaysOfWeek eDaysOfWeek) {
  return (eDaysOfWeek == icu::Calendar::SUNDAY) ? 7 : eDaysOfWeek - 1;
}

179
}  // namespace
180

181 182
// Implemented as iteration instead of recursion to avoid stack overflow for
// very long input strings.
183 184 185 186
bool JSLocale::Is38AlphaNumList(const std::string& in) {
  std::string value = in;
  while (true) {
    std::size_t found_dash = value.find("-");
187
    if (found_dash == std::string::npos) {
188 189
      return IsAlphanum(value, 3, 8);
    }
190 191
    if (!IsAlphanum(value.substr(0, found_dash), 3, 8)) return false;
    value = value.substr(found_dash + 1);
192
  }
193 194
}

195 196 197 198
bool JSLocale::Is3Alpha(const std::string& value) {
  return IsAlpha(value, 3, 3);
}

199 200
// TODO(ftang) Replace the following check w/ icu::LocaleBuilder
// once ICU64 land in March 2019.
201
bool JSLocale::StartsWithUnicodeLanguageId(const std::string& value) {
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
  // unicode_language_id =
  // unicode_language_subtag (sep unicode_script_subtag)?
  //   (sep unicode_region_subtag)? (sep unicode_variant_subtag)* ;
  std::vector<std::string> tokens;
  std::string token;
  std::istringstream token_stream(value);
  while (std::getline(token_stream, token, '-')) {
    tokens.push_back(token);
  }
  if (tokens.size() == 0) return false;

  // length >= 1
  if (!IsUnicodeLanguageSubtag(tokens[0])) return false;

  if (tokens.size() == 1) return true;

  // length >= 2
  if (IsExtensionSingleton(tokens[1])) return true;

  size_t index = 1;
  if (IsUnicodeScriptSubtag(tokens[index])) {
    index++;
    if (index == tokens.size()) return true;
  }
  if (IsUnicodeRegionSubtag(tokens[index])) {
    index++;
  }
  while (index < tokens.size()) {
    if (IsExtensionSingleton(tokens[index])) return true;
    if (!IsUnicodeVariantSubtag(tokens[index])) return false;
    index++;
  }
  return true;
}

237
namespace {
238 239 240
Maybe<bool> ApplyOptionsToTag(Isolate* isolate, Handle<String> tag,
                              Handle<JSReceiver> options,
                              icu::LocaleBuilder* builder) {
241
  v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate);
242 243 244
  if (tag->length() == 0) {
    THROW_NEW_ERROR_RETURN_VALUE(
        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()});
Frank Tang's avatar
Frank Tang committed
250 251
  DCHECK_LT(0, bcp47_tag.length());
  DCHECK_NOT_NULL(*bcp47_tag);
252 253
  // 2. If IsStructurallyValidLanguageTag(tag) is false, throw a RangeError
  // exception.
254
  if (!JSLocale::StartsWithUnicodeLanguageId(*bcp47_tag)) {
255
    return Just(false);
256
  }
257
  UErrorCode status = U_ZERO_ERROR;
258 259
  icu::Locale canonicalized = builder->build(status);
  canonicalized.canonicalize(status);
260
  if (U_FAILURE(status)) {
261
    return Just(false);
262
  }
263
  builder->setLocale(canonicalized);
264

265 266 267 268 269
  // 3. Let language be ? GetOption(options, "language", "string", undefined,
  // undefined).
  const std::vector<const char*> empty_values = {};
  std::unique_ptr<char[]> language_str = nullptr;
  Maybe<bool> maybe_language =
270 271
      GetStringOption(isolate, options, "language", empty_values,
                      "ApplyOptionsToTag", &language_str);
272
  MAYBE_RETURN(maybe_language, Nothing<bool>());
273 274
  // 4. If language is not undefined, then
  if (maybe_language.FromJust()) {
275 276
    builder->setLanguage(language_str.get());
    builder->build(status);
277 278
    // a. If language does not match the unicode_language_subtag production,
    //    throw a RangeError exception.
279 280 281
    if (U_FAILURE(status) || language_str[0] == '\0' ||
        IsAlpha(language_str.get(), 4, 4)) {
      return Just(false);
282 283 284 285 286 287
    }
  }
  // 5. Let script be ? GetOption(options, "script", "string", undefined,
  // undefined).
  std::unique_ptr<char[]> script_str = nullptr;
  Maybe<bool> maybe_script =
288 289
      GetStringOption(isolate, options, "script", empty_values,
                      "ApplyOptionsToTag", &script_str);
290
  MAYBE_RETURN(maybe_script, Nothing<bool>());
291 292
  // 6. If script is not undefined, then
  if (maybe_script.FromJust()) {
293 294
    builder->setScript(script_str.get());
    builder->build(status);
295 296
    // a. If script does not match the unicode_script_subtag production, throw
    //    a RangeError exception.
297 298
    if (U_FAILURE(status) || script_str[0] == '\0') {
      return Just(false);
299 300 301 302 303 304
    }
  }
  // 7. Let region be ? GetOption(options, "region", "string", undefined,
  // undefined).
  std::unique_ptr<char[]> region_str = nullptr;
  Maybe<bool> maybe_region =
305 306
      GetStringOption(isolate, options, "region", empty_values,
                      "ApplyOptionsToTag", &region_str);
307
  MAYBE_RETURN(maybe_region, Nothing<bool>());
308 309 310 311
  // 8. If region is not undefined, then
  if (maybe_region.FromJust()) {
    // a. If region does not match the region production, throw a RangeError
    // exception.
312 313 314 315
    builder->setRegion(region_str.get());
    builder->build(status);
    if (U_FAILURE(status) || region_str[0] == '\0') {
      return Just(false);
316 317
    }
  }
318

319
  // 9. Set tag to CanonicalizeLanguageTag(tag).
320
  // 10.  If language is not undefined,
321 322 323
  // a. Assert: tag matches the unicode_locale_id production.
  // b. Set tag to tag with the substring corresponding to the
  //    unicode_language_subtag production replaced by the string language.
324
  // 11. If script is not undefined, then
325 326 327 328 329 330
  // a. If tag does not contain a unicode_script_subtag production, then
  //   i. Set tag to the concatenation of the unicode_language_subtag
  //      production of tag, "-", script, and the rest of tag.
  // b. Else,
  //   i. Set tag to tag with the substring corresponding to the
  //      unicode_script_subtag production replaced by the string script.
331
  // 12. If region is not undefined, then
332 333 334 335 336 337 338 339
  // a. If tag does not contain a unicode_region_subtag production, then
  //   i. Set tag to the concatenation of the unicode_language_subtag
  //      production of tag, the substring corresponding to the  "-"
  //      unicode_script_subtag production if present, "-", region, and
  //      the rest of tag.
  // b. Else,
  // i. Set tag to tag with the substring corresponding to the
  //    unicode_region_subtag production replaced by the string region.
340
  // 13.  Return CanonicalizeLanguageTag(tag).
341
  return Just(true);
342 343 344 345
}

}  // namespace

346 347 348
MaybeHandle<JSLocale> JSLocale::New(Isolate* isolate, Handle<Map> map,
                                    Handle<String> locale_str,
                                    Handle<JSReceiver> options) {
349 350 351 352 353
  icu::LocaleBuilder builder;
  Maybe<bool> maybe_apply =
      ApplyOptionsToTag(isolate, locale_str, options, &builder);
  MAYBE_RETURN(maybe_apply, MaybeHandle<JSLocale>());
  if (!maybe_apply.FromJust()) {
354 355 356 357
    THROW_NEW_ERROR(isolate,
                    NewRangeError(MessageTemplate::kLocaleBadParameters),
                    JSLocale);
  }
358

359 360 361 362 363
  Maybe<bool> maybe_insert =
      InsertOptionsIntoLocale(isolate, options, &builder);
  MAYBE_RETURN(maybe_insert, MaybeHandle<JSLocale>());
  UErrorCode status = U_ZERO_ERROR;
  icu::Locale icu_locale = builder.build(status);
364 365 366

  icu_locale.canonicalize(status);

367
  if (!maybe_insert.FromJust() || U_FAILURE(status)) {
368 369 370
    THROW_NEW_ERROR(isolate,
                    NewRangeError(MessageTemplate::kLocaleBadParameters),
                    JSLocale);
371 372
  }

373 374 375
  // 31. Set locale.[[Locale]] to r.[[locale]].
  Handle<Managed<icu::Locale>> managed_locale =
      Managed<icu::Locale>::FromRawPtr(isolate, 0, icu_locale.clone());
376

377 378 379
  // Now all properties are ready, so we can allocate the result object.
  Handle<JSLocale> locale = Handle<JSLocale>::cast(
      isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
380
  DisallowGarbageCollection no_gc;
381
  locale->set_icu_locale(*managed_locale);
382
  return locale;
383 384
}

385
namespace {
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401

MaybeHandle<JSLocale> Construct(Isolate* isolate,
                                const icu::Locale& icu_locale) {
  Handle<Managed<icu::Locale>> managed_locale =
      Managed<icu::Locale>::FromRawPtr(isolate, 0, icu_locale.clone());

  Handle<JSFunction> constructor(
      isolate->native_context()->intl_locale_function(), isolate);

  Handle<Map> map;
  ASSIGN_RETURN_ON_EXCEPTION(
      isolate, map,
      JSFunction::GetDerivedMap(isolate, constructor, constructor), JSLocale);

  Handle<JSLocale> locale = Handle<JSLocale>::cast(
      isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
402
  DisallowGarbageCollection no_gc;
403 404
  locale->set_icu_locale(*managed_locale);
  return locale;
405
}
406

407 408
}  // namespace

409 410
MaybeHandle<JSLocale> JSLocale::Maximize(Isolate* isolate,
                                         Handle<JSLocale> locale) {
411 412 413 414 415
  // ICU has limitation on the length of the locale while addLikelySubtags
  // is called. Work around the issue by only perform addLikelySubtags
  // on the base locale and merge the extension if needed.
  icu::Locale source(*(locale->icu_locale().raw()));
  icu::Locale result = icu::Locale::createFromName(source.getBaseName());
416
  UErrorCode status = U_ZERO_ERROR;
417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433
  result.addLikelySubtags(status);
  if (strlen(source.getBaseName()) != strlen(result.getBaseName())) {
    // Base name is changed
    if (strlen(source.getBaseName()) != strlen(source.getName())) {
      // the source has extensions, get the extensions from the source.
      result = icu::LocaleBuilder()
                   .setLocale(source)
                   .setLanguage(result.getLanguage())
                   .setRegion(result.getCountry())
                   .setScript(result.getScript())
                   .setVariant(result.getVariant())
                   .build(status);
    }
  } else {
    // Base name is not changed
    result = source;
  }
434 435 436 437 438 439 440
  if (U_FAILURE(status) || result.isBogus()) {
    // Due to https://unicode-org.atlassian.net/browse/ICU-21639
    // Valid but super long locale will fail. Just throw here for now.
    THROW_NEW_ERROR(isolate,
                    NewRangeError(MessageTemplate::kLocaleBadParameters),
                    JSLocale);
  }
441
  return Construct(isolate, result);
442 443
}

444 445
MaybeHandle<JSLocale> JSLocale::Minimize(Isolate* isolate,
                                         Handle<JSLocale> locale) {
446 447 448 449 450
  // ICU has limitation on the length of the locale while minimizeSubtags
  // is called. Work around the issue by only perform addLikelySubtags
  // on the base locale and merge the extension if needed.
  icu::Locale source(*(locale->icu_locale().raw()));
  icu::Locale result = icu::Locale::createFromName(source.getBaseName());
451
  UErrorCode status = U_ZERO_ERROR;
452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
  result.minimizeSubtags(status);
  if (strlen(source.getBaseName()) != strlen(result.getBaseName())) {
    // Base name is changed
    if (strlen(source.getBaseName()) != strlen(source.getName())) {
      // the source has extensions, get the extensions from the source.
      result = icu::LocaleBuilder()
                   .setLocale(source)
                   .setLanguage(result.getLanguage())
                   .setRegion(result.getCountry())
                   .setScript(result.getScript())
                   .setVariant(result.getVariant())
                   .build(status);
    }
  } else {
    // Base name is not changed
    result = source;
  }
469 470 471 472 473 474 475
  if (U_FAILURE(status) || result.isBogus()) {
    // Due to https://unicode-org.atlassian.net/browse/ICU-21639
    // Valid but super long locale will fail. Just throw here for now.
    THROW_NEW_ERROR(isolate,
                    NewRangeError(MessageTemplate::kLocaleBadParameters),
                    JSLocale);
  }
476
  return Construct(isolate, result);
477 478
}

479
template <typename T>
480 481 482 483 484 485
MaybeHandle<JSArray> GetKeywordValuesFromLocale(Isolate* isolate,
                                                const char* key,
                                                const char* unicode_key,
                                                const icu::Locale& locale,
                                                bool (*removes)(const char*),
                                                bool commonly_used, bool sort) {
486 487 488 489 490 491 492 493 494 495 496 497
  Factory* factory = isolate->factory();
  UErrorCode status = U_ZERO_ERROR;
  std::string ext =
      locale.getUnicodeKeywordValue<std::string>(unicode_key, status);
  if (!ext.empty()) {
    Handle<FixedArray> fixed_array = factory->NewFixedArray(1);
    Handle<String> str = factory->NewStringFromAsciiChecked(ext.c_str());
    fixed_array->set(0, *str);
    return factory->NewJSArrayWithElements(fixed_array);
  }
  status = U_ZERO_ERROR;
  std::unique_ptr<icu::StringEnumeration> enumeration(
498
      T::getKeywordValuesForLocale(key, locale, commonly_used, status));
499 500 501 502
  if (U_FAILURE(status)) {
    THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
                    JSArray);
  }
503 504
  return Intl::ToJSArray(isolate, unicode_key, enumeration.get(), removes,
                         sort);
505 506
}

507 508 509 510 511 512 513 514 515 516 517
namespace {

MaybeHandle<JSArray> CalendarsForLocale(Isolate* isolate,
                                        const icu::Locale& icu_locale,
                                        bool commonly_used, bool sort) {
  return GetKeywordValuesFromLocale<icu::Calendar>(
      isolate, "calendar", "ca", icu_locale, nullptr, commonly_used, sort);
}

}  // namespace

518 519 520
MaybeHandle<JSArray> JSLocale::Calendars(Isolate* isolate,
                                         Handle<JSLocale> locale) {
  icu::Locale icu_locale(*(locale->icu_locale().raw()));
521 522 523 524 525 526
  return CalendarsForLocale(isolate, icu_locale, true, false);
}

MaybeHandle<JSArray> Intl::AvailableCalendars(Isolate* isolate) {
  icu::Locale icu_locale("und");
  return CalendarsForLocale(isolate, icu_locale, false, true);
527 528 529 530 531
}

MaybeHandle<JSArray> JSLocale::Collations(Isolate* isolate,
                                          Handle<JSLocale> locale) {
  icu::Locale icu_locale(*(locale->icu_locale().raw()));
532 533 534
  return GetKeywordValuesFromLocale<icu::Collator>(
      isolate, "collations", "co", icu_locale, Intl::RemoveCollation, true,
      false);
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662
}

MaybeHandle<JSArray> JSLocale::HourCycles(Isolate* isolate,
                                          Handle<JSLocale> locale) {
  // Let preferred be loc.[[HourCycle]].
  // Let locale be loc.[[Locale]].
  icu::Locale icu_locale(*(locale->icu_locale().raw()));
  Factory* factory = isolate->factory();

  // Assert: locale matches the unicode_locale_id production.

  // Let list be a List of 1 or more hour cycle identifiers, which must be
  // String values indicating either the 12-hour format ("h11", "h12") or the
  // 24-hour format ("h23", "h24"), sorted in descending preference of those in
  // common use in the locale for date and time formatting.

  // Return CreateArrayFromListAndPreferred( list, preferred ).
  Handle<FixedArray> fixed_array = factory->NewFixedArray(1);
  UErrorCode status = U_ZERO_ERROR;
  std::string ext =
      icu_locale.getUnicodeKeywordValue<std::string>("hc", status);
  if (!ext.empty()) {
    Handle<String> str = factory->NewStringFromAsciiChecked(ext.c_str());
    fixed_array->set(0, *str);
    return factory->NewJSArrayWithElements(fixed_array);
  }
  status = U_ZERO_ERROR;
  std::unique_ptr<icu::DateTimePatternGenerator> generator(
      icu::DateTimePatternGenerator::createInstance(icu_locale, status));
  if (U_FAILURE(status)) {
    THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
                    JSArray);
  }

  UDateFormatHourCycle hc = generator->getDefaultHourCycle(status);
  if (U_FAILURE(status)) {
    THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
                    JSArray);
  }
  Handle<String> hour_cycle;

  switch (hc) {
    case UDAT_HOUR_CYCLE_11:
      hour_cycle = factory->h11_string();
      break;
    case UDAT_HOUR_CYCLE_12:
      hour_cycle = factory->h12_string();
      break;
    case UDAT_HOUR_CYCLE_23:
      hour_cycle = factory->h23_string();
      break;
    case UDAT_HOUR_CYCLE_24:
      hour_cycle = factory->h24_string();
      break;
    default:
      break;
  }
  fixed_array->set(0, *hour_cycle);
  return factory->NewJSArrayWithElements(fixed_array);
}

MaybeHandle<JSArray> JSLocale::NumberingSystems(Isolate* isolate,
                                                Handle<JSLocale> locale) {
  // Let preferred be loc.[[NumberingSystem]].

  // Let locale be loc.[[Locale]].
  icu::Locale icu_locale(*(locale->icu_locale().raw()));
  Factory* factory = isolate->factory();

  // Assert: locale matches the unicode_locale_id production.

  // Let list be a List of 1 or more numbering system identifiers, which must be
  // String values conforming to the type sequence from UTS 35 Unicode Locale
  // Identifier, section 3.2, sorted in descending preference of those in common
  // use in the locale for formatting numeric values.

  // Return CreateArrayFromListAndPreferred( list, preferred ).
  UErrorCode status = U_ZERO_ERROR;
  Handle<FixedArray> fixed_array = factory->NewFixedArray(1);
  std::string numbering_system =
      icu_locale.getUnicodeKeywordValue<std::string>("nu", status);
  if (numbering_system.empty()) {
    numbering_system = Intl::GetNumberingSystem(icu_locale);
  }
  Handle<String> str =
      factory->NewStringFromAsciiChecked(numbering_system.c_str());

  fixed_array->set(0, *str);
  return factory->NewJSArrayWithElements(fixed_array);
}

MaybeHandle<Object> JSLocale::TimeZones(Isolate* isolate,
                                        Handle<JSLocale> locale) {
  // Let loc be the this value.

  // Perform ? RequireInternalSlot(loc, [[InitializedLocale]])

  // Let locale be loc.[[Locale]].
  icu::Locale icu_locale(*(locale->icu_locale().raw()));
  Factory* factory = isolate->factory();

  // If the unicode_language_id production of locale does not contain the
  // ["-" unicode_region_subtag] sequence, return undefined.
  const char* region = icu_locale.getCountry();
  if (region == nullptr || strlen(region) == 0) {
    return factory->undefined_value();
  }

  // Return TimeZonesOfLocale(loc).

  // Let locale be loc.[[Locale]].

  // Assert: locale matches the unicode_locale_id production.

  // Let region be the substring of locale corresponding to the
  // unicode_region_subtag production of the unicode_language_id.

  // Let list be a List of 1 or more time zone identifiers, which must be String
  // values indicating a Zone or Link name of the IANA Time Zone Database,
  // sorted in descending preference of those in common use in region.
  UErrorCode status = U_ZERO_ERROR;
  std::unique_ptr<icu::StringEnumeration> enumeration(
      icu::TimeZone::createTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL,
                                                 region, nullptr, status));
  if (U_FAILURE(status)) {
    THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
                    JSArray);
  }
663
  return Intl::ToJSArray(isolate, nullptr, enumeration.get(), nullptr, true);
664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691
}

MaybeHandle<JSObject> JSLocale::TextInfo(Isolate* isolate,
                                         Handle<JSLocale> locale) {
  // Let loc be the this value.

  // Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).

  // Let locale be loc.[[Locale]].

  // Assert: locale matches the unicode_locale_id production.

  Factory* factory = isolate->factory();
  // Let info be ! ObjectCreate(%Object.prototype%).
  Handle<JSObject> info = factory->NewJSObject(isolate->object_function());

  // Let dir be "ltr".
  Handle<String> dir = factory->ltr_string();

  // If the default general ordering of characters (characterOrder) within a
  // line in the locale is right-to-left, then
  UErrorCode status = U_ZERO_ERROR;
  ULayoutType orientation = uloc_getCharacterOrientation(
      (locale->icu_locale().raw())->getName(), &status);
  if (U_FAILURE(status)) {
    THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
                    JSObject);
  }
692
  if (orientation == ULOC_LAYOUT_RTL) {
693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729
    // Let dir be "rtl".
    dir = factory->rtl_string();
  }

  // Perform ! CreateDataPropertyOrThrow(info, "direction", dir).
  CHECK(JSReceiver::CreateDataProperty(
            isolate, info, factory->direction_string(), dir, Just(kDontThrow))
            .FromJust());

  // Return info.
  return info;
}

MaybeHandle<JSObject> JSLocale::WeekInfo(Isolate* isolate,
                                         Handle<JSLocale> locale) {
  // Let loc be the this value.

  // Perform ? RequireInternalSlot(loc, [[InitializedLocale]]).

  // Let locale be loc.[[Locale]].

  // Assert: locale matches the unicode_locale_id production.
  Factory* factory = isolate->factory();

  // Let info be ! ObjectCreate(%Object.prototype%).
  Handle<JSObject> info = factory->NewJSObject(isolate->object_function());
  UErrorCode status = U_ZERO_ERROR;
  std::unique_ptr<icu::Calendar> calendar(
      icu::Calendar::createInstance(*(locale->icu_locale().raw()), status));
  if (U_FAILURE(status)) {
    THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
                    JSObject);
  }

  // Let fd be the weekday value indicating which day of the week is considered
  // the 'first' day, for calendar purposes, in the locale.
  int32_t fd = weekdayFromEDaysOfWeek(calendar->getFirstDayOfWeek());
730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747

  // Let wi be ! WeekInfoOfLocale(loc).
  // Let we be ! CreateArrayFromList( wi.[[Weekend]] ).
  Handle<FixedArray> wi = Handle<FixedArray>::cast(factory->NewFixedArray(2));
  int32_t length = 0;
  for (int32_t i = 1; i <= 7; i++) {
    UCalendarDaysOfWeek day =
        (i == 7) ? UCAL_SUNDAY : static_cast<UCalendarDaysOfWeek>(i + 1);
    if (UCAL_WEEKDAY != calendar->getDayOfWeekType(day, status)) {
      wi->set(length++, Smi::FromInt(i));
      CHECK_LE(length, 2);
    }
  }
  if (length != 2) {
    wi = wi->ShrinkOrEmpty(isolate, wi, length);
  }
  Handle<JSArray> we = factory->NewJSArrayWithElements(wi);

748 749 750 751 752 753 754 755 756 757 758 759 760 761 762
  if (U_FAILURE(status)) {
    THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
                    JSObject);
  }

  // Let md be the minimal days required in the first week of a month or year,
  // for calendar purposes, in the locale.
  int32_t md = calendar->getMinimalDaysInFirstWeek();

  // Perform ! CreateDataPropertyOrThrow(info, "firstDay", fd).
  CHECK(JSReceiver::CreateDataProperty(
            isolate, info, factory->firstDay_string(),
            factory->NewNumberFromInt(fd), Just(kDontThrow))
            .FromJust());

763 764 765
  // Perform ! CreateDataPropertyOrThrow(info, "weekend", we).
  CHECK(JSReceiver::CreateDataProperty(isolate, info, factory->weekend_string(),
                                       we, Just(kDontThrow))
766 767 768 769 770 771 772 773 774 775 776 777
            .FromJust());

  // Perform ! CreateDataPropertyOrThrow(info, "minimalDays", md).
  CHECK(JSReceiver::CreateDataProperty(
            isolate, info, factory->minimalDays_string(),
            factory->NewNumberFromInt(md), Just(kDontThrow))
            .FromJust());

  // Return info.
  return info;
}

778 779
Handle<Object> JSLocale::Language(Isolate* isolate, Handle<JSLocale> locale) {
  Factory* factory = isolate->factory();
780
  const char* language = locale->icu_locale().raw()->getLanguage();
781 782
  if (strlen(language) == 0) return factory->undefined_value();
  return factory->NewStringFromAsciiChecked(language);
783 784
}

785 786
Handle<Object> JSLocale::Script(Isolate* isolate, Handle<JSLocale> locale) {
  Factory* factory = isolate->factory();
787
  const char* script = locale->icu_locale().raw()->getScript();
788 789
  if (strlen(script) == 0) return factory->undefined_value();
  return factory->NewStringFromAsciiChecked(script);
790 791
}

792 793
Handle<Object> JSLocale::Region(Isolate* isolate, Handle<JSLocale> locale) {
  Factory* factory = isolate->factory();
794
  const char* region = locale->icu_locale().raw()->getCountry();
795 796 797 798 799 800
  if (strlen(region) == 0) return factory->undefined_value();
  return factory->NewStringFromAsciiChecked(region);
}

Handle<String> JSLocale::BaseName(Isolate* isolate, Handle<JSLocale> locale) {
  icu::Locale icu_locale =
801
      icu::Locale::createFromName(locale->icu_locale().raw()->getBaseName());
802
  std::string base_name = Intl::ToLanguageTag(icu_locale).FromJust();
803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823
  return isolate->factory()->NewStringFromAsciiChecked(base_name.c_str());
}

Handle<Object> JSLocale::Calendar(Isolate* isolate, Handle<JSLocale> locale) {
  return UnicodeKeywordValue(isolate, locale, "ca");
}

Handle<Object> JSLocale::CaseFirst(Isolate* isolate, Handle<JSLocale> locale) {
  return UnicodeKeywordValue(isolate, locale, "kf");
}

Handle<Object> JSLocale::Collation(Isolate* isolate, Handle<JSLocale> locale) {
  return UnicodeKeywordValue(isolate, locale, "co");
}

Handle<Object> JSLocale::HourCycle(Isolate* isolate, Handle<JSLocale> locale) {
  return UnicodeKeywordValue(isolate, locale, "hc");
}

Handle<Object> JSLocale::Numeric(Isolate* isolate, Handle<JSLocale> locale) {
  Factory* factory = isolate->factory();
824
  icu::Locale* icu_locale = locale->icu_locale().raw();
825 826 827 828 829 830 831 832 833 834 835
  UErrorCode status = U_ZERO_ERROR;
  std::string numeric =
      icu_locale->getUnicodeKeywordValue<std::string>("kn", status);
  return (numeric == "true") ? factory->true_value() : factory->false_value();
}

Handle<Object> JSLocale::NumberingSystem(Isolate* isolate,
                                         Handle<JSLocale> locale) {
  return UnicodeKeywordValue(isolate, locale, "nu");
}

836
std::string JSLocale::ToString(Handle<JSLocale> locale) {
837
  icu::Locale* icu_locale = locale->icu_locale().raw();
838 839 840 841 842
  return Intl::ToLanguageTag(*icu_locale).FromJust();
}

Handle<String> JSLocale::ToString(Isolate* isolate, Handle<JSLocale> locale) {
  std::string locale_str = JSLocale::ToString(locale);
843
  return isolate->factory()->NewStringFromAsciiChecked(locale_str.c_str());
844 845
}

846 847
}  // namespace internal
}  // namespace v8