#include "src/objects/js-date-time-format.h"
#include <algorithm>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "src/base/bit-field.h"
#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"
#ifdef V8_TEMPORAL_SUPPORT
#include "src/objects/js-temporal-objects-inl.h"
#include "temporal_rs/AnyCalendarKind.hpp"
#include "temporal_rs/Instant.hpp"
#endif
#include "src/objects/managed-inl.h"
#include "src/objects/option-utils.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"
#ifndef V8_INTL_SUPPORT
#error Internationalization is expected to be enabled.
#endif
namespace v8::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;
}
}
JSDateTimeFormat::HourCycle DefaultHourCycle12(
const icu::Locale& locale, JSDateTimeFormat::HourCycle defaultHourCycle) {
if (defaultHourCycle == JSDateTimeFormat::HourCycle::kH11 ||
defaultHourCycle == JSDateTimeFormat::HourCycle::kH12) {
return defaultHourCycle;
}
if (std::strcmp(locale.getCountry(), "JP") == 0) {
return JSDateTimeFormat::HourCycle::kH11;
}
return JSDateTimeFormat::HourCycle::kH12;
}
JSDateTimeFormat::HourCycle DefaultHourCycle24(
const icu::Locale& locale, JSDateTimeFormat::HourCycle defaultHourCycle) {
if (defaultHourCycle == JSDateTimeFormat::HourCycle::kH23 ||
defaultHourCycle == JSDateTimeFormat::HourCycle::kH24) {
return defaultHourCycle;
}
return JSDateTimeFormat::HourCycle::kH23;
}
Maybe<JSDateTimeFormat::HourCycle> GetHourCycle(
Isolate* isolate, DirectHandle<JSReceiver> options,
const char* method_name) {
return GetStringOption<JSDateTimeFormat::HourCycle>(
isolate, options, isolate->factory()->hourCycle_string(), method_name,
std::to_array<const std::string_view>({"h11", "h12", "h23", "h24"}),
std::array{
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;
};
#define BIT_FIELDS(V, _) \
V(Era, bool, 1, _) \
V(Year, bool, 1, _) \
V(Month, bool, 1, _) \
V(Weekday, bool, 1, _) \
V(Day, bool, 1, _) \
V(DayPeriod, bool, 1, _) \
V(Hour, bool, 1, _) \
V(Minute, bool, 1, _) \
V(Second, bool, 1, _) \
V(TimeZoneName, bool, 1, _) \
V(FractionalSecondDigits, bool, 1, _)
DEFINE_BIT_FIELDS(BIT_FIELDS)
#undef BIT_FIELDS
enum class DateTimeProperty {
kWeekday,
kEra,
kYear,
kMonth,
kDay,
kDayPeriod,
kHour,
kMinute,
kSecond,
kTimeZoneName,
};
class PatternItem {
public:
PatternItem(int32_t shift, DateTimeProperty property,
std::vector<PatternMap> pairs,
std::span<const std::string_view> allowed_values)
: bitShift(shift),
property(property),
pairs(std::move(pairs)),
allowed_values(allowed_values) {}
virtual ~PatternItem() = default;
int32_t bitShift;
DateTimeProperty property;
std::vector<PatternMap> pairs;
std::span<const std::string_view> allowed_values;
};
static const auto k2DigitNumeric =
std::to_array<std::string_view>({"2-digit", "numeric"});
static std::vector<PatternItem> BuildPatternItems() {
static const auto kNarrowLongShort =
std::to_array<std::string_view>({"narrow", "long", "short"});
static const auto kNarrowLongShort2DigitNumeric =
std::to_array<std::string_view>(
{"narrow", "long", "short", "2-digit", "numeric"});
std::vector<PatternItem> items = {
PatternItem(Weekday::kShift, DateTimeProperty::kWeekday,
{{"EEEEE", "narrow"},
{"EEEE", "long"},
{"EEE", "short"},
{"ccccc", "narrow"},
{"cccc", "long"},
{"ccc", "short"}},
kNarrowLongShort),
PatternItem(Era::kShift, DateTimeProperty::kEra,
{{"GGGGG", "narrow"}, {"GGGG", "long"}, {"GGG", "short"}},
kNarrowLongShort),
PatternItem(Year::kShift, DateTimeProperty::kYear,
{{"yy", "2-digit"}, {"y", "numeric"}}, k2DigitNumeric)};
items.push_back(PatternItem(Month::kShift, DateTimeProperty::kMonth,
{{"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::kShift, DateTimeProperty::kDay,
{{"dd", "2-digit"}, {"d", "numeric"}},
k2DigitNumeric));
items.push_back(PatternItem(DayPeriod::kShift, DateTimeProperty::kDayPeriod,
{{"BBBBB", "narrow"},
{"bbbbb", "narrow"},
{"BBBB", "long"},
{"bbbb", "long"},
{"B", "short"},
{"b", "short"}},
kNarrowLongShort));
items.push_back(PatternItem(Hour::kShift, DateTimeProperty::kHour,
{{"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::kShift, DateTimeProperty::kMinute,
{{"mm", "2-digit"}, {"m", "numeric"}},
k2DigitNumeric));
items.push_back(PatternItem(Second::kShift, DateTimeProperty::kSecond,
{{"ss", "2-digit"}, {"s", "numeric"}},
k2DigitNumeric));
static const auto kTimezone = std::to_array<std::string_view>(
{"long", "short", "longOffset", "shortOffset", "longGeneric",
"shortGeneric"});
items.push_back(PatternItem(TimeZoneName::kShift,
DateTimeProperty::kTimeZoneName,
{{"zzzz", "long"},
{"z", "short"},
{"OOOO", "longOffset"},
{"O", "shortOffset"},
{"vvvv", "longGeneric"},
{"v", "shortGeneric"}},
kTimezone));
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(int32_t shift, DateTimeProperty property,
std::vector<PatternMap> pairs,
std::span<const std::string_view> allowed_values)
: bitShift(shift), property(property), allowed_values(allowed_values) {
for (const auto& pair : pairs) {
map.insert(std::make_pair(pair.value, pair.pattern));
}
}
virtual ~PatternData() = default;
int32_t bitShift;
DateTimeProperty property;
std::map<const std::string, const std::string, std::less<>> map;
std::span<const std::string_view> allowed_values;
};
Handle<String> GetPropertyString(Factory& factory, DateTimeProperty property) {
switch (property) {
case DateTimeProperty::kWeekday:
return factory.weekday_string();
case DateTimeProperty::kEra:
return factory.era_string();
case DateTimeProperty::kYear:
return factory.year_string();
case DateTimeProperty::kMonth:
return factory.month_string();
case DateTimeProperty::kDay:
return factory.day_string();
case DateTimeProperty::kDayPeriod:
return factory.dayPeriod_string();
case DateTimeProperty::kHour:
return factory.hour_string();
case DateTimeProperty::kMinute:
return factory.minute_string();
case DateTimeProperty::kSecond:
return factory.second_string();
case DateTimeProperty::kTimeZoneName:
return factory.timeZoneName_string();
}
}
const std::vector<PatternData> CreateCommonData(const PatternData& hour_data) {
std::vector<PatternData> build;
for (const PatternItem& item : GetPatternItems()) {
if (item.property == DateTimeProperty::kHour) {
build.push_back(hour_data);
} else {
build.push_back(PatternData(item.bitShift, item.property, item.pairs,
item.allowed_values));
}
}
return build;
}
const std::vector<PatternData> CreateData(const char* digit2,
const char* numeric) {
return CreateCommonData(
PatternData(Hour::kShift, DateTimeProperty::kHour,
{{digit2, "2-digit"}, {numeric, "numeric"}}, k2DigitNumeric));
}
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 "";
}
bool IsAsciiAlpha(char ch) {
return base::IsInRange(ch, 'A', 'Z') || base::IsInRange(ch, 'a', 'z');
}
char LocaleIndependentAsciiToUpper(char ch) {
return (base::IsInRange(ch, 'a', 'z')) ? (ch - 'a' + 'A') : ch;
}
char LocaleIndependentAsciiToLower(char ch) {
return (base::IsInRange(ch, 'A', 'Z')) ? (ch - 'A' + 'a') : ch;
}
std::string ToTitleCaseTimezoneLocation(const std::string& input) {
std::string title_cased;
int word_length = 0;
for (char ch : input) {
if (IsAsciiAlpha(ch)) {
title_cased += word_length == 0 ? LocaleIndependentAsciiToUpper(ch)
: LocaleIndependentAsciiToLower(ch);
word_length++;
} else if (ch == '_' || ch == '-' || ch == '/') {
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 {
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_;
};
}
std::string JSDateTimeFormat::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";
return upper;
} else if (upper.length() == 7 && '0' <= upper[3] && upper[3] <= '9') {
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) {
title[1] = 'S';
}
return title;
} else if (strncmp(upper.c_str(), "SYSTEMV/", 8) == 0) {
upper.replace(0, 8, "SystemV/");
return upper;
}
}
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);
}
namespace {
DirectHandle<String> DateTimeStyleAsString(
Isolate* isolate, JSDateTimeFormat::DateTimeStyle style) {
switch (style) {
case JSDateTimeFormat::DateTimeStyle::kFull:
return isolate->factory()->full_string();
case JSDateTimeFormat::DateTimeStyle::kLong:
return isolate->factory()->long_string();
case JSDateTimeFormat::DateTimeStyle::kMedium:
return isolate->factory()->medium_string();
case JSDateTimeFormat::DateTimeStyle::kShort:
return isolate->factory()->short_string();
case JSDateTimeFormat::DateTimeStyle::kUndefined:
UNREACHABLE();
}
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;
}
}
DirectHandle<Object> JSDateTimeFormat::TimeZoneId(Isolate* isolate,
const icu::TimeZone& tz) {
Factory* factory = isolate->factory();
icu::UnicodeString time_zone;
tz.getID(time_zone);
icu::UnicodeString canonical_time_zone;
if (time_zone == u"GMT") {
canonical_time_zone = u"+00:00";
} else {
UErrorCode status = U_ZERO_ERROR;
icu::TimeZone::getCanonicalID(time_zone, canonical_time_zone, status);
if (U_FAILURE(status)) {
return factory->undefined_value();
}
}
DirectHandle<String> timezone_value;
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, timezone_value,
Intl::TimeZoneIdToString(isolate, canonical_time_zone),
DirectHandle<Object>());
return timezone_value;
}
namespace {
DirectHandle<String> GetCalendar(
Isolate* isolate, const icu::SimpleDateFormat& simple_date_format) {
std::string calendar_str = simple_date_format.getCalendar()->getType();
if (calendar_str == "gregorian") {
calendar_str = "gregory";
} else if (calendar_str == "ethiopic-amete-alem") {
calendar_str = "ethioaa";
}
return isolate->factory()->NewStringFromAsciiChecked(calendar_str.c_str());
}
DirectHandle<Object> GetTimeZone(
Isolate* isolate, const icu::SimpleDateFormat& simple_date_format) {
return JSDateTimeFormat::TimeZoneId(
isolate, simple_date_format.getCalendar()->getTimeZone());
}
}
DirectHandle<String> JSDateTimeFormat::Calendar(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format) {
return GetCalendar(isolate,
*(date_time_format->icu_simple_date_format()->ptr()));
}
DirectHandle<Object> JSDateTimeFormat::TimeZone(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format) {
return GetTimeZone(isolate,
*(date_time_format->icu_simple_date_format()->ptr()));
}
MaybeDirectHandle<JSObject> JSDateTimeFormat::ResolvedOptions(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format) {
Factory* factory = isolate->factory();
DirectHandle<JSObject> options =
factory->NewJSObject(isolate->object_function());
DirectHandle<Object> resolved_obj;
DirectHandle<String> locale(date_time_format->locale(), isolate);
DCHECK(!date_time_format->icu_locale().is_null());
Managed<icu::Locale>::Ptr icu_locale = date_time_format->icu_locale()->ptr();
DCHECK_NOT_NULL(icu_locale);
Managed<icu::SimpleDateFormat>::Ptr icu_simple_date_format =
date_time_format->icu_simple_date_format()->ptr();
DirectHandle<Object> timezone =
JSDateTimeFormat::TimeZone(isolate, date_time_format);
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);
Maybe<bool> maybe_create_locale = JSReceiver::CreateDataProperty(
isolate, options, factory->locale_string(), locale, Just(kDontThrow));
DCHECK(maybe_create_locale.FromJust());
USE(maybe_create_locale);
DirectHandle<String> calendar =
JSDateTimeFormat::Calendar(isolate, date_time_format);
Maybe<bool> maybe_create_calendar = JSReceiver::CreateDataProperty(
isolate, options, factory->calendar_string(), calendar, 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, Just(kDontThrow));
DCHECK(maybe_create_time_zone.FromJust());
USE(maybe_create_time_zone);
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(isolate), Just(kDontThrow));
DCHECK(maybe_create_hour_cycle.FromJust());
USE(maybe_create_hour_cycle);
switch (hc) {
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;
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;
case HourCycle::kUndefined:
break;
}
}
if (date_time_format->date_style() == DateTimeStyle::kUndefined &&
date_time_format->time_style() == DateTimeStyle::kUndefined) {
for (const auto& item : GetPatternItems()) {
if (item.property == DateTimeProperty::kTimeZoneName) {
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, GetPropertyString(*factory, item.property),
factory->NewStringFromAsciiChecked(pair.value.c_str()),
Just(kDontThrow));
DCHECK(maybe_create_property.FromJust());
USE(maybe_create_property);
break;
}
}
}
}
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);
}
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 {
bool IsTemporalObject(DirectHandle<Object> value) {
#ifdef V8_TEMPORAL_SUPPORT
if (!IsJSReceiver(*value)) {
return false;
}
if (!IsJSTemporalPlainDate(*value) && !IsJSTemporalPlainTime(*value) &&
!IsJSTemporalPlainDateTime(*value) &&
!IsJSTemporalZonedDateTime(*value) &&
!IsJSTemporalPlainYearMonth(*value) &&
!IsJSTemporalPlainMonthDay(*value) && !IsJSTemporalInstant(*value)) {
return false;
}
return true;
#else
return false;
#endif
}
bool SameTemporalType(DirectHandle<Object> x, DirectHandle<Object> y) {
#ifdef V8_TEMPORAL_SUPPORT
if (!IsTemporalObject(x)) return false;
if (!IsTemporalObject(y)) return false;
if (IsJSTemporalPlainDate(*x) && !IsJSTemporalPlainDate(*y)) return false;
if (IsJSTemporalPlainTime(*x) && !IsJSTemporalPlainTime(*y)) return false;
if (IsJSTemporalPlainDateTime(*x) && !IsJSTemporalPlainDateTime(*y)) {
return false;
}
if (IsJSTemporalZonedDateTime(*x) && !IsJSTemporalZonedDateTime(*y)) {
return false;
}
if (IsJSTemporalPlainYearMonth(*x) && !IsJSTemporalPlainYearMonth(*y)) {
return false;
}
if (IsJSTemporalPlainMonthDay(*x) && !IsJSTemporalPlainMonthDay(*y)) {
return false;
}
if (IsJSTemporalInstant(*x) && !IsJSTemporalInstant(*y)) return false;
return true;
#else
return false;
#endif
}
enum class PatternKind {
kDate,
kPlainDate,
kPlainDateTime,
kPlainTime,
kPlainYearMonth,
kPlainMonthDay,
kInstant,
};
struct DateTimeValueRecord {
double epoch_milliseconds;
PatternKind kind;
};
#ifdef V8_TEMPORAL_SUPPORT
bool CalendarEquals(temporal_rs::AnyCalendarKind kind,
const icu::SimpleDateFormat& date_time_format) {
using enum temporal_rs::AnyCalendarKind::Value;
std::string_view other_kind = date_time_format.getCalendar()->getType();
switch (kind) {
case Buddhist:
return other_kind == "buddhist";
case Chinese:
return other_kind == "chinese";
case Coptic:
return other_kind == "coptic";
case Dangi:
return other_kind == "dangi";
case Ethiopian:
return other_kind == "ethiopic";
case EthiopianAmeteAlem:
return other_kind == "ethiopic-amete-alem" || other_kind == "ethioaa";
case Gregorian:
return other_kind == "gregorian";
case Hebrew:
return other_kind == "hebrew";
case Indian:
return other_kind == "indian";
case HijriTabularTypeIIFriday:
return other_kind == "islamic-civil" || other_kind == "islamicc" ||
other_kind == "islamic";
case HijriTabularTypeIIThursday:
return other_kind == "islamic-tbla" || other_kind == "islamic";
case HijriUmmAlQura:
return other_kind == "islamic-umalqura" || other_kind == "islamic";
case Iso:
return other_kind == "iso8601";
case Japanese:
case JapaneseExtended:
return other_kind == "japanese";
case Persian:
return other_kind == "persian";
case Roc:
return other_kind == "roc";
}
UNREACHABLE();
}
Maybe<DateTimeValueRecord> HandleDateTimeTemporalInstant(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<JSTemporalInstant> instant, const char* method_name) {
DisallowGarbageCollection no_gc;
double milliseconds = instant->instant()->raw(no_gc)->epoch_milliseconds();
return Just(DateTimeValueRecord{milliseconds, PatternKind::kInstant});
}
template <typename T>
Maybe<DateTimeValueRecord> HandleDateTimeTemporalGeneric(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
PatternKind kind, DirectHandle<T> temporal) {
Managed<icu::SimpleDateFormat>::Ptr icu_date_format =
date_time_format->icu_simple_date_format()->ptr();
if constexpr (T::kTypeContainsCalendar) {
auto calendar_kind = temporal->wrapped_rust().calendar().kind();
bool throw_mismatch_calendar =
!CalendarEquals(calendar_kind, *icu_date_format);
if (std::is_same<T, JSTemporalPlainDateTime>::value ||
std::is_same<T, JSTemporalPlainDate>::value) {
throw_mismatch_calendar &=
(calendar_kind != temporal_rs::AnyCalendarKind::Value::Iso);
}
if (throw_mismatch_calendar) {
THROW_NEW_ERROR(isolate,
NewRangeError(MessageTemplate::kMismatchedCalendars));
}
}
DirectHandle<Object> time_zone_obj = GetTimeZone(isolate, *icu_date_format);
DirectHandle<String> time_zone_str;
ASSIGN_RETURN_ON_EXCEPTION(isolate, time_zone_str,
Object::ToString(isolate, time_zone_obj));
int64_t epoch_milliseconds = 0;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, epoch_milliseconds,
temporal->GetEpochMillisecondsFor(isolate, time_zone_str->ToStdString()));
return Just(
DateTimeValueRecord{static_cast<double>(epoch_milliseconds), kind});
}
Maybe<DateTimeValueRecord> HandleDateTimeTemporalYearMonth(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<JSTemporalPlainYearMonth> temporal_year_month,
const char* method_name) {
DateTimeValueRecord res;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, res,
HandleDateTimeTemporalGeneric<JSTemporalPlainYearMonth>(
isolate, date_time_format, PatternKind::kPlainYearMonth,
temporal_year_month));
if ((date_time_format->date_style() ==
JSDateTimeFormat::DateTimeStyle::kUndefined) &&
(date_time_format->time_style() !=
JSDateTimeFormat::DateTimeStyle::kUndefined)) {
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kInvalidArgumentForTemporal,
temporal_year_month));
}
return Just(res);
}
Maybe<DateTimeValueRecord> HandleDateTimeTemporalMonthDay(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<JSTemporalPlainMonthDay> temporal_month_day,
const char* method_name) {
DateTimeValueRecord res;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, res,
HandleDateTimeTemporalGeneric<JSTemporalPlainMonthDay>(
isolate, date_time_format, PatternKind::kPlainMonthDay,
temporal_month_day));
if ((date_time_format->date_style() ==
JSDateTimeFormat::DateTimeStyle::kUndefined) &&
(date_time_format->time_style() !=
JSDateTimeFormat::DateTimeStyle::kUndefined)) {
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kInvalidArgumentForTemporal,
temporal_month_day));
}
return Just(res);
}
Maybe<DateTimeValueRecord> HandleDateTimeTemporalDateTime(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<JSTemporalPlainDateTime> date_time, const char* method_name) {
return HandleDateTimeTemporalGeneric<JSTemporalPlainDateTime>(
isolate, date_time_format, PatternKind::kPlainDateTime, date_time);
}
Maybe<DateTimeValueRecord> HandleDateTimeTemporalDate(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<JSTemporalPlainDate> temporal_date, const char* method_name) {
DateTimeValueRecord res;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, res,
HandleDateTimeTemporalGeneric<JSTemporalPlainDate>(
isolate, date_time_format, PatternKind::kPlainDate, temporal_date));
if ((date_time_format->date_style() ==
JSDateTimeFormat::DateTimeStyle::kUndefined) &&
(date_time_format->time_style() !=
JSDateTimeFormat::DateTimeStyle::kUndefined)) {
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kInvalidArgumentForTemporal,
temporal_date));
}
return Just(res);
}
Maybe<DateTimeValueRecord> HandleDateTimeTemporalTime(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<JSTemporalPlainTime> temporal_time, const char* method_name) {
DateTimeValueRecord res;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, res,
HandleDateTimeTemporalGeneric<JSTemporalPlainTime>(
isolate, date_time_format, PatternKind::kPlainTime, temporal_time));
if ((date_time_format->date_style() !=
JSDateTimeFormat::DateTimeStyle::kUndefined) &&
(date_time_format->time_style() ==
JSDateTimeFormat::DateTimeStyle::kUndefined)) {
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kInvalidArgumentForTemporal,
temporal_time));
}
return Just(res);
}
#endif
Maybe<DateTimeValueRecord> HandleDateTimeOthers(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<Object> x_obj, const char* method_name) {
DCHECK(!IsTemporalObject(x_obj));
double x;
if (IsUndefined(*x_obj)) {
x = static_cast<double>(JSDate::CurrentTimeValue(isolate));
} else {
ASSIGN_RETURN_ON_EXCEPTION(isolate, x_obj,
Object::ToNumber(isolate, x_obj));
x = Object::NumberValue(*x_obj);
}
if (!DateCache::TryTimeClip(&x)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue));
}
return Just(DateTimeValueRecord({x, PatternKind::kDate}));
}
Maybe<DateTimeValueRecord> HandleDateTimeValue(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<Object> x, const char* method_name) {
#ifdef V8_TEMPORAL_SUPPORT
if (IsTemporalObject(x)) {
if (IsJSTemporalPlainDate(*x)) {
return HandleDateTimeTemporalDate(
isolate, date_time_format, Cast<JSTemporalPlainDate>(x), method_name);
}
if (IsJSTemporalPlainYearMonth(*x)) {
return HandleDateTimeTemporalYearMonth(isolate, date_time_format,
Cast<JSTemporalPlainYearMonth>(x),
method_name);
}
if (IsJSTemporalPlainMonthDay(*x)) {
return HandleDateTimeTemporalMonthDay(isolate, date_time_format,
Cast<JSTemporalPlainMonthDay>(x),
method_name);
}
if (IsJSTemporalPlainTime(*x)) {
return HandleDateTimeTemporalTime(
isolate, date_time_format, Cast<JSTemporalPlainTime>(x), method_name);
}
if (IsJSTemporalPlainDateTime(*x)) {
return HandleDateTimeTemporalDateTime(isolate, date_time_format,
Cast<JSTemporalPlainDateTime>(x),
method_name);
}
if (IsJSTemporalInstant(*x)) {
return HandleDateTimeTemporalInstant(
isolate, date_time_format, Cast<JSTemporalInstant>(x), method_name);
}
THROW_NEW_ERROR(
isolate, NewTypeError(MessageTemplate::kInvalidArgumentForTemporal, x));
}
#endif
return HandleDateTimeOthers(isolate, date_time_format, x, method_name);
}
char16_t EqualventSkeletonchar(char16_t in) {
switch (in) {
case 'L':
return 'M';
case 'h':
return 'j';
case 'H':
return 'j';
case 'k':
return 'j';
case 'K':
return 'j';
case 'O':
return 'z';
case 'v':
return 'z';
default:
return '\0';
}
}
icu::UnicodeString AdjustDateTimeStyleFormat(
const icu::UnicodeString& input,
const std::set<char16_t>& allowed_options) {
std::set<char16_t> allowed(allowed_options);
for (int ch : allowed_options) {
auto also = EqualventSkeletonchar(ch);
if (also) {
allowed.emplace(also);
}
}
icu::UnicodeString result;
for (int32_t i = 0; i < input.length(); i++) {
char16_t ch = input.charAt(i);
if (allowed.count(ch) > 0) {
result.append(ch);
}
}
return result;
}
enum Inherit { kAll, kRelevant };
enum EraOp { kCopyEra, kIgnoreEra };
enum HourCycleOp { kCopyHourCycle, kIgnoreHourCycle };
icu::UnicodeString GetDateTimeFormat(const icu::UnicodeString& options,
int32_t explicit_components_in_options,
const std::set<char16_t>& required_options,
const std::set<char16_t>& defaults_options,
Inherit inherit, EraOp era_op,
HourCycleOp hour_cycle_op,
bool defaults_is_zone_date_time = false) {
icu::UnicodeString format_options;
if (inherit == Inherit::kAll) {
format_options = options;
} else {
if (era_op == EraOp::kCopyEra) {
for (int32_t i = 0; i < options.length(); i++) {
char16_t ch = options.charAt(i);
if (ch == 'G') {
format_options.append(ch);
}
}
}
if (hour_cycle_op == HourCycleOp::kCopyHourCycle) {
for (int32_t i = 0; i < options.length(); i++) {
char16_t ch = options.charAt(i);
if (ch == 'h' || ch == 'H' || ch == 'k' || ch == 'K') {
format_options.append(ch);
}
}
}
}
bool any_present =
Weekday::decode(explicit_components_in_options) ||
Year::decode(explicit_components_in_options) ||
Month::decode(explicit_components_in_options) ||
Day::decode(explicit_components_in_options) ||
DayPeriod::decode(explicit_components_in_options) ||
Hour::decode(explicit_components_in_options) ||
Minute::decode(explicit_components_in_options) ||
Second::decode(explicit_components_in_options) ||
FractionalSecondDigits::decode(explicit_components_in_options);
icu::UnicodeString result;
bool need_defaults = true;
std::set<char16_t> to_be_added(defaults_options);
char16_t last_ch = '\0';
for (int32_t i = 0; i < options.length(); i++) {
char16_t ch = options.charAt(i);
if (required_options.find(ch) != required_options.end()) {
to_be_added.erase(ch);
auto also = EqualventSkeletonchar(ch);
if (also) {
to_be_added.erase(also);
}
if (last_ch != ch) {
int32_t found = -1;
while ((found = format_options.indexOf(ch)) >= 0) {
format_options.remove(found, 1);
}
}
format_options.append(ch);
need_defaults = false;
}
last_ch = ch;
}
if (need_defaults) {
if (any_present && inherit == Inherit::kRelevant) {
return "";
}
for (auto it = to_be_added.begin(); it != to_be_added.end(); ++it) {
format_options.append(*it);
}
if (defaults_is_zone_date_time &&
(format_options.indexOf('z') < 0 && format_options.indexOf('O') < 0 &&
format_options.indexOf('v') < 0)) {
format_options.append('z');
}
}
return format_options;
}
std::set<char16_t> ExplicitComponentsSet(int32_t components) {
std::set<char16_t> result;
if (Weekday::decode(components)) {
result.insert('E');
result.insert('c');
}
if (Era::decode(components)) {
result.insert('G');
}
if (Year::decode(components)) {
result.insert('y');
}
if (Month::decode(components)) {
result.insert('M');
result.insert('L');
}
if (Day::decode(components)) {
result.insert('d');
}
if (DayPeriod::decode(components)) {
result.insert('B');
result.insert('b');
}
if (Hour::decode(components)) {
result.insert('H');
result.insert('h');
result.insert('K');
result.insert('k');
}
if (Minute::decode(components)) {
result.insert('m');
}
if (Second::decode(components)) {
result.insert('s');
}
if (TimeZoneName::decode(components)) {
result.insert('z');
result.insert('O');
result.insert('v');
}
if (FractionalSecondDigits::decode(components)) {
result.insert('S');
}
return result;
}
const icu::UnicodeString OrigionalOptions(
const icu::UnicodeString& best_format,
int32_t explicit_components_in_options) {
std::set<char16_t> keep =
ExplicitComponentsSet(explicit_components_in_options);
icu::UnicodeString result;
for (int32_t i = 0; i < best_format.length(); i++) {
char16_t ch = best_format.charAt(i);
if (keep.find(ch) != keep.end()) {
result.append(ch);
}
}
return result;
}
icu::UnicodeString GetSkeletonForPatternKind(
const icu::UnicodeString& best_format,
int32_t explicit_components_in_options, PatternKind kind,
JSDateTimeFormat::DateTimeStyle date_style,
JSDateTimeFormat::DateTimeStyle time_style,
bool has_to_locale_string_time_zone) {
if (kind == PatternKind::kDate) {
return best_format;
}
if (date_style != JSDateTimeFormat::DateTimeStyle::kUndefined ||
time_style != JSDateTimeFormat::DateTimeStyle::kUndefined) {
if (date_style != JSDateTimeFormat::DateTimeStyle::kUndefined) {
if (kind == PatternKind::kPlainDate) {
return AdjustDateTimeStyleFormat(
best_format,
{'E', 'c', 'G', 'y', 'M', 'L', 'd'});
}
if (kind == PatternKind::kPlainYearMonth) {
return AdjustDateTimeStyleFormat(best_format,
{'G', 'y', 'M', 'L'});
}
if (kind == PatternKind::kPlainMonthDay) {
return AdjustDateTimeStyleFormat(best_format,
{'M', 'L', 'd'});
}
}
if (time_style != JSDateTimeFormat::DateTimeStyle::kUndefined) {
if (kind == PatternKind::kPlainTime) {
return AdjustDateTimeStyleFormat(
best_format,
{'h', 'H', 'k', 'K', 'j', 'm', 's', 'B', 'b', 'a', 'S'});
}
}
switch (kind) {
case PatternKind::kPlainDateTime:
return AdjustDateTimeStyleFormat(
best_format,
{'E', 'c', 'G', 'y', 'M', 'L', 'd', 'h', 'H', 'k', 'K', 'j', 'm',
's', 'B', 'b', 'a', 'S'});
case PatternKind::kInstant:
return best_format;
default:
FATAL("unreachable");
}
} else {
const icu::UnicodeString& options =
OrigionalOptions(best_format, explicit_components_in_options);
static const std::initializer_list<char16_t> kRequiredAny{
'E', 'c', 'G', 'y', 'M', 'L', 'd', 'h', 'H',
'k', 'K', 'j', 'm', 's', 'B', 'b', 'a', 'S'};
static const std::initializer_list<char16_t> kDefaultsAll{'y', 'M', 'd',
'j', 'm', 's'};
switch (kind) {
case PatternKind::kPlainDate: {
static const std::initializer_list<char16_t> kRequiredDate{
'E', 'c', 'G', 'y', 'M', 'L', 'd'};
static const std::initializer_list<char16_t> kDefaultsDate{'y', 'M',
'd'};
return GetDateTimeFormat(options, explicit_components_in_options,
kRequiredDate, kDefaultsDate,
Inherit::kRelevant, EraOp::kCopyEra,
HourCycleOp::kIgnoreHourCycle);
}
case PatternKind::kPlainYearMonth: {
static const std::initializer_list<char16_t> kRequiredYearMonth{
'G', 'y', 'M', 'L'};
static const std::initializer_list<char16_t> kDefaultsYearMonth{'y',
'M'};
return GetDateTimeFormat(options, explicit_components_in_options,
kRequiredYearMonth, kDefaultsYearMonth,
Inherit::kRelevant, EraOp::kCopyEra,
HourCycleOp::kIgnoreHourCycle);
}
case PatternKind::kPlainMonthDay: {
static const std::initializer_list<char16_t> kRequiredMonthDay{'M', 'L',
'd'};
static const std::initializer_list<char16_t> kDefaultsMonthDay{'M',
'd'};
return GetDateTimeFormat(options, explicit_components_in_options,
kRequiredMonthDay, kDefaultsMonthDay,
Inherit::kRelevant, EraOp::kIgnoreEra,
HourCycleOp::kIgnoreHourCycle);
}
case PatternKind::kPlainTime: {
static const std::initializer_list<char16_t> kRequiredTime{
'h', 'H', 'k', 'K', 'j', 'm', 's', 'B', 'b', 'a', 'S'};
static const std::initializer_list<char16_t> kDefaultsTime{'j', 'm',
's'};
return GetDateTimeFormat(options, explicit_components_in_options,
kRequiredTime, kDefaultsTime,
Inherit::kRelevant, EraOp::kIgnoreEra,
HourCycleOp::kCopyHourCycle);
}
case PatternKind::kPlainDateTime:
return GetDateTimeFormat(options, explicit_components_in_options,
kRequiredAny, kDefaultsAll, Inherit::kRelevant,
EraOp::kCopyEra, HourCycleOp::kCopyHourCycle);
case PatternKind::kInstant:
if (has_to_locale_string_time_zone) {
return GetDateTimeFormat(options, explicit_components_in_options,
kRequiredAny, kDefaultsAll, Inherit::kAll,
EraOp::kCopyEra, HourCycleOp::kCopyHourCycle,
true);
} else {
return GetDateTimeFormat(options, explicit_components_in_options,
kRequiredAny, kDefaultsAll, Inherit::kAll,
EraOp::kCopyEra,
HourCycleOp::kCopyHourCycle);
}
default:
FATAL("unreachable");
}
}
}
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;
}
std::unique_ptr<icu::SimpleDateFormat> GetSimpleDateTimeForTemporal(
const icu::SimpleDateFormat& date_format,
int32_t explicit_components_in_options, PatternKind kind,
JSDateTimeFormat::DateTimeStyle date_style,
JSDateTimeFormat::DateTimeStyle time_style,
bool has_to_locale_string_time_zone) {
DCHECK_NE(kind, PatternKind::kDate);
icu::UnicodeString skeleton = GetSkeletonForPatternKind(
SkeletonFromDateFormat(date_format), explicit_components_in_options, kind,
date_style, time_style, has_to_locale_string_time_zone);
if (skeleton.length() == 0) {
return nullptr;
}
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<icu::SimpleDateFormat> result(
static_cast<icu::SimpleDateFormat*>(
icu::DateFormat::createInstanceForSkeleton(
skeleton, date_format.getSmpFmtLocale(), status)));
DCHECK(result);
DCHECK(U_SUCCESS(status));
result->setTimeZone(date_format.getTimeZone());
return result;
}
icu::UnicodeString Replace202F(icu::UnicodeString& string) {
return string.findAndReplace(icu::UnicodeString(0x202f),
icu::UnicodeString(0x20));
}
std::optional<icu::UnicodeString> CallICUFormat(
const icu::SimpleDateFormat& date_format,
int32_t explicit_components_in_options, PatternKind kind,
JSDateTimeFormat::DateTimeStyle date_style,
JSDateTimeFormat::DateTimeStyle time_style,
bool has_to_locale_string_time_zone, double time_in_milliseconds,
icu::FieldPositionIterator* fp_iter, UErrorCode& status) {
if (U_FAILURE(status)) {
return std::nullopt;
}
icu::UnicodeString result;
if (kind == PatternKind::kDate) {
date_format.format(time_in_milliseconds, result, fp_iter, status);
if (U_FAILURE(status)) {
return std::nullopt;
}
result = Replace202F(result);
return result;
}
std::unique_ptr<icu::SimpleDateFormat> pattern(GetSimpleDateTimeForTemporal(
date_format, explicit_components_in_options, kind, date_style, time_style,
has_to_locale_string_time_zone));
if (pattern == nullptr) {
return std::nullopt;
}
pattern->format(time_in_milliseconds, result, fp_iter, status);
if (U_FAILURE(status)) {
return std::nullopt;
}
result = Replace202F(result);
return result;
}
void ApplyBritishRemoveFullWeekdayComma(
icu::UnicodeString& result, const icu::SimpleDateFormat& date_format) {
if (v8_flags.icu_british_remove_full_weekday_comma) {
int32_t found = result.indexOf(',');
if (found != -1) {
const icu::Locale& locale = date_format.getSmpFmtLocale();
if (strcmp(locale.getLanguage(), "en") == 0 &&
(strcmp(locale.getCountry(), "AU") == 0 ||
strcmp(locale.getCountry(), "GB") == 0 ||
strcmp(locale.getCountry(), "IN") == 0)) {
icu::UnicodeString pattern;
date_format.toPattern(pattern);
if (pattern.startsWith("EEEE,")) {
result = result.remove(found, 1);
}
}
}
}
}
MaybeDirectHandle<String> FormatDateTime(
Isolate* isolate, const icu::SimpleDateFormat& date_format, double x) {
if (!DateCache::TryTimeClip(&x)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue));
}
icu::UnicodeString result;
date_format.format(x, result);
DCHECK_NE(v8_flags.icu_datetime_compat_lang, nullptr);
if (strcmp(v8_flags.icu_datetime_compat_lang, "*") == 0 ||
strcmp(v8_flags.icu_datetime_compat_lang,
date_format.getSmpFmtLocale().getLanguage()) == 0) {
result = Replace202F(result);
}
ApplyBritishRemoveFullWeekdayComma(result, date_format);
return Intl::ToString(isolate, result);
}
MaybeDirectHandle<String> FormatMillisecondsByKindToString(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<Object> value, PatternKind kind, double x) {
Managed<icu::SimpleDateFormat>::Ptr icu_date_format =
date_time_format->icu_simple_date_format()->ptr();
UErrorCode status = U_ZERO_ERROR;
std::optional<icu::UnicodeString> result = CallICUFormat(
*icu_date_format, date_time_format->explicit_components_in_options(),
kind, date_time_format->date_style(), date_time_format->time_style(),
date_time_format->has_to_locale_string_time_zone(), x, nullptr, status);
if (U_FAILURE(status) || !result.has_value()) {
THROW_NEW_ERROR(
isolate,
NewTypeError(MessageTemplate::kInvalidArgumentForTemporal, value));
}
icu::UnicodeString result_unwrapped = std::move(result).value();
ApplyBritishRemoveFullWeekdayComma(result_unwrapped, *icu_date_format);
return Intl::ToString(isolate, result_unwrapped);
}
MaybeDirectHandle<String> FormatDateTimeWithTemporalSupport(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<Object> x, const char* method_name) {
DateTimeValueRecord record;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, record,
HandleDateTimeValue(isolate, date_time_format, x, method_name));
return FormatMillisecondsByKindToString(
isolate, date_time_format, x, record.kind, record.epoch_milliseconds);
}
}
MaybeDirectHandle<String> JSDateTimeFormat::DateTimeFormat(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<Object> date, const char* method_name) {
if (v8_flags.harmony_temporal) {
return FormatDateTimeWithTemporalSupport(isolate, date_time_format, date,
method_name);
}
double x;
if (IsUndefined(*date)) {
x = static_cast<double>(JSDate::CurrentTimeValue(isolate));
} else {
ASSIGN_RETURN_ON_EXCEPTION(isolate, date, Object::ToNumber(isolate, date));
DCHECK(IsNumber(*date));
x = Object::NumberValue(*date);
}
Managed<icu::SimpleDateFormat>::Ptr format =
date_time_format->icu_simple_date_format()->ptr();
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;
}
}
}
MaybeDirectHandle<String> JSDateTimeFormat::ToLocaleDateTime(
Isolate* isolate, DirectHandle<Object> date, DirectHandle<Object> locales,
DirectHandle<Object> options, RequiredOption required,
DefaultsOption defaults, const char* method_name) {
Isolate::ICUObjectCacheType cache_type = ConvertToCacheType(defaults);
Factory* factory = isolate->factory();
if (!IsJSDate(*date)) {
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
factory->Date_string()));
}
double const x = Cast<JSDate>(date)->value();
if (std::isnan(x)) {
return factory->Invalid_Date_string();
}
bool can_cache = (IsString(*locales) || IsUndefined(*locales, isolate)) &&
IsUndefined(*options, isolate);
if (can_cache) {
icu::SimpleDateFormat* cached_icu_simple_date_format =
static_cast<icu::SimpleDateFormat*>(
isolate->get_cached_icu_object(cache_type, locales));
if (cached_icu_simple_date_format != nullptr) {
return FormatDateTime(isolate, *cached_icu_simple_date_format, x);
}
}
DirectHandle<JSFunction> constructor(
Cast<JSFunction>(isolate->context()
->native_context()
->intl_date_time_format_function()),
isolate);
DirectHandle<Map> map;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, map,
JSFunction::GetDerivedMap(isolate, constructor, constructor));
DirectHandle<JSDateTimeFormat> date_time_format;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, date_time_format,
JSDateTimeFormat::CreateDateTimeFormat(
isolate, map, locales, options, required, defaults, {}, method_name));
if (can_cache) {
isolate->set_icu_object_in_cache(
cache_type, locales,
std::static_pointer_cast<icu::UMemory>(
date_time_format->icu_simple_date_format()->get()));
}
Managed<icu::SimpleDateFormat>::Ptr format =
date_time_format->icu_simple_date_format()->ptr();
return FormatDateTime(isolate, *format, x);
}
#ifdef V8_TEMPORAL_SUPPORT
MaybeDirectHandle<String> JSDateTimeFormat::TemporalToLocaleString(
Isolate* isolate, DirectHandle<JSReceiver> x, DirectHandle<Object> locales,
DirectHandle<Object> options, RequiredOption required,
DefaultsOption defaults, const char* method_name) {
DirectHandle<JSFunction> constructor(
isolate->context()->native_context()->intl_date_time_format_function(),
isolate);
DirectHandle<Map> map =
JSFunction::GetDerivedMap(isolate, constructor, constructor)
.ToHandleChecked();
DirectHandle<JSDateTimeFormat> date_time_format;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, date_time_format,
JSDateTimeFormat::CreateDateTimeFormat(
isolate, map, locales, options, required, defaults, {}, method_name));
return FormatDateTimeWithTemporalSupport(isolate, date_time_format, x,
method_name);
}
MaybeDirectHandle<String> JSDateTimeFormat::TemporalZonedDateTimeToLocaleString(
Isolate* isolate, DirectHandle<JSReceiver> x, DirectHandle<Object> locales,
DirectHandle<Object> options, const char* method_name) {
DirectHandle<JSTemporalZonedDateTime> zdt = Cast<JSTemporalZonedDateTime>(x);
DirectHandle<JSFunction> constructor(
isolate->context()->native_context()->intl_date_time_format_function(),
isolate);
DirectHandle<Map> map =
JSFunction::GetDerivedMap(isolate, constructor, constructor)
.ToHandleChecked();
DirectHandle<String> time_zone;
ASSIGN_RETURN_ON_EXCEPTION(isolate, time_zone,
JSTemporalZonedDateTime::TimeZoneId(isolate, zdt));
DirectHandle<JSDateTimeFormat> date_time_format;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, date_time_format,
JSDateTimeFormat::CreateDateTimeFormat(
isolate, map, locales, options, RequiredOption::kAny,
DefaultsOption::kAll, time_zone, method_name));
auto calendar_kind = zdt->wrapped_rust().calendar().kind();
if (calendar_kind != temporal_rs::AnyCalendarKind::Value::Iso) {
bool equals;
{
DisallowGarbageCollection no_gc;
equals = CalendarEquals(
calendar_kind,
*(date_time_format->icu_simple_date_format()->raw(no_gc)));
}
if (!equals) {
THROW_NEW_ERROR(isolate,
NewRangeError(MessageTemplate::kMismatchedCalendars));
}
}
DirectHandle<JSTemporalInstant> instant;
ASSIGN_RETURN_ON_EXCEPTION(isolate, instant,
JSTemporalZonedDateTime::ToInstant(isolate, zdt));
return FormatDateTimeWithTemporalSupport(isolate, date_time_format, instant,
method_name);
}
#endif
MaybeDirectHandle<JSDateTimeFormat> JSDateTimeFormat::UnwrapDateTimeFormat(
Isolate* isolate, Handle<JSReceiver> format_holder) {
DirectHandle<Context> native_context(isolate->context()->native_context(),
isolate);
DirectHandle<JSFunction> constructor(
Cast<JSFunction>(native_context->intl_date_time_format_function()),
isolate);
DirectHandle<Object> dtf;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, dtf,
Intl::LegacyUnwrapReceiver(isolate, format_holder, constructor,
IsJSDateTimeFormat(*format_holder)));
if (!IsJSDateTimeFormat(*dtf)) {
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kIncompatibleMethodReceiver,
isolate->factory()->NewStringFromAsciiChecked(
"UnwrapDateTimeFormat"),
format_holder));
}
return Cast<JSDateTimeFormat>(dtf);
}
std::optional<std::string> GetOffsetTimeZone(Isolate* isolate,
DirectHandle<String> time_zone) {
time_zone = String::Flatten(isolate, time_zone);
DisallowGarbageCollection no_gc;
const String::FlatContent& flat = time_zone->GetFlatContent(no_gc);
int32_t len = flat.length();
if (len < 3) {
return std::nullopt;
}
std::string tz("GMT");
switch (flat.Get(0)) {
case 0x2212:
case '-':
tz += '-';
break;
case '+':
tz += '+';
break;
default:
return std::nullopt;
}
uint16_t h0 = flat.Get(1);
uint16_t h1 = flat.Get(2);
if ((h0 >= '0' && h0 <= '1' && h1 >= '0' && h1 <= '9') ||
(h0 == '2' && h1 >= '0' && h1 <= '3')) {
tz += h0;
tz += h1;
} else {
return std::nullopt;
}
if (len == 3) {
return tz;
}
int32_t p = 3;
uint16_t m0 = flat.Get(p);
if (m0 == ':') {
p++;
if (len == p) {
return std::nullopt;
}
m0 = flat.Get(p);
}
if (len - p != 2) {
return std::nullopt;
}
uint16_t m1 = flat.Get(p + 1);
if (m0 >= '0' && m0 <= '5' && m1 >= '0' && m1 <= '9') {
tz += m0;
tz += m1;
return tz;
}
return std::nullopt;
}
std::unique_ptr<icu::TimeZone> JSDateTimeFormat::CreateTimeZone(
Isolate* isolate, DirectHandle<String> time_zone_string) {
std::optional<std::string> offsetTimeZone =
GetOffsetTimeZone(isolate, time_zone_string);
if (offsetTimeZone.has_value()) {
std::unique_ptr<icu::TimeZone> tz(
icu::TimeZone::createTimeZone(offsetTimeZone->c_str()));
return tz;
}
std::string time_zone = time_zone_string->ToStdString();
std::string canonicalized = CanonicalizeTimeZoneID(time_zone);
if (canonicalized.empty()) return std::unique_ptr<icu::TimeZone>();
std::unique_ptr<icu::TimeZone> tz(
icu::TimeZone::createTimeZone(canonicalized.c_str()));
if (!Intl::IsValidTimeZoneName(*tz)) return std::unique_ptr<icu::TimeZone>();
return tz;
}
namespace {
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();
}
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() ||
strcmp(calendar->getType(), "iso8601") == 0) {
icu::GregorianCalendar* gc =
static_cast<icu::GregorianCalendar*>(calendar.get());
status = U_ZERO_ERROR;
const double start_of_time = -9007199254740992;
gc->setGregorianChange(start_of_time, status);
DCHECK(U_SUCCESS(status));
}
if (map_.size() > 8) {
map_.clear();
}
map_[key] = std::move(calendar);
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;
char16_t last = u'\0';
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':
[[fallthrough]];
case 'h':
[[fallthrough]];
case 'K':
[[fallthrough]];
case 'k':
if (replace && last == u'd') {
result.append(' ');
}
result.append(replace ? replacement : ch);
break;
default:
result.append(ch);
break;
}
last = ch;
}
return result;
}
std::unique_ptr<icu::SimpleDateFormat> CreateICUDateFormat(
const icu::Locale& icu_locale, const icu::UnicodeString& skeleton,
icu::DateTimePatternGenerator* generator, JSDateTimeFormat::HourCycle hc) {
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));
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) {
map_.clear();
}
std::unique_ptr<icu::SimpleDateFormat> instance(
CreateICUDateFormat(icu_locale, skeleton, generator, hc));
if (instance == 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));
}
std::unique_ptr<icu::DateIntervalFormat> LazyCreateDateIntervalFormat(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
PatternKind kind, JSDateTimeFormat::DateTimeStyle date_style,
JSDateTimeFormat::DateTimeStyle time_style,
bool has_to_locale_string_time_zone) {
if (kind == PatternKind::kDate) {
DisallowGarbageCollection no_gc;
icu::DateIntervalFormat* icu_format =
date_time_format->icu_date_interval_format()->raw(no_gc);
if (icu_format)
return std::unique_ptr<icu::DateIntervalFormat>(icu_format->clone());
}
UErrorCode status = U_ZERO_ERROR;
icu::Locale loc = *(date_time_format->icu_locale()->get());
std::string hcString = ToHourCycleString(date_time_format->hour_cycle());
if (!hcString.empty()) {
loc.setUnicodeKeywordValue("hc", hcString, status);
}
Managed<icu::SimpleDateFormat>::Ptr icu_simple_date_format =
date_time_format->icu_simple_date_format()->ptr();
icu::UnicodeString skeleton = GetSkeletonForPatternKind(
SkeletonFromDateFormat(*icu_simple_date_format),
date_time_format->explicit_components_in_options(), kind, date_style,
time_style, has_to_locale_string_time_zone);
std::unique_ptr<icu::DateIntervalFormat> date_interval_format(
icu::DateIntervalFormat::createInstance(skeleton, loc, status));
DCHECK(U_SUCCESS(status));
date_interval_format->setTimeZone(icu_simple_date_format->getTimeZone());
if (kind != PatternKind::kDate) {
return date_interval_format;
}
DirectHandle<Managed<icu::DateIntervalFormat>> managed_interval_format =
Managed<icu::DateIntervalFormat>::From(isolate, 0,
std::move(date_interval_format));
date_time_format->set_icu_date_interval_format(*managed_interval_format);
DisallowGarbageCollection no_gc;
return std::unique_ptr<icu::DateIntervalFormat>(
managed_interval_format->raw(no_gc)->clone());
}
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]) {
case 'a':
[[fallthrough]];
case 'b':
[[fallthrough]];
case 'B':
break;
case 'h':
[[fallthrough]];
case 'H':
[[fallthrough]];
case 'K':
[[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)));
if (result != 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;
if (result.get() == nullptr) {
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;
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;
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 (hc == HourCycleFromPattern(pattern)) {
return result;
}
return CreateICUDateFormatFromCache(icu_locale, ReplaceSkeleton(skeleton, hc),
generator, hc);
}
class DateTimePatternGeneratorCache {
public:
icu::DateTimePatternGenerator* CreateGenerator(Isolate* isolate,
const icu::Locale& locale) {
std::string key(locale.getName());
base::MutexGuard guard(&mutex_);
auto it = map_.find(key);
icu::DateTimePatternGenerator* orig;
if (it != map_.end()) {
DCHECK(it->second != nullptr);
orig = it->second.get();
} else {
UErrorCode status = U_ZERO_ERROR;
orig = icu::DateTimePatternGenerator::createInstance(locale, status);
if (U_FAILURE(status)) {
status = U_ZERO_ERROR;
orig = icu::DateTimePatternGenerator::createInstance("root", status);
}
if (U_SUCCESS(status) && orig != nullptr) {
map_[key].reset(orig);
} else {
DCHECK(status == U_MEMORY_ALLOCATION_ERROR);
V8::FatalProcessOutOfMemory(
isolate, "DateTimePatternGeneratorCache::CreateGenerator");
}
}
icu::DateTimePatternGenerator* clone = orig ? orig->clone() : nullptr;
if (clone == nullptr) {
V8::FatalProcessOutOfMemory(
isolate, "DateTimePatternGeneratorCache::CreateGenerator");
}
return clone;
}
private:
std::map<std::string, std::unique_ptr<icu::DateTimePatternGenerator>> map_;
base::Mutex mutex_;
};
}
enum FormatMatcherOption { kBestFit, kBasic };
MaybeDirectHandle<JSDateTimeFormat> JSDateTimeFormat::New(
Isolate* isolate, DirectHandle<Map> map, DirectHandle<Object> locales,
DirectHandle<Object> input_options, const char* service) {
return JSDateTimeFormat::CreateDateTimeFormat(
isolate, map, locales, input_options, RequiredOption::kAny,
DefaultsOption::kDate, {}, service);
}
MaybeDirectHandle<JSDateTimeFormat> JSDateTimeFormat::CreateDateTimeFormat(
Isolate* isolate, DirectHandle<Map> map, DirectHandle<Object> locales,
DirectHandle<Object> input_options, RequiredOption required,
DefaultsOption defaults, MaybeDirectHandle<String> toLocaleStringTimeZone,
const char* service) {
Factory* factory = isolate->factory();
Maybe<std::vector<std::string>> maybe_requested_locales =
Intl::CanonicalizeLocaleList(isolate, locales);
MAYBE_RETURN(maybe_requested_locales, {});
std::vector<std::string> requested_locales =
maybe_requested_locales.FromJust();
DirectHandle<JSReceiver> options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options, CoerceOptionsToObject(isolate, input_options, service));
Maybe<Intl::MatcherOption> maybe_locale_matcher =
Intl::GetLocaleMatcher(isolate, options, service);
MAYBE_RETURN(maybe_locale_matcher, {});
Intl::MatcherOption locale_matcher = maybe_locale_matcher.FromJust();
DirectHandle<String> calendar_str;
std::string numbering_system_str;
Maybe<bool> maybe_calendar =
GetStringOption(isolate, options, isolate->factory()->calendar_string(),
service, &calendar_str);
MAYBE_RETURN(maybe_calendar, {});
std::string calendar_stdstr;
if (maybe_calendar.FromJust()) {
calendar_stdstr = calendar_str->ToStdString();
icu::Locale default_locale;
if (!Intl::IsWellFormedCalendar(calendar_stdstr)) {
THROW_NEW_ERROR(isolate,
NewRangeError(MessageTemplate::kInvalid,
factory->calendar_string(), calendar_str));
}
}
Maybe<bool> maybe_numberingSystem =
Intl::GetNumberingSystem(isolate, options, service, numbering_system_str);
MAYBE_RETURN(maybe_numberingSystem, {});
bool hour12;
Maybe<bool> maybe_get_hour12 = GetBoolOption(
isolate, options, factory->hour12_string(), service, &hour12);
MAYBE_RETURN(maybe_get_hour12, {});
Maybe<HourCycle> maybe_hour_cycle = GetHourCycle(isolate, options, service);
MAYBE_RETURN(maybe_hour_cycle, {});
HourCycle hour_cycle = maybe_hour_cycle.FromJust();
if (maybe_get_hour12.FromJust()) {
hour_cycle = HourCycle::kUndefined;
}
Intl::ResolvedLocale r;
if (!Intl::ResolveLocale(isolate, JSDateTimeFormat::GetAvailableLocales(),
requested_locales, locale_matcher,
{"nu", "ca", "hc"})
.To(&r)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kIcuError));
}
icu::Locale icu_locale = r.icu_locale;
DCHECK(!icu_locale.isBogus());
UErrorCode status = U_ZERO_ERROR;
if (maybe_calendar.FromJust()) {
auto ca_extension_it = r.extensions.find("ca");
if (ca_extension_it != r.extensions.end() &&
ca_extension_it->second != calendar_stdstr &&
Intl::IsValidCalendar(icu_locale, calendar_stdstr)) {
icu_locale.setUnicodeKeywordValue("ca", nullptr, status);
DCHECK(U_SUCCESS(status));
}
}
if (maybe_numberingSystem.FromJust()) {
auto nu_extension_it = r.extensions.find("nu");
if (nu_extension_it != r.extensions.end() &&
nu_extension_it->second != numbering_system_str &&
Intl::IsValidNumberingSystem(numbering_system_str)) {
icu_locale.setUnicodeKeywordValue("nu", nullptr, status);
DCHECK(U_SUCCESS(status));
}
}
icu::Locale resolved_locale(icu_locale);
if (maybe_calendar.FromJust() &&
Intl::IsValidCalendar(icu_locale, calendar_stdstr)) {
icu_locale.setUnicodeKeywordValue("ca", calendar_stdstr, status);
DCHECK(U_SUCCESS(status));
}
if (maybe_numberingSystem.FromJust() &&
Intl::IsValidNumberingSystem(numbering_system_str)) {
icu_locale.setUnicodeKeywordValue("nu", numbering_system_str, 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(isolate, icu_locale));
HourCycle hc_default = ToHourCycle(generator->getDefaultHourCycle(status));
DCHECK(U_SUCCESS(status));
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);
}
} else {
hc = hour_cycle;
}
if (maybe_get_hour12.FromJust()) {
if (hour12) {
hc = DefaultHourCycle12(icu_locale, hc_default);
} else {
hc = DefaultHourCycle24(icu_locale, hc_default);
}
} else {
if (hc == HourCycle::kUndefined) {
hc = hc_default;
}
}
DirectHandle<Object> time_zone_obj;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, time_zone_obj,
Object::GetPropertyOrElement(isolate, options,
isolate->factory()->timeZone_string()));
std::unique_ptr<icu::TimeZone> tz;
if (IsUndefined(*time_zone_obj, isolate)) {
if (!toLocaleStringTimeZone.IsEmpty()) {
tz = JSDateTimeFormat::CreateTimeZone(
isolate, toLocaleStringTimeZone.ToHandleChecked());
} else {
tz.reset(icu::TimeZone::createDefault());
}
} else {
if (!toLocaleStringTimeZone.IsEmpty()) {
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kInvalidTimeZone,
toLocaleStringTimeZone.ToHandleChecked()));
}
DirectHandle<String> time_zone;
ASSIGN_RETURN_ON_EXCEPTION(isolate, time_zone,
Object::ToString(isolate, time_zone_obj));
tz = JSDateTimeFormat::CreateTimeZone(isolate, time_zone);
}
if (tz.get() == nullptr) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeZone,
time_zone_obj));
}
std::unique_ptr<icu::Calendar> calendar(
CreateCalendar(isolate, icu_locale, tz.release()));
if (calendar.get() == nullptr) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeZone,
time_zone_obj));
}
DateTimeStyle date_style = DateTimeStyle::kUndefined;
DateTimeStyle time_style = DateTimeStyle::kUndefined;
std::unique_ptr<icu::SimpleDateFormat> icu_date_format;
int32_t explicit_format_components =
0;
bool has_hour_option = false;
std::string skeleton;
for (const PatternData& item : GetPatternData(hc)) {
if (item.property == DateTimeProperty::kTimeZoneName) {
int fsd;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, fsd,
GetNumberOption(isolate, options,
factory->fractionalSecondDigits_string(), 1, 3, 0));
if (fsd > 0) {
explicit_format_components =
FractionalSecondDigits::update(explicit_format_components, true);
}
for (int i = 0; i < fsd; i++) {
skeleton += "S";
}
}
std::string_view found_value;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, found_value,
GetStringOption<std::string_view>(
isolate, options, GetPropertyString(*factory, item.property),
service, item.allowed_values, item.allowed_values,
std::string_view("")));
if (!found_value.empty()) {
if (item.property == DateTimeProperty::kHour) {
has_hour_option = true;
}
skeleton += item.map.find(found_value)->second;
explicit_format_components |= 1 << static_cast<int32_t>(item.bitShift);
}
}
Maybe<FormatMatcherOption> maybe_format_matcher =
GetStringOption<FormatMatcherOption>(
isolate, options, isolate->factory()->formatMatcher_string(), service,
std::to_array<const std::string_view>({"best fit", "basic"}),
std::array{FormatMatcherOption::kBestFit,
FormatMatcherOption::kBasic},
FormatMatcherOption::kBestFit);
MAYBE_RETURN(maybe_format_matcher, {});
Maybe<DateTimeStyle> maybe_date_style = GetStringOption<DateTimeStyle>(
isolate, options, isolate->factory()->dateStyle_string(), service,
std::to_array<const std::string_view>(
{"full", "long", "medium", "short"}),
std::array{DateTimeStyle::kFull, DateTimeStyle::kLong,
DateTimeStyle::kMedium, DateTimeStyle::kShort},
DateTimeStyle::kUndefined);
MAYBE_RETURN(maybe_date_style, {});
date_style = maybe_date_style.FromJust();
Maybe<DateTimeStyle> maybe_time_style = GetStringOption<DateTimeStyle>(
isolate, options, isolate->factory()->timeStyle_string(), service,
std::to_array<const std::string_view>(
{"full", "long", "medium", "short"}),
std::array{DateTimeStyle::kFull, DateTimeStyle::kLong,
DateTimeStyle::kMedium, DateTimeStyle::kShort},
DateTimeStyle::kUndefined);
MAYBE_RETURN(maybe_time_style, {});
time_style = maybe_time_style.FromJust();
HourCycle dateTimeFormatHourCycle = HourCycle::kUndefined;
if (time_style != DateTimeStyle::kUndefined) {
dateTimeFormatHourCycle = hc;
}
if (date_style != DateTimeStyle::kUndefined ||
time_style != DateTimeStyle::kUndefined) {
if (explicit_format_components != 0) {
THROW_NEW_ERROR(
isolate, NewTypeError(MessageTemplate::kInvalid,
factory->NewStringFromStaticChars("option"),
factory->NewStringFromStaticChars("option")));
}
if (required == RequiredOption::kDate &&
time_style != DateTimeStyle::kUndefined) {
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kInvalid,
factory->NewStringFromStaticChars("option"),
factory->timeStyle_string()));
}
if (required == RequiredOption::kTime &&
date_style != DateTimeStyle::kUndefined) {
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kInvalid,
factory->NewStringFromStaticChars("option"),
factory->dateStyle_string()));
}
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));
}
} else {
bool needDefaults = true;
if (required == RequiredOption::kDate || required == RequiredOption::kAny) {
needDefaults &= !Weekday::decode(explicit_format_components);
needDefaults &= !Year::decode(explicit_format_components);
needDefaults &= !Month::decode(explicit_format_components);
needDefaults &= !Day::decode(explicit_format_components);
}
if (required == RequiredOption::kTime || required == RequiredOption::kAny) {
needDefaults &= !DayPeriod::decode(explicit_format_components);
needDefaults &= !Hour::decode(explicit_format_components);
needDefaults &= !Minute::decode(explicit_format_components);
needDefaults &= !Second::decode(explicit_format_components);
needDefaults &=
!FractionalSecondDigits::decode(explicit_format_components);
}
if (needDefaults && ((DefaultsOption::kDate == defaults) ||
(DefaultsOption::kAll == defaults))) {
skeleton += "yMd";
}
if (needDefaults && ((DefaultsOption::kTime == defaults) ||
(DefaultsOption::kAll == defaults))) {
switch (hc) {
case HourCycle::kH12:
skeleton += "hms";
break;
case HourCycle::kH23:
case HourCycle::kUndefined:
skeleton += "Hms";
break;
case HourCycle::kH11:
skeleton += "Kms";
break;
case HourCycle::kH24:
skeleton += "kms";
break;
}
}
if (has_hour_option) {
dateTimeFormatHourCycle = hc;
} else {
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) {
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));
}
}
}
icu_date_format->adoptCalendar(calendar.release());
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())) {
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, {});
DirectHandle<String> locale_str =
isolate->factory()->NewStringFromAsciiChecked(
maybe_locale_str.FromJust().c_str());
DirectHandle<Managed<icu::Locale>> managed_locale =
Managed<icu::Locale>::From(
isolate, 0, std::shared_ptr<icu::Locale>{icu_locale.clone()});
DirectHandle<Managed<icu::SimpleDateFormat>> managed_format =
Managed<icu::SimpleDateFormat>::From(isolate, 0,
std::move(icu_date_format));
DirectHandle<Managed<icu::DateIntervalFormat>> managed_interval_format =
Managed<icu::DateIntervalFormat>::From(isolate, 0, nullptr);
DirectHandle<JSDateTimeFormat> date_time_format = Cast<JSDateTimeFormat>(
isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
DisallowGarbageCollection 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_has_to_locale_string_time_zone(
!toLocaleStringTimeZone.IsEmpty());
date_time_format->set_explicit_components_in_options(
explicit_format_components);
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 {
DirectHandle<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:
UNREACHABLE();
}
}
MaybeDirectHandle<JSArray> FieldPositionIteratorToArray(
Isolate* isolate, const icu::UnicodeString& formatted,
icu::FieldPositionIterator fp_iter, bool output_source);
MaybeDirectHandle<JSArray> FormatMillisecondsByKindToArray(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<Object> value, PatternKind kind, double x,
bool output_source) {
icu::FieldPositionIterator fp_iter;
UErrorCode status = U_ZERO_ERROR;
Managed<icu::SimpleDateFormat>::Ptr icu_date_format =
date_time_format->icu_simple_date_format()->ptr();
auto formatted = CallICUFormat(
*icu_date_format, date_time_format->explicit_components_in_options(),
kind, date_time_format->date_style(), date_time_format->time_style(),
date_time_format->has_to_locale_string_time_zone(), x, &fp_iter, status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError));
}
if (!formatted.has_value()) {
THROW_NEW_ERROR(
isolate,
NewTypeError(MessageTemplate::kInvalidArgumentForTemporal, value));
}
return FieldPositionIteratorToArray(isolate, formatted.value(), fp_iter,
output_source);
}
MaybeDirectHandle<JSArray> FormatMillisecondsByKindToArrayOutputSource(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<Object> value, PatternKind kind, double x) {
return FormatMillisecondsByKindToArray(isolate, date_time_format, value, kind,
x, true);
}
MaybeDirectHandle<JSArray> FormatToPartsWithTemporalSupport(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<Object> x, bool output_source, const char* method_name) {
DateTimeValueRecord x_record;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, x_record,
HandleDateTimeValue(isolate, date_time_format, x, method_name));
return FormatMillisecondsByKindToArray(
isolate, date_time_format, x, x_record.kind, x_record.epoch_milliseconds,
output_source);
}
MaybeDirectHandle<JSArray> FormatMillisecondsToArray(
Isolate* isolate, const icu::SimpleDateFormat& format, double value,
bool output_source) {
icu::UnicodeString formatted;
icu::FieldPositionIterator fp_iter;
UErrorCode status = U_ZERO_ERROR;
format.format(value, formatted, &fp_iter, status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError));
}
return FieldPositionIteratorToArray(isolate, formatted, fp_iter,
output_source);
}
MaybeDirectHandle<JSArray> FormatMillisecondsToArrayOutputSource(
Isolate* isolate, const icu::SimpleDateFormat& format, double value) {
return FormatMillisecondsToArray(isolate, format, value, true);
}
}
MaybeDirectHandle<JSArray> JSDateTimeFormat::FormatToParts(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<Object> x, bool output_source, const char* method_name) {
Factory* factory = isolate->factory();
if (v8_flags.harmony_temporal) {
return FormatToPartsWithTemporalSupport(isolate, date_time_format, x,
output_source, method_name);
}
if (IsUndefined(*x, isolate)) {
x = factory->NewNumberFromInt64(JSDate::CurrentTimeValue(isolate));
} else {
ASSIGN_RETURN_ON_EXCEPTION(isolate, x, Object::ToNumber(isolate, x));
}
double date_value = Object::NumberValue(*x);
if (!DateCache::TryTimeClip(&date_value)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue));
}
return FormatMillisecondsToArray(
isolate, *(date_time_format->icu_simple_date_format()->ptr()), date_value,
output_source);
}
namespace {
MaybeDirectHandle<JSArray> FieldPositionIteratorToArray(
Isolate* isolate, const icu::UnicodeString& formatted,
icu::FieldPositionIterator fp_iter, bool output_source) {
Factory* factory = isolate->factory();
icu::FieldPosition fp;
DirectHandle<JSArray> result = factory->NewJSArray(0);
int32_t length = formatted.length();
if (length == 0) return result;
int index = 0;
int32_t previous_end_pos = 0;
DirectHandle<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));
if (output_source) {
Intl::AddElement(isolate, result, index,
IcuDateFieldIdToDateType(-1, isolate), substring,
isolate->factory()->source_string(),
isolate->factory()->shared_string());
} else {
Intl::AddElement(isolate, result, index,
IcuDateFieldIdToDateType(-1, isolate), substring);
}
++index;
}
ASSIGN_RETURN_ON_EXCEPTION(
isolate, substring,
Intl::ToString(isolate, formatted, begin_pos, end_pos));
if (output_source) {
Intl::AddElement(isolate, result, index,
IcuDateFieldIdToDateType(fp.getField(), isolate),
substring, isolate->factory()->source_string(),
isolate->factory()->shared_string());
} else {
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));
if (output_source) {
Intl::AddElement(isolate, result, index,
IcuDateFieldIdToDateType(-1, isolate), substring,
isolate->factory()->source_string(),
isolate->factory()->shared_string());
} else {
Intl::AddElement(isolate, result, index,
IcuDateFieldIdToDateType(-1, isolate), substring);
}
}
JSObject::ValidateElements(isolate, *result);
return result;
}
}
const std::set<std::string>& JSDateTimeFormat::GetAvailableLocales() {
return Intl::GetAvailableLocalesForDateFormat();
}
Handle<String> JSDateTimeFormat::HourCycleAsString(Isolate* isolate) const {
switch (hour_cycle()) {
case HourCycle::kUndefined:
return isolate->factory()->undefined_string();
case HourCycle::kH11:
return isolate->factory()->h11_string();
case HourCycle::kH12:
return isolate->factory()->h12_string();
case HourCycle::kH23:
return isolate->factory()->h23_string();
case HourCycle::kH24:
return isolate->factory()->h24_string();
default:
UNREACHABLE();
}
}
namespace {
Maybe<bool> AddPartForFormatRange(
Isolate* isolate, DirectHandle<JSArray> array,
const icu::UnicodeString& string, int32_t index, int32_t field,
int32_t start, int32_t end, const Intl::FormatRangeSourceTracker& tracker) {
DirectHandle<String> substring;
ASSIGN_RETURN_ON_EXCEPTION(isolate, substring,
Intl::ToString(isolate, string, start, end));
Intl::AddElement(isolate, array, index,
IcuDateFieldIdToDateType(field, isolate), substring,
isolate->factory()->source_string(),
Intl::SourceString(isolate, tracker.GetSource(start, end)));
return Just(true);
}
std::optional<MaybeDirectHandle<String>> FormattedToString(
Isolate* isolate, const icu::FormattedValue& formatted) {
UErrorCode status = U_ZERO_ERROR;
icu::UnicodeString result = formatted.toString(status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError));
}
icu::ConstrainedFieldPosition cfpos;
while (formatted.nextPosition(cfpos, status)) {
if (cfpos.getCategory() == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) {
return Intl::ToString(isolate, Replace202F(result));
}
}
return std::nullopt;
}
std::optional<MaybeDirectHandle<JSArray>> FormattedDateIntervalToJSArray(
Isolate* isolate, const icu::FormattedValue& formatted) {
UErrorCode status = U_ZERO_ERROR;
icu::UnicodeString result = formatted.toString(status);
result = Replace202F(result);
Factory* factory = isolate->factory();
DirectHandle<JSArray> array = factory->NewJSArray(0);
icu::ConstrainedFieldPosition cfpos;
int index = 0;
int32_t previous_end_pos = 0;
Intl::FormatRangeSourceTracker tracker;
bool output_range = false;
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);
output_range = true;
tracker.Add(field, start, limit);
} else {
DCHECK(category == UFIELD_CATEGORY_DATE);
if (start > previous_end_pos) {
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();
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));
}
JSObject::ValidateElements(isolate, *array);
if (output_range) return array;
return std::nullopt;
}
template <typename T, std::optional<MaybeDirectHandle<T>> (*Format)(
Isolate*, const icu::FormattedValue&)>
std::optional<MaybeDirectHandle<T>> CallICUFormatRange(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
const icu::DateIntervalFormat* format, const icu::Calendar* calendar,
double x, double y);
template <typename T, std::optional<MaybeDirectHandle<T>> (*Format)(
Isolate*, const icu::FormattedValue&)>
std::optional<MaybeDirectHandle<T>> PartitionDateTimeRangePattern(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format, double x,
double y, const char* method_name) {
if (!DateCache::TryTimeClip(&x)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue));
}
if (!DateCache::TryTimeClip(&y)) {
THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue));
}
std::unique_ptr<icu::DateIntervalFormat> format(LazyCreateDateIntervalFormat(
isolate, date_time_format, PatternKind::kDate,
date_time_format->date_style(), date_time_format->time_style(),
date_time_format->has_to_locale_string_time_zone()));
if (format.get() == nullptr) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError));
}
Managed<icu::SimpleDateFormat>::Ptr date_format =
date_time_format->icu_simple_date_format()->ptr();
const icu::Calendar* calendar = date_format->getCalendar();
return CallICUFormatRange<T, Format>(isolate, date_time_format, format.get(),
calendar, x, y);
}
template <typename T, std::optional<MaybeDirectHandle<T>> (*Format)(
Isolate*, const icu::FormattedValue&)>
std::optional<MaybeDirectHandle<T>> CallICUFormatRange(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_Format,
const icu::DateIntervalFormat* format, const icu::Calendar* calendar,
double x, double y) {
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<icu::Calendar> c1(calendar->clone());
std::unique_ptr<icu::Calendar> c2(calendar->clone());
c1->setTime(x, status);
c2->setTime(y, status);
icu::FormattedDateInterval formatted =
format->formatToValue(*c1, *c2, status);
if (U_FAILURE(status)) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError));
}
return Format(isolate, formatted);
}
template <
typename T,
std::optional<MaybeDirectHandle<T>> (*Format)(Isolate*,
const icu::FormattedValue&),
MaybeDirectHandle<T> (*Fallback)(Isolate*, DirectHandle<JSDateTimeFormat>,
DirectHandle<Object>, PatternKind, double)>
MaybeDirectHandle<T> FormatRangeCommonWithTemporalSupport(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<Object> x_obj, DirectHandle<Object> y_obj,
const char* method_name) {
bool x_is_temporal = IsTemporalObject(x_obj);
bool y_is_temporal = IsTemporalObject(y_obj);
if (!x_is_temporal) {
ASSIGN_RETURN_ON_EXCEPTION(isolate, x_obj,
Object::ToNumber(isolate, x_obj));
}
if (!y_is_temporal) {
ASSIGN_RETURN_ON_EXCEPTION(isolate, y_obj,
Object::ToNumber(isolate, y_obj));
}
if (x_is_temporal || y_is_temporal) {
if (!SameTemporalType(x_obj, y_obj)) {
THROW_NEW_ERROR(
isolate,
NewTypeError(MessageTemplate::kInvalidArgumentForTemporal, y_obj));
}
}
DateTimeValueRecord x_record;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, x_record,
HandleDateTimeValue(isolate, date_time_format, x_obj, method_name));
DateTimeValueRecord y_record;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, y_record,
HandleDateTimeValue(isolate, date_time_format, y_obj, method_name));
std::unique_ptr<icu::DateIntervalFormat> format(LazyCreateDateIntervalFormat(
isolate, date_time_format, x_record.kind, date_time_format->date_style(),
date_time_format->time_style(),
date_time_format->has_to_locale_string_time_zone()));
if (format.get() == nullptr) {
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError));
}
Managed<icu::SimpleDateFormat>::Ptr icu_date_format =
date_time_format->icu_simple_date_format()->ptr();
std::optional<MaybeDirectHandle<T>> result = CallICUFormatRange<T, Format>(
isolate, date_time_format, format.get(), icu_date_format->getCalendar(),
x_record.epoch_milliseconds, y_record.epoch_milliseconds);
if (result.has_value()) return *result;
return Fallback(isolate, date_time_format, x_obj, x_record.kind,
x_record.epoch_milliseconds);
}
template <typename T,
std::optional<MaybeDirectHandle<T>> (*Format)(
Isolate*, const icu::FormattedValue&),
MaybeDirectHandle<T> (*Fallback)(
Isolate*, const icu::SimpleDateFormat&, double)>
MaybeDirectHandle<T> FormatRangeCommon(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<Object> x_obj, DirectHandle<Object> y_obj,
const char* method_name) {
ASSIGN_RETURN_ON_EXCEPTION(isolate, x_obj, Object::ToNumber(isolate, x_obj));
double x = Object::NumberValue(*x_obj);
ASSIGN_RETURN_ON_EXCEPTION(isolate, y_obj, Object::ToNumber(isolate, y_obj));
double y = Object::NumberValue(*y_obj);
std::optional<MaybeDirectHandle<T>> result =
PartitionDateTimeRangePattern<T, Format>(isolate, date_time_format, x, y,
method_name);
if (result.has_value()) return *result;
return Fallback(isolate, *(date_time_format->icu_simple_date_format()->ptr()),
x);
}
}
MaybeDirectHandle<String> JSDateTimeFormat::FormatRange(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<Object> x, DirectHandle<Object> y, const char* method_name) {
isolate->CountUsage(v8::Isolate::UseCounterFeature::kDateTimeFormatRange);
if (v8_flags.harmony_temporal) {
return FormatRangeCommonWithTemporalSupport<
String, FormattedToString, FormatMillisecondsByKindToString>(
isolate, date_time_format, x, y, method_name);
}
return FormatRangeCommon<String, FormattedToString, FormatDateTime>(
isolate, date_time_format, x, y, method_name);
}
MaybeDirectHandle<JSArray> JSDateTimeFormat::FormatRangeToParts(
Isolate* isolate, DirectHandle<JSDateTimeFormat> date_time_format,
DirectHandle<Object> x, DirectHandle<Object> y, const char* method_name) {
isolate->CountUsage(v8::Isolate::UseCounterFeature::kDateTimeFormatRange);
if (v8_flags.harmony_temporal) {
return FormatRangeCommonWithTemporalSupport<
JSArray, FormattedDateIntervalToJSArray,
FormatMillisecondsByKindToArrayOutputSource>(isolate, date_time_format,
x, y, method_name);
}
return FormatRangeCommon<JSArray, FormattedDateIntervalToJSArray,
FormatMillisecondsToArrayOutputSource>(
isolate, date_time_format, x, y, method_name);
}
}