Implement Temporal.ZonedDateTime.prototype.toLocaleString

Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
This commit is contained in:
Seonghyun Kim 2025-11-27 13:22:25 +09:00 committed by Patrick Kim
commit 45313ea2ce
9 changed files with 299 additions and 93 deletions

View file

@ -220,21 +220,23 @@ static Value builtinDateToTimeString(ExecutionState& state, Value thisValue, siz
}
#if defined(ENABLE_ICU) && defined(ENABLE_INTL)
#define INTL_DATE_TIME_FORMAT_FORMAT(REQUIRED, DEFUALT) \
double x = thisObject->primitiveValue(); \
if (std::isnan(x)) { \
return new ASCIIStringFromExternalMemory("Invalid Date"); \
} \
Value locales, options; \
if (argc >= 1) { \
locales = argv[0]; \
} \
if (argc >= 2) { \
options = argv[1]; \
} \
auto dateTimeOption = IntlDateTimeFormatObject::toDateTimeOptions(state, options, String::fromASCII(REQUIRED), String::fromASCII(DEFUALT)); \
IntlDateTimeFormatObject* dateFormat = new IntlDateTimeFormatObject(state, locales, dateTimeOption); \
auto result = dateFormat->format(state, x); \
#define INTL_DATE_TIME_FORMAT_FORMAT(REQUIRED, DEFUALT) \
double x = thisObject->primitiveValue(); \
if (std::isnan(x)) { \
return new ASCIIStringFromExternalMemory("Invalid Date"); \
} \
Value locales, options; \
if (argc >= 1) { \
locales = argv[0]; \
} \
if (argc >= 2) { \
options = argv[1]; \
} \
auto dateTimeOption = IntlDateTimeFormatObject:: \
toDateTimeOptions(state, options, String::fromASCII(REQUIRED), String::fromASCII(DEFUALT)) \
.first; \
IntlDateTimeFormatObject* dateFormat = new IntlDateTimeFormatObject(state, locales, dateTimeOption); \
auto result = dateFormat->format(state, x); \
return new UTF16String(result.data(), result.length());
#endif

View file

@ -198,13 +198,8 @@ static Value builtinIntlDateTimeFormatFormat(ExecutionState& state, Value thisVa
}
IntlDateTimeFormatObject* dateTimeFormat = callee->internalSlot()->asIntlDateTimeFormatObject();
double value;
if (argc == 0 || argv[0].isUndefined()) {
value = DateObject::currentTime();
} else {
value = argv[0].toNumber(state);
}
auto result = dateTimeFormat->format(state, value);
Value value = argc ? argv[0] : Value();
UTF16StringDataNonGCStd result = dateTimeFormat->format(state, value);
return Value(new UTF16String(result.data(), result.length()));
}

View file

