[Intl] Move toLDMLString & canonicalizeTimeZoneID js->C++

Bug: v8:8066
Cq-Include-Trybots: luci.v8.try:v8_linux_noi18n_rel_ng;luci.chromium.try:linux_chromium_rel_ng
Change-Id: I74fe09bb6bb4428d57c66811b77f5f35144f717f
Reviewed-on: https://chromium-review.googlesource.com/1186153
Commit-Queue: Frank Tang <ftang@chromium.org>
Reviewed-by: Jungshik Shin <jshin@chromium.org>
Reviewed-by: Sathya Gunasekaran <gsathya@chromium.org>
Reviewed-by: Adam Klein <adamk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#55625}
This commit is contained in:
Frank Tang 2018-09-04 14:48:12 -07:00 committed by Commit Bot
parent e9e583ec59
commit d7ae63e6f2
10 changed files with 325 additions and 295 deletions

View File

@ -125,42 +125,6 @@ function GetServiceRE() {
return SERVICE_RE;
}
/**
* Matches valid IANA time zone names.
*/
var TIMEZONE_NAME_CHECK_RE = UNDEFINED;
var GMT_OFFSET_TIMEZONE_NAME_CHECK_RE = UNDEFINED;
function GetTimezoneNameCheckRE() {
if (IS_UNDEFINED(TIMEZONE_NAME_CHECK_RE)) {
TIMEZONE_NAME_CHECK_RE = new GlobalRegExp(
'^([A-Za-z]+)/([A-Za-z_-]+)((?:\/[A-Za-z_-]+)+)*$');
}
return TIMEZONE_NAME_CHECK_RE;
}
function GetGMTOffsetTimezoneNameCheckRE() {
if (IS_UNDEFINED(GMT_OFFSET_TIMEZONE_NAME_CHECK_RE)) {
GMT_OFFSET_TIMEZONE_NAME_CHECK_RE = new GlobalRegExp(
'^(?:ETC/GMT)(?<offset>0|[+-](?:[0-9]|1[0-4]))$');
}
return GMT_OFFSET_TIMEZONE_NAME_CHECK_RE;
}
/**
* Matches valid location parts of IANA time zone names.
*/
var TIMEZONE_NAME_LOCATION_PART_RE = UNDEFINED;
function GetTimezoneNameLocationPartRE() {
if (IS_UNDEFINED(TIMEZONE_NAME_LOCATION_PART_RE)) {
TIMEZONE_NAME_LOCATION_PART_RE =
new GlobalRegExp('^([A-Za-z]+)((?:[_-][A-Za-z]+)+)*$');
}
return TIMEZONE_NAME_LOCATION_PART_RE;
}
/**
* Returns a getOption function that extracts property value for given
* options object. If property is missing it returns defaultValue. If value
@ -468,43 +432,6 @@ function defineWECProperty(object, property, value) {
configurable: true});
}
/**
* Returns titlecased word, aMeRricA -> America.
*/
function toTitleCaseWord(word) {
return %StringToUpperCaseIntl(%_Call(StringSubstr, word, 0, 1)) +
%StringToLowerCaseIntl(%_Call(StringSubstr, word, 1));
}
/**
* Returns titlecased location, bueNos_airES -> Buenos_Aires
* or ho_cHi_minH -> Ho_Chi_Minh. It is locale-agnostic and only
* deals with ASCII only characters.
* 'of', 'au' and 'es' are special-cased and lowercased.
*/
function toTitleCaseTimezoneLocation(location) {
var match = %regexp_internal_match(GetTimezoneNameLocationPartRE(), location)
if (IS_NULL(match)) throw %make_range_error(kExpectedLocation, location);
var result = toTitleCaseWord(match[1]);
if (!IS_UNDEFINED(match[2]) && 2 < match.length) {
// The first character is a separator, '_' or '-'.
// None of IANA zone names has both '_' and '-'.
var separator = %_Call(StringSubstring, match[2], 0, 1);
var parts = %StringSplit(match[2], separator, kMaxUint32);
for (var i = 1; i < parts.length; i++) {
var part = parts[i]
var lowercasedPart = %StringToLowerCaseIntl(part);
result = result + separator +
((lowercasedPart !== 'es' &&
lowercasedPart !== 'of' && lowercasedPart !== 'au') ?
toTitleCaseWord(part) : lowercasedPart);
}
}
return result;
}
/**
* Returns an InternalArray where all locales are canonicalized and duplicates
* removed.
@ -594,69 +521,6 @@ DEFINE_METHOD(
}
);
/**
* Returns a string that matches LDML representation of the options object.
*/
function toLDMLString(options) {
var getOption = getGetOption(options, 'dateformat');
var ldmlString = '';
var option = getOption('weekday', 'string', ['narrow', 'short', 'long']);
ldmlString += appendToLDMLString(
option, {narrow: 'EEEEE', short: 'EEE', long: 'EEEE'});
option = getOption('era', 'string', ['narrow', 'short', 'long']);
ldmlString += appendToLDMLString(
option, {narrow: 'GGGGG', short: 'GGG', long: 'GGGG'});
option = getOption('year', 'string', ['2-digit', 'numeric']);
ldmlString += appendToLDMLString(option, {'2-digit': 'yy', 'numeric': 'y'});
option = getOption('month', 'string',
['2-digit', 'numeric', 'narrow', 'short', 'long']);
ldmlString += appendToLDMLString(option, {'2-digit': 'MM', 'numeric': 'M',
'narrow': 'MMMMM', 'short': 'MMM', 'long': 'MMMM'});
option = getOption('day', 'string', ['2-digit', 'numeric']);
ldmlString += appendToLDMLString(
option, {'2-digit': 'dd', 'numeric': 'd'});
var hr12 = getOption('hour12', 'boolean');
option = getOption('hour', 'string', ['2-digit', 'numeric']);
if (IS_UNDEFINED(hr12)) {
ldmlString += appendToLDMLString(option, {'2-digit': 'jj', 'numeric': 'j'});
} else if (hr12 === true) {
ldmlString += appendToLDMLString(option, {'2-digit': 'hh', 'numeric': 'h'});
} else {
ldmlString += appendToLDMLString(option, {'2-digit': 'HH', 'numeric': 'H'});
}
option = getOption('minute', 'string', ['2-digit', 'numeric']);
ldmlString += appendToLDMLString(option, {'2-digit': 'mm', 'numeric': 'm'});
option = getOption('second', 'string', ['2-digit', 'numeric']);
ldmlString += appendToLDMLString(option, {'2-digit': 'ss', 'numeric': 's'});
option = getOption('timeZoneName', 'string', ['short', 'long']);
ldmlString += appendToLDMLString(option, {short: 'z', long: 'zzzz'});
return ldmlString;
}
/**
* Returns either LDML equivalent of the current option or empty string.
*/
function appendToLDMLString(option, pairs) {
if (!IS_UNDEFINED(option)) {
return pairs[option];
} else {
return '';
}
}
/**
* Initializes the given object so it's a valid DateTimeFormat instance.
* Useful for subclassing.
@ -677,14 +541,6 @@ function CreateDateTimeFormat(locales, options) {
var matcher = getOption('formatMatcher', 'string',
['basic', 'best fit'], 'best fit');
// Build LDML string for the skeleton that we pass to the formatter.
var ldmlString = toLDMLString(options);
// Filter out supported extension keys so we know what to put in resolved
// section later on.
// We need to pass calendar and number system to the method.
var tz = canonicalizeTimeZoneID(options.timeZone);
// ICU prefers options to be passed using -u- extension key/values, so
// we need to build that.
var internalOptions = {__proto__: null};
@ -708,9 +564,7 @@ function CreateDateTimeFormat(locales, options) {
// to JSDateTimeFormat
var resolved = {__proto__: null};
var dateFormat = %CreateDateTimeFormat(
requestedLocale,
{__proto__: null, skeleton: ldmlString, timeZone: tz}, resolved);
var dateFormat = %CreateDateTimeFormat(requestedLocale, options, resolved);
%MarkAsInitializedIntlObjectOfType(dateFormat, DATE_TIME_FORMAT_TYPE);
@ -744,58 +598,6 @@ DEFINE_METHOD(
}
);
/**
* Returns canonical Area/Location(/Location) name, or throws an exception
* if the zone name is invalid IANA name.
*/
function canonicalizeTimeZoneID(tzID) {
// Skip undefined zones.
if (IS_UNDEFINED(tzID)) {
return tzID;
}
// Convert zone name to string.
tzID = TO_STRING(tzID);
// Special case handling (UTC, GMT).
var upperID = %StringToUpperCaseIntl(tzID);
if (upperID === 'UTC' || upperID === 'GMT' ||
upperID === 'ETC/UTC' || upperID === 'ETC/GMT') {
return 'UTC';
}
// We expect only _, '-' and / beside ASCII letters.
// All inputs should conform to Area/Location(/Location)*, or Etc/GMT* .
// TODO(jshin): 1. Support 'GB-Eire", 'EST5EDT", "ROK', 'US/*', 'NZ' and many
// other aliases/linked names when moving timezone validation code to C++.
// See crbug.com/364374 and crbug.com/v8/8007 .
// 2. Resolve the difference betwee CLDR/ICU and IANA time zone db.
// See http://unicode.org/cldr/trac/ticket/9892 and crbug.com/645807 .
let match = %regexp_internal_match(GetTimezoneNameCheckRE(), tzID);
if (IS_NULL(match)) {
let match =
%regexp_internal_match(GetGMTOffsetTimezoneNameCheckRE(), upperID);
if (!IS_NULL(match) && match.length == 2)
return "Etc/GMT" + match.groups.offset;
else
throw %make_range_error(kInvalidTimeZone, tzID);
}
let result = toTitleCaseTimezoneLocation(match[1]) + '/' +
toTitleCaseTimezoneLocation(match[2]);
if (!IS_UNDEFINED(match[3]) && 3 < match.length) {
let locations = %StringSplit(match[3], '/', kMaxUint32);
// The 1st element is empty. Starts with i=1.
for (var i = 1; i < locations.length; i++) {
result = result + '/' + toTitleCaseTimezoneLocation(locations[i]);
}
}
return result;
}
/**
* Initializes the given object so it's a valid BreakIterator instance.
* Useful for subclassing.

View File

@ -21,6 +21,7 @@
#include "src/isolate.h"
#include "src/objects-inl.h"
#include "src/objects/js-collator-inl.h"
#include "src/objects/js-date-time-format-inl.h"
#include "src/objects/js-number-format-inl.h"
#include "src/objects/managed.h"
#include "src/objects/string.h"
@ -89,39 +90,62 @@ bool ExtractStringSetting(Isolate* isolate, Handle<JSObject> options,
return false;
}
icu::SimpleDateFormat* CreateICUDateFormat(Isolate* isolate,
const icu::Locale& icu_locale,
Handle<JSObject> options) {
// ecma-402/#sec-isvalidtimezonename
bool IsValidTimeZoneName(const icu::TimeZone& tz) {
UErrorCode status = U_ZERO_ERROR;
icu::UnicodeString id;
tz.getID(id);
icu::UnicodeString canonical;
icu::TimeZone::getCanonicalID(id, canonical, status);
return U_SUCCESS(status) &&
canonical != icu::UnicodeString("Etc/Unknown", -1, US_INV);
}
std::unique_ptr<icu::TimeZone> CreateTimeZone(Isolate* isolate,
const char* timezone) {
// Create time zone as specified by the user. We have to re-create time zone
// since calendar takes ownership.
icu::TimeZone* tz = nullptr;
icu::UnicodeString timezone;
if (ExtractStringSetting(isolate, options, "timeZone", &timezone)) {
tz = icu::TimeZone::createTimeZone(timezone);
} else {
tz = icu::TimeZone::createDefault();
if (timezone == nullptr) {
return std::unique_ptr<icu::TimeZone>(icu::TimeZone::createDefault());
}
std::string canonicalized =
JSDateTimeFormat::CanonicalizeTimeZoneID(isolate, timezone);
if (canonicalized.empty()) return std::unique_ptr<icu::TimeZone>();
std::unique_ptr<icu::TimeZone> tz(
icu::TimeZone::createTimeZone(canonicalized.c_str()));
if (!IsValidTimeZoneName(*tz)) return std::unique_ptr<icu::TimeZone>();
return tz;
}
std::unique_ptr<icu::Calendar> CreateCalendar(Isolate* isolate,
const icu::Locale& icu_locale,
const char* timezone) {
std::unique_ptr<icu::TimeZone> tz = CreateTimeZone(isolate, timezone);
if (tz.get() == nullptr) return std::unique_ptr<icu::Calendar>();
// Create a calendar using locale, and apply time zone to it.
UErrorCode status = U_ZERO_ERROR;
icu::Calendar* calendar =
icu::Calendar::createInstance(tz, icu_locale, status);
std::unique_ptr<icu::Calendar> calendar(
icu::Calendar::createInstance(tz.release(), icu_locale, status));
CHECK(U_SUCCESS(status));
CHECK_NOT_NULL(calendar.get());
if (calendar->getDynamicClassID() ==
icu::GregorianCalendar::getStaticClassID()) {
icu::GregorianCalendar* gc = (icu::GregorianCalendar*)calendar;
icu::GregorianCalendar* gc =
static_cast<icu::GregorianCalendar*>(calendar.get());
UErrorCode status = U_ZERO_ERROR;
// The beginning of ECMAScript time, namely -(2**53)
const double start_of_time = -9007199254740992;
gc->setGregorianChange(start_of_time, status);
DCHECK(U_SUCCESS(status));
}
return calendar;
}
// Make formatter from skeleton. Calendar and numbering system are added
// to the locale as Unicode extension (if they were specified at all).
icu::SimpleDateFormat* date_format = nullptr;
icu::UnicodeString skeleton;
if (ExtractStringSetting(isolate, options, "skeleton", &skeleton)) {
std::unique_ptr<icu::SimpleDateFormat> CreateICUDateFormat(
Isolate* isolate, const icu::Locale& icu_locale,
const std::string skeleton) {
// See https://github.com/tc39/ecma402/issues/225 . The best pattern
// generation needs to be done in the base locale according to the
// current spec however odd it may be. See also crbug.com/826549 .
@ -133,25 +157,23 @@ icu::SimpleDateFormat* CreateICUDateFormat(Isolate* isolate,
// do with 'related year' part when 'chinese/dangi' calendar is specified
// has to be discussed. Revisit once the spec is clarified/revised.
icu::Locale no_extension_locale(icu_locale.getBaseName());
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<icu::DateTimePatternGenerator> generator(
icu::DateTimePatternGenerator::createInstance(no_extension_locale,
status));
icu::UnicodeString pattern;
if (U_SUCCESS(status))
pattern = generator->getBestPattern(skeleton, status);
date_format = new icu::SimpleDateFormat(pattern, icu_locale, status);
if (U_SUCCESS(status)) {
date_format->adoptCalendar(calendar);
}
pattern =
generator->getBestPattern(icu::UnicodeString(skeleton.c_str()), status);
}
if (U_FAILURE(status)) {
delete calendar;
delete date_format;
date_format = nullptr;
}
// Make formatter from skeleton. Calendar and numbering system are added
// to the locale as Unicode extension (if they were specified at all).
std::unique_ptr<icu::SimpleDateFormat> date_format(
new icu::SimpleDateFormat(pattern, icu_locale, status));
if (U_FAILURE(status)) return std::unique_ptr<icu::SimpleDateFormat>();
CHECK_NOT_NULL(date_format.get());
return date_format;
}
@ -303,48 +325,49 @@ icu::Locale Intl::CreateICULocale(Isolate* isolate,
return icu_locale;
}
bool DateFormat::IsValidTimeZone(icu::SimpleDateFormat* date_format) {
UErrorCode status = U_ZERO_ERROR;
// Set time zone and calendar.
const icu::Calendar* calendar = date_format->getCalendar();
const icu::TimeZone& tz = calendar->getTimeZone();
icu::UnicodeString time_zone;
tz.getID(time_zone);
icu::UnicodeString canonical_time_zone;
icu::TimeZone::getCanonicalID(time_zone, canonical_time_zone, status);
std::string timezone_str;
canonical_time_zone.toUTF8String(timezone_str);
if (U_SUCCESS(status)) return timezone_str != "Etc/Unknown";
return true;
}
// static
icu::SimpleDateFormat* DateFormat::InitializeDateTimeFormat(
Maybe<icu::SimpleDateFormat*> DateFormat::InitializeDateTimeFormat(
Isolate* isolate, Handle<String> locale, Handle<JSObject> options,
Handle<JSObject> resolved) {
icu::Locale icu_locale = Intl::CreateICULocale(isolate, locale);
DCHECK(!icu_locale.isBogus());
icu::SimpleDateFormat* date_format =
CreateICUDateFormat(isolate, icu_locale, options);
if (!date_format) {
// Remove extensions and try again.
icu::Locale no_extension_locale(icu_locale.getBaseName());
date_format = CreateICUDateFormat(isolate, no_extension_locale, options);
static std::vector<const char*> empty_values = {};
std::unique_ptr<char[]> timezone = nullptr;
Maybe<bool> maybe_timezone =
Intl::GetStringOption(isolate, options, "timeZone", empty_values,
"Intl.DateTimeFormat", &timezone);
MAYBE_RETURN(maybe_timezone, Nothing<icu::SimpleDateFormat*>());
if (!date_format) {
Maybe<std::string> maybe_skeleton =
JSDateTimeFormat::OptionsToSkeleton(isolate, options);
MAYBE_RETURN(maybe_skeleton, Nothing<icu::SimpleDateFormat*>());
CHECK(!maybe_skeleton.FromJust().empty());
std::string skeleton = maybe_skeleton.FromJust();
std::unique_ptr<icu::Calendar> calendar(
CreateCalendar(isolate, icu_locale, timezone.get()));
if (calendar.get() == nullptr) {
THROW_NEW_ERROR_RETURN_VALUE(
isolate,
NewRangeError(
MessageTemplate::kInvalidTimeZone,
isolate->factory()->NewStringFromAsciiChecked(timezone.get())),
Nothing<icu::SimpleDateFormat*>());
}
std::unique_ptr<icu::SimpleDateFormat> date_format(
CreateICUDateFormat(isolate, icu_locale, skeleton));
if (date_format.get() == nullptr) {
// Remove extensions and try again.
icu_locale = icu::Locale(icu_locale.getBaseName());
date_format = CreateICUDateFormat(isolate, icu_locale, skeleton);
if (date_format.get() == nullptr) {
FATAL("Failed to create ICU date format, are ICU data files missing?");
}
// Set resolved settings (pattern, numbering system, calendar).
SetResolvedDateSettings(isolate, no_extension_locale, date_format,
resolved);
} else {
SetResolvedDateSettings(isolate, icu_locale, date_format, resolved);
}
CHECK_NOT_NULL(date_format);
return date_format;
date_format->adoptCalendar(calendar.release());
SetResolvedDateSettings(isolate, icu_locale, date_format.get(), resolved);
return Just(date_format.release());
}
icu::SimpleDateFormat* DateFormat::UnpackDateFormat(Handle<JSObject> obj) {

View File

@ -21,10 +21,7 @@
namespace U_ICU_NAMESPACE {
class BreakIterator;
class Collator;
class DecimalFormat;
class NumberFormat;
class PluralRules;
class SimpleDateFormat;
class UnicodeString;
}
@ -39,16 +36,13 @@ class DateFormat {
public:
// Create a formatter for the specificied locale and options. Returns the
// resolved settings for the locale / options.
static icu::SimpleDateFormat* InitializeDateTimeFormat(
static Maybe<icu::SimpleDateFormat*> InitializeDateTimeFormat(
Isolate* isolate, Handle<String> locale, Handle<JSObject> options,
Handle<JSObject> resolved);
// Unpacks date format object from corresponding JavaScript object.
static icu::SimpleDateFormat* UnpackDateFormat(Handle<JSObject> obj);
// Determine the TimeZone is valid.
static bool IsValidTimeZone(icu::SimpleDateFormat* date_format);
// Release memory we allocated for the DateFormat once the JS object that
// holds the pointer gets garbage collected.
static void DeleteDateFormat(const v8::WeakCallbackInfo<void>& data);

View File

@ -18,6 +18,10 @@
// Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h"
namespace U_ICU_NAMESPACE {
class Collator;
} // namespace U_ICU_NAMESPACE
namespace v8 {
namespace internal {

View File

@ -95,19 +95,76 @@ static const std::vector<PatternItem>& GetPatternItems() {
return kPatternItems;
}
class PatternData {
public:
PatternData(const std::string property, std::vector<PatternMap> pairs,
std::vector<const char*>* allowed_values)
: property(property), allowed_values(allowed_values) {
for (const auto& pair : pairs) {
map.insert(std::make_pair(pair.value, pair.pattern));
}
}
virtual ~PatternData() {}
const std::string property;
std::map<const std::string, const std::string> map;
std::vector<const char*>* allowed_values;
};
enum HourOption {
H_UNKNOWN,
H_12,
H_24,
};
static const std::vector<PatternData> CreateCommonData() {
std::vector<PatternData> build;
for (const auto& item : GetPatternItems()) {
if (item.property != "hour") {
build.push_back(
PatternData(item.property, item.pairs, item.allowed_values));
}
}
return build;
}
static const std::vector<PatternData> CreateData(const char* digit2,
const char* numeric) {
static std::vector<const char*> k2DigitNumeric = {"2-digit", "numeric"};
static const std::vector<PatternData> common = CreateCommonData();
std::vector<PatternData> build(common);
build.push_back(PatternData(
"hour", {{digit2, "2-digit"}, {numeric, "numeric"}}, &k2DigitNumeric));
return build;
}
static const std::vector<PatternData>& GetPatternData(HourOption option) {
static const std::vector<PatternData> data = CreateData("jj", "j");
static const std::vector<PatternData> data_h12 = CreateData("hh", "h");
static const std::vector<PatternData> data_h24 = CreateData("HH", "H");
switch (option) {
case HourOption::H_12:
return data_h12;
case HourOption::H_24:
return data_h24;
case HourOption::H_UNKNOWN:
return data;
}
}
void SetPropertyFromPattern(Isolate* isolate, const std::string& pattern,
Handle<JSObject> options) {
Factory* factory = isolate->factory();
const std::vector<PatternItem>& items = GetPatternItems();
for (auto item = items.cbegin(); item != items.cend(); ++item) {
for (auto pair = item->pairs.cbegin(); pair != item->pairs.cend(); ++pair) {
if (pattern.find(pair->pattern) != std::string::npos) {
for (const auto& item : items) {
for (const auto& pair : item.pairs) {
if (pattern.find(pair.pattern) != std::string::npos) {
// After we find the first pair in the item which matching the pattern,
// we set the property and look for the next item in kPatternItems.
CHECK(JSReceiver::CreateDataProperty(
isolate, options,
factory->NewStringFromAsciiChecked(item->property.c_str()),
factory->NewStringFromAsciiChecked(pair->value.c_str()),
factory->NewStringFromAsciiChecked(item.property.c_str()),
factory->NewStringFromAsciiChecked(pair.value.c_str()),
kDontThrow)
.FromJust());
break;
@ -133,8 +190,103 @@ void SetPropertyFromPattern(Isolate* isolate, const std::string& pattern,
}
}
std::string GetGMTTzID(Isolate* isolate, const std::string& input) {
std::string ret = "Etc/GMT";
switch (input.length()) {
case 8:
if (input[7] == '0') return ret + '0';
break;
case 9:
if ((input[7] == '+' || input[7] == '-') &&
IsInRange(input[8], '0', '9')) {
return ret + input[7] + input[8];
}
break;
case 10:
if ((input[7] == '+' || input[7] == '-') && (input[8] == '1') &&
IsInRange(input[9], '0', '4')) {
return ret + input[7] + input[8] + input[9];
}
break;
}
return "";
}
// Locale independenty version of isalpha for ascii range. This will return
// false if the ch is alpha but not in ascii range.
bool IsAsciiAlpha(char ch) {
return IsInRange(ch, 'A', 'Z') || IsInRange(ch, 'a', 'z');
}
// Locale independent toupper for ascii range. This will not return İ (dotted I)
// for i under Turkish locale while std::toupper may.
char LocaleIndependentAsciiToUpper(char ch) {
return (IsInRange(ch, 'a', 'z')) ? (ch - 'a' + 'A') : ch;
}
// Locale independent tolower for ascii range.
char LocaleIndependentAsciiToLower(char ch) {
return (IsInRange(ch, 'A', 'Z')) ? (ch - 'A' + 'a') : ch;
}
// Returns titlecased location, bueNos_airES -> Buenos_Aires
// or ho_cHi_minH -> Ho_Chi_Minh. It is locale-agnostic and only
// deals with ASCII only characters.
// 'of', 'au' and 'es' are special-cased and lowercased.
// ICU's timezone parsing is case sensitive, but ECMAScript is case insensitive
std::string ToTitleCaseTimezoneLocation(Isolate* isolate,
const std::string& input) {
std::string title_cased;
int word_length = 0;
for (char ch : input) {
// Convert first char to upper case, the rest to lower case
if (IsAsciiAlpha(ch)) {
title_cased += word_length == 0 ? LocaleIndependentAsciiToUpper(ch)
: LocaleIndependentAsciiToLower(ch);
word_length++;
} else if (ch == '_' || ch == '-' || ch == '/') {
// Special case Au/Es/Of to be lower case.
if (word_length == 2) {
size_t pos = title_cased.length() - 2;
std::string substr = title_cased.substr(pos, 2);
if (substr == "Of" || substr == "Es" || substr == "Au") {
title_cased[pos] = LocaleIndependentAsciiToLower(title_cased[pos]);
}
}
title_cased += ch;
word_length = 0;
} else {
// Invalid input
return std::string();
}
}
return title_cased;
}
} // namespace
std::string JSDateTimeFormat::CanonicalizeTimeZoneID(Isolate* isolate,
const std::string& input) {
std::string upper = input;
transform(upper.begin(), upper.end(), upper.begin(),
LocaleIndependentAsciiToUpper);
if (upper == "UTC" || upper == "GMT" || upper == "ETC/UTC" ||
upper == "ETC/GMT") {
return "UTC";
}
// We expect only _, '-' and / beside ASCII letters.
// All inputs should conform to Area/Location(/Location)*, or Etc/GMT* .
// TODO(jshin): 1. Support 'GB-Eire", 'EST5EDT", "ROK', 'US/*', 'NZ' and many
// other aliases/linked names when moving timezone validation code to C++.
// See crbug.com/364374 and crbug.com/v8/8007 .
// 2. Resolve the difference betwee CLDR/ICU and IANA time zone db.
// See http://unicode.org/cldr/trac/ticket/9892 and crbug.com/645807 .
if (strncmp(upper.c_str(), "ETC/GMT", 7) == 0) {
return GetGMTTzID(isolate, input);
}
return ToTitleCaseTimezoneLocation(isolate, input);
}
MaybeHandle<JSObject> JSDateTimeFormat::ResolvedOptions(
Isolate* isolate, Handle<JSReceiver> format_holder) {
Factory* factory = isolate->factory();
@ -270,5 +422,31 @@ MaybeHandle<JSObject> JSDateTimeFormat::ResolvedOptions(
return options;
}
Maybe<std::string> JSDateTimeFormat::OptionsToSkeleton(
Isolate* isolate, Handle<JSReceiver> options) {
std::string result;
bool hour12;
Maybe<bool> maybe_get_hour12 = Intl::GetBoolOption(
isolate, options, "hour12", "Intl.DateTimeFormat", &hour12);
MAYBE_RETURN(maybe_get_hour12, Nothing<std::string>());
HourOption hour_option = HourOption::H_UNKNOWN;
if (maybe_get_hour12.FromJust()) {
hour_option = hour12 ? HourOption::H_12 : HourOption::H_24;
}
for (const auto& item : GetPatternData(hour_option)) {
std::unique_ptr<char[]> input;
Maybe<bool> maybe_get_option = Intl::GetStringOption(
isolate, options, item.property.c_str(), *(item.allowed_values),
"Intl.DateTimeFormat", &input);
MAYBE_RETURN(maybe_get_option, Nothing<std::string>());
if (maybe_get_option.FromJust()) {
DCHECK_NOT_NULL(input.get());
result += item.map.find(input.get())->second;
}
}
return Just(result);
}
} // namespace internal
} // namespace v8

View File

@ -26,6 +26,15 @@ class JSDateTimeFormat : public JSObject {
V8_WARN_UNUSED_RESULT static MaybeHandle<JSObject> ResolvedOptions(
Isolate* isolate, Handle<JSReceiver> date_time_holder);
// Convert the options to ICU DateTimePatternGenerator skeleton.
static Maybe<std::string> OptionsToSkeleton(Isolate* isolate,
Handle<JSReceiver> options);
// Return the time zone id which match ICU's expectation of title casing
// return empty string when error.
static std::string CanonicalizeTimeZoneID(Isolate* isolate,
const std::string& input);
DECL_CAST(JSDateTimeFormat)
// Layout description.

View File

@ -18,6 +18,10 @@
// Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h"
namespace U_ICU_NAMESPACE {
class NumberFormat;
} // namespace U_ICU_NAMESPACE
namespace v8 {
namespace internal {

View File

@ -18,6 +18,10 @@
// Has to be the last include (doesn't have include guards):
#include "src/objects/object-macros.h"
namespace U_ICU_NAMESPACE {
class PluralRules;
} // namespace U_ICU_NAMESPACE
namespace v8 {
namespace internal {

View File

@ -173,16 +173,11 @@ RUNTIME_FUNCTION(Runtime_CreateDateTimeFormat) {
JSObject::New(constructor, constructor));
// Set date time formatter as embedder field of the resulting JS object.
icu::SimpleDateFormat* date_format =
Maybe<icu::SimpleDateFormat*> maybe_date_format =
DateFormat::InitializeDateTimeFormat(isolate, locale, options, resolved);
MAYBE_RETURN(maybe_date_format, ReadOnlyRoots(isolate).exception());
icu::SimpleDateFormat* date_format = maybe_date_format.FromJust();
CHECK_NOT_NULL(date_format);
if (!DateFormat::IsValidTimeZone(date_format)) {
delete date_format;
THROW_NEW_ERROR_RETURN_FAILURE(
isolate,
NewRangeError(MessageTemplate::kInvalidTimeZone,
isolate->factory()->NewStringFromStaticChars("Etc/GMT")));
}
local_object->SetEmbedderField(DateFormat::kSimpleDateFormatIndex,
reinterpret_cast<Smi*>(date_format));

View File

@ -0,0 +1,17 @@
// 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.
//
// Tests time zone support with conversion.
df = Intl.DateTimeFormat(undefined, {timeZone: 'America/Los_Angeles'});
assertEquals('America/Los_Angeles', df.resolvedOptions().timeZone);
df = Intl.DateTimeFormat(undefined, {timeZone: {toString() { return 'America/Los_Angeles'}}});
assertEquals('America/Los_Angeles', df.resolvedOptions().timeZone);
assertThrows(() => Intl.DateTimeFormat(
undefined, {timeZone: {toString() { throw new Error("should throw"); }}}));
assertThrows(() => Intl.DateTimeFormat(
undefined, {get timeZone() { throw new Error("should throw"); }}));