blob: 3e9cff0df8261c629a70291591ee524916a3bee8 [file] [log] [blame]
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_INTL_SUPPORT
#error Internationalization is expected to be enabled.
#endif // V8_INTL_SUPPORT
#include "src/objects/js-date-time-format.h"
#include <algorithm>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "src/date/date.h"
#include "src/execution/isolate.h"
#include "src/heap/factory.h"
#include "src/objects/intl-objects.h"
#include "src/objects/js-date-time-format-inl.h"
#include "unicode/calendar.h"
#include "unicode/dtitvfmt.h"
#include "unicode/dtptngen.h"
#include "unicode/fieldpos.h"
#include "unicode/gregocal.h"
#include "unicode/smpdtfmt.h"
#include "unicode/unistr.h"
namespace v8 {
namespace internal {
namespace {
std::string ToHourCycleString(JSDateTimeFormat::HourCycle hc) {
switch (hc) {
case JSDateTimeFormat::HourCycle::kH11:
return "h11";
case JSDateTimeFormat::HourCycle::kH12:
return "h12";
case JSDateTimeFormat::HourCycle::kH23:
return "h23";
case JSDateTimeFormat::HourCycle::kH24:
return "h24";
case JSDateTimeFormat::HourCycle::kUndefined:
return "";
default:
UNREACHABLE();
}
}
JSDateTimeFormat::HourCycle ToHourCycle(const std::string& hc) {
if (hc == "h11") return JSDateTimeFormat::HourCycle::kH11;
if (hc == "h12") return JSDateTimeFormat::HourCycle::kH12;
if (hc == "h23") return JSDateTimeFormat::HourCycle::kH23;
if (hc == "h24") return JSDateTimeFormat::HourCycle::kH24;
return JSDateTimeFormat::HourCycle::kUndefined;
}
JSDateTimeFormat::HourCycle ToHourCycle(UDateFormatHourCycle hc) {
switch (hc) {
case UDAT_HOUR_CYCLE_11:
return JSDateTimeFormat::HourCycle::kH11;
case UDAT_HOUR_CYCLE_12:
return JSDateTimeFormat::HourCycle::kH12;
case UDAT_HOUR_CYCLE_23:
return JSDateTimeFormat::HourCycle::kH23;
case UDAT_HOUR_CYCLE_24:
return JSDateTimeFormat::HourCycle::kH24;
default:
return JSDateTimeFormat::HourCycle::kUndefined;
}
}
Maybe<JSDateTimeFormat::HourCycle> GetHourCycle(Isolate* isolate,
Handle<JSReceiver> options,
const char* method) {
return Intl::GetStringOption<JSDateTimeFormat::HourCycle>(
isolate, options, "hourCycle", method, {"h11", "h12", "h23", "h24"},
{JSDateTimeFormat::HourCycle::kH11, JSDateTimeFormat::HourCycle::kH12,
JSDateTimeFormat::HourCycle::kH23, JSDateTimeFormat::HourCycle::kH24},
JSDateTimeFormat::HourCycle::kUndefined);
}
class PatternMap {
public:
PatternMap(std::string pattern, std::string value)
: pattern(std::move(pattern)), value(std::move(value)) {}
virtual ~PatternMap() = default;
std::string pattern;
std::string value;
};
class PatternItem {
public:
PatternItem(const std::string property, std::vector<PatternMap> pairs,
std::vector<const char*> allowed_values)
: property(std::move(property)),
pairs(std::move(pairs)),
allowed_values(allowed_values) {}
virtual ~PatternItem() = default;
const std::string property;
// It is important for the pattern in the pairs from longer one to shorter one
// if the longer one contains substring of an shorter one.
std::vector<PatternMap> pairs;
std::vector<const char*> allowed_values;
};
static std::vector<PatternItem> BuildPatternItems() {
const std::vector<const char*> kLongShort = {"long", "short"};
const std::vector<const char*> kNarrowLongShort = {"narrow", "long", "short"};
const std::vector<const char*> k2DigitNumeric = {"2-digit", "numeric"};
const std::vector<const char*> kNarrowLongShort2DigitNumeric = {
"narrow", "long", "short", "2-digit", "numeric"};
std::vector<PatternItem> items = {
PatternItem("weekday",
{{"EEEEE", "narrow"},
{"EEEE", "long"},
{"EEE", "short"},
{"ccccc", "narrow"},
{"cccc", "long"},
{"ccc", "short"}},
kNarrowLongShort),
PatternItem("era",
{{"GGGGG", "narrow"}, {"GGGG", "long"}, {"GGG", "short"}},
kNarrowLongShort),
PatternItem("year", {{"yy", "2-digit"}, {"y", "numeric"}},
k2DigitNumeric)};
// Sometimes we get L instead of M for month - standalone name.
items.push_back(PatternItem("month",
{{"MMMMM", "narrow"},
{"MMMM", "long"},
{"MMM", "short"},
{"MM", "2-digit"},
{"M", "numeric"},
{"LLLLL", "narrow"},
{"LLLL", "long"},
{"LLL", "short"},
{"LL", "2-digit"},
{"L", "numeric"}},
kNarrowLongShort2DigitNumeric));
items.push_back(PatternItem("day", {{"dd", "2-digit"}, {"d", "numeric"}},
k2DigitNumeric));
if (FLAG_harmony_intl_dateformat_day_period) {
items.push_back(PatternItem("dayPeriod",
{{"BBBBB", "narrow"},
{"bbbbb", "narrow"},
{"BBBB", "long"},
{"bbbb", "long"},
{"B", "short"},
{"b", "short"}},
kNarrowLongShort));
}
items.push_back(PatternItem("hour",
{{"HH", "2-digit"},
{"H", "numeric"},
{"hh", "2-digit"},
{"h", "numeric"},
{"kk", "2-digit"},
{"k", "numeric"},
{"KK", "2-digit"},
{"K", "numeric"}},
k2DigitNumeric));
items.push_back(PatternItem("minute", {{"mm", "2-digit"}, {"m", "numeric"}},
k2DigitNumeric));
items.push_back(PatternItem("second", {{"ss", "2-digit"}, {"s", "numeric"}},
k2DigitNumeric));
items.push_back(PatternItem("timeZoneName",
{{"zzzz", "long"}, {"z", "short"}}, kLongShort));
return items;
}
class PatternItems {
public:
PatternItems() : data(BuildPatternItems()) {}
virtual ~PatternItems() = default;
const std::vector<PatternItem>& Get() const { return data; }
private:
const std::vector<PatternItem> data;
};
static const std::vector<PatternItem>& GetPatternItems() {
static base::LazyInstance<PatternItems>::type items =
LAZY_INSTANCE_INITIALIZER;
return items.Pointer()->Get();
}
class PatternData {
public:
PatternData(const std::string property, std::vector<PatternMap> pairs,
std::vector<const char*> allowed_values)
: property(std::move(property)), allowed_values(allowed_values) {
for (const auto& pair : pairs) {
map.insert(std::make_pair(pair.value, pair.pattern));
}
}
virtual ~PatternData() = default;
const std::string property;
std::map<const std::string, const std::string> map;
std::vector<const char*> allowed_values;
};
const std::vector<PatternData> CreateCommonData(const PatternData& hour_data) {
std::vector<PatternData> build;
for (const PatternItem& item : GetPatternItems()) {
if (item.property == "hour") {
build.push_back(hour_data);
} else {
build.push_back(
PatternData(item.property, item.pairs, item.allowed_values));
}
}
return build;
}
const std::vector<PatternData> CreateData(const char* digit2,
const char* numeric) {
return CreateCommonData(
PatternData("hour", {{digit2, "2-digit"}, {numeric, "numeric"}},
{"2-digit", "numeric"}));
}
// According to "Date Field Symbol Table" in
// http://userguide.icu-project.org/formatparse/datetime
// Symbol | Meaning | Example(s)
// h hour in am/pm (1~12) h 7
// hh 07
// H hour in day (0~23) H 0
// HH 00
// k hour in day (1~24) k 24
// kk 24
// K hour in am/pm (0~11) K 0
// KK 00
class Pattern {
public:
Pattern(const char* d1, const char* d2) : data(CreateData(d1, d2)) {}
virtual ~Pattern() = default;
virtual const std::vector<PatternData>& Get() const { return data; }
private:
std::vector<PatternData> data;
};
#define DEFFINE_TRAIT(name, d1, d2) \
struct name { \
static void Construct(void* allocated_ptr) { \
new (allocated_ptr) Pattern(d1, d2); \
} \
};
DEFFINE_TRAIT(H11Trait, "KK", "K")
DEFFINE_TRAIT(H12Trait, "hh", "h")
DEFFINE_TRAIT(H23Trait, "HH", "H")
DEFFINE_TRAIT(H24Trait, "kk", "k")
DEFFINE_TRAIT(HDefaultTrait, "jj", "j")
#undef DEFFINE_TRAIT
const std::vector<PatternData>& GetPatternData(
JSDateTimeFormat::HourCycle hour_cycle) {
switch (hour_cycle) {
case JSDateTimeFormat::HourCycle::kH11: {
static base::LazyInstance<Pattern, H11Trait>::type h11 =
LAZY_INSTANCE_INITIALIZER;
return h11.Pointer()->Get();
}
case JSDateTimeFormat::HourCycle::kH12: {
static base::LazyInstance<Pattern, H12Trait>::type h12 =
LAZY_INSTANCE_INITIALIZER;
return h12.Pointer()->Get();
}
case JSDateTimeFormat::HourCycle::kH23: {
static base::LazyInstance<Pattern, H23Trait>::type h23 =
LAZY_INSTANCE_INITIALIZER;
return h23.Pointer()->Get();
}
case JSDateTimeFormat::HourCycle::kH24: {
static base::LazyInstance<Pattern, H24Trait>::type h24 =
LAZY_INSTANCE_INITIALIZER;
return h24.Pointer()->Get();
}
case JSDateTimeFormat::HourCycle::kUndefined: {
static base::LazyInstance<Pattern, HDefaultTrait>::type hDefault =
LAZY_INSTANCE_INITIALIZER;
return hDefault.Pointer()->Get();
}
default:
UNREACHABLE();
}
}
std::string GetGMTTzID(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] == '-') &&
base::IsInRange(input[8], '0', '9')) {
return ret + input[7] + input[8];
}
break;
case 10:
if ((input[7] == '+' || input[7] == '-') && (input[8] == '1') &&
base::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 base::IsInRange(ch, 'A', 'Z') || base::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 (base::IsInRange(ch, 'a', 'z')) ? (ch - 'a' + 'A') : ch;
}
// Locale independent tolower for ascii range.
char LocaleIndependentAsciiToLower(char ch) {
return (base::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(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;
}
class SpecialTimeZoneMap {
public:
SpecialTimeZoneMap() {
Add("America/Argentina/ComodRivadavia");
Add("America/Knox_IN");
Add("Antarctica/DumontDUrville");
Add("Antarctica/McMurdo");
Add("Australia/ACT");
Add("Australia/LHI");
Add("Australia/NSW");
Add("Brazil/DeNoronha");
Add("Chile/EasterIsland");
Add("GB");
Add("GB-Eire");
Add("Mexico/BajaNorte");
Add("Mexico/BajaSur");
Add("NZ");
Add("NZ-CHAT");
Add("W-SU");
}
std::string Find(const std::string& id) {
auto it = map_.find(id);
if (it != map_.end()) {
return it->second;
}
return "";
}
private:
void Add(const char* id) {
std::string upper(id);
transform(upper.begin(), upper.end(), upper.begin(),
LocaleIndependentAsciiToUpper);
map_.insert({upper, id});
}
std::map<std::string, std::string> map_;
};
// Return the time zone id which match ICU's expectation of title casing
// return empty string when error.
std::string CanonicalizeTimeZoneID(const std::string& input) {
std::string upper = input;
transform(upper.begin(), upper.end(), upper.begin(),
LocaleIndependentAsciiToUpper);
if (upper.length() == 3) {
if (upper == "GMT") return "UTC";
// For id such as "CET", return upper case.
return upper;
} else if (upper.length() == 7 && '0' <= upper[3] && upper[3] <= '9') {
// For id such as "CST6CDT", return upper case.
return upper;
} else if (upper.length() > 3) {
if (memcmp(upper.c_str(), "ETC", 3) == 0) {
if (upper == "ETC/UTC" || upper == "ETC/GMT" || upper == "ETC/UCT") {
return "UTC";
}
if (strncmp(upper.c_str(), "ETC/GMT", 7) == 0) {
return GetGMTTzID(input);
}
} else if (memcmp(upper.c_str(), "GMT", 3) == 0) {
if (upper == "GMT0" || upper == "GMT+0" || upper == "GMT-0") {
return "UTC";
}
} else if (memcmp(upper.c_str(), "US/", 3) == 0) {
std::string title = ToTitleCaseTimezoneLocation(input);
if (title.length() >= 2) {
// Change "Us/" to "US/"
title[1] = 'S';
}
return title;
} else if (memcmp(upper.c_str(), "SYSTEMV/", 8) == 0) {
upper.replace(0, 8, "SystemV/");
return upper;
}
}
// We expect only _, '-' and / beside ASCII letters.
static base::LazyInstance<SpecialTimeZoneMap>::type special_time_zone_map =
LAZY_INSTANCE_INITIALIZER;
std::string special_case = special_time_zone_map.Pointer()->Find(upper);
if (!special_case.empty()) {
return special_case;
}
return ToTitleCaseTimezoneLocation(input);
}
Handle<String> DateTimeStyleAsString(Isolate* isolate,
JSDateTimeFormat::DateTimeStyle style) {
switch (style) {
case JSDateTimeFormat::DateTimeStyle::kFull:
return ReadOnlyRoots(isolate).full_string_handle();
case JSDateTimeFormat::DateTimeStyle::kLong:
return ReadOnlyRoots(isolate).long_string_handle();
case JSDateTimeFormat::DateTimeStyle::kMedium:
return ReadOnlyRoots(isolate).medium_string_handle();
case JSDateTimeFormat::DateTimeStyle::kShort:
return ReadOnlyRoots(isolate).short_string_handle();
case JSDateTimeFormat::DateTimeStyle::kUndefined:
UNREACHABLE();
}
}
int FractionalSecondDigitsFromPattern(const std::string& pattern) {
int result = 0;
for (size_t i = 0; i < pattern.length() && result < 3; i++) {
if (pattern[i] == 'S') {
result++;
}
}
return result;
}
} // namespace
// ecma402 #sec-intl.datetimeformat.prototype.resolvedoptions
MaybeHandle<JSObject> JSDateTimeFormat::ResolvedOptions(
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format) {
Factory* factory = isolate->factory();
// 4. Let options be ! ObjectCreate(%ObjectPrototype%).
Handle<JSObject> options = factory->NewJSObject(isolate->object_function());
Handle<Object> resolved_obj;
Handle<String> locale = Handle<String>(date_time_format->locale(), isolate);
DCHECK(!date_time_format->icu_locale().is_null());
DCHECK_NOT_NULL(date_time_format->icu_locale().raw());
icu::Locale* icu_locale = date_time_format->icu_locale().raw();
icu::SimpleDateFormat* icu_simple_date_format =
date_time_format->icu_simple_date_format().raw();
// calendar
const icu::Calendar* calendar = icu_simple_date_format->getCalendar();
// getType() returns legacy calendar type name instead of LDML/BCP47 calendar
// key values. intl.js maps them to BCP47 values for key "ca".
// TODO(jshin): Consider doing it here, instead.
std::string calendar_str = calendar->getType();
// Maps ICU calendar names to LDML/BCP47 types for key 'ca'.
// See typeMap section in third_party/icu/source/data/misc/keyTypeData.txt
// and
// http://www.unicode.org/repos/cldr/tags/latest/common/bcp47/calendar.xml
if (calendar_str == "gregorian") {
calendar_str = "gregory";
} else if (calendar_str == "ethiopic-amete-alem") {
calendar_str = "ethioaa";
}
const icu::TimeZone& tz = calendar->getTimeZone();
icu::UnicodeString time_zone;
tz.getID(time_zone);
UErrorCode status = U_ZERO_ERROR;
icu::UnicodeString canonical_time_zone;
icu::TimeZone::getCanonicalID(time_zone, canonical_time_zone, status);
Handle<Object> timezone_value;
if (U_SUCCESS(status)) {
// In CLDR (http://unicode.org/cldr/trac/ticket/9943), Etc/UTC is made
// a separate timezone ID from Etc/GMT even though they're still the same
// timezone. We have Etc/UTC because 'UTC', 'Etc/Universal',
// 'Etc/Zulu' and others are turned to 'Etc/UTC' by ICU. Etc/GMT comes
// from Etc/GMT0, Etc/GMT+0, Etc/GMT-0, Etc/Greenwich.
// ecma402#sec-canonicalizetimezonename step 3
if (canonical_time_zone == UNICODE_STRING_SIMPLE("Etc/UTC") ||
canonical_time_zone == UNICODE_STRING_SIMPLE("Etc/GMT")) {
timezone_value = factory->UTC_string();
} else {
ASSIGN_RETURN_ON_EXCEPTION(isolate, timezone_value,
Intl::ToString(isolate, canonical_time_zone),
JSObject);
}
} else {
// Somehow on Windows we will reach here.
timezone_value = factory->undefined_value();
}
// Ugly hack. ICU doesn't expose numbering system in any way, so we have
// to assume that for given locale NumberingSystem constructor produces the
// same digits as NumberFormat/Calendar would.
// Tracked by https://unicode-org.atlassian.net/browse/ICU-13431
std::string numbering_system = Intl::GetNumberingSystem(*icu_locale);
icu::UnicodeString pattern_unicode;
icu_simple_date_format->toPattern(pattern_unicode);
std::string pattern;
pattern_unicode.toUTF8String(pattern);
// 5. For each row of Table 6, except the header row, in table order, do
// Table 6: Resolved Options of DateTimeFormat Instances
// Internal Slot Property
// [[Locale]] "locale"
// [[Calendar]] "calendar"
// [[NumberingSystem]] "numberingSystem"
// [[TimeZone]] "timeZone"
// [[HourCycle]] "hourCycle"
// "hour12"
// [[Weekday]] "weekday"
// [[Era]] "era"
// [[Year]] "year"
// [[Month]] "month"
// [[Day]] "day"
// [[Hour]] "hour"
// [[Minute]] "minute"
// [[Second]] "second"
// [[FractionalSecondDigits]] "fractionalSecondDigits"
// [[TimeZoneName]] "timeZoneName"
Maybe<bool> maybe_create_locale = JSReceiver::CreateDataProperty(
isolate, options, factory->locale_string(), locale, Just(kDontThrow));
DCHECK(maybe_create_locale.FromJust());
USE(maybe_create_locale);
Maybe<bool> maybe_create_calendar = JSReceiver::CreateDataProperty(
isolate, options, factory->calendar_string(),
factory->NewStringFromAsciiChecked(calendar_str.c_str()),
Just(kDontThrow));
DCHECK(maybe_create_calendar.FromJust());
USE(maybe_create_calendar);
if (!numbering_system.empty()) {
Maybe<bool> maybe_create_numbering_system = JSReceiver::CreateDataProperty(
isolate, options, factory->numberingSystem_string(),
factory->NewStringFromAsciiChecked(numbering_system.c_str()),
Just(kDontThrow));
DCHECK(maybe_create_numbering_system.FromJust());
USE(maybe_create_numbering_system);
}
Maybe<bool> maybe_create_time_zone = JSReceiver::CreateDataProperty(
isolate, options, factory->timeZone_string(), timezone_value,
Just(kDontThrow));
DCHECK(maybe_create_time_zone.FromJust());
USE(maybe_create_time_zone);
// 5.b.i. Let hc be dtf.[[HourCycle]].
HourCycle hc = date_time_format->hour_cycle();
if (hc != HourCycle::kUndefined) {
Maybe<bool> maybe_create_hour_cycle = JSReceiver::CreateDataProperty(
isolate, options, factory->hourCycle_string(),
date_time_format->HourCycleAsString(), Just(kDontThrow));
DCHECK(maybe_create_hour_cycle.FromJust());
USE(maybe_create_hour_cycle);
switch (hc) {
// ii. If hc is "h11" or "h12", let v be true.
case HourCycle::kH11:
case HourCycle::kH12: {
Maybe<bool> maybe_create_hour12 = JSReceiver::CreateDataProperty(
isolate, options, factory->hour12_string(), factory->true_value(),
Just(kDontThrow));
DCHECK(maybe_create_hour12.FromJust());
USE(maybe_create_hour12);
} break;
// iii. Else if, hc is "h23" or "h24", let v be false.
case HourCycle::kH23:
case HourCycle::kH24: {
Maybe<bool> maybe_create_hour12 = JSReceiver::CreateDataProperty(
isolate, options, factory->hour12_string(), factory->false_value(),
Just(kDontThrow));
DCHECK(maybe_create_hour12.FromJust());
USE(maybe_create_hour12);
} break;
// iv. Else, let v be undefined.
case HourCycle::kUndefined:
break;
}
}
// If dateStyle and timeStyle are undefined, then internal slots
// listed in "Table 1: Components of date and time formats" will be set
// in Step 33.f.iii.1 of InitializeDateTimeFormat
if (date_time_format->date_style() == DateTimeStyle::kUndefined &&
date_time_format->time_style() == DateTimeStyle::kUndefined) {
for (const auto& item : GetPatternItems()) {
// fractionalSecondsDigits need to be added before timeZoneName
if (item.property == "timeZoneName") {
int fsd = FractionalSecondDigitsFromPattern(pattern);
if (fsd > 0) {
Maybe<bool> maybe_create_fractional_seconds_digits =
JSReceiver::CreateDataProperty(
isolate, options, factory->fractionalSecondDigits_string(),
factory->NewNumberFromInt(fsd), Just(kDontThrow));
DCHECK(maybe_create_fractional_seconds_digits.FromJust());
USE(maybe_create_fractional_seconds_digits);
}
}
for (const auto& pair : item.pairs) {
if (pattern.find(pair.pattern) != std::string::npos) {
Maybe<bool> maybe_create_property = JSReceiver::CreateDataProperty(
isolate, options,
factory->NewStringFromAsciiChecked(item.property.c_str()),
factory->NewStringFromAsciiChecked(pair.value.c_str()),
Just(kDontThrow));
DCHECK(maybe_create_property.FromJust());
USE(maybe_create_property);
break;
}
}
}
}
// dateStyle
if (date_time_format->date_style() != DateTimeStyle::kUndefined) {
Maybe<bool> maybe_create_date_style = JSReceiver::CreateDataProperty(
isolate, options, factory->dateStyle_string(),
DateTimeStyleAsString(isolate, date_time_format->date_style()),
Just(kDontThrow));
DCHECK(maybe_create_date_style.FromJust());
USE(maybe_create_date_style);
}
// timeStyle
if (date_time_format->time_style() != DateTimeStyle::kUndefined) {
Maybe<bool> maybe_create_time_style = JSReceiver::CreateDataProperty(
isolate, options, factory->timeStyle_string(),
DateTimeStyleAsString(isolate, date_time_format->time_style()),
Just(kDontThrow));
DCHECK(maybe_create_time_style.FromJust());
USE(maybe_create_time_style);
}
return options;
}
namespace {
// ecma402/#sec-formatdatetime
// FormatDateTime( dateTimeFormat, x )
MaybeHandle<String> FormatDateTime(Isolate* isolate,
const icu::SimpleDateFormat& date_format,
double x) {
double date_value = DateCache::TimeClip(x);
if (std::isnan(date_value)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
String);
}
icu::UnicodeString result;
date_format.format(date_value, result);
return Intl::ToString(isolate, result);
}
} // namespace
// ecma402/#sec-datetime-format-functions
// DateTime Format Functions
MaybeHandle<String> JSDateTimeFormat::DateTimeFormat(
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
Handle<Object> date) {
// 2. Assert: Type(dtf) is Object and dtf has an [[InitializedDateTimeFormat]]
// internal slot.
// 3. If date is not provided or is undefined, then
double x;
if (date->IsUndefined()) {
// 3.a Let x be Call(%Date_now%, undefined).
x = JSDate::CurrentTimeValue(isolate);
} else {
// 4. Else,
// a. Let x be ? ToNumber(date).
ASSIGN_RETURN_ON_EXCEPTION(isolate, date, Object::ToNumber(isolate, date),
String);
DCHECK(date->IsNumber());
x = date->Number();
}
// 5. Return FormatDateTime(dtf, x).
icu::SimpleDateFormat* format =
date_time_format->icu_simple_date_format().raw();
return FormatDateTime(isolate, *format, x);
}
namespace {
Isolate::ICUObjectCacheType ConvertToCacheType(
JSDateTimeFormat::DefaultsOption type) {
switch (type) {
case JSDateTimeFormat::DefaultsOption::kDate:
return Isolate::ICUObjectCacheType::kDefaultSimpleDateFormatForDate;
case JSDateTimeFormat::DefaultsOption::kTime:
return Isolate::ICUObjectCacheType::kDefaultSimpleDateFormatForTime;
case JSDateTimeFormat::DefaultsOption::kAll:
return Isolate::ICUObjectCacheType::kDefaultSimpleDateFormat;
}
}
} // namespace
MaybeHandle<String> JSDateTimeFormat::ToLocaleDateTime(
Isolate* isolate, Handle<Object> date, Handle<Object> locales,
Handle<Object> options, RequiredOption required, DefaultsOption defaults,
const char* method) {
Isolate::ICUObjectCacheType cache_type = ConvertToCacheType(defaults);
Factory* factory = isolate->factory();
// 1. Let x be ? thisTimeValue(this value);
if (!date->IsJSDate()) {
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
factory->Date_string()),
String);
}
double const x = Handle<JSDate>::cast(date)->value().Number();
// 2. If x is NaN, return "Invalid Date"
if (std::isnan(x)) {
return factory->Invalid_Date_string();
}
// We only cache the instance when both locales and options are undefined,
// as that is the only case when the specified side-effects of examining
// those arguments are unobservable.
bool can_cache =
locales->IsUndefined(isolate) && options->IsUndefined(isolate);
if (can_cache) {
// Both locales and options are undefined, check the cache.
icu::SimpleDateFormat* cached_icu_simple_date_format =
static_cast<icu::SimpleDateFormat*>(
isolate->get_cached_icu_object(cache_type));
if (cached_icu_simple_date_format != nullptr) {
return FormatDateTime(isolate, *cached_icu_simple_date_format, x);
}
}
// 3. Let options be ? ToDateTimeOptions(options, required, defaults).
Handle<JSObject> internal_options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, internal_options,
ToDateTimeOptions(isolate, options, required, defaults), String);
// 4. Let dateFormat be ? Construct(%DateTimeFormat%, « locales, options »).
Handle<JSFunction> constructor = Handle<JSFunction>(
JSFunction::cast(
isolate->context().native_context().intl_date_time_format_function()),
isolate);
Handle<Map> map;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, map,
JSFunction::GetDerivedMap(isolate, constructor, constructor), String);
Handle<JSDateTimeFormat> date_time_format;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, date_time_format,
JSDateTimeFormat::New(isolate, map, locales, internal_options, method),
String);
if (can_cache) {
isolate->set_icu_object_in_cache(
cache_type, std::static_pointer_cast<icu::UMemory>(
date_time_format->icu_simple_date_format().get()));
}
// 5. Return FormatDateTime(dateFormat, x).
icu::SimpleDateFormat* format =
date_time_format->icu_simple_date_format().raw();
return FormatDateTime(isolate, *format, x);
}
namespace {
Maybe<bool> IsPropertyUndefined(Isolate* isolate, Handle<JSObject> options,
Handle<String> property) {
// i. Let prop be the property name.
// ii. Let value be ? Get(options, prop).
Handle<Object> value;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, value, Object::GetPropertyOrElement(isolate, options, property),
Nothing<bool>());
return Just(value->IsUndefined(isolate));
}
Maybe<bool> NeedsDefault(Isolate* isolate, Handle<JSObject> options,
const std::vector<Handle<String>>& props) {
bool needs_default = true;
for (const auto& prop : props) {
// i. Let prop be the property name.
// ii. Let value be ? Get(options, prop)
Maybe<bool> maybe_undefined = IsPropertyUndefined(isolate, options, prop);
MAYBE_RETURN(maybe_undefined, Nothing<bool>());
// iii. If value is not undefined, let needDefaults be false.
if (!maybe_undefined.FromJust()) {
needs_default = false;
}
}
return Just(needs_default);
}
Maybe<bool> CreateDefault(Isolate* isolate, Handle<JSObject> options,
const std::vector<std::string>& props) {
Factory* factory = isolate->factory();
// i. Perform ? CreateDataPropertyOrThrow(options, prop, "numeric").
for (const auto& prop : props) {
MAYBE_RETURN(
JSReceiver::CreateDataProperty(
isolate, options, factory->NewStringFromAsciiChecked(prop.c_str()),
factory->numeric_string(), Just(kThrowOnError)),
Nothing<bool>());
}
return Just(true);
}
} // namespace
// ecma-402/#sec-todatetimeoptions
MaybeHandle<JSObject> JSDateTimeFormat::ToDateTimeOptions(
Isolate* isolate, Handle<Object> input_options, RequiredOption required,
DefaultsOption defaults) {
Factory* factory = isolate->factory();
// 1. If options is undefined, let options be null; otherwise let options be ?
// ToObject(options).
Handle<JSObject> options;
if (input_options->IsUndefined(isolate)) {
options = factory->NewJSObjectWithNullProto();
} else {
Handle<JSReceiver> options_obj;
ASSIGN_RETURN_ON_EXCEPTION(isolate, options_obj,
Object::ToObject(isolate, input_options),
JSObject);
// 2. Let options be ObjectCreate(options).
ASSIGN_RETURN_ON_EXCEPTION(isolate, options,
JSObject::ObjectCreate(isolate, options_obj),
JSObject);
}
// 3. Let needDefaults be true.
bool needs_default = true;
// 4. If required is "date" or "any", then
if (required == RequiredOption::kAny || required == RequiredOption::kDate) {
// a. For each of the property names "weekday", "year", "month",
// "day", do
std::vector<Handle<String>> list(
{factory->weekday_string(), factory->year_string()});
list.push_back(factory->month_string());
list.push_back(factory->day_string());
Maybe<bool> maybe_needs_default = NeedsDefault(isolate, options, list);
MAYBE_RETURN(maybe_needs_default, Handle<JSObject>());
needs_default = maybe_needs_default.FromJust();
}
// 5. If required is "time" or "any", then
if (required == RequiredOption::kAny || required == RequiredOption::kTime) {
// a. For each of the property names "dayPeriod", "hour", "minute",
// "second", "fractionalSecondDigits", do
std::vector<Handle<String>> list;
if (FLAG_harmony_intl_dateformat_day_period) {
list.push_back(factory->dayPeriod_string());
}
list.push_back(factory->hour_string());
list.push_back(factory->minute_string());
list.push_back(factory->second_string());
list.push_back(factory->fractionalSecondDigits_string());
Maybe<bool> maybe_needs_default = NeedsDefault(isolate, options, list);
MAYBE_RETURN(maybe_needs_default, Handle<JSObject>());
needs_default &= maybe_needs_default.FromJust();
}
// 6. Let dateStyle be ? Get(options, "dateStyle").
Maybe<bool> maybe_datestyle_undefined =
IsPropertyUndefined(isolate, options, factory->dateStyle_string());
MAYBE_RETURN(maybe_datestyle_undefined, Handle<JSObject>());
// 7. Let timeStyle be ? Get(options, "timeStyle").
Maybe<bool> maybe_timestyle_undefined =
IsPropertyUndefined(isolate, options, factory->timeStyle_string());
MAYBE_RETURN(maybe_timestyle_undefined, Handle<JSObject>());
// 8. If dateStyle is not undefined or timeStyle is not undefined, let
// needDefaults be false.
if (!maybe_datestyle_undefined.FromJust() ||
!maybe_timestyle_undefined.FromJust()) {
needs_default = false;
}
// 9. If required is "date" and timeStyle is not undefined,
if (required == RequiredOption::kDate &&
!maybe_timestyle_undefined.FromJust()) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(
isolate,
NewTypeError(MessageTemplate::kInvalid,
factory->NewStringFromStaticChars("option"),
factory->NewStringFromStaticChars("timeStyle")),
JSObject);
}
// 10. If required is "time" and dateStyle is not undefined,
if (required == RequiredOption::kTime &&
!maybe_datestyle_undefined.FromJust()) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(
isolate,
NewTypeError(MessageTemplate::kInvalid,
factory->NewStringFromStaticChars("option"),
factory->NewStringFromStaticChars("dateStyle")),
JSObject);
}
// 11. If needDefaults is true and defaults is either "date" or "all", then
if (needs_default) {
if (defaults == DefaultsOption::kAll || defaults == DefaultsOption::kDate) {
// a. For each of the property names "year", "month", "day", do)
const std::vector<std::string> list({"year", "month", "day"});
MAYBE_RETURN(CreateDefault(isolate, options, list), Handle<JSObject>());
}
// 12. If needDefaults is true and defaults is either "time" or "all", then
if (defaults == DefaultsOption::kAll || defaults == DefaultsOption::kTime) {
// a. For each of the property names "hour", "minute", "second", do
const std::vector<std::string> list({"hour", "minute", "second"});
MAYBE_RETURN(CreateDefault(isolate, options, list), Handle<JSObject>());
}
}
// 13. Return options.
return options;
}
MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::UnwrapDateTimeFormat(
Isolate* isolate, Handle<JSReceiver> format_holder) {
Handle<Context> native_context =
Handle<Context>(isolate->context().native_context(), isolate);
Handle<JSFunction> constructor = Handle<JSFunction>(
JSFunction::cast(native_context->intl_date_time_format_function()),
isolate);
Handle<Object> dtf;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, dtf,
Intl::LegacyUnwrapReceiver(isolate, format_holder, constructor,
format_holder->IsJSDateTimeFormat()),
JSDateTimeFormat);
// 2. If Type(dtf) is not Object or dtf does not have an
// [[InitializedDateTimeFormat]] internal slot, then
if (!dtf->IsJSDateTimeFormat()) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kIncompatibleMethodReceiver,
isolate->factory()->NewStringFromAsciiChecked(
"UnwrapDateTimeFormat"),
format_holder),
JSDateTimeFormat);
}
// 3. Return dtf.
return Handle<JSDateTimeFormat>::cast(dtf);
}
namespace {
// 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(const char* timezone) {
// Create time zone as specified by the user. We have to re-create time zone
// since calendar takes ownership.
if (timezone == nullptr) {
// 19.a. Else / Let timeZone be DefaultTimeZone().
return std::unique_ptr<icu::TimeZone>(icu::TimeZone::createDefault());
}
std::string canonicalized = CanonicalizeTimeZoneID(timezone);
if (canonicalized.empty()) return std::unique_ptr<icu::TimeZone>();
std::unique_ptr<icu::TimeZone> tz(
icu::TimeZone::createTimeZone(canonicalized.c_str()));
// 18.b If the result of IsValidTimeZoneName(timeZone) is false, then
// i. Throw a RangeError exception.
if (!IsValidTimeZoneName(*tz)) return std::unique_ptr<icu::TimeZone>();
return tz;
}
class CalendarCache {
public:
icu::Calendar* CreateCalendar(const icu::Locale& locale, icu::TimeZone* tz) {
icu::UnicodeString tz_id;
tz->getID(tz_id);
std::string key;
tz_id.toUTF8String<std::string>(key);
key += ":";
key += locale.getName();
base::MutexGuard guard(&mutex_);
auto it = map_.find(key);
if (it != map_.end()) {
delete tz;
return it->second->clone();
}
// Create a calendar using locale, and apply time zone to it.
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<icu::Calendar> calendar(
icu::Calendar::createInstance(tz, locale, status));
DCHECK(U_SUCCESS(status));
DCHECK_NOT_NULL(calendar.get());
if (calendar->getDynamicClassID() ==
icu::GregorianCalendar::getStaticClassID()) {
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));
}
if (map_.size() > 8) { // Cache at most 8 calendars.
map_.clear();
}
map_[key].reset(calendar.release());
return map_[key]->clone();
}
private:
std::map<std::string, std::unique_ptr<icu::Calendar>> map_;
base::Mutex mutex_;
};
icu::Calendar* CreateCalendar(Isolate* isolate, const icu::Locale& icu_locale,
icu::TimeZone* tz) {
static base::LazyInstance<CalendarCache>::type calendar_cache =
LAZY_INSTANCE_INITIALIZER;
return calendar_cache.Pointer()->CreateCalendar(icu_locale, tz);
}
icu::UnicodeString ReplaceHourCycleInPattern(icu::UnicodeString pattern,
JSDateTimeFormat::HourCycle hc) {
char16_t replacement;
switch (hc) {
case JSDateTimeFormat::HourCycle::kUndefined:
return pattern;
case JSDateTimeFormat::HourCycle::kH11:
replacement = 'K';
break;
case JSDateTimeFormat::HourCycle::kH12:
replacement = 'h';
break;
case JSDateTimeFormat::HourCycle::kH23:
replacement = 'H';
break;
case JSDateTimeFormat::HourCycle::kH24:
replacement = 'k';
break;
}
bool replace = true;
icu::UnicodeString result;
for (int32_t i = 0; i < pattern.length(); i++) {
char16_t ch = pattern.charAt(i);
switch (ch) {
case '\'':
replace = !replace;
result.append(ch);
break;
case 'H':
V8_FALLTHROUGH;
case 'h':
V8_FALLTHROUGH;
case 'K':
V8_FALLTHROUGH;
case 'k':
result.append(replace ? replacement : ch);
break;
default:
result.append(ch);
break;
}
}
return result;
}
std::unique_ptr<icu::SimpleDateFormat> CreateICUDateFormat(
const icu::Locale& icu_locale, const icu::UnicodeString& skeleton,
icu::DateTimePatternGenerator* generator, JSDateTimeFormat::HourCycle hc) {
// 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 .
// This is a temporary work-around to get v8's external behavior to match
// the current spec, but does not follow the spec provisions mentioned
// in the above Ecma 402 issue.
// TODO(jshin): The spec may need to be revised because using the base
// locale for the pattern match is not quite right. Moreover, what to
// do with 'related year' part when 'chinese/dangi' calendar is specified
// has to be discussed. Revisit once the spec is clarified/revised.
icu::UnicodeString pattern;
UErrorCode status = U_ZERO_ERROR;
pattern = generator->getBestPattern(skeleton, UDATPG_MATCH_HOUR_FIELD_LENGTH,
status);
pattern = ReplaceHourCycleInPattern(pattern, hc);
DCHECK(U_SUCCESS(status));
// Make formatter from skeleton. Calendar and numbering system are added
// to the locale as Unicode extension (if they were specified at all).
status = U_ZERO_ERROR;
std::unique_ptr<icu::SimpleDateFormat> date_format(
new icu::SimpleDateFormat(pattern, icu_locale, status));
if (U_FAILURE(status)) return std::unique_ptr<icu::SimpleDateFormat>();
DCHECK_NOT_NULL(date_format.get());
return date_format;
}
class DateFormatCache {
public:
icu::SimpleDateFormat* Create(const icu::Locale& icu_locale,
const icu::UnicodeString& skeleton,
icu::DateTimePatternGenerator* generator,
JSDateTimeFormat::HourCycle hc) {
std::string key;
skeleton.toUTF8String<std::string>(key);
key += ":";
key += icu_locale.getName();
base::MutexGuard guard(&mutex_);
auto it = map_.find(key);
if (it != map_.end()) {
return static_cast<icu::SimpleDateFormat*>(it->second->clone());
}
if (map_.size() > 8) { // Cache at most 8 DateFormats.
map_.clear();
}
std::unique_ptr<icu::SimpleDateFormat> instance(
CreateICUDateFormat(icu_locale, skeleton, generator, hc));
if (instance.get() == nullptr) return nullptr;
map_[key] = std::move(instance);
return static_cast<icu::SimpleDateFormat*>(map_[key]->clone());
}
private:
std::map<std::string, std::unique_ptr<icu::SimpleDateFormat>> map_;
base::Mutex mutex_;
};
std::unique_ptr<icu::SimpleDateFormat> CreateICUDateFormatFromCache(
const icu::Locale& icu_locale, const icu::UnicodeString& skeleton,
icu::DateTimePatternGenerator* generator, JSDateTimeFormat::HourCycle hc) {
static base::LazyInstance<DateFormatCache>::type cache =
LAZY_INSTANCE_INITIALIZER;
return std::unique_ptr<icu::SimpleDateFormat>(
cache.Pointer()->Create(icu_locale, skeleton, generator, hc));
}
icu::UnicodeString SkeletonFromDateFormat(
const icu::SimpleDateFormat& icu_date_format) {
icu::UnicodeString pattern;
pattern = icu_date_format.toPattern(pattern);
UErrorCode status = U_ZERO_ERROR;
icu::UnicodeString skeleton =
icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status);
DCHECK(U_SUCCESS(status));
return skeleton;
}
icu::DateIntervalFormat* LazyCreateDateIntervalFormat(
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format) {
Managed<icu::DateIntervalFormat> managed_format =
date_time_format->icu_date_interval_format();
if (managed_format.get()) {
return managed_format.raw();
}
icu::SimpleDateFormat* icu_simple_date_format =
date_time_format->icu_simple_date_format().raw();
UErrorCode status = U_ZERO_ERROR;
icu::Locale loc = *(date_time_format->icu_locale().raw());
// We need to pass in the hc to DateIntervalFormat by using Unicode 'hc'
// extension.
std::string hcString = ToHourCycleString(date_time_format->hour_cycle());
if (!hcString.empty()) {
loc.setUnicodeKeywordValue("hc", hcString, status);
}
std::unique_ptr<icu::DateIntervalFormat> date_interval_format(
icu::DateIntervalFormat::createInstance(
SkeletonFromDateFormat(*icu_simple_date_format), loc, status));
if (U_FAILURE(status)) {
return nullptr;
}
date_interval_format->setTimeZone(icu_simple_date_format->getTimeZone());
Handle<Managed<icu::DateIntervalFormat>> managed_interval_format =
Managed<icu::DateIntervalFormat>::FromUniquePtr(
isolate, 0, std::move(date_interval_format));
date_time_format->set_icu_date_interval_format(*managed_interval_format);
return (*managed_interval_format).raw();
}
JSDateTimeFormat::HourCycle HourCycleFromPattern(
const icu::UnicodeString pattern) {
bool in_quote = false;
for (int32_t i = 0; i < pattern.length(); i++) {
char16_t ch = pattern[i];
switch (ch) {
case '\'':
in_quote = !in_quote;
break;
case 'K':
if (!in_quote) return JSDateTimeFormat::HourCycle::kH11;
break;
case 'h':
if (!in_quote) return JSDateTimeFormat::HourCycle::kH12;
break;
case 'H':
if (!in_quote) return JSDateTimeFormat::HourCycle::kH23;
break;
case 'k':
if (!in_quote) return JSDateTimeFormat::HourCycle::kH24;
break;
}
}
return JSDateTimeFormat::HourCycle::kUndefined;
}
icu::DateFormat::EStyle DateTimeStyleToEStyle(
JSDateTimeFormat::DateTimeStyle style) {
switch (style) {
case JSDateTimeFormat::DateTimeStyle::kFull:
return icu::DateFormat::EStyle::kFull;
case JSDateTimeFormat::DateTimeStyle::kLong:
return icu::DateFormat::EStyle::kLong;
case JSDateTimeFormat::DateTimeStyle::kMedium:
return icu::DateFormat::EStyle::kMedium;
case JSDateTimeFormat::DateTimeStyle::kShort:
return icu::DateFormat::EStyle::kShort;
case JSDateTimeFormat::DateTimeStyle::kUndefined:
UNREACHABLE();
}
}
icu::UnicodeString ReplaceSkeleton(const icu::UnicodeString input,
JSDateTimeFormat::HourCycle hc) {
icu::UnicodeString result;
char16_t to;
switch (hc) {
case JSDateTimeFormat::HourCycle::kH11:
to = 'K';
break;
case JSDateTimeFormat::HourCycle::kH12:
to = 'h';
break;
case JSDateTimeFormat::HourCycle::kH23:
to = 'H';
break;
case JSDateTimeFormat::HourCycle::kH24:
to = 'k';
break;
case JSDateTimeFormat::HourCycle::kUndefined:
UNREACHABLE();
}
for (int32_t i = 0; i < input.length(); i++) {
switch (input[i]) {
// We need to skip 'a', 'b', 'B' here due to
// https://unicode-org.atlassian.net/browse/ICU-20437
case 'a':
V8_FALLTHROUGH;
case 'b':
V8_FALLTHROUGH;
case 'B':
// ignore
break;
case 'h':
V8_FALLTHROUGH;
case 'H':
V8_FALLTHROUGH;
case 'K':
V8_FALLTHROUGH;
case 'k':
result += to;
break;
default:
result += input[i];
break;
}
}
return result;
}
std::unique_ptr<icu::SimpleDateFormat> DateTimeStylePattern(
JSDateTimeFormat::DateTimeStyle date_style,
JSDateTimeFormat::DateTimeStyle time_style, icu::Locale& icu_locale,
JSDateTimeFormat::HourCycle hc, icu::DateTimePatternGenerator* generator) {
std::unique_ptr<icu::SimpleDateFormat> result;
if (date_style != JSDateTimeFormat::DateTimeStyle::kUndefined) {
if (time_style != JSDateTimeFormat::DateTimeStyle::kUndefined) {
result.reset(reinterpret_cast<icu::SimpleDateFormat*>(
icu::DateFormat::createDateTimeInstance(
DateTimeStyleToEStyle(date_style),
DateTimeStyleToEStyle(time_style), icu_locale)));
} else {
result.reset(reinterpret_cast<icu::SimpleDateFormat*>(
icu::DateFormat::createDateInstance(DateTimeStyleToEStyle(date_style),
icu_locale)));
// For instance without time, we do not need to worry about the hour cycle
// impact so we can return directly.
if (result.get() != nullptr) {
return result;
}
}
} else {
if (time_style != JSDateTimeFormat::DateTimeStyle::kUndefined) {
result.reset(reinterpret_cast<icu::SimpleDateFormat*>(
icu::DateFormat::createTimeInstance(DateTimeStyleToEStyle(time_style),
icu_locale)));
} else {
UNREACHABLE();
}
}
UErrorCode status = U_ZERO_ERROR;
// Somehow we fail to create the instance.
if (result.get() == nullptr) {
// Fallback to the locale without "nu".
if (!icu_locale.getUnicodeKeywordValue<std::string>("nu", status).empty()) {
status = U_ZERO_ERROR;
icu_locale.setUnicodeKeywordValue("nu", nullptr, status);
return DateTimeStylePattern(date_style, time_style, icu_locale, hc,
generator);
}
status = U_ZERO_ERROR;
// Fallback to the locale without "hc".
if (!icu_locale.getUnicodeKeywordValue<std::string>("hc", status).empty()) {
status = U_ZERO_ERROR;
icu_locale.setUnicodeKeywordValue("hc", nullptr, status);
return DateTimeStylePattern(date_style, time_style, icu_locale, hc,
generator);
}
status = U_ZERO_ERROR;
// Fallback to the locale without "ca".
if (!icu_locale.getUnicodeKeywordValue<std::string>("ca", status).empty()) {
status = U_ZERO_ERROR;
icu_locale.setUnicodeKeywordValue("ca", nullptr, status);
return DateTimeStylePattern(date_style, time_style, icu_locale, hc,
generator);
}
return nullptr;
}
icu::UnicodeString pattern;
pattern = result->toPattern(pattern);
status = U_ZERO_ERROR;
icu::UnicodeString skeleton =
icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status);
DCHECK(U_SUCCESS(status));
// If the skeleton match the HourCycle, we just return it.
if (hc == HourCycleFromPattern(pattern)) {
return result;
}
return CreateICUDateFormatFromCache(icu_locale, ReplaceSkeleton(skeleton, hc),
generator, hc);
}
class DateTimePatternGeneratorCache {
public:
// Return a clone copy that the caller have to free.
icu::DateTimePatternGenerator* CreateGenerator(const icu::Locale& locale) {
std::string key(locale.getName());
base::MutexGuard guard(&mutex_);
auto it = map_.find(key);
if (it != map_.end()) {
return it->second->clone();
}
UErrorCode status = U_ZERO_ERROR;
map_[key].reset(
icu::DateTimePatternGenerator::createInstance(locale, status));
// Fallback to use "root".
if (U_FAILURE(status)) {
status = U_ZERO_ERROR;
map_[key].reset(
icu::DateTimePatternGenerator::createInstance("root", status));
}
DCHECK(U_SUCCESS(status));
return map_[key]->clone();
}
private:
std::map<std::string, std::unique_ptr<icu::DateTimePatternGenerator>> map_;
base::Mutex mutex_;
};
} // namespace
enum FormatMatcherOption { kBestFit, kBasic };
// ecma402/#sec-initializedatetimeformat
MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::New(
Isolate* isolate, Handle<Map> map, Handle<Object> locales,
Handle<Object> input_options, const char* service) {
Factory* factory = isolate->factory();
// 1. Let requestedLocales be ? CanonicalizeLocaleList(locales).
Maybe<std::vector<std::string>> maybe_requested_locales =
Intl::CanonicalizeLocaleList(isolate, locales);
MAYBE_RETURN(maybe_requested_locales, Handle<JSDateTimeFormat>());
std::vector<std::string> requested_locales =
maybe_requested_locales.FromJust();
// 2. Let options be ? ToDateTimeOptions(options, "any", "date").
Handle<JSObject> options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options,
JSDateTimeFormat::ToDateTimeOptions(
isolate, input_options, RequiredOption::kAny, DefaultsOption::kDate),
JSDateTimeFormat);
// 4. Let matcher be ? GetOption(options, "localeMatcher", "string",
// « "lookup", "best fit" », "best fit").
// 5. Set opt.[[localeMatcher]] to matcher.
Maybe<Intl::MatcherOption> maybe_locale_matcher =
Intl::GetLocaleMatcher(isolate, options, service);
MAYBE_RETURN(maybe_locale_matcher, MaybeHandle<JSDateTimeFormat>());
Intl::MatcherOption locale_matcher = maybe_locale_matcher.FromJust();
std::unique_ptr<char[]> calendar_str = nullptr;
std::unique_ptr<char[]> numbering_system_str = nullptr;
const std::vector<const char*> empty_values = {};
// 6. Let calendar be ? GetOption(options, "calendar",
// "string", undefined, undefined).
Maybe<bool> maybe_calendar = Intl::GetStringOption(
isolate, options, "calendar", empty_values, service, &calendar_str);
MAYBE_RETURN(maybe_calendar, MaybeHandle<JSDateTimeFormat>());
if (maybe_calendar.FromJust() && calendar_str != nullptr) {
icu::Locale default_locale;
if (!Intl::IsWellFormedCalendar(calendar_str.get())) {
THROW_NEW_ERROR(
isolate,
NewRangeError(MessageTemplate::kInvalid, factory->calendar_string(),
factory->NewStringFromAsciiChecked(calendar_str.get())),
JSDateTimeFormat);
}
}
// 8. Let numberingSystem be ? GetOption(options, "numberingSystem",
// "string", undefined, undefined).
Maybe<bool> maybe_numberingSystem = Intl::GetNumberingSystem(
isolate, options, service, &numbering_system_str);
MAYBE_RETURN(maybe_numberingSystem, MaybeHandle<JSDateTimeFormat>());
// 6. Let hour12 be ? GetOption(options, "hour12", "boolean", undefined,
// undefined).
bool hour12;
Maybe<bool> maybe_get_hour12 =
Intl::GetBoolOption(isolate, options, "hour12", service, &hour12);
MAYBE_RETURN(maybe_get_hour12, Handle<JSDateTimeFormat>());
// 7. Let hourCycle be ? GetOption(options, "hourCycle", "string", « "h11",
// "h12", "h23", "h24" », undefined).
Maybe<HourCycle> maybe_hour_cycle = GetHourCycle(isolate, options, service);
MAYBE_RETURN(maybe_hour_cycle, MaybeHandle<JSDateTimeFormat>());
HourCycle hour_cycle = maybe_hour_cycle.FromJust();
// 8. If hour12 is not undefined, then
if (maybe_get_hour12.FromJust()) {
// a. Let hourCycle be null.
hour_cycle = HourCycle::kUndefined;
}
// 9. Set opt.[[hc]] to hourCycle.
// ecma402/#sec-intl.datetimeformat-internal-slots
// The value of the [[RelevantExtensionKeys]] internal slot is
// « "ca", "nu", "hc" ».
std::set<std::string> relevant_extension_keys = {"nu", "ca", "hc"};
// 10. Let localeData be %DateTimeFormat%.[[LocaleData]].
// 11. Let r be ResolveLocale( %DateTimeFormat%.[[AvailableLocales]],
// requestedLocales, opt, %DateTimeFormat%.[[RelevantExtensionKeys]],
// localeData).
//
Maybe<Intl::ResolvedLocale> maybe_resolve_locale = Intl::ResolveLocale(
isolate, JSDateTimeFormat::GetAvailableLocales(), requested_locales,
locale_matcher, relevant_extension_keys);
if (maybe_resolve_locale.IsNothing()) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
JSDateTimeFormat);
}
Intl::ResolvedLocale r = maybe_resolve_locale.FromJust();
icu::Locale icu_locale = r.icu_locale;
DCHECK(!icu_locale.isBogus());
UErrorCode status = U_ZERO_ERROR;
if (calendar_str != nullptr) {
auto ca_extension_it = r.extensions.find("ca");
if (ca_extension_it != r.extensions.end() &&
ca_extension_it->second != calendar_str.get()) {
icu_locale.setUnicodeKeywordValue("ca", nullptr, status);
DCHECK(U_SUCCESS(status));
}
}
if (numbering_system_str != nullptr) {
auto nu_extension_it = r.extensions.find("nu");
if (nu_extension_it != r.extensions.end() &&
nu_extension_it->second != numbering_system_str.get()) {
icu_locale.setUnicodeKeywordValue("nu", nullptr, status);
DCHECK(U_SUCCESS(status));
}
}
// Need to keep a copy of icu_locale which not changing "ca", "nu", "hc"
// by option.
icu::Locale resolved_locale(icu_locale);
if (calendar_str != nullptr &&
Intl::IsValidCalendar(icu_locale, calendar_str.get())) {
icu_locale.setUnicodeKeywordValue("ca", calendar_str.get(), status);
DCHECK(U_SUCCESS(status));
}
if (numbering_system_str != nullptr &&
Intl::IsValidNumberingSystem(numbering_system_str.get())) {
icu_locale.setUnicodeKeywordValue("nu", numbering_system_str.get(), status);
DCHECK(U_SUCCESS(status));
}
static base::LazyInstance<DateTimePatternGeneratorCache>::type
generator_cache = LAZY_INSTANCE_INITIALIZER;
std::unique_ptr<icu::DateTimePatternGenerator> generator(
generator_cache.Pointer()->CreateGenerator(icu_locale));
// 15.Let hcDefault be dataLocaleData.[[hourCycle]].
HourCycle hc_default = ToHourCycle(generator->getDefaultHourCycle(status));
DCHECK(U_SUCCESS(status));
// 16.Let hc be r.[[hc]].
HourCycle hc = HourCycle::kUndefined;
if (hour_cycle == HourCycle::kUndefined) {
auto hc_extension_it = r.extensions.find("hc");
if (hc_extension_it != r.extensions.end()) {
hc = ToHourCycle(hc_extension_it->second.c_str());
}
} else {
hc = hour_cycle;
}
// 17. If hc is null, then
if (hc == HourCycle::kUndefined) {
// a. Set hc to hcDefault.
hc = hc_default;
}
// 18. If hour12 is not undefined, then
if (maybe_get_hour12.FromJust()) {
// a. If hour12 is true, then
if (hour12) {
// i. If hcDefault is "h11" or "h23", then
if (hc_default == HourCycle::kH11 || hc_default == HourCycle::kH23) {
// 1. Set hc to "h11".
hc = HourCycle::kH11;
// ii. Else,
} else {
// 1. Set hc to "h12".
hc = HourCycle::kH12;
}
// b. Else,
} else {
// ii. If hcDefault is "h11" or "h23", then
if (hc_default == HourCycle::kH11 || hc_default == HourCycle::kH23) {
// 1. Set hc to "h23".
hc = HourCycle::kH23;
// iii. Else,
} else {
// 1. Set hc to "h24".
hc = HourCycle::kH24;
}
}
}
// 17. Let timeZone be ? Get(options, "timeZone").
std::unique_ptr<char[]> timezone = nullptr;
Maybe<bool> maybe_timezone = Intl::GetStringOption(
isolate, options, "timeZone", empty_values, service, &timezone);
MAYBE_RETURN(maybe_timezone, Handle<JSDateTimeFormat>());
std::unique_ptr<icu::TimeZone> tz = CreateTimeZone(timezone.get());
if (tz.get() == nullptr) {
THROW_NEW_ERROR(
isolate,
NewRangeError(MessageTemplate::kInvalidTimeZone,
factory->NewStringFromAsciiChecked(timezone.get())),
JSDateTimeFormat);
}
std::unique_ptr<icu::Calendar> calendar(
CreateCalendar(isolate, icu_locale, tz.release()));
// 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,
factory->NewStringFromAsciiChecked(timezone.get())),
JSDateTimeFormat);
}
DateTimeStyle date_style = DateTimeStyle::kUndefined;
DateTimeStyle time_style = DateTimeStyle::kUndefined;
std::unique_ptr<icu::SimpleDateFormat> icu_date_format;
// 28. For each row of Table 1, except the header row, do
bool has_hour_option = false;
std::string skeleton;
for (const PatternData& item : GetPatternData(hc)) {
// Need to read fractionalSecondDigits before reading the timeZoneName
if (item.property == "timeZoneName") {
// Let _value_ be ? GetNumberOption(options, "fractionalSecondDigits", 1,
// 3, *undefined*). The *undefined* is represented by value 0 here.
Maybe<int> maybe_fsd = Intl::GetNumberOption(
isolate, options, factory->fractionalSecondDigits_string(), 1, 3, 0);
MAYBE_RETURN(maybe_fsd, MaybeHandle<JSDateTimeFormat>());
// Convert fractionalSecondDigits to skeleton.
int fsd = maybe_fsd.FromJust();
for (int i = 0; i < fsd; i++) {
skeleton += "S";
}
}
std::unique_ptr<char[]> input;
// i. Let prop be the name given in the Property column of the row.
// ii. Let value be ? GetOption(options, prop, "string", « the strings
// given in the Values column of the row », undefined).
Maybe<bool> maybe_get_option =
Intl::GetStringOption(isolate, options, item.property.c_str(),
item.allowed_values, service, &input);
MAYBE_RETURN(maybe_get_option, Handle<JSDateTimeFormat>());
if (maybe_get_option.FromJust()) {
if (item.property == "hour") {
has_hour_option = true;
}
DCHECK_NOT_NULL(input.get());
// iii. Set opt.[[<prop>]] to value.
skeleton += item.map.find(input.get())->second;
}
}
// 29. Let matcher be ? GetOption(options, "formatMatcher", "string", «
// "basic", "best fit" », "best fit").
enum FormatMatcherOption { kBestFit, kBasic };
// We implement only best fit algorithm, but still need to check
// if the formatMatcher values are in range.
// c. Let matcher be ? GetOption(options, "formatMatcher", "string",
// « "basic", "best fit" », "best fit").
Maybe<FormatMatcherOption> maybe_format_matcher =
Intl::GetStringOption<FormatMatcherOption>(
isolate, options, "formatMatcher", service, {"best fit", "basic"},
{FormatMatcherOption::kBestFit, FormatMatcherOption::kBasic},
FormatMatcherOption::kBestFit);
MAYBE_RETURN(maybe_format_matcher, MaybeHandle<JSDateTimeFormat>());
// TODO(ftang): uncomment the following line and handle format_matcher.
// FormatMatcherOption format_matcher = maybe_format_matcher.FromJust();
// 32. Let dateStyle be ? GetOption(options, "dateStyle", "string", «
// "full", "long", "medium", "short" », undefined).
Maybe<DateTimeStyle> maybe_date_style = Intl::GetStringOption<DateTimeStyle>(
isolate, options, "dateStyle", service,
{"full", "long", "medium", "short"},
{DateTimeStyle::kFull, DateTimeStyle::kLong, DateTimeStyle::kMedium,
DateTimeStyle::kShort},
DateTimeStyle::kUndefined);
MAYBE_RETURN(maybe_date_style, MaybeHandle<JSDateTimeFormat>());
// 33. Set dateTimeFormat.[[DateStyle]] to dateStyle.
date_style = maybe_date_style.FromJust();
// 34. Let timeStyle be ? GetOption(options, "timeStyle", "string", «
// "full", "long", "medium", "short" »).
Maybe<DateTimeStyle> maybe_time_style = Intl::GetStringOption<DateTimeStyle>(
isolate, options, "timeStyle", service,
{"full", "long", "medium", "short"},
{DateTimeStyle::kFull, DateTimeStyle::kLong, DateTimeStyle::kMedium,
DateTimeStyle::kShort},
DateTimeStyle::kUndefined);
MAYBE_RETURN(maybe_time_style, MaybeHandle<JSDateTimeFormat>());
// 35. Set dateTimeFormat.[[TimeStyle]] to timeStyle.
time_style = maybe_time_style.FromJust();
// 36. If timeStyle is not undefined, then
HourCycle dateTimeFormatHourCycle = HourCycle::kUndefined;
if (time_style != DateTimeStyle::kUndefined) {
// a. Set dateTimeFormat.[[HourCycle]] to hc.
dateTimeFormatHourCycle = hc;
}
// 37. If dateStyle or timeStyle are not undefined, then
if (date_style != DateTimeStyle::kUndefined ||
time_style != DateTimeStyle::kUndefined) {
// a. For each row in Table 1, except the header row, do
// i. Let prop be the name given in the Property column of the row.
// ii. Let p be opt.[[<prop>]].
// iii. If p is not undefined, then
// 1. Throw a TypeError exception.
if (skeleton.length() > 0) {
std::string prop;
for (const auto& item : GetPatternItems()) {
for (const auto& pair : item.pairs) {
if (skeleton.find(pair.pattern) != std::string::npos) {
prop.assign(item.property);
break;
}
}
if (!prop.empty()) {
break;
}
}
if (prop.empty() && skeleton.find("S") != std::string::npos) {
prop.assign("fractionalSecondDigits");
}
if (!prop.empty()) {
THROW_NEW_ERROR(
isolate,
NewTypeError(MessageTemplate::kCantSetOptionXWhenYIsUsed,
factory->NewStringFromAsciiChecked(prop.c_str()),
date_style != DateTimeStyle::kUndefined
? factory->dateStyle_string()
: factory->timeStyle_string()),
JSDateTimeFormat);
}
UNREACHABLE();
}
// b. Let pattern be DateTimeStylePattern(dateStyle, timeStyle,
// dataLocaleData, hc).
isolate->CountUsage(
v8::Isolate::UseCounterFeature::kDateTimeFormatDateTimeStyle);
icu_date_format =
DateTimeStylePattern(date_style, time_style, icu_locale,
dateTimeFormatHourCycle, generator.get());
if (icu_date_format.get() == nullptr) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
JSDateTimeFormat);
}
} else {
// e. If dateTimeFormat.[[Hour]] is not undefined, then
if (has_hour_option) {
// v. Set dateTimeFormat.[[HourCycle]] to hc.
dateTimeFormatHourCycle = hc;
} else {
// f. Else,
// Set dateTimeFormat.[[HourCycle]] to undefined.
dateTimeFormatHourCycle = HourCycle::kUndefined;
}
icu::UnicodeString skeleton_ustr(skeleton.c_str());
icu_date_format = CreateICUDateFormatFromCache(
icu_locale, skeleton_ustr, generator.get(), dateTimeFormatHourCycle);
if (icu_date_format.get() == nullptr) {
// Remove extensions and try again.
icu_locale = icu::Locale(icu_locale.getBaseName());
icu_date_format = CreateICUDateFormatFromCache(
icu_locale, skeleton_ustr, generator.get(), dateTimeFormatHourCycle);
if (icu_date_format.get() == nullptr) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError),
JSDateTimeFormat);
}
}
}
// The creation of Calendar depends on timeZone so we have to put 13 after 17.
// Also icu_date_format is not created until here.
// 13. Set dateTimeFormat.[[Calendar]] to r.[[ca]].
icu_date_format->adoptCalendar(calendar.release());
// 12.1.1 InitializeDateTimeFormat ( dateTimeFormat, locales, options )
//
// Steps 8-9 set opt.[[hc]] to value *other than undefined*
// if "hour12" is set or "hourCycle" is set in the option.
//
// 9.2.6 ResolveLocale (... )
// Step 8.h / 8.i and 8.k
//
// An hour12 option always overrides an hourCycle option.
// Additionally hour12 and hourCycle both clear out any existing Unicode
// extension key in the input locale.
//
// See details in https://github.com/tc39/test262/pull/2035
if (maybe_get_hour12.FromJust() ||
maybe_hour_cycle.FromJust() != HourCycle::kUndefined) {
auto hc_extension_it = r.extensions.find("hc");
if (hc_extension_it != r.extensions.end()) {
if (dateTimeFormatHourCycle !=
ToHourCycle(hc_extension_it->second.c_str())) {
// Remove -hc- if it does not agree with what we used.
UErrorCode status = U_ZERO_ERROR;
resolved_locale.setUnicodeKeywordValue("hc", nullptr, status);
DCHECK(U_SUCCESS(status));
}
}
}
Maybe<std::string> maybe_locale_str = Intl::ToLanguageTag(resolved_locale);
MAYBE_RETURN(maybe_locale_str, MaybeHandle<JSDateTimeFormat>());
Handle<String> locale_str = isolate->factory()->NewStringFromAsciiChecked(
maybe_locale_str.FromJust().c_str());
Handle<Managed<icu::Locale>> managed_locale =
Managed<icu::Locale>::FromRawPtr(isolate, 0, icu_locale.clone());
Handle<Managed<icu::SimpleDateFormat>> managed_format =
Managed<icu::SimpleDateFormat>::FromUniquePtr(isolate, 0,
std::move(icu_date_format));
Handle<Managed<icu::DateIntervalFormat>> managed_interval_format =
Managed<icu::DateIntervalFormat>::FromRawPtr(isolate, 0, nullptr);
// Now all properties are ready, so we can allocate the result object.
Handle<JSDateTimeFormat> date_time_format = Handle<JSDateTimeFormat>::cast(
isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
DisallowHeapAllocation no_gc;
date_time_format->set_flags(0);
if (date_style != DateTimeStyle::kUndefined) {
date_time_format->set_date_style(date_style);
}
if (time_style != DateTimeStyle::kUndefined) {
date_time_format->set_time_style(time_style);
}
date_time_format->set_hour_cycle(dateTimeFormatHourCycle);
date_time_format->set_locale(*locale_str);
date_time_format->set_icu_locale(*managed_locale);
date_time_format->set_icu_simple_date_format(*managed_format);
date_time_format->set_icu_date_interval_format(*managed_interval_format);
return date_time_format;
}
namespace {
// The list comes from third_party/icu/source/i18n/unicode/udat.h.
// They're mapped to DateTimeFormat components listed at
// https://tc39.github.io/ecma402/#sec-datetimeformat-abstracts .
Handle<String> IcuDateFieldIdToDateType(int32_t field_id, Isolate* isolate) {
switch (field_id) {
case -1:
return isolate->factory()->literal_string();
case UDAT_YEAR_FIELD:
case UDAT_EXTENDED_YEAR_FIELD:
return isolate->factory()->year_string();
case UDAT_YEAR_NAME_FIELD:
return isolate->factory()->yearName_string();
case UDAT_MONTH_FIELD:
case UDAT_STANDALONE_MONTH_FIELD:
return isolate->factory()->month_string();
case UDAT_DATE_FIELD:
return isolate->factory()->day_string();
case UDAT_HOUR_OF_DAY1_FIELD:
case UDAT_HOUR_OF_DAY0_FIELD:
case UDAT_HOUR1_FIELD:
case UDAT_HOUR0_FIELD:
return isolate->factory()->hour_string();
case UDAT_MINUTE_FIELD:
return isolate->factory()->minute_string();
case UDAT_SECOND_FIELD:
return isolate->factory()->second_string();
case UDAT_DAY_OF_WEEK_FIELD:
case UDAT_DOW_LOCAL_FIELD:
case UDAT_STANDALONE_DAY_FIELD:
return isolate->factory()->weekday_string();
case UDAT_AM_PM_FIELD:
case UDAT_AM_PM_MIDNIGHT_NOON_FIELD:
case UDAT_FLEXIBLE_DAY_PERIOD_FIELD:
return isolate->factory()->dayPeriod_string();
case UDAT_TIMEZONE_FIELD:
case UDAT_TIMEZONE_RFC_FIELD:
case UDAT_TIMEZONE_GENERIC_FIELD:
case UDAT_TIMEZONE_SPECIAL_FIELD:
case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD:
case UDAT_TIMEZONE_ISO_FIELD:
case UDAT_TIMEZONE_ISO_LOCAL_FIELD:
return isolate->factory()->timeZoneName_string();
case UDAT_ERA_FIELD:
return isolate->factory()->era_string();
case UDAT_FRACTIONAL_SECOND_FIELD:
return isolate->factory()->fractionalSecond_string();
case UDAT_RELATED_YEAR_FIELD:
return isolate->factory()->relatedYear_string();
case UDAT_QUARTER_FIELD:
case UDAT_STANDALONE_QUARTER_FIELD:
default:
// Other UDAT_*_FIELD's cannot show up because there is no way to specify
// them via options of Intl.DateTimeFormat.
UNREACHABLE();
// To prevent MSVC from issuing C4715 warning.
return Handle<String>();
}
}
} // namespace
MaybeHandle<JSArray> JSDateTimeFormat::FormatToParts(
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format,
double date_value) {
Factory* factory = isolate->factory();
icu::SimpleDateFormat* format =
date_time_format->icu_simple_date_format().raw();
DCHECK_NOT_NULL(format);
icu::UnicodeString formatted;
icu::FieldPositionIterator fp_iter;
icu::FieldPosition fp;
UErrorCode status = U_ZERO_ERROR;
format->format(date_value, formatted, &fp_iter, status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), JSArray);
}
Handle<JSArray> result = factory->NewJSArray(0);
int32_t length = formatted.length();
if (length == 0) return result;
int index = 0;
int32_t previous_end_pos = 0;
Handle<String> substring;
while (fp_iter.next(fp)) {
int32_t begin_pos = fp.getBeginIndex();
int32_t end_pos = fp.getEndIndex();
if (previous_end_pos < begin_pos) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, substring,
Intl::ToString(isolate, formatted, previous_end_pos, begin_pos),
JSArray);
Intl::AddElement(isolate, result, index,
IcuDateFieldIdToDateType(-1, isolate), substring);
++index;
}
ASSIGN_RETURN_ON_EXCEPTION(
isolate, substring,
Intl::ToString(isolate, formatted, begin_pos, end_pos), JSArray);
Intl::AddElement(isolate, result, index,
IcuDateFieldIdToDateType(fp.getField(), isolate),
substring);
previous_end_pos = end_pos;
++index;
}
if (previous_end_pos < length) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, substring,
Intl::ToString(isolate, formatted, previous_end_pos, length), JSArray);
Intl::AddElement(isolate, result, index,
IcuDateFieldIdToDateType(-1, isolate), substring);
}
JSObject::ValidateElements(*result);
return result;
}
const std::set<std::string>& JSDateTimeFormat::GetAvailableLocales() {
return Intl::GetAvailableLocalesForDateFormat();
}
Handle<String> JSDateTimeFormat::HourCycleAsString() const {
switch (hour_cycle()) {
case HourCycle::kUndefined:
return GetReadOnlyRoots().undefined_string_handle();
case HourCycle::kH11:
return GetReadOnlyRoots().h11_string_handle();
case HourCycle::kH12:
return GetReadOnlyRoots().h12_string_handle();
case HourCycle::kH23:
return GetReadOnlyRoots().h23_string_handle();
case HourCycle::kH24:
return GetReadOnlyRoots().h24_string_handle();
default:
UNREACHABLE();
}
}
enum Source { kShared, kStartRange, kEndRange };
namespace {
class SourceTracker {
public:
SourceTracker() { start_[0] = start_[1] = limit_[0] = limit_[1] = 0; }
void Add(int32_t field, int32_t start, int32_t limit) {
DCHECK_LT(field, 2);
start_[field] = start;
limit_[field] = limit;
}
Source GetSource(int32_t start, int32_t limit) const {
Source source = Source::kShared;
if (FieldContains(0, start, limit)) {
source = Source::kStartRange;
} else if (FieldContains(1, start, limit)) {
source = Source::kEndRange;
}
return source;
}
private:
int32_t start_[2];
int32_t limit_[2];
bool FieldContains(int32_t field, int32_t start, int32_t limit) const {
DCHECK_LT(field, 2);
return (start_[field] <= start) && (start <= limit_[field]) &&
(start_[field] <= limit) && (limit <= limit_[field]);
}
};
Handle<String> SourceString(Isolate* isolate, Source source) {
switch (source) {
case Source::kShared:
return ReadOnlyRoots(isolate).shared_string_handle();
case Source::kStartRange:
return ReadOnlyRoots(isolate).startRange_string_handle();
case Source::kEndRange:
return ReadOnlyRoots(isolate).endRange_string_handle();
UNREACHABLE();
}
}
Maybe<bool> AddPartForFormatRange(Isolate* isolate, Handle<JSArray> array,
const icu::UnicodeString& string,
int32_t index, int32_t field, int32_t start,
int32_t end, const SourceTracker& tracker) {
Handle<String> substring;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, substring,
Intl::ToString(isolate, string, start, end),
Nothing<bool>());
Intl::AddElement(isolate, array, index,
IcuDateFieldIdToDateType(field, isolate), substring,
isolate->factory()->source_string(),
SourceString(isolate, tracker.GetSource(start, end)));
return Just(true);
}
// A helper function to convert the FormattedDateInterval to a
// MaybeHandle<JSArray> for the implementation of formatRangeToParts.
MaybeHandle<JSArray> FormattedDateIntervalToJSArray(
Isolate* isolate, const icu::FormattedValue& formatted) {
UErrorCode status = U_ZERO_ERROR;
icu::UnicodeString result = formatted.toString(status);
Factory* factory = isolate->factory();
Handle<JSArray> array = factory->NewJSArray(0);
icu::ConstrainedFieldPosition cfpos;
int index = 0;
int32_t previous_end_pos = 0;
SourceTracker tracker;
while (formatted.nextPosition(cfpos, status)) {
int32_t category = cfpos.getCategory();
int32_t field = cfpos.getField();
int32_t start = cfpos.getStart();
int32_t limit = cfpos.getLimit();
if (category == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) {
DCHECK_LE(field, 2);
tracker.Add(field, start, limit);
} else {
DCHECK(category == UFIELD_CATEGORY_DATE);
if (start > previous_end_pos) {
// Add "literal" from the previous end position to the start if
// necessary.
Maybe<bool> maybe_added =
AddPartForFormatRange(isolate, array, result, index, -1,
previous_end_pos, start, tracker);
MAYBE_RETURN(maybe_added, Handle<JSArray>());
previous_end_pos = start;
index++;
}
Maybe<bool> maybe_added = AddPartForFormatRange(
isolate, array, result, index, field, start, limit, tracker);
MAYBE_RETURN(maybe_added, Handle<JSArray>());
previous_end_pos = limit;
++index;
}
}
int32_t end = result.length();
// Add "literal" in the end if necessary.
if (end > previous_end_pos) {
Maybe<bool> maybe_added = AddPartForFormatRange(
isolate, array, result, index, -1, previous_end_pos, end, tracker);
MAYBE_RETURN(maybe_added, Handle<JSArray>());
}
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), JSArray);
}
JSObject::ValidateElements(*array);
return array;
}
// The shared code between formatRange and formatRangeToParts
template <typename T>
MaybeHandle<T> FormatRangeCommon(
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x,
double y,
MaybeHandle<T> (*formatToResult)(Isolate*, const icu::FormattedValue&)) {
// Track newer feature formateRange and formatRangeToParts
isolate->CountUsage(v8::Isolate::UseCounterFeature::kDateTimeFormatRange);
// #sec-partitiondatetimerangepattern
// 1. Let x be TimeClip(x).
x = DateCache::TimeClip(x);
// 2. If x is NaN, throw a RangeError exception.
if (std::isnan(x)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
T);
}
// 3. Let y be TimeClip(y).
y = DateCache::TimeClip(y);
// 4. If y is NaN, throw a RangeError exception.
if (std::isnan(y)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue),
T);
}
icu::DateIntervalFormat* format =
LazyCreateDateIntervalFormat(isolate, date_time_format);
if (format == nullptr) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), T);
}
UErrorCode status = U_ZERO_ERROR;
icu::SimpleDateFormat* date_format =
date_time_format->icu_simple_date_format().raw();
const icu::Calendar* calendar = date_format->getCalendar();
std::unique_ptr<icu::Calendar> c1(calendar->clone());
std::unique_ptr<icu::Calendar> c2(calendar->clone());
c1->setTime(x, status);
c2->setTime(y, status);
// We need to format by Calendar because we need the Gregorian change
// adjustment already in the SimpleDateFormat to set the correct value of date
// older than Oct 15, 1582.
icu::FormattedDateInterval formatted =
format->formatToValue(*c1, *c2, status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), T);
}
return formatToResult(isolate, formatted);
}
} // namespace
MaybeHandle<String> JSDateTimeFormat::FormatRange(
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x,
double y) {
return FormatRangeCommon<String>(isolate, date_time_format, x, y,
Intl::FormattedToString);
}
MaybeHandle<JSArray> JSDateTimeFormat::FormatRangeToParts(
Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x,
double y) {
return FormatRangeCommon<JSArray>(isolate, date_time_format, x, y,
FormattedDateIntervalToJSArray);
}
} // namespace internal
} // namespace v8