@ -35,6 +35,8 @@
#include "runtime/DateObject.h"
#include "runtime/ArrayObject.h"
#include "intl/IntlDateTimeFormat.h"
namespace Escargot {
#if defined(ENABLE_TEMPORAL)
@ -1288,7 +1290,12 @@ static Value builtinTemporalZonedDateTimeToJSON(ExecutionState& state, Value thi
static Value builtinTemporalZonedDateTimeToLocaleString(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
{
RESOLVE_THIS_BINDING_TO_ZONEDDATETIME2(zonedDateTime, toLocaleString);
return zonedDateTime->toString(state, Value());
Value locales = argc > 0 ? argv[0] : Value();
Value options = argc > 1 ? argv[1] : Value();
auto dateFormat = new IntlDateTimeFormatObject(state, locales, options, zonedDateTime->timeZone().timeZoneName());
auto result = dateFormat->format(state, zonedDateTime, true);
return new UTF16String(result.data(), result.length());
}
static Value builtinTemporalZonedDateTimeEquals(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)

View file

@ -54,6 +54,10 @@
#include "Intl.h"
#include "IntlDateTimeFormat.h"
#if defined(ENABLE_TEMPORAL)
#include "runtime/TemporalObject.h"
#endif
namespace Escargot {
static const char* const intlDateTimeFormatRelevantExtensionKeys[3] = { "ca", "nu", "hc" };
@ -474,14 +478,16 @@ static String* icuFieldTypeToPartName(ExecutionState& state, int32_t fieldName)
}
}
IntlDateTimeFormatObject::IntlDateTimeFormatObject(ExecutionState& state, Value locales, Value options)
: IntlDateTimeFormatObject(state, state.context()->globalObject()->intlDateTimeFormatPrototype(), locales, options)
IntlDateTimeFormatObject::IntlDateTimeFormatObject(ExecutionState& state, Value locales, Value options, Optional<String*> toLocaleStringTimeZone)
: IntlDateTimeFormatObject(state, state.context()->globalObject()->intlDateTimeFormatPrototype(), locales, options, toLocaleStringTimeZone)
{
}
IntlDateTimeFormatObject::IntlDateTimeFormatObject(ExecutionState& state, Object* proto, Value locales, Value options)
IntlDateTimeFormatObject::IntlDateTimeFormatObject(ExecutionState& state, Object* proto, Value locales, Value options, Optional<String*> toLocaleStringTimeZone)
: DerivedObject(state, proto)
, m_wasThereNoFormatOption(false)
, m_locale(String::emptyString())
, m_dataLocale(String::emptyString())
, m_calendar(String::emptyString())
, m_numberingSystem(String::emptyString())
, m_timeZone(String::emptyString())
@ -490,7 +496,9 @@ IntlDateTimeFormatObject::IntlDateTimeFormatObject(ExecutionState& state, Object
{
// Let requestedLocales be ? CanonicalizeLocaleList(locales).
ValueVector requestedLocales = Intl::canonicalizeLocaleList(state, locales);
options = toDateTimeOptions(state, options, state.context()->staticStrings().lazyAny().string(), state.context()->staticStrings().lazyDate().string());
auto toDateTimeOptionsResult = toDateTimeOptions(state, options, state.context()->staticStrings().lazyAny().string(), state.context()->staticStrings().lazyDate().string());
options = toDateTimeOptionsResult.first;
m_wasThereNoFormatOption = toDateTimeOptionsResult.second;
// Let opt be a new Record.
StringMap opt;
// Let matcher be ? GetOption(options, "localeMatcher", "string", « "lookup", "best fit" », "best fit").
@ -575,9 +583,21 @@ IntlDateTimeFormatObject::IntlDateTimeFormatObject(ExecutionState& state, Object
Value timeZone = options.asObject()->get(state, state.context()->staticStrings().lazyTimeZone()).value(state, options.asObject());
// If timeZone is undefined, then
if (timeZone.isUndefined()) {
// Let timeZone be DefaultTimeZone().
timeZone = defaultTimeZone(state);
// If toLocaleStringTimeZone is present, then
if (toLocaleStringTimeZone) {
// Set timeZone to toLocaleStringTimeZone.
timeZone = toLocaleStringTimeZone.value();
} else {
// Else,
// Let timeZone be DefaultTimeZone().
timeZone = defaultTimeZone(state);
}
} else {
// If toLocaleStringTimeZone is present, throw a TypeError exception.
if (toLocaleStringTimeZone) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "got invalid timezone value");
}
// Else,
// Let timeZone be ? ToString(timeZone).
// If the result of IsValidTimeZoneName(timeZone) is false, then Throw a RangeError exception.
@ -610,18 +630,9 @@ IntlDateTimeFormatObject::IntlDateTimeFormatObject(ExecutionState& state, Object
// [[Second]] "second" "2-digit", "numeric"
// [[FractionalSecondDigits]] "fractionalSecondDigits" 1𝔽, 2𝔽, 3𝔽
// [[TimeZoneName]] "timeZoneName" "short", "long", "shortOffset", "longOffset", "shortGeneric", "longGeneric"
std::function<void(String * prop, Value * values, size_t valuesSize)> doTable4 = [&](String* prop, Value* values, size_t valuesSize) {
Value value = Intl::getOption(state, options.asObject(), prop, Intl::StringValue, values, valuesSize, Value());
// Set opt.[[<prop>]] to value.
if (!value.isUndefined()) {
opt.insert(std::make_pair(prop->toNonGCUTF8StringData(), value.toString(state)));
}
};
StringBuilder skeletonBuilder;
String* hour = initDateTimeFormatMainHelper(state, opt, options, hour12, doTable4, skeletonBuilder);
String* hour = initDateTimeFormatMainHelper(state, opt, options.asObject(), hour12, skeletonBuilder);
// Let dataLocaleData be localeData.[[<dataLocale>]].
@ -639,11 +650,32 @@ IntlDateTimeFormatObject::IntlDateTimeFormatObject(ExecutionState& state, Object
// Set dateTimeFormat.[[TimeStyle]] to timeStyle.
m_timeStyle = timeStyle;
initDateTimeFormatOtherHelper(state, String::fromUTF8(dataLocaleWithExtensions.data(), dataLocaleWithExtensions.size()), dateStyle, timeStyle, hourCycle, hour12, hour, opt, dataLocaleWithExtensions, skeletonBuilder);
m_dataLocale = String::fromUTF8(dataLocaleWithExtensions.data(), dataLocaleWithExtensions.size());
auto result = initDateTimeFormatOtherHelper(state, this, m_dataLocale, m_timeZone, dateStyle, timeStyle, hourCycle, m_hourCycle, hour12, hour, opt, skeletonBuilder);
m_hourCycle = result.newHourCycle;
m_timeZoneICU = result.timeZoneICU.value();
m_icuDateFormat = result.icuDateFormat.value();
addFinalizer([](PointerValue* obj, void* data) {
IntlDateTimeFormatObject* self = (IntlDateTimeFormatObject*)obj;
udat_close(self->m_icuDateFormat);
if (self->m_icuDateIntervalFormat) {
udtitvfmt_close(self->m_icuDateIntervalFormat.value());
}
},
nullptr);
}
String* IntlDateTimeFormatObject::initDateTimeFormatMainHelper(ExecutionState& state, StringMap& opt, const Value& options, const Value& hour12, std::function<void(String* prop, Value* values, size_t valuesSize)>& doTable4, StringBuilder& skeletonBuilder)
String* IntlDateTimeFormatObject::initDateTimeFormatMainHelper(ExecutionState& state, StringMap& opt, Object* options, const Value& hour12, StringBuilder& skeletonBuilder)
{
std::function<void(String * prop, Value * values, size_t valuesSize)> doTable4 = [&](String* prop, Value* values, size_t valuesSize) {
Value value = Intl::getOption(state, options, prop, Intl::StringValue, values, valuesSize, Value());
// Set opt.[[<prop>]] to value.
if (!value.isUndefined()) {
opt.insert(std::make_pair(prop->toNonGCUTF8StringData(), value.toString(state)));
}
};
// For each row of Table 4, except the header row, in table order, do
// Let prop be the name given in the Property column of the row.
// If prop is "fractionalSecondDigits", then
@ -767,7 +799,7 @@ String* IntlDateTimeFormatObject::initDateTimeFormatMainHelper(ExecutionState& s
skeletonBuilder.appendString("s");
}
Value fractionalSecondDigits = Intl::getNumberOption(state, options.asObject(), state.context()->staticStrings().lazyFractionalSecondDigits().string(), 1, 3, Value());
Value fractionalSecondDigits = Intl::getNumberOption(state, options, state.context()->staticStrings().lazyFractionalSecondDigits().string(), 1, 3, Value());
if (!fractionalSecondDigits.isUndefined()) {
for (double i = 0; i < fractionalSecondDigits.asNumber(); i++) {
skeletonBuilder.appendString("S");
@ -801,17 +833,19 @@ String* IntlDateTimeFormatObject::initDateTimeFormatMainHelper(ExecutionState& s
return hour;
}
void IntlDateTimeFormatObject::initDateTimeFormatOtherHelper(ExecutionState& state, const Value& dataLocale, const Value& dateStyle, const Value& timeStyle, const Value& hourCycle, const Value& hour12, String* hour, const StringMap& opt, std::string& dataLocaleWithExtensions, StringBuilder& skeletonBuilder)
IntlDateTimeFormatObject::DateTimeFormatOtherHelperResult IntlDateTimeFormatObject::initDateTimeFormatOtherHelper(ExecutionState& state, Optional<IntlDateTimeFormatObject*> dateObject, const Value& dataLocale, String* timeZone, const Value& dateStyle, const Value& timeStyle, const Value& computedHourCycle, const Value& hourCycle, const Value& hour12, String* hour, const StringMap& opt, StringBuilder& skeletonBuilder)
{
UErrorCode status = U_ZERO_ERROR;
UTF16StringData skeleton;
UTF16StringDataNonGCStd patternBuffer;
LocalResourcePointer<UDateTimePatternGenerator> generator(udatpg_open(dataLocale.toString(state)->toUTF8StringData().data(), &status),
auto u8DataLocale = dataLocale.toString(state)->toUTF8StringData();
LocalResourcePointer<UDateTimePatternGenerator> generator(udatpg_open(u8DataLocale.data(), &status),
[](UDateTimePatternGenerator* d) { udatpg_close(d); });
if (U_FAILURE(status)) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "failed to initialize DateTimeFormat");
return;
return {};
}
// If dateStyle is not undefined or timeStyle is not undefined, then
@ -848,12 +882,12 @@ void IntlDateTimeFormatObject::initDateTimeFormatOtherHelper(ExecutionState& sta
status = U_ZERO_ERROR;
LocalResourcePointer<UDateFormat> dateFormatFromStyle(
udat_open(convertFormatStyle(m_timeStyle), convertFormatStyle(m_dateStyle), dataLocaleWithExtensions.data(), m_timeZone->toUTF16StringData().data(), m_timeZone->length(), nullptr, -1, &status),
udat_open(convertFormatStyle(timeStyle), convertFormatStyle(dateStyle), u8DataLocale.data(), timeZone->toUTF16StringData().data(), timeZone->length(), nullptr, -1, &status),
[](UDateFormat* fmt) { udat_close(fmt); });
if (U_FAILURE(status)) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "failed to initialize DateTimeFormat");
return;
return {};
}
auto toPatternResult = INTL_ICU_STRING_BUFFER_OPERATION(udat_toPattern, dateFormatFromStyle.get(), false);
@ -861,7 +895,7 @@ void IntlDateTimeFormatObject::initDateTimeFormatOtherHelper(ExecutionState& sta
replaceNarrowNoBreakSpaceOrThinSpaceWithNormalSpace(patternBuffer);
if (U_FAILURE(toPatternResult.first)) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "failed to initialize DateTimeFormat");
return;
return {};
}
// It is possible that timeStyle includes dayPeriod, which is sensitive to hour-cycle.
@ -874,8 +908,8 @@ void IntlDateTimeFormatObject::initDateTimeFormatOtherHelper(ExecutionState& sta
// We do not care about h11 vs. h12 and h23 vs. h24 difference here since this will be later adjusted by replaceHourCycleInPattern.
//
// test262/test/intl402/DateTimeFormat/prototype/format/timedatestyle-en.js includes the test for this behavior.
if (!Value(m_timeStyle).isUndefined() && (!hourCycle.isUndefinedOrNull() || !hour12.isUndefined())) {
bool isHour12 = hourCycle.isString() && (hourCycle.asString()->equals("h11") || hourCycle.asString()->equals("h12"));
if (!timeStyle.isUndefined() && (!computedHourCycle.isUndefinedOrNull() || !hour12.isUndefined())) {
bool isHour12 = computedHourCycle.isString() && (computedHourCycle.asString()->equals("h11") || computedHourCycle.asString()->equals("h12"));
bool specifiedHour12 = false;
// If hour12 is specified, we prefer it and ignore hourCycle.
if (!hour12.isUndefined()) {
@ -888,7 +922,7 @@ void IntlDateTimeFormatObject::initDateTimeFormatOtherHelper(ExecutionState& sta
auto getSkeletonResult = INTL_ICU_STRING_BUFFER_OPERATION(udatpg_getSkeleton, nullptr, (UChar*)patternBuffer.data(), patternBuffer.size());
if (U_FAILURE(getSkeletonResult.first)) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "failed to initialize DateTimeFormat");
return;
return {};
}
UTF16StringDataNonGCStd skeleton = std::move(getSkeletonResult.second);
@ -906,7 +940,7 @@ void IntlDateTimeFormatObject::initDateTimeFormatOtherHelper(ExecutionState& sta
auto getBestPatternWithOptionsResult = INTL_ICU_STRING_BUFFER_OPERATION(udatpg_getBestPatternWithOptions, generator.get(), (UChar*)skeleton.data(), skeleton.length(), UDATPG_MATCH_HOUR_FIELD_LENGTH);
if (U_FAILURE(getBestPatternWithOptionsResult.first)) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "failed to initialize DateTimeFormat");
return;
return {};
}
patternBuffer = std::move(getBestPatternWithOptionsResult.second);
replaceNarrowNoBreakSpaceOrThinSpaceWithNormalSpace(patternBuffer);
@ -924,13 +958,14 @@ void IntlDateTimeFormatObject::initDateTimeFormatOtherHelper(ExecutionState& sta
auto getBestPatternWithOptionsResult = INTL_ICU_STRING_BUFFER_OPERATION(udatpg_getBestPatternWithOptions, generator.get(), (UChar*)skeleton.data(), skeleton.length(), UDATPG_MATCH_HOUR_FIELD_LENGTH);
if (U_FAILURE(getBestPatternWithOptionsResult.first)) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "failed to initialize DateTimeFormat");
return;
return {};
}
patternBuffer = std::move(getBestPatternWithOptionsResult.second);
replaceNarrowNoBreakSpaceOrThinSpaceWithNormalSpace(patternBuffer);
}
Value hourCycleBefore = m_hourCycle;
Value hourCycleBefore = hourCycle;
Value hourCycleAfter = hourCycleBefore;
// If dateTimeFormat.[[Hour]] is not undefined, then
bool hasHourOption = hour->length();
if (hasHourOption) {
@ -942,7 +977,7 @@ void IntlDateTimeFormatObject::initDateTimeFormatOtherHelper(ExecutionState& sta
replaceNarrowNoBreakSpaceOrThinSpaceWithNormalSpace(patternBuffer);
auto hcDefault = readHourCycleFromPattern(patternBuffer);
// Let hc be dateTimeFormat.[[HourCycle]].
auto hc = m_hourCycle;
auto hc = hourCycle;
// If hc is null, then
if (Value(hc).isUndefined()) {
// Set hc to hcDefault.
@ -981,18 +1016,18 @@ void IntlDateTimeFormatObject::initDateTimeFormatOtherHelper(ExecutionState& sta
// Else,
// Let pattern be bestFormat.[[pattern]].
// Let rangePatterns be bestFormat.[[rangePatterns]].
m_hourCycle = hc;
hourCycleAfter = hc;
} else {
// Set dateTimeFormat.[[HourCycle]] to undefined.
// Let pattern be bestFormat.[[pattern]].
// Let rangePatterns be bestFormat.[[rangePatterns]].
m_hourCycle = Value();
hourCycleAfter = Value();
}
// Set dateTimeFormat.[[Pattern]] to pattern.
// Set dateTimeFormat.[[RangePatterns]] to rangePatterns.
// Return dateTimeFormat.
Value hc = m_hourCycle;
Value hc = hourCycleAfter;
if (hour12.isUndefined()) {
if (!hc.isString() && hourCycleBefore.isString()) {
hc = hourCycleBefore.asString();
@ -1010,10 +1045,12 @@ void IntlDateTimeFormatObject::initDateTimeFormatOtherHelper(ExecutionState& sta
// If bestFormat has a field [[<prop>]], then
// Let p be bestFormat.[[<prop>]].
// Set dateTimeFormat's internal slot whose name is the Internal Slot column of the row to p.
setDateFromPattern(state, patternBuffer, hasHourOption);
if (dateObject) {
dateObject->setDateFromPattern(state, patternBuffer, hasHourOption);
}
status = U_ZERO_ERROR;
UTF16StringData timeZoneView = m_timeZone->toUTF16StringData();
UTF16StringData timeZoneView = timeZone->toUTF16StringData();
if (auto offset = parseUTCOffsetInMinutes((UTF16String(timeZoneView.data(), timeZoneView.length())).bufferAccessData())) {
int64_t minutes = offset.value();
int64_t absMinutes = std::abs(minutes);
@ -1021,29 +1058,26 @@ void IntlDateTimeFormatObject::initDateTimeFormatOtherHelper(ExecutionState& sta
+ pad('0', 2, std::to_string(absMinutes / 60)) + pad('0', 2, std::to_string(absMinutes % 60));
timeZoneView = utf8StringToUTF16String(timeZoneForICU.data(), timeZoneForICU.length());
}
m_timeZoneICU = new UTF16String(std::move(timeZoneView));
m_icuDateFormat = udat_open(UDAT_PATTERN, UDAT_PATTERN, dataLocaleWithExtensions.data(), m_timeZoneICU->bufferAccessData().bufferAs16Bit,
m_timeZoneICU->length(), (UChar*)patternBuffer.data(), patternBuffer.length(), &status);
auto timeZoneICU = new UTF16String(std::move(timeZoneView));
auto icuDateFormat = udat_open(UDAT_PATTERN, UDAT_PATTERN, u8DataLocale.data(), timeZoneICU->bufferAccessData().bufferAs16Bit,
timeZoneICU->length(), (UChar*)patternBuffer.data(), patternBuffer.length(), &status);
if (U_FAILURE(status)) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "failed to initialize DateTimeFormat");
return;
return {};
}
addFinalizer([](PointerValue* obj, void* data) {
IntlDateTimeFormatObject* self = (IntlDateTimeFormatObject*)obj;
udat_close(self->m_icuDateFormat);
if (self->m_icuDateIntervalFormat) {
udtitvfmt_close(self->m_icuDateIntervalFormat.value());
}
},
nullptr);
status = U_ZERO_ERROR;
UCalendar* cal = const_cast<UCalendar*>(udat_getCalendar(m_icuDateFormat));
UCalendar* cal = const_cast<UCalendar*>(udat_getCalendar(icuDateFormat));
std::string type(ucal_getType(cal, &status));
if (status == U_ZERO_ERROR && std::string("gregorian") == type) {
ucal_setGregorianChange(cal, minECMAScriptTime, &status);
}
return {
icuDateFormat,
hourCycleAfter,
timeZoneICU
};
}
void IntlDateTimeFormatObject::setDateFromPattern(ExecutionState& state, UTF16StringDataNonGCStd& patternBuffer, bool hasHourOption)
@ -1187,7 +1221,84 @@ void IntlDateTimeFormatObject::setDateFromPattern(ExecutionState& state, UTF16St
}
}
UTF16StringDataNonGCStd IntlDateTimeFormatObject::format(ExecutionState& state, Value value, bool allowZonedDateTime)
{
LocalResourcePointer<UDateFormat> newFormatHolder(nullptr, [](UDateFormat* fmt) {
udat_close(fmt);
});
auto icuFormat = m_icuDateFormat;
double x;
if (value.isUndefined()) {
x = DateObject::currentTime();
} else {
#if defined(ENABLE_TEMPORAL)
auto dateTimeValue = Temporal::handleDateTimeValue(state, this, value, allowZonedDateTime);
x = dateTimeValue.first;
if (dateTimeValue.second && m_wasThereNoFormatOption && m_dateStyle.isUndefined() && m_timeStyle.isUndefined()) {
StringMap opt;
StringBuilder skeletonBuilder;
Object* options = new Object(state, Object::PrototypeIsNull);
String* numeric = state.context()->staticStrings().lazyNumeric().string();
String* shortString = state.context()->staticStrings().lazyShort().string();
if (dateTimeValue.second.value() == TemporalKind::Instant) {
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyYear(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyMonth(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyDay(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyHour(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyMinute(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazySecond(), ObjectPropertyDescriptor(numeric));
} else if (dateTimeValue.second.value() == TemporalKind::ZonedDateTime) {
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyYear(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyMonth(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyDay(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyHour(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyMinute(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazySecond(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyTimeZoneName(), ObjectPropertyDescriptor(m_timeZoneName.isUndefined() ? shortString : Value(m_timeZoneName)));
} else if (dateTimeValue.second.value() == TemporalKind::PlainDate) {
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyYear(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyMonth(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyDay(), ObjectPropertyDescriptor(numeric));
} else if (dateTimeValue.second.value() == TemporalKind::PlainTime) {
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyHour(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyMinute(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazySecond(), ObjectPropertyDescriptor(numeric));
} else if (dateTimeValue.second.value() == TemporalKind::PlainDateTime) {
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyYear(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyMonth(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyDay(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyHour(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyMinute(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazySecond(), ObjectPropertyDescriptor(numeric));
} else if (dateTimeValue.second.value() == TemporalKind::PlainMonthDay) {
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyMonth(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyDay(), ObjectPropertyDescriptor(numeric));
} else if (dateTimeValue.second.value() == TemporalKind::PlainYearMonth) {
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyYear(), ObjectPropertyDescriptor(numeric));
options->directDefineOwnProperty(state, state.context()->staticStrings().lazyMonth(), ObjectPropertyDescriptor(numeric));
} else {
ASSERT_NOT_REACHED();
}
String* hour = initDateTimeFormatMainHelper(state, opt, options, Value(), skeletonBuilder);
auto result = initDateTimeFormatOtherHelper(state, NullOption, m_dataLocale, m_timeZone, Value(), Value(), Value(), Value(), Value(), hour, opt, skeletonBuilder);
newFormatHolder.reset(result.icuDateFormat.value());
icuFormat = newFormatHolder.get();
}
#else
x = value.toNumber(state);
#endif
}
return format(state, icuFormat, x);
}
UTF16StringDataNonGCStd IntlDateTimeFormatObject::format(ExecutionState& state, double x)
{
return format(state, m_icuDateFormat, x);
}
UTF16StringDataNonGCStd IntlDateTimeFormatObject::format(ExecutionState& state, UDateFormat* dateFormat, double x)
{
// If x is not a finite Number, then throw a RangeError exception.
// If x is NaN, throw a RangeError exception
@ -1199,7 +1310,7 @@ UTF16StringDataNonGCStd IntlDateTimeFormatObject::format(ExecutionState& state,
x = Value(Value::DoubleToIntConvertibleTestNeeds, x).toInteger(state);
// Delegate remaining steps to ICU.
auto formatResult = INTL_ICU_STRING_BUFFER_OPERATION_COMPLEX(udat_format, nullptr, m_icuDateFormat, x);
auto formatResult = INTL_ICU_STRING_BUFFER_OPERATION_COMPLEX(udat_format, nullptr, dateFormat, x);
if (U_FAILURE(formatResult.first)) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "failed to format date value");
}
@ -1319,8 +1430,10 @@ ArrayObject* IntlDateTimeFormatObject::formatToParts(ExecutionState& state, doub
return result;
}
Value IntlDateTimeFormatObject::toDateTimeOptions(ExecutionState& state, Value options, Value required, Value defaults)
std::pair<Value, bool> IntlDateTimeFormatObject::toDateTimeOptions(ExecutionState& state, Value options, Value required, Value defaults)
{
bool wasThereNoFormatOption = false;
// If options is undefined, then let options be null, else let options be ToObject(options).
if (options.isUndefined()) {
options = Value(Value::Null);
@ -1358,6 +1471,10 @@ Value IntlDateTimeFormatObject::toDateTimeOptions(ExecutionState& state, Value o
toDateTimeOptionsTest(state, options, state.context()->staticStrings().lazyFractionalSecondDigits(), needDefaults);
}
if (needDefaults) {
wasThereNoFormatOption = true;
}
// Let dateStyle be ? Get(options, "dateStyle").
bool dateStyle = options.asObject()->hasProperty(state, state.context()->staticStrings().lazyDateStyle());
// Let timeStyle be ? Get(options, "timeStyle").
@ -1401,7 +1518,7 @@ Value IntlDateTimeFormatObject::toDateTimeOptions(ExecutionState& state, Value o
options.asObject()->defineOwnPropertyThrowsException(state, ObjectPropertyName(state.context()->staticStrings().lazySecond()), ObjectPropertyDescriptor(v, ObjectPropertyDescriptor::AllPresent));
}
return options;
return std::make_pair(options, wasThereNoFormatOption);
}
void IntlDateTimeFormatObject::initICUIntervalFormatIfNecessary(ExecutionState& state)

View file

@ -28,19 +28,20 @@ namespace Escargot {
class IntlDateTimeFormatObject : public DerivedObject {
public:
IntlDateTimeFormatObject(ExecutionState& state, Value locales, Value options);
IntlDateTimeFormatObject(ExecutionState& state, Object* proto, Value locales, Value options);
IntlDateTimeFormatObject(ExecutionState& state, Value locales, Value options, Optional<String*> toLocaleStringTimeZone = NullOption);
IntlDateTimeFormatObject(ExecutionState& state, Object* proto, Value locales, Value options, Optional<String*> toLocaleStringTimeZone = NullOption);
virtual bool isIntlDateTimeFormatObject() const override
{
return true;
}
UTF16StringDataNonGCStd format(ExecutionState& state, Value x, bool allowZonedDateTime = false);
UTF16StringDataNonGCStd format(ExecutionState& state, double x);
ArrayObject* formatToParts(ExecutionState& state, double x);
UTF16StringDataNonGCStd formatRange(ExecutionState& state, double startDate, double endDate);
ArrayObject* formatRangeToParts(ExecutionState& state, double startDate, double endDate);
static Value toDateTimeOptions(ExecutionState& state, Value options, Value required, Value defaults);
static std::pair<Value, bool> toDateTimeOptions(ExecutionState& state, Value options, Value required, Value defaults);
static std::string readHourCycleFromPattern(const UTF16StringDataNonGCStd& patternString);
String* locale() const
{
@ -137,12 +138,29 @@ public:
return m_timeStyle;
}
UDateFormat* icuDateFormat()
{
return m_icuDateFormat;
}
bool allOptionsUndefined();
protected:
String* initDateTimeFormatMainHelper(ExecutionState& state, StringMap& opt, const Value& options, const Value& hour12, std::function<void(String* prop, Value* values, size_t valuesSize)>& doTable4, StringBuilder& skeletonBuilder);
void initDateTimeFormatOtherHelper(ExecutionState& state, const Value& dataLocale, const Value& dateStyle, const Value& timeStyle, const Value& hourCycle, const Value& hour12, String* hour, const StringMap& opt, std::string& dataLocaleWithExtensions, StringBuilder& skeletonBuilder);
static String* initDateTimeFormatMainHelper(ExecutionState& state, StringMap& opt, Object* options, const Value& hour12, StringBuilder& skeletonBuilder);
struct DateTimeFormatOtherHelperResult {
Optional<UDateFormat*> icuDateFormat;
Value newHourCycle;
Optional<String*> timeZoneICU;
};
static DateTimeFormatOtherHelperResult initDateTimeFormatOtherHelper(ExecutionState& state, Optional<IntlDateTimeFormatObject*> dateObject, const Value& dataLocale, String* timeZone, const Value& dateStyle, const Value& timeStyle, const Value& computedHourCycle, const Value& hourCycle, const Value& hour12, String* hour, const StringMap& opt, StringBuilder& skeletonBuilder);
void setDateFromPattern(ExecutionState& state, UTF16StringDataNonGCStd& patternBuffer, bool hasHourOption);
void initICUIntervalFormatIfNecessary(ExecutionState& state);
UTF16StringDataNonGCStd format(ExecutionState& state, UDateFormat* dateFormat, double x);
bool m_wasThereNoFormatOption;
String* m_locale;
String* m_dataLocale;
String* m_calendar;
String* m_numberingSystem;
String* m_timeZone;

View file

@ -223,6 +223,11 @@ public:
m_data.payload = (intptr_t)v;
}
bool isUndefined() const
{
return m_data.payload == (intptr_t)(ValueUndefined);
}
bool isStoredInHeap() const
{
if (HAS_SMI_TAG(m_data.payload)) {

View file

@ -58,6 +58,7 @@
#include "runtime/TemporalPlainYearMonthObject.h"
#include "runtime/TemporalZonedDateTimeObject.h"
#include "intl/Intl.h"
#include "intl/IntlDateTimeFormat.h"
namespace Escargot {
@ -3558,6 +3559,65 @@ double Temporal::totalRelativeDuration(ExecutionState& state, const ISO8601::Int
return totalTimeDuration(state, timeDuration, toTemporalUnit(unit));
}
std::pair<double, Optional<TemporalKind>> Temporal::handleDateTimeValue(ExecutionState& state, IntlDateTimeFormatObject* format, Value x, bool allowZonedDateTime)
{
if (x.isObject()) {
auto obj = x.asObject();
auto formatCalendar = Calendar::fromString(format->calendar());
if (obj->isTemporalPlainDateObject()) {
auto objCalendar = obj->asTemporalPlainDateObject()->calendarID();
if (!formatCalendar || (formatCalendar.value() != objCalendar && !objCalendar.isISO8601())) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid Temporal value");
}
auto timeZone = toTemporalTimezoneIdentifier(state, format->timeZone());
auto epochNs = getEpochNanosecondsFor(state, timeZone, ISO8601::PlainDateTime(obj->asTemporalPlainDateObject()->computeISODate(state), ISO8601::PlainTime(12, 0, 0, 0, 0, 0)), TemporalDisambiguationOption::Compatible);
return std::make_pair(ISO8601::ExactTime(epochNs).floorEpochMilliseconds(), TemporalKind::PlainDate);
} else if (obj->isTemporalPlainYearMonthObject()) {
auto objCalendar = obj->asTemporalPlainYearMonthObject()->calendarID();
if (!formatCalendar || formatCalendar.value() != objCalendar) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid Temporal value");
}
auto timeZone = toTemporalTimezoneIdentifier(state, format->timeZone());
auto epochNs = getEpochNanosecondsFor(state, timeZone, ISO8601::PlainDateTime(obj->asTemporalPlainYearMonthObject()->computeISODate(state), ISO8601::PlainTime(12, 0, 0, 0, 0, 0)), TemporalDisambiguationOption::Compatible);
return std::make_pair(ISO8601::ExactTime(epochNs).floorEpochMilliseconds(), TemporalKind::PlainYearMonth);
} else if (obj->isTemporalPlainMonthDayObject()) {
auto objCalendar = obj->asTemporalPlainMonthDayObject()->calendarID();
if (!formatCalendar || formatCalendar.value() != objCalendar) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid Temporal value");
}
auto timeZone = toTemporalTimezoneIdentifier(state, format->timeZone());
auto epochNs = getEpochNanosecondsFor(state, timeZone, ISO8601::PlainDateTime(obj->asTemporalPlainMonthDayObject()->computeISODate(state), ISO8601::PlainTime(12, 0, 0, 0, 0, 0)), TemporalDisambiguationOption::Compatible);
return std::make_pair(ISO8601::ExactTime(epochNs).floorEpochMilliseconds(), TemporalKind::PlainMonthDay);
} else if (obj->isTemporalPlainTimeObject()) {
auto plainTime = obj->asTemporalPlainTimeObject()->plainTime();
auto timeZone = toTemporalTimezoneIdentifier(state, format->timeZone());
auto epochNs = getEpochNanosecondsFor(state, timeZone, ISO8601::PlainDateTime(ISO8601::PlainDate(1970, 1, 1), plainTime), TemporalDisambiguationOption::Compatible);
return std::make_pair(ISO8601::ExactTime(epochNs).floorEpochMilliseconds(), TemporalKind::PlainTime);
} else if (obj->isTemporalPlainDateTimeObject()) {
auto objCalendar = obj->asTemporalPlainDateTimeObject()->calendarID();
if (!formatCalendar || (formatCalendar.value() != objCalendar && !objCalendar.isISO8601())) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid Temporal value");
}
auto timeZone = toTemporalTimezoneIdentifier(state, format->timeZone());
auto epochNs = getEpochNanosecondsFor(state, timeZone, ISO8601::PlainDateTime(obj->asTemporalPlainDateTimeObject()->computeISODate(state), obj->asTemporalPlainDateTimeObject()->plainTime()), TemporalDisambiguationOption::Compatible);
return std::make_pair(ISO8601::ExactTime(epochNs).floorEpochMilliseconds(), TemporalKind::PlainDateTime);
} else if (obj->isTemporalInstantObject()) {
return std::make_pair(ISO8601::ExactTime(obj->asTemporalInstantObject()->epochNanoseconds()).floorEpochMilliseconds(), TemporalKind::Instant);
} else if (obj->isTemporalZonedDateTimeObject()) {
if (allowZonedDateTime) {
auto objCalendar = obj->asTemporalZonedDateTimeObject()->calendarID();
if (!formatCalendar || (formatCalendar.value() != objCalendar && !objCalendar.isISO8601())) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid Temporal value");
}
return std::make_pair(ISO8601::ExactTime(obj->asTemporalZonedDateTimeObject()->epochNanoseconds()).floorEpochMilliseconds(), TemporalKind::ZonedDateTime);
} else {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Invalid Temporal value");
}
}
}
return std::make_pair(x.toNumber(state), NullOption);
}
} // namespace Escargot
#endif

View file

@ -284,6 +284,16 @@ enum class TemporalDirectionOption : uint8_t {
Previous
};
enum class TemporalKind : uint8_t {
PlainDate,
PlainYearMonth,
PlainMonthDay,
PlainTime,
PlainDateTime,
Instant,
ZonedDateTime
};
class Temporal {
public:
static ISO8601::PlainDate computeISODate(ExecutionState& state, UCalendar* ucal);
@ -529,6 +539,9 @@ public:
// https://tc39.es/proposal-temporal/#sec-temporal-totalrelativeduration
static double totalRelativeDuration(ExecutionState& state, const ISO8601::InternalDuration& duration, Int128 originEpochNs, Int128 destEpochNs, ISO8601::PlainDateTime isoDateTime, Optional<TimeZone> timeZone, Calendar calendar, ISO8601::DateTimeUnit unit);
// https://tc39.es/proposal-temporal/#sec-temporal-handledatetimevalue
static std::pair<double, Optional<TemporalKind>> handleDateTimeValue(ExecutionState& state, IntlDateTimeFormatObject* format, Value x, bool allowZonedDateTime = false);
};
} // namespace Escargot

View file

@ -318,17 +318,6 @@
<test id="intl402/Temporal/ZonedDateTime/prototype/since/dst-balancing-result"><reason>TODO</reason></test>
<test id="intl402/Temporal/ZonedDateTime/prototype/since/dst-rounding-result"><reason>TODO</reason></test>
<test id="intl402/Temporal/ZonedDateTime/prototype/startOfDay/dst-skipped-cross-midnight"><reason>TODO</reason></test>
<test id="intl402/Temporal/ZonedDateTime/prototype/toLocaleString/calendar-mismatch"><reason>TODO</reason></test>
<test id="intl402/Temporal/ZonedDateTime/prototype/toLocaleString/dateStyle"><reason>TODO</reason></test>
<test id="intl402/Temporal/ZonedDateTime/prototype/toLocaleString/dateStyle-timeStyle-undefined"><reason>TODO</reason></test>
<test id="intl402/Temporal/ZonedDateTime/prototype/toLocaleString/default-includes-time-and-time-zone-name"><reason>TODO</reason></test>
<test id="intl402/Temporal/ZonedDateTime/prototype/toLocaleString/locales-undefined"><reason>TODO</reason></test>
<test id="intl402/Temporal/ZonedDateTime/prototype/toLocaleString/lone-options-accepted"><reason>TODO</reason></test>
<test id="intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-conflict"><reason>TODO</reason></test>
<test id="intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-timeZone"><reason>TODO</reason></test>
<test id="intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-timeZoneName-affects-instance-time-zone"><reason>TODO</reason></test>
<test id="intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-undefined"><reason>TODO</reason></test>
<test id="intl402/Temporal/ZonedDateTime/prototype/toLocaleString/time-zone-canonicalized"><reason>TODO</reason></test>
<test id="intl402/Temporal/ZonedDateTime/prototype/until/dst-balancing-result"><reason>TODO</reason></test>
<test id="intl402/Temporal/ZonedDateTime/prototype/until/dst-rounding-result"><reason>TODO</reason></test>
<test id="intl402/Temporal/ZonedDateTime/prototype/with/non-iso-calendar-fields"><reason>TODO</reason></test>