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", ®ion_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