[Intl] Add numberingSystem/calendar

Implement ECMA402 PR https://github.com/tc39/ecma402/pull/175
Add numberingSystem option to NumberFormat
And numberingSystem and calendar option to DateTimeFormat


Bug: v8:9154
Change-Id: Ic4e85a232a9ad26c17ee20385f839b0e09a56c77
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1575919
Reviewed-by: Sathya Gunasekaran <gsathya@chromium.org>
Commit-Queue: Frank Tang <ftang@chromium.org>
Cr-Commit-Position: refs/heads/master@{#61061}
This commit is contained in:
Frank Tang 2019-04-19 15:31:21 -07:00 committed by Commit Bot
parent 30eb6e7ed9
commit 411fd9cfd6
10 changed files with 319 additions and 23 deletions

View File

@ -4198,6 +4198,7 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_await_optimization)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_hashbang)
#ifdef V8_INTL_SUPPORT
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_intl_add_calendar_numbering_system)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_intl_bigint)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_intl_datetime_style)
#endif // V8_INTL_SUPPORT

View File

@ -220,8 +220,10 @@ DEFINE_IMPLICATION(harmony_private_methods, harmony_private_fields)
V(harmony_weak_refs, "harmony weak references")
#ifdef V8_INTL_SUPPORT
#define HARMONY_INPROGRESS(V) \
HARMONY_INPROGRESS_BASE(V) \
#define HARMONY_INPROGRESS(V) \
HARMONY_INPROGRESS_BASE(V) \
V(harmony_intl_add_calendar_numbering_system, \
"Add calendar and numberingSystem to DateTimeFormat") \
V(harmony_intl_date_format_range, "DateTimeFormat formatRange")
#else
#define HARMONY_INPROGRESS(V) HARMONY_INPROGRESS_BASE(V)

View File

@ -304,11 +304,11 @@ namespace internal {
"a location, got %") \
T(InvalidArrayBufferLength, "Invalid array buffer length") \
T(ArrayBufferAllocationFailed, "Array buffer allocation failed") \
T(Invalid, "Invalid %s : %") \
T(InvalidArrayLength, "Invalid array length") \
T(InvalidAtomicAccessIndex, "Invalid atomic access index") \
T(InvalidCodePoint, "Invalid code point %") \
T(InvalidCountValue, "Invalid count value") \
T(InvalidCurrencyCode, "Invalid currency code: %") \
T(InvalidDataViewAccessorOffset, \
"Offset is outside the bounds of the DataView") \
T(InvalidDataViewLength, "Invalid DataView length %") \

View File

