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