@ -1448,6 +1448,7 @@ MaybeHandle<JSObject> Intl::SupportedLocalesOf(
}
namespace {
template <typename T>
bool IsValidExtension(const icu::Locale& locale, const char* key,
const std::string& value) {
@ -1468,17 +1469,20 @@ bool IsValidExtension(const icu::Locale& locale, const char* key,
return false;
}
bool IsValidCalendar(const icu::Locale& locale, const std::string& value) {
return IsValidExtension<icu::Calendar>(locale, "calendar", value);
}
bool IsValidCollation(const icu::Locale& locale, const std::string& value) {
std::set<std::string> invalid_values = {"standard", "search"};
if (invalid_values.find(value) != invalid_values.end()) return false;
return IsValidExtension<icu::Collator>(locale, "collation", value);
}
bool IsValidNumberingSystem(const std::string& value) {
} // namespace
bool Intl::IsValidCalendar(const icu::Locale& locale,
const std::string& value) {
return IsValidExtension<icu::Calendar>(locale, "calendar", value);
}
bool Intl::IsValidNumberingSystem(const std::string& value) {
std::set<std::string> invalid_values = {"native", "traditio", "finance"};
if (invalid_values.find(value) != invalid_values.end()) return false;
UErrorCode status = U_ZERO_ERROR;
@ -1487,6 +1491,8 @@ bool IsValidNumberingSystem(const std::string& value) {
return U_SUCCESS(status) && numbering_system.get() != nullptr;
}
namespace {
std::map<std::string, std::string> LookupAndValidateUnicodeExtensions(
icu::Locale* icu_locale, const std::set<std::string>& relevant_keys) {
std::map<std::string, std::string> extensions;
@ -1528,7 +1534,7 @@ std::map<std::string, std::string> LookupAndValidateUnicodeExtensions(
bool is_valid_value = false;
// 8.h.ii.1.a If keyLocaleData contains requestedValue, then
if (strcmp("ca", bcp47_key) == 0) {
is_valid_value = IsValidCalendar(*icu_locale, bcp47_value);
is_valid_value = Intl::IsValidCalendar(*icu_locale, bcp47_value);
} else if (strcmp("co", bcp47_key) == 0) {
is_valid_value = IsValidCollation(*icu_locale, bcp47_value);
} else if (strcmp("hc", bcp47_key) == 0) {
@ -1548,7 +1554,7 @@ std::map<std::string, std::string> LookupAndValidateUnicodeExtensions(
std::set<std::string> valid_values = {"upper", "lower", "false"};
is_valid_value = valid_values.find(bcp47_value) != valid_values.end();
} else if (strcmp("nu", bcp47_key) == 0) {
is_valid_value = IsValidNumberingSystem(bcp47_value);
is_valid_value = Intl::IsValidNumberingSystem(bcp47_value);
}
if (is_valid_value) {
extensions.insert(

View File

@ -249,6 +249,13 @@ class Intl {
V8_WARN_UNUSED_RESULT static Maybe<MatcherOption> GetLocaleMatcher(
Isolate* isolate, Handle<JSReceiver> options, const char* method);
// Check the calendar is valid or not for that locale.
static bool IsValidCalendar(const icu::Locale& locale,
const std::string& value);
// Check the numberingSystem is valid or not.
static bool IsValidNumberingSystem(const std::string& value);
struct ResolvedLocale {
std::string locale;
icu::Locale icu_locale;

View File

@ -1161,6 +1161,7 @@ MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::Initialize(
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
Handle<Object> locales, Handle<Object> input_options) {
date_time_format->set_flags(0);
Factory* factory = isolate->factory();
// 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
Maybe<std::vector<std::string>> maybe_requested_locales =
Intl::CanonicalizeLocaleList(isolate, locales);
@ -1178,6 +1179,49 @@ MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::Initialize(
// 4. Let matcher be ? GetOption(options, "localeMatcher", "string",
// « "lookup", "best fit" », "best fit").
// 5. Set opt.[[localeMatcher]] to matcher.
std::unique_ptr<char[]> calendar_str = nullptr;
std::unique_ptr<char[]> numbering_system_str = nullptr;
if (FLAG_harmony_intl_add_calendar_numbering_system) {
const std::vector<const char*> empty_values = {};
// 6. Let numberingSystem be ? GetOption(options, "calendar",
// "string", undefined, undefined).
Maybe<bool> maybe_calendar =
Intl::GetStringOption(isolate, options, "calendar", empty_values,
"Intl.NumberFormat", &calendar_str);
MAYBE_RETURN(maybe_calendar, MaybeHandle<JSDateTimeFormat>());
if (maybe_calendar.FromJust() && calendar_str != nullptr) {
icu::Locale default_locale;
if (!Intl::IsValidCalendar(default_locale, calendar_str.get())) {
THROW_NEW_ERROR(
isolate,
NewRangeError(
MessageTemplate::kInvalid,
factory->NewStringFromStaticChars("calendar"),
factory->NewStringFromAsciiChecked(calendar_str.get())),
JSDateTimeFormat);
}
}
// 8. Let numberingSystem be ? GetOption(options, "numberingSystem",
// "string", undefined, undefined).
Maybe<bool> maybe_numberingSystem =
Intl::GetStringOption(isolate, options, "numberingSystem", empty_values,
"Intl.NumberFormat", &numbering_system_str);
MAYBE_RETURN(maybe_numberingSystem, MaybeHandle<JSDateTimeFormat>());
if (maybe_numberingSystem.FromJust() && numbering_system_str != nullptr) {
if (!Intl::IsValidNumberingSystem(numbering_system_str.get())) {
THROW_NEW_ERROR(
isolate,
NewRangeError(
MessageTemplate::kInvalid,
factory->NewStringFromStaticChars("numberingSystem"),
factory->NewStringFromAsciiChecked(numbering_system_str.get())),
JSDateTimeFormat);
}
}
}
Maybe<Intl::MatcherOption> maybe_locale_matcher =
Intl::GetLocaleMatcher(isolate, options, "Intl.DateTimeFormat");
MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSDateTimeFormat>());
@ -1221,6 +1265,17 @@ MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::Initialize(
icu::Locale icu_locale = r.icu_locale;
DCHECK(!icu_locale.isBogus());
UErrorCode status = U_ZERO_ERROR;
if (calendar_str != nullptr) {
icu_locale.setUnicodeKeywordValue("ca", calendar_str.get(), status);
CHECK(U_SUCCESS(status));
}
if (numbering_system_str != nullptr) {
icu_locale.setUnicodeKeywordValue("nu", numbering_system_str.get(), status);
CHECK(U_SUCCESS(status));
}
// 17. Let timeZone be ? Get(options, "timeZone").
const std::vector<const char*> empty_values;
std::unique_ptr<char[]> timezone = nullptr;
@ -1231,11 +1286,11 @@ MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::Initialize(
std::unique_ptr<icu::TimeZone> tz = CreateTimeZone(isolate, timezone.get());
if (tz.get() == nullptr) {
THROW_NEW_ERROR(isolate,
NewRangeError(MessageTemplate::kInvalidTimeZone,
isolate->factory()->NewStringFromAsciiChecked(
timezone.get())),
JSDateTimeFormat);
THROW_NEW_ERROR(
isolate,
NewRangeError(MessageTemplate::kInvalidTimeZone,
factory->NewStringFromAsciiChecked(timezone.get())),
JSDateTimeFormat);
}
std::unique_ptr<icu::Calendar> calendar(
@ -1244,11 +1299,11 @@ MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::Initialize(
// 18.b If the result of IsValidTimeZoneName(timeZone) is false, then
// i. Throw a RangeError exception.
if (calendar.get() == nullptr) {
THROW_NEW_ERROR(isolate,
NewRangeError(MessageTemplate::kInvalidTimeZone,
isolate->factory()->NewStringFromAsciiChecked(
timezone.get())),
JSDateTimeFormat);
THROW_NEW_ERROR(
isolate,
NewRangeError(MessageTemplate::kInvalidTimeZone,
factory->NewStringFromAsciiChecked(timezone.get())),
JSDateTimeFormat);
}
static base::LazyInstance<DateTimePatternGeneratorCache>::type
@ -1258,7 +1313,6 @@ MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::Initialize(
generator_cache.Pointer()->CreateGenerator(icu_locale));
// 15.Let hcDefault be dataLocaleData.[[hourCycle]].
UErrorCode status = U_ZERO_ERROR;
icu::UnicodeString hour_pattern = generator->getBestPattern("jjmm", status);
CHECK(U_SUCCESS(status));
Intl::HourCycle hc_default = HourCycleFromPattern(hour_pattern);

View File

@ -252,6 +252,28 @@ MaybeHandle<JSNumberFormat> JSNumberFormat::Initialize(
MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSNumberFormat>());
Intl::MatcherOption matcher = maybe_locale_matcher.FromJust();
std::unique_ptr<char[]> numbering_system_str = nullptr;
if (FLAG_harmony_intl_add_calendar_numbering_system) {
// 7. Let numberingSystem be ? GetOption(options, "numberingSystem",
// "string", undefined, undefined).
const std::vector<const char*> empty_values = {};
Maybe<bool> maybe_numberingSystem =
Intl::GetStringOption(isolate, options, "numberingSystem", empty_values,
"Intl.NumberFormat", &numbering_system_str);
MAYBE_RETURN(maybe_numberingSystem, MaybeHandle<JSNumberFormat>());
if (maybe_numberingSystem.FromJust() && numbering_system_str != nullptr) {
if (!Intl::IsValidNumberingSystem(numbering_system_str.get())) {
THROW_NEW_ERROR(
isolate,
NewRangeError(
MessageTemplate::kInvalid,
factory->NewStringFromStaticChars("numberingSystem"),
factory->NewStringFromAsciiChecked(numbering_system_str.get())),
JSNumberFormat);
}
}
}
// 7. Let localeData be %NumberFormat%.[[LocaleData]].
// 8. Let r be ResolveLocale(%NumberFormat%.[[AvailableLocales]],
// requestedLocales, opt, %NumberFormat%.[[RelevantExtensionKeys]],
@ -261,6 +283,14 @@ MaybeHandle<JSNumberFormat> JSNumberFormat::Initialize(
Intl::ResolveLocale(isolate, JSNumberFormat::GetAvailableLocales(),
requested_locales, matcher, relevant_extension_keys);
UErrorCode status = U_ZERO_ERROR;
if (numbering_system_str != nullptr) {
r.icu_locale.setUnicodeKeywordValue("nu", numbering_system_str.get(),
status);
CHECK(U_SUCCESS(status));
r.locale = Intl::ToLanguageTag(r.icu_locale).FromJust();
}
// 9. Set numberFormat.[[Locale]] to r.[[locale]].
Handle<String> locale_str =
isolate->factory()->NewStringFromAsciiChecked(r.locale.c_str());
@ -298,7 +328,8 @@ MaybeHandle<JSNumberFormat> JSNumberFormat::Initialize(
if (!IsWellFormedCurrencyCode(currency)) {
THROW_NEW_ERROR(
isolate,
NewRangeError(MessageTemplate::kInvalidCurrencyCode,
NewRangeError(MessageTemplate::kInvalid,
factory->NewStringFromStaticChars("currency code"),
factory->NewStringFromAsciiChecked(currency.c_str())),
JSNumberFormat);
}
@ -335,7 +366,6 @@ MaybeHandle<JSNumberFormat> JSNumberFormat::Initialize(
CurrencyDisplay currency_display = maybe_currencyDisplay.FromJust();
UNumberFormatStyle format_style = ToNumberFormatStyle(currency_display);
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<icu::NumberFormat> icu_number_format;
icu::Locale no_extension_locale(r.icu_locale.getBaseName());
if (style == Style::DECIMAL) {

View File

@ -0,0 +1,60 @@
// Copyright 2019 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.
// Flags: --harmony-intl-add-calendar-numbering-system
let invalidCalendar = [
"invalid",
"abce",
];
// https://www.unicode.org/repos/cldr/tags/latest/common/bcp47/calendar.xml
let validCalendar= [
"buddhist",
"chinese",
"coptic",
"dangi",
"ethioaa",
"ethiopic",
"gregory",
"hebrew",
"indian",
"islamic",
"islamic-umalqura",
"islamic-tbla",
"islamic-civil",
"islamic-rgsa",
"iso8601",
"japanese",
"persian",
"roc",
];
let locales = [
"en",
"ar",
];
invalidCalendar.forEach(function(calendar) {
assertThrows(
() => new Intl.DateTimeFormat(["en"], {calendar}),
RangeError);
}
);
let value = new Date();
validCalendar.forEach(function(calendar) {
locales.forEach(function(base) {
let l = base + "-u-ca-" + calendar;
let dtf = new Intl.DateTimeFormat([base], {calendar});
assertEquals(l, dtf.resolvedOptions().locale);
// Test the formatting result is the same as passing in via u-ca-
// in the locale.
let dtf2 = new Intl.DateTimeFormat([l]);
assertEquals(dtf2.format(value), dtf.format(value));
});
}
);

View File

@ -0,0 +1,68 @@
// Copyright 2019 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.
// Flags: --harmony-intl-add-calendar-numbering-system
let invalidNumberingSystem = [
"invalid",
"abce",
"finance",
"native",
"traditio",
];
// https://tc39.github.io/ecma402/#table-numbering-system-digits
let validNumberingSystem= [
"arab",
"arabext",
"bali",
"beng",
"deva",
"fullwide",
"gujr",
"guru",
"hanidec",
"khmr",
"knda",
"laoo",
"latn",
"limb",
"mlym",
"mong",
"mymr",
"orya",
"tamldec",
"telu",
"thai",
"tibt",
];
let locales = [
"en",
"ar",
];
invalidNumberingSystem.forEach(function(numberingSystem) {
assertThrows(
() => new Intl.DateTimeFormat(["en"], {numberingSystem}),
RangeError);
}
);
let value = new Date();
validNumberingSystem.forEach(function(numberingSystem) {
locales.forEach(function(base) {
let l = base + "-u-nu-" + numberingSystem;
let dtf = new Intl.DateTimeFormat([base], {numberingSystem});
assertEquals(l, dtf.resolvedOptions().locale);
assertEquals(numberingSystem, dtf.resolvedOptions().numberingSystem);
// Test the formatting result is the same as passing in via u-nu-
// in the locale.
let dtf2 = new Intl.DateTimeFormat([l]);
assertEquals(dtf2.format(value), dtf.format(value));
});
}
);

View File

@ -0,0 +1,68 @@
// Copyright 2019 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.
// Flags: --harmony-intl-add-calendar-numbering-system
let invalidNumberingSystem = [
"invalid",
"abce",
"finance",
"native",
"traditio",
];
// https://tc39.github.io/ecma402/#table-numbering-system-digits
let validNumberingSystem= [
"arab",
"arabext",
"bali",
"beng",
"deva",
"fullwide",
"gujr",
"guru",
"hanidec",
"khmr",
"knda",
"laoo",
"latn",
"limb",
"mlym",
"mong",
"mymr",
"orya",
"tamldec",
"telu",
"thai",
"tibt",
];
let locales = [
"en",
"ar",
];
invalidNumberingSystem.forEach(function(numberingSystem) {
assertThrows(
() => new Intl.NumberFormat(["en"], {numberingSystem}),
RangeError);
}
);
let value = 1234567.89;
validNumberingSystem.forEach(function(numberingSystem) {
locales.forEach(function(base) {
let l = base + "-u-nu-" + numberingSystem;
let nf = new Intl.NumberFormat([base], {numberingSystem});
assertEquals(l, nf.resolvedOptions().locale);
assertEquals(numberingSystem, nf.resolvedOptions().numberingSystem);
// Test the formatting result is the same as passing in via u-nu-
// in the locale.
let nf2 = new Intl.NumberFormat([l]);
assertEquals(nf2.format(value), nf.format(value));
});
}
);