Implement Temporal.Instant.{since, until}, Temporal.duration.{toString, negated}

Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
This commit is contained in:
Seonghyun Kim 2025-09-04 13:54:42 +09:00 committed by Patrick Kim
commit 34c2f0a20e
15 changed files with 1092 additions and 373 deletions

View file

@ -75,6 +75,24 @@ static Value builtinTemporalDurationFrom(ExecutionState& state, Value thisValue,
} \
TemporalDurationObject* NAME = thisValue.asObject()->asTemporalDurationObject();
#define RESOLVE_THIS_BINDING_TO_DURATION2(NAME, BUILT_IN_METHOD) \
if (!thisValue.isObject() || !thisValue.asObject()->isTemporalDurationObject()) { \
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, state.context()->staticStrings().lazyCapitalDuration().string(), true, state.context()->staticStrings().BUILT_IN_METHOD.string(), ErrorObject::Messages::GlobalObject_CalledOnIncompatibleReceiver); \
} \
TemporalDurationObject* NAME = thisValue.asObject()->asTemporalDurationObject();
static Value builtinTemporalDurationToString(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
{
RESOLVE_THIS_BINDING_TO_DURATION2(duration, toString);
return duration->toString(state, argc ? argv[0] : Value());
}
static Value builtinTemporalDurationNegated(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
{
RESOLVE_THIS_BINDING_TO_DURATION(duration, Negated);
return new TemporalDurationObject(state, TemporalDurationObject::createNegatedTemporalDuration(duration->duration()));
}
static Value builtinTemporalInstantConstructor(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
{
// If NewTarget is undefined, throw a TypeError exception.
@ -201,6 +219,18 @@ static Value builtinTemporalInstantRound(ExecutionState& state, Value thisValue,
return instant->round(state, argv[0]);
}
static Value builtinTemporalInstantUntil(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
{
RESOLVE_THIS_BINDING_TO_INSTANT(instant, Until);
return instant->differenceTemporalInstant(state, TemporalInstantObject::DifferenceTemporalInstantOperation::Until, argv[0], argc > 1 ? argv[1] : Value());
}
static Value builtinTemporalInstantSince(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
{
RESOLVE_THIS_BINDING_TO_INSTANT(instant, Since);
return instant->differenceTemporalInstant(state, TemporalInstantObject::DifferenceTemporalInstantOperation::Since, argv[0], argc > 1 ? argv[1] : Value());
}
#define RESOLVE_THIS_BINDING_TO_PLAINDATE(NAME, BUILT_IN_METHOD) \
if (!thisValue.isObject() || !thisValue.asObject()->isTemporalPlainDateObject()) { \
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, state.context()->staticStrings().lazyCapitalPlainDate().string(), true, state.context()->staticStrings().lazy##BUILT_IN_METHOD().string(), ErrorObject::Messages::GlobalObject_CalledOnIncompatibleReceiver); \
@ -371,6 +401,11 @@ void GlobalObject::installTemporal(ExecutionState& state)
m_temporalDurationPrototype->directDefineOwnProperty(state, ObjectPropertyName(state.context()->vmInstance()->globalSymbols().toStringTag),
ObjectPropertyDescriptor(Value(strings->lazyTemporalDotDuration().string()), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::ConfigurablePresent)));
m_temporalDurationPrototype->directDefineOwnProperty(state, ObjectPropertyName(strings->toString), ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->toString, builtinTemporalDurationToString, 0, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
m_temporalDurationPrototype->directDefineOwnProperty(state, ObjectPropertyName(strings->lazyNegated()), ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->lazyNegated(), builtinTemporalDurationNegated, 0, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
#define DEFINE_DURATION_PROTOTYPE_GETTER_PROPERTY(name, stringName, Name) \
{ \
AtomicString name(state.context(), "get " stringName); \
@ -445,6 +480,8 @@ void GlobalObject::installTemporal(ExecutionState& state)
m_temporalInstantPrototype->directDefineOwnProperty(state, ObjectPropertyName(strings->toJSON), ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->toJSON, builtinTemporalInstantToJSON, 0, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
m_temporalInstantPrototype->directDefineOwnProperty(state, ObjectPropertyName(strings->toLocaleString), ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->toLocaleString, builtinTemporalInstantToLocaleString, 0, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
m_temporalInstantPrototype->directDefineOwnProperty(state, ObjectPropertyName(strings->round), ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->round, builtinTemporalInstantRound, 1, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
m_temporalInstantPrototype->directDefineOwnProperty(state, ObjectPropertyName(strings->lazyUntil()), ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->lazyUntil(), builtinTemporalInstantUntil, 1, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
m_temporalInstantPrototype->directDefineOwnProperty(state, ObjectPropertyName(strings->lazySince()), ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->lazySince(), builtinTemporalInstantSince, 1, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
{
auto getter = new NativeFunctionObject(state, NativeFunctionInfo(strings->lazyGetEpochMilliseconds(), builtinTemporalInstantGetEpochMilliseconds, 0, NativeFunctionInfo::Strict));

View file

@ -2884,6 +2884,17 @@ Value Intl::supportedLocales(ExecutionState& state, const Vector<String*, GCUtil
return Object::createArrayFromList(state, subset);
}
Optional<Object*> Intl::getOptionsObject(ExecutionState& state, const Value& options)
{
Optional<Object*> resolvedOptions;
if (options.isObject()) {
resolvedOptions = options.asObject();
} else if (!options.isUndefined()) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Invalid options value");
}
return resolvedOptions;
}
Value Intl::getOption(ExecutionState& state, Object* options, Value property, Intl::OptionValueType type, Value* values, size_t valuesLength, const Value& fallback)
{
// http://www.ecma-international.org/ecma-402/1.0/index.html#sec-9.2.9

View file

@ -43,6 +43,7 @@ public:
static ValueVector canonicalizeLocaleList(ExecutionState& state, Value locales);
static StringMap resolveLocale(ExecutionState& state, const Vector<String*, GCUtil::gc_malloc_allocator<String*>>& availableLocales, const ValueVector& requestedLocales, const StringMap& options, const char* const relevantExtensionKeys[], size_t relevantExtensionKeyCount, LocaleDataImplFunction localeData);
static Value supportedLocales(ExecutionState& state, const Vector<String*, GCUtil::gc_malloc_allocator<String*>>& availableLocales, const ValueVector& requestedLocales, Value options);
static Optional<Object*> getOptionsObject(ExecutionState& state, const Value& options); // https://tc39.es/proposal-temporal/#sec-getoptionsobject
enum OptionValueType {
StringValue,
BooleanValue,

View file

@ -588,20 +588,20 @@ inline double purifyNaN(double value)
return value;
}
static std::string buildDecimalFormat(DurationRecord::Type unit, Int128 ns)
static std::string buildDecimalFormat(ISO8601::DateTimeUnit unit, Int128 ns)
{
ASSERT(unit == DurationRecord::Type::Seconds || unit == DurationRecord::Type::Milliseconds || unit == DurationRecord::Type::Microseconds);
ASSERT(unit == ISO8601::DateTimeUnit::Second || unit == ISO8601::DateTimeUnit::Millisecond || unit == ISO8601::DateTimeUnit::Microsecond);
int flactionalDigits = 0;
int64_t exponent = 0;
if (unit == DurationRecord::Type::Seconds) {
if (unit == ISO8601::DateTimeUnit::Second) {
flactionalDigits = 9;
exponent = 1000000000;
} else if (unit == DurationRecord::Type::Milliseconds) {
} else if (unit == ISO8601::DateTimeUnit::Millisecond) {
flactionalDigits = 6;
exponent = 1000000;
} else {
ASSERT(unit == DurationRecord::Type::Microseconds);
ASSERT(unit == ISO8601::DateTimeUnit::Microsecond);
flactionalDigits = 3;
exponent = 1000;
}
@ -681,7 +681,7 @@ std::vector<IntlDurationFormatObject::Element> IntlDurationFormatObject::collect
std::vector<Element> elements;
Optional<DurationSignType> durationSign;
for (uint8_t index = 0; index < 10 && !done; ++index) {
DurationRecord::Type unit = static_cast<DurationRecord::Type>(index);
ISO8601::DateTimeUnit unit = static_cast<ISO8601::DateTimeUnit>(index);
auto unitData = durationFormat->data(index);
double value = duration[unit];
Optional<Int128> totalNanosecondsValue;
@ -690,27 +690,27 @@ std::vector<IntlDurationFormatObject::Element> IntlDurationFormatObject::collect
switch (unit) {
// 3.j. If unit is "seconds", "milliseconds", or "microseconds", then
case DurationRecord::Type::Seconds:
case DurationRecord::Type::Milliseconds:
case DurationRecord::Type::Microseconds: {
case ISO8601::DateTimeUnit::Second:
case ISO8601::DateTimeUnit::Millisecond:
case ISO8601::DateTimeUnit::Microsecond: {
skeletonBuilder = u"rounding-mode-down";
String* nextStyle = state.context()->staticStrings().lazyLong().string();
if (unit == DurationRecord::Type::Seconds)
nextStyle = durationFormat->data(static_cast<unsigned>(DurationRecord::Type::Milliseconds)).first;
else if (unit == DurationRecord::Type::Milliseconds)
nextStyle = durationFormat->data(static_cast<unsigned>(DurationRecord::Type::Microseconds)).first;
if (unit == ISO8601::DateTimeUnit::Second)
nextStyle = durationFormat->data(static_cast<unsigned>(ISO8601::DateTimeUnit::Millisecond)).first;
else if (unit == ISO8601::DateTimeUnit::Millisecond)
nextStyle = durationFormat->data(static_cast<unsigned>(ISO8601::DateTimeUnit::Microsecond)).first;
else {
ASSERT(unit == DurationRecord::Type::Microseconds);
nextStyle = durationFormat->data(static_cast<unsigned>(DurationRecord::Type::Nanoseconds)).first;
ASSERT(unit == ISO8601::DateTimeUnit::Microsecond);
nextStyle = durationFormat->data(static_cast<unsigned>(ISO8601::DateTimeUnit::Nanosecond)).first;
}
if (nextStyle->equals("numeric")) {
if (unit == DurationRecord::Type::Seconds) {
totalNanosecondsValue = duration.totalNanoseconds(DurationRecord::Type::Seconds);
} else if (unit == DurationRecord::Type::Milliseconds) {
totalNanosecondsValue = duration.totalNanoseconds(DurationRecord::Type::Milliseconds);
if (unit == ISO8601::DateTimeUnit::Second) {
totalNanosecondsValue = duration.totalNanoseconds(ISO8601::DateTimeUnit::Second);
} else if (unit == ISO8601::DateTimeUnit::Millisecond) {
totalNanosecondsValue = duration.totalNanoseconds(ISO8601::DateTimeUnit::Millisecond);
} else {
ASSERT(unit == DurationRecord::Type::Microseconds);
totalNanosecondsValue = duration.totalNanoseconds(DurationRecord::Type::Microseconds);
ASSERT(unit == ISO8601::DateTimeUnit::Microsecond);
totalNanosecondsValue = duration.totalNanoseconds(ISO8601::DateTimeUnit::Microsecond);
}
ASSERT(totalNanosecondsValue);
@ -807,7 +807,7 @@ std::vector<IntlDurationFormatObject::Element> IntlDurationFormatObject::collect
auto formatIntl128AsDecimal = [&](const UTF16StringDataNonGCStd& skeleton) -> LocalResourcePointer<UFormattedNumber> {
ASSERT(totalNanosecondsValue);
ASSERT(unit == DurationRecord::Type::Seconds || unit == DurationRecord::Type::Milliseconds || unit == DurationRecord::Type::Microseconds);
ASSERT(unit == ISO8601::DateTimeUnit::Second || unit == ISO8601::DateTimeUnit::Millisecond || unit == ISO8601::DateTimeUnit::Microsecond);
UErrorCode status = U_ZERO_ERROR;
LocalResourcePointer<UNumberFormatter> numberFormatter(unumf_openForSkeletonAndLocale(skeleton.data(), skeleton.length(), durationFormat->m_dataLocaleWithExtensions->toNonGCUTF8StringData().data(), &status),
@ -839,19 +839,19 @@ std::vector<IntlDurationFormatObject::Element> IntlDurationFormatObject::collect
// 3.l.i. If style is "2-digit" or "numeric", then
if (style->equals("2-digit") || style->equals("numeric")) {
// https://tc39.es/proposal-intl-duration-format/#sec-formatnumericunits
ASSERT(unit == DurationRecord::Type::Hours || unit == DurationRecord::Type::Minutes || unit == DurationRecord::Type::Seconds);
ASSERT(unit == ISO8601::DateTimeUnit::Hour || unit == ISO8601::DateTimeUnit::Minute || unit == ISO8601::DateTimeUnit::Second);
double secondsValue = duration[DurationRecord::Type::Seconds];
if (durationFormat->data(static_cast<unsigned>(DurationRecord::Type::Milliseconds)).first->equals("numeric")) {
secondsValue = secondsValue + duration[DurationRecord::Type::Milliseconds] / 1000.0 + duration[DurationRecord::Type::Microseconds] / 1000000.0 + duration[DurationRecord::Type::Nanoseconds] / 1000000000.0;
double secondsValue = duration[ISO8601::DateTimeUnit::Second];
if (durationFormat->data(static_cast<unsigned>(ISO8601::DateTimeUnit::Millisecond)).first->equals("numeric")) {
secondsValue = secondsValue + duration[ISO8601::DateTimeUnit::Millisecond] / 1000.0 + duration[ISO8601::DateTimeUnit::Microsecond] / 1000000.0 + duration[ISO8601::DateTimeUnit::Nanosecond] / 1000000000.0;
}
bool needsFormatHours = duration[DurationRecord::Type::Hours] || !durationFormat->data(static_cast<unsigned>(DurationRecord::Type::Hours)).second->equals("auto");
bool needsFormatSeconds = secondsValue || !durationFormat->data(static_cast<unsigned>(DurationRecord::Type::Seconds)).second->equals("auto");
bool needsFormatMinutes = (needsFormatHours && needsFormatSeconds) || duration[DurationRecord::Type::Minutes] || !durationFormat->data(static_cast<unsigned>(DurationRecord::Type::Minutes)).second->equals("auto");
bool needsFormatHours = duration[ISO8601::DateTimeUnit::Hour] || !durationFormat->data(static_cast<unsigned>(ISO8601::DateTimeUnit::Hour)).second->equals("auto");
bool needsFormatSeconds = secondsValue || !durationFormat->data(static_cast<unsigned>(ISO8601::DateTimeUnit::Second)).second->equals("auto");
bool needsFormatMinutes = (needsFormatHours && needsFormatSeconds) || duration[ISO8601::DateTimeUnit::Minute] || !durationFormat->data(static_cast<unsigned>(ISO8601::DateTimeUnit::Minute)).second->equals("auto");
bool needsFormat = (unit == DurationRecord::Type::Hours && needsFormatHours) || (unit == DurationRecord::Type::Minutes && needsFormatMinutes) || (unit == DurationRecord::Type::Seconds && needsFormatSeconds);
bool needsSeparator = (unit == DurationRecord::Type::Hours && needsFormatHours && needsFormatMinutes) || (unit == DurationRecord::Type::Minutes && needsFormatSeconds);
bool needsFormat = (unit == ISO8601::DateTimeUnit::Hour && needsFormatHours) || (unit == ISO8601::DateTimeUnit::Minute && needsFormatMinutes) || (unit == ISO8601::DateTimeUnit::Second && needsFormatSeconds);
bool needsSeparator = (unit == ISO8601::DateTimeUnit::Hour && needsFormatHours && needsFormatMinutes) || (unit == ISO8601::DateTimeUnit::Minute && needsFormatSeconds);
if (needsFormat) {
adjustSignDisplay();

View file

@ -56,7 +56,7 @@ protected:
struct Element {
ElementType m_type;
bool m_valueSignBit;
DurationRecord::Type m_unit;
ISO8601::DateTimeUnit m_unit;
UTF16StringDataNonGCStd m_string;
LocalResourcePointer<UFormattedNumber> m_formattedNumber;
};

View file

@ -983,18 +983,22 @@ namespace Escargot {
F(InLeapYear, "inLeapYear") \
F(Instant, "instant") \
F(ISO8601, "iso8601") \
F(LargestUnit, "largestUnit") \
F(MonthCode, "monthCode") \
F(MonthsInYear, "monthsInYear") \
F(Negated, "negated") \
F(Overflow, "overflow") \
F(PlainDateISO, "plainDateISO") \
F(PlainDateTimeISO, "plainDateTimeISO") \
F(PlainTimeISO, "plainTimeISO") \
F(Since, "since") \
F(SmallestUnit, "smallestUnit") \
F(TemporalDotDuration, "Temporal.Duration") \
F(TemporalDotInstant, "Temporal.Instant") \
F(TemporalDotNow, "Temporal.Now") \
F(TemporalDotPlainDate, "Temporal.PlainDate") \
F(TimeZoneId, "timeZoneId") \
F(Until, "until") \
F(WeekOfYear, "weekOfYear") \
F(YearOfWeek, "yearOfWeek") \
F(ZonedDateTimeISO, "zonedDateTimeISO")

View file

@ -1,5 +1,7 @@
#if defined(ENABLE_TEMPORAL)
/*
* Copyright (C) 2021 Sony Interactive Entertainment Inc.
* Copyright (C) 2021-2023 Apple Inc. All rights reserved.
* Copyright (c) 2025-present Samsung Electronics Co., Ltd
*
* This library is free software; you can redistribute it and/or
@ -58,6 +60,310 @@ TemporalDurationObject::TemporalDurationObject(ExecutionState& state, const ISO8
{
}
ISO8601::Duration TemporalDurationObject::temporalDurationFromInternal(ExecutionState& state, ISO8601::InternalDuration internalDuration, ISO8601::DateTimeUnit largestUnit)
{
// Let days, hours, minutes, seconds, milliseconds, and microseconds be 0.
double days = 0, hours = 0, minutes = 0, seconds = 0;
Int128 milliseconds = 0, microseconds = 0;
// Let sign be TimeDurationSign(internalDuration.[[Time]]).
int32_t sign = internalDuration.sign();
// Let nanoseconds be abs(internalDuration.[[Time]]).
auto nanoseconds = std::abs(internalDuration.time());
// If TemporalUnitCategory(largestUnit) is date, then
if (ISO8601::toDateTimeCategory(largestUnit) == ISO8601::DateTimeUnitCategory::Date) {
// Set microseconds to floor(nanoseconds / 1000).
// Set nanoseconds to nanoseconds modulo 1000.
// Set milliseconds to floor(microseconds / 1000).
// Set microseconds to microseconds modulo 1000.
// Set seconds to floor(milliseconds / 1000).
// Set milliseconds to milliseconds modulo 1000.
// Set minutes to floor(seconds / 60).
// Set seconds to seconds modulo 60.
// Set hours to floor(minutes / 60).
// Set minutes to minutes modulo 60.
// Set days to floor(hours / 24).
// Set hours to hours modulo 24.
microseconds = nanoseconds / 1000;
nanoseconds = nanoseconds % 1000;
milliseconds = microseconds / 1000;
microseconds = microseconds % 1000;
seconds = (double)(milliseconds / 1000);
milliseconds = milliseconds % 1000;
minutes = std::trunc(seconds / 60);
seconds = std::fmod(seconds, 60);
hours = std::trunc(minutes / 60);
minutes = std::fmod(minutes, 60);
days = std::trunc(hours / 24);
hours = std::fmod(hours, 24);
} else if (largestUnit == ISO8601::DateTimeUnit::Hour) {
// Else if largestUnit is hour, then
// Set microseconds to floor(nanoseconds / 1000).
// Set nanoseconds to nanoseconds modulo 1000.
// Set milliseconds to floor(microseconds / 1000).
// Set microseconds to microseconds modulo 1000.
// Set seconds to floor(milliseconds / 1000).
// Set milliseconds to milliseconds modulo 1000.
// Set minutes to floor(seconds / 60).
// Set seconds to seconds modulo 60.
// Set hours to floor(minutes / 60).
// Set minutes to minutes modulo 60.
microseconds = nanoseconds / 1000;
nanoseconds = nanoseconds % 1000;
milliseconds = microseconds / 1000;
microseconds = microseconds % 1000;
seconds = (double)(milliseconds / 1000);
milliseconds = milliseconds % 1000;
minutes = std::trunc(seconds / 60);
seconds = std::fmod(seconds, 60);
hours = std::trunc(minutes / 60);
minutes = std::fmod(minutes, 60);
} else if (largestUnit == ISO8601::DateTimeUnit::Minute) {
// Else if largestUnit is minute, then
// Set microseconds to floor(nanoseconds / 1000).
// Set nanoseconds to nanoseconds modulo 1000.
// Set milliseconds to floor(microseconds / 1000).
// Set microseconds to microseconds modulo 1000.
// Set seconds to floor(milliseconds / 1000).
// Set milliseconds to milliseconds modulo 1000.
// Set minutes to floor(seconds / 60).
// Set seconds to seconds modulo 60.
microseconds = nanoseconds / 1000;
nanoseconds = nanoseconds % 1000;
milliseconds = microseconds / 1000;
microseconds = microseconds % 1000;
seconds = (double)(milliseconds / 1000);
milliseconds = milliseconds % 1000;
minutes = std::trunc(seconds / 60);
seconds = std::fmod(seconds, 60);
} else if (largestUnit == ISO8601::DateTimeUnit::Second) {
// Else if largestUnit is second, then
// Set microseconds to floor(nanoseconds / 1000).
// Set nanoseconds to nanoseconds modulo 1000.
// Set milliseconds to floor(microseconds / 1000).
// Set microseconds to microseconds modulo 1000.
// Set seconds to floor(milliseconds / 1000).
// Set milliseconds to milliseconds modulo 1000.
microseconds = nanoseconds / 1000;
nanoseconds = nanoseconds % 1000;
milliseconds = microseconds / 1000;
microseconds = microseconds % 1000;
seconds = (double)(milliseconds / 1000);
milliseconds = milliseconds % 1000;
} else if (largestUnit == ISO8601::DateTimeUnit::Millisecond) {
// Else if largestUnit is millisecond, then
// Set microseconds to floor(nanoseconds / 1000).
// Set nanoseconds to nanoseconds modulo 1000.
// Set milliseconds to floor(microseconds / 1000).
// Set microseconds to microseconds modulo 1000.
microseconds = nanoseconds / 1000;
nanoseconds = nanoseconds % 1000;
milliseconds = microseconds / 1000;
microseconds = microseconds % 1000;
} else if (largestUnit == ISO8601::DateTimeUnit::Microsecond) {
// Else if largestUnit is microsecond, then
// Set microseconds to floor(nanoseconds / 1000).
// Set nanoseconds to nanoseconds modulo 1000.
microseconds = nanoseconds / 1000;
nanoseconds = nanoseconds % 1000;
} else {
// Assert: largestUnit is nanosecond.
}
// Return ? CreateTemporalDuration(internalDuration.[[Date]].[[Years]], internalDuration.[[Date]].[[Months]],
// internalDuration.[[Date]].[[Weeks]], internalDuration.[[Date]].[[Days]] + days × sign,
// hours × sign, minutes × sign, seconds × sign, milliseconds × sign, microseconds × sign, nanoseconds × sign).
if (hours) {
hours *= sign;
}
if (minutes) {
minutes *= sign;
}
if (seconds) {
seconds *= sign;
}
if (milliseconds) {
milliseconds *= sign;
}
if (microseconds) {
microseconds *= sign;
}
if (nanoseconds) {
nanoseconds *= sign;
}
return ISO8601::Duration{ internalDuration.dateDuration().years(), internalDuration.dateDuration().months(), internalDuration.dateDuration().weeks(), internalDuration.dateDuration().days() + days * sign, hours, minutes,
static_cast<double>(seconds), static_cast<double>(milliseconds), static_cast<double>(microseconds), static_cast<double>(nanoseconds) };
}
ISO8601::Duration TemporalDurationObject::createNegatedTemporalDuration(ISO8601::Duration duration)
{
// Return ! CreateTemporalDuration(-duration.[[Years]], -duration.[[Months]], -duration.[[Weeks]], -duration.[[Days]],
// -duration.[[Hours]], -duration.[[Minutes]], -duration.[[Seconds]], -duration.[[Milliseconds]], -duration.[[Microseconds]], -duration.[[Nanoseconds]]).
for (size_t i = 0; i < 10; i++) {
// avoiding -0.0
if (duration[i]) {
duration[i] = -duration[i];
}
}
return duration;
}
static void appendInteger(StringBuilder& builder, double value)
{
ASSERT(std::isfinite(value));
auto absValue = std::abs(value);
if (absValue <= 9007199254740991.0 /* MAX_SAFE_INTEGER */) {
builder.appendString(String::fromDouble(absValue));
return;
}
BigIntData bi(absValue);
std::string s = bi.toNonGCStdString();
builder.appendString(String::fromASCII(s.data(), s.length()));
}
static void appendInteger(StringBuilder& builder, Int128 value)
{
std::string s = std::to_string(std::abs(value));
builder.appendString(String::fromASCII(s.data(), s.length()));
}
String* TemporalDurationObject::temporalDurationToString(ISO8601::Duration duration, Value precision)
{
auto balancedMicroseconds = static_cast<Int128>(duration.microseconds()) + static_cast<Int128>(std::trunc(duration.nanoseconds() / 1000));
auto balancedNanoseconds = static_cast<Int128>(duration.nanoseconds()) % 1000;
auto balancedMilliseconds = static_cast<Int128>(duration.milliseconds()) + (balancedMicroseconds / 1000);
balancedMicroseconds = balancedMicroseconds % 1000;
auto balancedSeconds = static_cast<Int128>(duration.seconds()) + (balancedMilliseconds / 1000);
balancedMilliseconds = balancedMilliseconds % 1000;
StringBuilder builder;
auto sign = duration.sign();
if (sign < 0)
builder.appendChar('-');
builder.appendChar('P');
if (duration.years()) {
appendInteger(builder, duration.years());
builder.appendChar('Y');
}
if (duration.months()) {
appendInteger(builder, duration.months());
builder.appendChar('M');
}
if (duration.weeks()) {
appendInteger(builder, duration.weeks());
builder.appendChar('W');
}
if (duration.days()) {
appendInteger(builder, duration.days());
builder.appendChar('D');
}
// The zero value is displayed in seconds.
auto usesSeconds = balancedSeconds || balancedMilliseconds || balancedMicroseconds || balancedNanoseconds || !sign || !precision.isString() || !precision.asString()->equals("auto");
if (!duration.hours() && !duration.minutes() && !usesSeconds)
return builder.finalize();
builder.appendChar('T');
if (duration.hours()) {
appendInteger(builder, duration.hours());
builder.appendChar('H');
}
if (duration.minutes()) {
appendInteger(builder, duration.minutes());
builder.appendChar('M');
}
if (usesSeconds) {
appendInteger(builder, balancedSeconds);
// Int128 fraction = static_cast<int64_t>(std::abs(balancedMilliseconds) * 1e6 + std::abs(balancedMicroseconds) * 1e3 + std::abs(balancedNanoseconds));
Int128 fraction = std::abs(balancedMilliseconds) * 1000000 + std::abs(balancedMicroseconds) * 1000 + std::abs(balancedNanoseconds);
Temporal::formatSecondsStringFraction(builder, static_cast<int64_t>(fraction), precision);
builder.appendChar('S');
}
return builder.finalize();
}
String* TemporalDurationObject::toString(ExecutionState& state, Value options)
{
// Let duration be the this value.
// Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]).
// Let resolvedOptions be ? GetOptionsObject(options).
auto resolvedOptions = Intl::getOptionsObject(state, options);
// NOTE: The following steps read options and perform independent validation in alphabetical order (GetTemporalFractionalSecondDigitsOption reads "fractionalSecondDigits" and GetRoundingModeOption reads "roundingMode").
// Let digits be ? GetTemporalFractionalSecondDigitsOption(resolvedOptions).
auto digits = Temporal::getTemporalFractionalSecondDigitsOption(state, resolvedOptions);
// Let roundingMode be ? GetRoundingModeOption(resolvedOptions, trunc).
auto roundingMode = Temporal::getRoundingModeOption(state, resolvedOptions, state.context()->staticStrings().trunc.string());
// Let smallestUnit be ? GetTemporalUnitValuedOption(resolvedOptions, "smallestUnit", unset).
auto smallestUnit = Temporal::getTemporalUnitValuedOption(state, resolvedOptions, state.context()->staticStrings().lazySmallestUnit().string(), NullOption);
// Perform ? ValidateTemporalUnitValue(smallestUnit, time).
Temporal::validateTemporalUnitValue(state, smallestUnit, ISO8601::DateTimeUnitCategory::Time, nullptr, 0);
// If smallestUnit is hour or minute, throw a RangeError exception.
if (smallestUnit && (smallestUnit.value() == TemporalUnit::Hour || smallestUnit.value() == TemporalUnit::Minute)) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid smallestUnit value");
}
// Let precision be ToSecondsStringPrecisionRecord(smallestUnit, digits).
auto precision = Temporal::toSecondsStringPrecisionRecord(state, toDateTimeUnit(smallestUnit), digits);
// If precision.[[Unit]] is nanosecond and precision.[[Increment]] = 1, then
if (precision.unit == ISO8601::DateTimeUnit::Nanosecond && precision.increment == 1) {
// Return TemporalDurationToString(duration, precision.[[Precision]]).
return temporalDurationToString(m_duration, precision.precision);
}
// Let largestUnit be DefaultTemporalLargestUnit(duration).
auto largestUnit = m_duration.defaultTemporalLargestUnit();
// Let internalDuration be ToInternalDurationRecord(duration).
ISO8601::InternalDuration internalDuration = toInternalDurationRecord(m_duration);
// Let timeDuration be ? RoundTimeDuration(internalDuration.[[Time]], precision.[[Increment]], precision.[[Unit]], roundingMode).
auto timeDuration = roundTimeDuration(state, internalDuration.time(), precision.increment, precision.unit, roundingMode);
// Set internalDuration to CombineDateAndTimeDuration(internalDuration.[[Date]], timeDuration).
internalDuration = ISO8601::InternalDuration::combineDateAndTimeDuration(internalDuration.dateDuration(), timeDuration);
// Let roundedLargestUnit be LargerOfTwoTemporalUnits(largestUnit, second).
auto roundedLargestUnit = Temporal::largerOfTwoTemporalUnits(largestUnit, ISO8601::DateTimeUnit::Second);
// Let roundedDuration be ? TemporalDurationFromInternal(internalDuration, roundedLargestUnit).
auto roundedDuration = temporalDurationFromInternal(state, internalDuration, roundedLargestUnit);
// Return TemporalDurationToString(roundedDuration, precision.[[Precision]]).
return temporalDurationToString(roundedDuration, precision.precision);
}
ISO8601::InternalDuration TemporalDurationObject::toInternalDurationRecord(ISO8601::Duration duration)
{
ISO8601::Duration dateDuration{
duration[0], duration[1], duration[2], duration[3], 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
};
auto t = duration.totalNanoseconds(ISO8601::DateTimeUnit::Hour);
return ISO8601::InternalDuration(dateDuration, t);
}
Int128 TemporalDurationObject::roundTimeDuration(ExecutionState& state, Int128 timeDuration, unsigned increment, ISO8601::DateTimeUnit unit, ISO8601::RoundingMode roundingMode)
{
// Let divisor be the value in the "Length in Nanoseconds" column of the row of Table 21 whose "Value" column contains unit.
Int128 divisor = ISO8601::lengthInNanoseconds(unit);
// Return ? RoundTimeDurationToIncrement(timeDuration, divisor × increment, roundingMode)
return TemporalDurationObject::roundTimeDurationToIncrement(state, timeDuration, divisor * increment, roundingMode);
}
Int128 TemporalDurationObject::roundTimeDurationToIncrement(ExecutionState& state, Int128 d, Int128 increment, ISO8601::RoundingMode roundingMode)
{
// Let rounded be RoundNumberToIncrement(d, increment, roundingMode).
Int128 rounded = ISO8601::roundNumberToIncrement(d, increment, roundingMode);
// 2. If abs(rounded) > maxTimeDuration, throw a RangeError exception.
if (std::abs(rounded) > ISO8601::InternalDuration::maxTimeDuration) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Rounded time duration exceeds maximum");
}
// 3. Return rounded.
return rounded;
}
} // namespace Escargot
#endif

View file

@ -35,6 +35,12 @@ public:
TemporalDurationObject(ExecutionState& state, const ISO8601::Duration& duration);
// https://tc39.es/proposal-temporal/#sec-temporal-temporaldurationfrominternal
static ISO8601::Duration temporalDurationFromInternal(ExecutionState& state, ISO8601::InternalDuration internalDuration, ISO8601::DateTimeUnit largestUnit);
// https://tc39.es/proposal-temporal/#sec-temporal-createnegatedtemporalduration
static ISO8601::Duration createNegatedTemporalDuration(ISO8601::Duration duration);
virtual bool isTemporalDurationObject() const override
{
return true;
@ -50,7 +56,7 @@ public:
return m_duration[static_cast<unsigned>(idx)];
}
double operator[](ISO8601::Duration::Type idx) const
double operator[](ISO8601::DateTimeUnit idx) const
{
return operator[](static_cast<size_t>(idx));
}
@ -60,7 +66,7 @@ public:
return m_duration[static_cast<unsigned>(idx)];
}
double& operator[](ISO8601::Duration::Type idx)
double& operator[](ISO8601::DateTimeUnit idx)
{
return operator[](static_cast<size_t>(idx));
}
@ -68,17 +74,20 @@ public:
// https://tc39.es/proposal-temporal/#sec-durationsign
int sign() const
{
for (const auto& v : m_duration) {
if (v < 0) {
return -1;
}
if (v > 0) {
return 1;
}
}
return 0;
return m_duration.sign();
}
String* toString(ExecutionState& state, Value options);
static String* temporalDurationToString(ISO8601::Duration duration, Value precision);
// https://tc39.es/proposal-temporal/#sec-temporal-tointernaldurationrecord
static ISO8601::InternalDuration toInternalDurationRecord(ISO8601::Duration duration);
// https://tc39.es/proposal-temporal/#sec-temporal-roundtimeduration
static Int128 roundTimeDuration(ExecutionState& state, Int128 timeDuration, unsigned increment, ISO8601::DateTimeUnit unit, ISO8601::RoundingMode roundingMode);
// https://tc39.es/proposal-temporal/#sec-temporal-roundtimedurationtoincrement
static Int128 roundTimeDurationToIncrement(ExecutionState& state, Int128 d, Int128 increment, ISO8601::RoundingMode roundingMode);
private:
ISO8601::Duration m_duration;
};

View file

@ -20,6 +20,7 @@
#include "Escargot.h"
#include "TemporalInstantObject.h"
#include "TemporalDurationObject.h"
#include "intl/Intl.h"
#include "util/ISO8601.h"
@ -39,42 +40,11 @@ Value TemporalInstantObject::epochMilliseconds() const
}
static Int128 resolveNanosecondsValueByUnit(String* unit)
{
Int128 maximum = 0;
constexpr int64_t hoursPerDay = 24;
constexpr int64_t minutesPerHour = 60;
constexpr int64_t secondsPerMinute = 60;
constexpr int64_t msPerDay = hoursPerDay * minutesPerHour * secondsPerMinute * 1000;
if (unit->equals("hour")) {
maximum = static_cast<Int128>(hoursPerDay);
} else if (unit->equals("minute")) {
maximum = static_cast<Int128>(minutesPerHour * hoursPerDay);
} else if (unit->equals("second")) {
maximum = static_cast<Int128>(secondsPerMinute * minutesPerHour * hoursPerDay);
} else if (unit->equals("millisecond")) {
maximum = static_cast<Int128>(msPerDay);
} else if (unit->equals("microsecond")) {
maximum = static_cast<Int128>(msPerDay * 1000);
} else if (unit->equals("nanosecond")) {
maximum = ISO8601::ExactTime::nsPerDay;
}
return maximum;
}
String* TemporalInstantObject::toString(ExecutionState& state, Value options)
{
const char* msg = "Invalid options value";
// Let resolvedOptions be ? GetOptionsObject(options).
Optional<Object*> resolvedOptions;
if (options.isObject()) {
resolvedOptions = options.asObject();
} else if (!options.isUndefined()) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, msg);
}
Optional<Object*> resolvedOptions = Intl::getOptionsObject(state, options);
// Let digits be ? GetTemporalFractionalSecondDigitsOption(resolvedOptions).
auto digits = Temporal::getTemporalFractionalSecondDigitsOption(state, resolvedOptions);
// Let roundingMode be ? GetRoundingModeOption(resolvedOptions, trunc).
@ -91,7 +61,7 @@ String* TemporalInstantObject::toString(ExecutionState& state, Value options)
// Perform ? ValidateTemporalUnitValue(smallestUnit, time).
Temporal::validateTemporalUnitValue(state, smallestUnit, ISO8601::DateTimeUnitCategory::Time, nullptr, 0);
// If smallestUnit is hour, throw a RangeError exception.
if (smallestUnit.hasValue() && smallestUnit.value()->equals("hour")) {
if (smallestUnit.hasValue() && smallestUnit == TemporalUnit::Hour) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
}
// If timeZone is not undefined, then
@ -100,10 +70,10 @@ String* TemporalInstantObject::toString(ExecutionState& state, Value options)
computedTimeZone = Temporal::toTemporalTimezoneIdentifier(state, timeZone);
}
// Let precision be ToSecondsStringPrecisionRecord(smallestUnit, digits).
auto precision = Temporal::toSecondsStringPrecisionRecord(state, smallestUnit, digits);
auto precision = Temporal::toSecondsStringPrecisionRecord(state, toDateTimeUnit(smallestUnit), digits);
// Let roundedNs be RoundTemporalInstant(instant.[[EpochNanoseconds]], precision.[[Increment]], precision.[[Unit]], roundingMode).
// Let roundedInstant be ! CreateTemporalInstant(roundedNs).
Int128 maximum = resolveNanosecondsValueByUnit(precision.unit);
Int128 maximum = ISO8601::resolveNanosecondsValueByUnit(precision.unit);
Temporal::validateTemporalRoundingIncrement(state, precision.increment, maximum, true);
@ -196,22 +166,7 @@ String* TemporalInstantObject::toString(ExecutionState& state, Int128 epochNanos
builder.appendChar(':');
auto s = pad('0', 2, std::to_string(second));
builder.appendString(String::fromASCII(s.data(), s.length()));
if ((precision.isString() && precision.asString()->equals("auto") && fraction) || (precision.isInt32() && precision.asInt32())) {
auto padded = pad('0', 9, std::to_string(fraction));
padded = '.' + padded;
if (precision.isInt32()) {
padded = padded.substr(0, padded.length() - (9 - precision.asInt32()));
} else {
auto lengthWithoutTrailingZeroes = padded.length();
while (padded[lengthWithoutTrailingZeroes - 1] == '0') {
lengthWithoutTrailingZeroes--;
}
padded = padded.substr(0, lengthWithoutTrailingZeroes);
}
builder.appendString(String::fromASCII(padded.data(), padded.length()));
}
Temporal::formatSecondsStringFraction(builder, fraction, precision);
}
if (timeZone.empty()) {
@ -290,16 +245,38 @@ TemporalInstantObject* TemporalInstantObject::round(ExecutionState& state, Value
// Else,
// Assert: smallestUnit is nanosecond.
// Let maximum be nsPerDay.
Int128 maximum = resolveNanosecondsValueByUnit(smallestUnit);
Int128 maximum = ISO8601::resolveNanosecondsValueByUnit(toDateTimeUnit(smallestUnit));
// Perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, true).
Temporal::validateTemporalRoundingIncrement(state, roundingIncrement, maximum, true);
// Let roundedNs be RoundTemporalInstant(instant.[[EpochNanoseconds]], roundingIncrement, smallestUnit, roundingMode).
// Return ! CreateTemporalInstant(roundedNs).
auto roundedInstant = ISO8601::ExactTime(*m_nanoseconds).round(state, roundingIncrement, smallestUnit, roundingMode);
auto roundedInstant = ISO8601::ExactTime(*m_nanoseconds).round(state, roundingIncrement, toDateTimeUnit(smallestUnit), roundingMode);
return new TemporalInstantObject(state, state.context()->globalObject()->temporalInstantPrototype(), roundedInstant.epochNanoseconds());
}
TemporalDurationObject* TemporalInstantObject::differenceTemporalInstant(ExecutionState& state, DifferenceTemporalInstantOperation operation, Value otherInput, Value options)
{
// Set other to ? ToTemporalInstant(other).
TemporalInstantObject* other = Temporal::toTemporalInstant(state, otherInput);
// Let resolvedOptions be ? GetOptionsObject(options).
Optional<Object*> resolvedOptions = Intl::getOptionsObject(state, options);
// Let settings be ? GetDifferenceSettings(operation, resolvedOptions, time, « », nanosecond, second).
auto settings = Temporal::getDifferenceSettings(state, operation == DifferenceTemporalInstantOperation::Since, resolvedOptions, ISO8601::DateTimeUnitCategory::Time, nullptr, 0,
TemporalUnit::Nanosecond, TemporalUnit::Second);
// Let internalDuration be DifferenceInstant(instant.[[EpochNanoseconds]], other.[[EpochNanoseconds]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]).
auto internalDuration = differenceInstant(state, epochNanoseconds(), other->epochNanoseconds(), settings.roundingIncrement, settings.smallestUnit, settings.roundingMode);
// Let result be ! TemporalDurationFromInternal(internalDuration, settings.[[LargestUnit]]).
auto result = TemporalDurationObject::temporalDurationFromInternal(state, internalDuration, settings.largestUnit);
// If operation is since, set result to CreateNegatedTemporalDuration(result).
if (operation == DifferenceTemporalInstantOperation::Since) {
result = TemporalDurationObject::createNegatedTemporalDuration(result);
}
// Return result.
return new TemporalDurationObject(state, result);
}
TemporalInstantObject* TemporalInstantObject::addDurationToInstant(AddDurationOperation operation, const Value& temporalDurationLike)
{
TemporalInstantObject* instant = this;
@ -307,6 +284,17 @@ TemporalInstantObject* TemporalInstantObject::addDurationToInstant(AddDurationOp
return nullptr;
}
ISO8601::InternalDuration TemporalInstantObject::differenceInstant(ExecutionState& state, Int128 ns1, Int128 ns2, unsigned roundingIncrement,
ISO8601::DateTimeUnit smallestUnit, ISO8601::RoundingMode roundingMode)
{
// Let timeDuration be TimeDurationFromEpochNanosecondsDifference(ns2, ns1).
Int128 timeDuration = Temporal::timeDurationFromEpochNanosecondsDifference(ns2, ns1);
// Set timeDuration to ! RoundTimeDuration(timeDuration, roundingIncrement, smallestUnit, roundingMode).
timeDuration = TemporalDurationObject::roundTimeDuration(state, timeDuration, roundingIncrement, smallestUnit, roundingMode);
// Return CombineDateAndTimeDuration(ZeroDateDuration(), timeDuration).
return ISO8601::InternalDuration(ISO8601::Duration(), timeDuration);
}
} // namespace Escargot
#endif

View file

@ -25,6 +25,8 @@
namespace Escargot {
class TemporalDurationObject;
class TemporalInstantObject : public DerivedObject {
public:
TemporalInstantObject(ExecutionState& state, Object* proto, Int128 nanoseconds);
@ -44,6 +46,13 @@ public:
static String* toString(ExecutionState& state, Int128 epochNanoseconds, TimeZone timeZone, Value precision);
TemporalInstantObject* round(ExecutionState& state, Value roundTo);
// https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalinstant
enum class DifferenceTemporalInstantOperation {
Since,
Until
};
TemporalDurationObject* differenceTemporalInstant(ExecutionState& state, DifferenceTemporalInstantOperation operation, Value other, Value options);
private:
// https://tc39.es/proposal-temporal/#sec-temporal-adddurationtoinstant
enum class AddDurationOperation {
@ -52,6 +61,10 @@ private:
};
TemporalInstantObject* addDurationToInstant(AddDurationOperation operation, const Value& temporalDurationLike);
// https://tc39.es/proposal-temporal/#sec-temporal-differenceinstant
static ISO8601::InternalDuration differenceInstant(ExecutionState& state, Int128 ns1, Int128 ns2, unsigned roundingIncrement,
ISO8601::DateTimeUnit smallestUnit, ISO8601::RoundingMode roundingMode);
Int128* m_nanoseconds; // [[EpochNanoseconds]]
};

View file

@ -389,6 +389,25 @@ Value Temporal::toTemporalDate(ExecutionState& state, Value item, Value options)
return Value();
}
void Temporal::formatSecondsStringFraction(StringBuilder& builder, Int128 fraction, Value precision)
{
if ((precision.isString() && precision.asString()->equals("auto") && fraction) || (precision.isInt32() && precision.asInt32())) {
auto padded = pad('0', 9, std::to_string(fraction));
padded = '.' + padded;
if (precision.isInt32()) {
padded = padded.substr(0, padded.length() - (9 - precision.asInt32()));
} else {
auto lengthWithoutTrailingZeroes = padded.length();
while (padded[lengthWithoutTrailingZeroes - 1] == '0') {
lengthWithoutTrailingZeroes--;
}
padded = padded.substr(0, lengthWithoutTrailingZeroes);
}
builder.appendString(String::fromASCII(padded.data(), padded.length()));
}
}
Int128 Temporal::systemUTCEpochNanoseconds()
{
std::chrono::system_clock::duration d = std::chrono::system_clock::now().time_since_epoch();
@ -540,14 +559,37 @@ Optional<unsigned> Temporal::getTemporalFractionalSecondDigitsOption(ExecutionSt
return digitCount;
}
String* Temporal::getRoundingModeOption(ExecutionState& state, Optional<Object*> resolvedOptions, String* fallback)
static ISO8601::RoundingMode toRoundingMode(String* roundingMode)
{
if (roundingMode->equals("ceil")) {
return ISO8601::RoundingMode::Ceil;
} else if (roundingMode->equals("floor")) {
return ISO8601::RoundingMode::Floor;
} else if (roundingMode->equals("expand")) {
return ISO8601::RoundingMode::Expand;
} else if (roundingMode->equals("trunc")) {
return ISO8601::RoundingMode::Trunc;
} else if (roundingMode->equals("halfCeil")) {
return ISO8601::RoundingMode::HalfCeil;
} else if (roundingMode->equals("halfFloor")) {
return ISO8601::RoundingMode::HalfFloor;
} else if (roundingMode->equals("halfExpand")) {
return ISO8601::RoundingMode::HalfExpand;
} else if (roundingMode->equals("halfTrunc")) {
return ISO8601::RoundingMode::HalfTrunc;
}
ASSERT(roundingMode->equals("halfEven"));
return ISO8601::RoundingMode::HalfEven;
}
ISO8601::RoundingMode Temporal::getRoundingModeOption(ExecutionState& state, Optional<Object*> resolvedOptions, String* fallback)
{
// Let allowedStrings be the List of Strings from the "String Identifier" column of Table 27.
// Let stringFallback be the value from the "String Identifier" column of the row with fallback in its "Rounding Mode" column.
// Let stringValue be ? GetOption(options, "roundingMode", string, allowedStrings, stringFallback).
// Return the value from the "Rounding Mode" column of the row with stringValue in its "String Identifier" column.
if (!resolvedOptions) {
return fallback;
return toRoundingMode(fallback);
}
Value values[] = {
@ -562,10 +604,11 @@ String* Temporal::getRoundingModeOption(ExecutionState& state, Optional<Object*>
state.context()->staticStrings().lazyHalfEven().string()
};
return Intl::getOption(state, resolvedOptions.value(), state.context()->staticStrings().lazyRoundingMode().string(), Intl::OptionValueType::StringValue, values, 9, fallback).asString();
String* roundingMode = Intl::getOption(state, resolvedOptions.value(), state.context()->staticStrings().lazyRoundingMode().string(), Intl::OptionValueType::StringValue, values, 9, fallback).asString();
return toRoundingMode(roundingMode);
}
Optional<String*> Temporal::getTemporalUnitValuedOption(ExecutionState& state, Optional<Object*> resolvedOptions, String* key, Optional<Value> defaultValue)
Optional<TemporalUnit> Temporal::getTemporalUnitValuedOption(ExecutionState& state, Optional<Object*> resolvedOptions, String* key, Optional<Value> defaultValue)
{
// Let allowedStrings be a List containing all values in the "Singular property name" and "Plural property name" columns of Table 21, except the header row.
// Append "auto" to allowedStrings.
@ -605,32 +648,32 @@ Optional<String*> Temporal::getTemporalUnitValuedOption(ExecutionState& state, O
}
// If value is undefined, return unset.
if (value.isUndefined()) {
return nullptr;
return NullOption;
}
String* stringValue = value.asString();
// If value is "auto", return auto.
if (stringValue->equals("auto")) {
return stringValue;
return TemporalUnit::Auto;
}
// Return the value in the "Value" column of Table 21 corresponding to the row with value in its "Singular property name" or "Plural property name" column.
if (false) {}
#define DEFINE_COMPARE(name, Name, names, Names, index, category) \
else if (stringValue->equals(#name)) \
{ \
return state.context()->staticStrings().lazy##Name().string(); \
} \
else if (stringValue->equals(#names)) \
{ \
return state.context()->staticStrings().lazy##Name().string(); \
#define DEFINE_COMPARE(name, Name, names, Names, index, category) \
else if (stringValue->equals(#name)) \
{ \
return TemporalUnit::Name; \
} \
else if (stringValue->equals(#names)) \
{ \
return TemporalUnit::Name; \
}
PLAIN_DATETIME_UNITS(DEFINE_COMPARE)
#undef DEFINE_COMPARE
ASSERT_NOT_REACHED();
return String::emptyString();
return TemporalUnit::Auto;
}
void Temporal::validateTemporalUnitValue(ExecutionState& state, Optional<String*> value, ISO8601::DateTimeUnitCategory unitGroup, Optional<String*> extraValues, size_t extraValueSize)
void Temporal::validateTemporalUnitValue(ExecutionState& state, Optional<TemporalUnit> value, ISO8601::DateTimeUnitCategory unitGroup, Optional<TemporalUnit*> extraValues, size_t extraValueSize)
{
// If value is unset, return unused.
if (!value) {
@ -639,24 +682,19 @@ void Temporal::validateTemporalUnitValue(ExecutionState& state, Optional<String*
// If extraValues is present and extraValues contains value, return unused.
if (extraValues && value) {
for (size_t i = 0; i < extraValueSize; i++) {
if (extraValues.value()[i].equals(value.value())) {
if (extraValues.value()[i] == value.value()) {
return;
}
}
}
String* stringValue = value.value();
ISO8601::DateTimeUnitCategory categoryValue = ISO8601::DateTimeUnitCategory::DateTime;
const auto* msg = "Invalid temporal unit value";
// Let category be the value in the “Category” column of the row of Table 21 whose “Value” column contains value. If there is no such row, throw a RangeError exception.
if (false) {}
#define DEFINE_COMPARE(name, Name, names, Names, index, category) \
else if (stringValue->equals(#name)) \
{ \
categoryValue = category; \
} \
else if (stringValue->equals(#names)) \
{ \
categoryValue = category; \
#define DEFINE_COMPARE(name, Name, names, Names, index, category) \
else if (toDateTimeUnit(value.value()) == ISO8601::DateTimeUnit::Name) \
{ \
categoryValue = category; \
}
PLAIN_DATETIME_UNITS(DEFINE_COMPARE)
#undef DEFINE_COMPARE
@ -726,29 +764,29 @@ TimeZone Temporal::toTemporalTimezoneIdentifier(ExecutionState& state, const Val
return TimeZone(String::emptyString());
}
Temporal::StringPrecisionRecord Temporal::toSecondsStringPrecisionRecord(ExecutionState& state, Optional<String*> smallestUnit, Optional<unsigned> fractionalDigitCount)
Temporal::StringPrecisionRecord Temporal::toSecondsStringPrecisionRecord(ExecutionState& state, Optional<ISO8601::DateTimeUnit> smallestUnit, Optional<unsigned> fractionalDigitCount)
{
if (smallestUnit) {
// If smallestUnit is minute, then
if (smallestUnit->equals("minute")) {
if (smallestUnit.value() == ISO8601::DateTimeUnit::Minute) {
// Return the Record { [[Precision]]: minute, [[Unit]]: minute, [[Increment]]: 1 }.
return { state.context()->staticStrings().lazyMinute().string(), state.context()->staticStrings().lazyMinute().string(), 1 };
} else if (smallestUnit->equals("second")) {
return { state.context()->staticStrings().lazyMinute().string(), ISO8601::DateTimeUnit::Minute, 1 };
} else if (smallestUnit.value() == ISO8601::DateTimeUnit::Second) {
// If smallestUnit is second, then
// Return the Record { [[Precision]]: 0, [[Unit]]: second, [[Increment]]: 1 }.
return { Value(0), state.context()->staticStrings().lazySecond().string(), 1 };
} else if (smallestUnit->equals("millisecond")) {
return { Value(0), ISO8601::DateTimeUnit::Second, 1 };
} else if (smallestUnit.value() == ISO8601::DateTimeUnit::Millisecond) {
// If smallestUnit is millisecond, then
// Return the Record { [[Precision]]: 3, [[Unit]]: millisecond, [[Increment]]: 1 }.
return { Value(3), state.context()->staticStrings().lazyMillisecond().string(), 1 };
} else if (smallestUnit->equals("microsecond")) {
// If smallestUnit is millisecond, then
return { Value(3), ISO8601::DateTimeUnit::Millisecond, 1 };
} else if (smallestUnit.value() == ISO8601::DateTimeUnit::Microsecond) {
// If smallestUnit is microsecond, then
// Return the Record { [[Precision]]: 6, [[Unit]]: microsecond, [[Increment]]: 1 }.
return { Value(6), state.context()->staticStrings().lazyMicrosecond().string(), 1 };
} else if (smallestUnit->equals("nanosecond")) {
return { Value(6), ISO8601::DateTimeUnit::Microsecond, 1 };
} else if (smallestUnit.value() == ISO8601::DateTimeUnit::Nanosecond) {
// If smallestUnit is nanosecond, then
// Return the Record { [[Precision]]: 9, [[Unit]]: nanosecond, [[Increment]]: 1 }.
return { Value(9), state.context()->staticStrings().lazyNanosecond().string(), 1 };
return { Value(9), ISO8601::DateTimeUnit::Nanosecond, 1 };
}
}
// Assert: smallestUnit is unset.
@ -756,7 +794,7 @@ Temporal::StringPrecisionRecord Temporal::toSecondsStringPrecisionRecord(Executi
// If fractionalDigitCount is auto, then
if (!fractionalDigitCount.hasValue()) {
// Return the Record { [[Precision]]: auto, [[Unit]]: nanosecond, [[Increment]]: 1 }.
return { state.context()->staticStrings().lazyAuto().string(), state.context()->staticStrings().lazyNanosecond().string(), 1 };
return { state.context()->staticStrings().lazyAuto().string(), ISO8601::DateTimeUnit::Nanosecond, 1 };
}
auto pow10Unsigned = [](unsigned n) -> unsigned {
@ -769,20 +807,20 @@ Temporal::StringPrecisionRecord Temporal::toSecondsStringPrecisionRecord(Executi
// If fractionalDigitCount = 0, then
if (fractionalDigitCount && fractionalDigitCount.value() == 0) {
// Return the Record { [[Precision]]: 0, [[Unit]]: second, [[Increment]]: 1 }.
return { Value(0), state.context()->staticStrings().lazySecond().string(), 1 };
return { Value(0), ISO8601::DateTimeUnit::Second, 1 };
} else if (fractionalDigitCount && fractionalDigitCount.value() >= 1 && fractionalDigitCount.value() <= 3) {
// If fractionalDigitCount is in the inclusive interval from 1 to 3, then
// Return the Record { [[Precision]]: fractionalDigitCount, [[Unit]]: millisecond, [[Increment]]: 10**(3 - fractionalDigitCount) }
return { Value(fractionalDigitCount.value()), state.context()->staticStrings().lazyMillisecond().string(), pow10Unsigned(3 - fractionalDigitCount.value()) };
return { Value(fractionalDigitCount.value()), ISO8601::DateTimeUnit::Millisecond, pow10Unsigned(3 - fractionalDigitCount.value()) };
} else if (fractionalDigitCount && fractionalDigitCount.value() >= 4 && fractionalDigitCount.value() <= 6) {
// If fractionalDigitCount is in the inclusive interval from 4 to 6, then
// Return the Record { [[Precision]]: fractionalDigitCount, [[Unit]]: microsecond, [[Increment]]: 10**(6 - fractionalDigitCount) }.
return { Value(fractionalDigitCount.value()), state.context()->staticStrings().lazyMicrosecond().string(), pow10Unsigned(6 - fractionalDigitCount.value()) };
return { Value(fractionalDigitCount.value()), ISO8601::DateTimeUnit::Microsecond, pow10Unsigned(6 - fractionalDigitCount.value()) };
}
// Assert: fractionalDigitCount is in the inclusive interval from 7 to 9.
ASSERT(fractionalDigitCount && fractionalDigitCount.value() >= 7 && fractionalDigitCount.value() <= 9);
// Return the Record { [[Precision]]: fractionalDigitCount, [[Unit]]: nanosecond, [[Increment]]: 10**(9 - fractionalDigitCount) }.
return { Value(fractionalDigitCount.value()), state.context()->staticStrings().lazyNanosecond().string(), pow10Unsigned(9 - fractionalDigitCount.value()) };
return { Value(fractionalDigitCount.value()), ISO8601::DateTimeUnit::Nanosecond, pow10Unsigned(9 - fractionalDigitCount.value()) };
}
void Temporal::validateTemporalRoundingIncrement(ExecutionState& state, unsigned increment, Int128 dividend, bool inclusive)
@ -811,10 +849,13 @@ void Temporal::validateTemporalRoundingIncrement(ExecutionState& state, unsigned
// Return unused.
}
int32_t Temporal::getRoundingIncrementOption(ExecutionState& state, Object* options)
unsigned Temporal::getRoundingIncrementOption(ExecutionState& state, Optional<Object*> options)
{
if (!options) {
return 1;
}
// Let value be ? Get(options, "roundingIncrement").
Value value = options->get(state, ObjectPropertyName(state.context()->staticStrings().lazyRoundingIncrement())).value(state, options);
Value value = options.value()->get(state, ObjectPropertyName(state.context()->staticStrings().lazyRoundingIncrement())).value(state, options.value());
// If value is undefined, return 1𝔽.
if (value.isUndefined()) {
return 1;
@ -829,6 +870,153 @@ int32_t Temporal::getRoundingIncrementOption(ExecutionState& state, Object* opti
return integerIncrement;
}
Temporal::DifferenceSettingsRecord Temporal::getDifferenceSettings(ExecutionState& state, bool isSinceOperation, Optional<Object*> options, ISO8601::DateTimeUnitCategory unitGroup,
Optional<TemporalUnit*> disallowedUnits, size_t disallowedUnitsLength, TemporalUnit fallbackSmallestUnit, TemporalUnit smallestLargestDefaultUnit)
{
// NOTE: The following steps read options and perform independent validation in alphabetical order.
// Let largestUnit be ? GetTemporalUnitValuedOption(options, "largestUnit", unset).
Optional<TemporalUnit> largestUnit = Temporal::getTemporalUnitValuedOption(state, options, state.context()->staticStrings().lazyLargestUnit().string(), NullOption);
// Let roundingIncrement be ? GetRoundingIncrementOption(options).
auto roundingIncrement = Temporal::getRoundingIncrementOption(state, options);
// Let roundingMode be ? GetRoundingModeOption(options, trunc).
auto roundingMode = Temporal::getRoundingModeOption(state, options, state.context()->staticStrings().trunc.string());
// Let smallestUnit be ? GetTemporalUnitValuedOption(options, "smallestUnit", unset).
Optional<TemporalUnit> smallestUnit = Temporal::getTemporalUnitValuedOption(state, options, state.context()->staticStrings().lazySmallestUnit().string(), NullOption);
// Perform ? ValidateTemporalUnitValue(largestUnit, unitGroup, « auto »).
TemporalUnit extraValues[1] = { TemporalUnit::Auto };
Temporal::validateTemporalUnitValue(state, largestUnit, unitGroup, extraValues, 1);
// If largestUnit is unset, then
if (!largestUnit) {
// Set largestUnit to auto.
largestUnit = TemporalUnit::Auto;
}
// If disallowedUnits contains largestUnit, throw a RangeError exception.
if (disallowedUnits) {
for (size_t i = 0; i < disallowedUnitsLength; i++) {
if (disallowedUnits.value()[i] == largestUnit.value()) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid largestUnit value");
}
}
}
// If operation is since, then
if (isSinceOperation) {
// Set roundingMode to NegateRoundingMode(roundingMode).
roundingMode = Temporal::negateRoundingMode(state, roundingMode);
}
// Perform ? ValidateTemporalUnitValue(smallestUnit, unitGroup).
Temporal::validateTemporalUnitValue(state, smallestUnit, unitGroup, nullptr, 0);
// If smallestUnit is unset, then
if (!smallestUnit) {
// Set smallestUnit to fallbackSmallestUnit.
smallestUnit = fallbackSmallestUnit;
}
// If disallowedUnits contains smallestUnit, throw a RangeError exception.
if (disallowedUnits) {
for (size_t i = 0; i < disallowedUnitsLength; i++) {
if (disallowedUnits.value()[i] == smallestUnit.value()) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid largestUnit value");
}
}
}
// Let defaultLargestUnit be LargerOfTwoTemporalUnits(smallestLargestDefaultUnit, smallestUnit).
auto defaultLargestUnit = Temporal::largerOfTwoTemporalUnits(toDateTimeUnit(smallestLargestDefaultUnit), toDateTimeUnit(smallestUnit.value()));
// If largestUnit is auto, set largestUnit to defaultLargestUnit.
if (largestUnit == TemporalUnit::Auto) {
largestUnit = static_cast<TemporalUnit>(defaultLargestUnit);
}
// If LargerOfTwoTemporalUnits(largestUnit, smallestUnit) is not largestUnit, throw a RangeError exception.
if (!(Temporal::largerOfTwoTemporalUnits(toDateTimeUnit(largestUnit.value()), toDateTimeUnit(smallestUnit.value())) == toDateTimeUnit(largestUnit.value()))) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid largestUnit or smallestUnit value");
}
// Let maximum be MaximumTemporalDurationRoundingIncrement(smallestUnit).
auto maximum = Temporal::maximumTemporalDurationRoundingIncrement(toDateTimeUnit(smallestUnit.value()));
// If maximum is not unset, perform ? ValidateTemporalRoundingIncrement(roundingIncrement, maximum, false).
if (maximum) {
Temporal::validateTemporalRoundingIncrement(state, roundingIncrement, maximum.value(), false);
}
// Return the Record { [[SmallestUnit]]: smallestUnit, [[LargestUnit]]: largestUnit, [[RoundingMode]]: roundingMode, [[RoundingIncrement]]: roundingIncrement, }.
return { toDateTimeUnit(smallestUnit.value()), toDateTimeUnit(largestUnit.value()), roundingMode, roundingIncrement };
}
ISO8601::RoundingMode Temporal::negateRoundingMode(ExecutionState& state, ISO8601::RoundingMode roundingMode)
{
// If roundingMode is ceil, return floor.
if (roundingMode == ISO8601::RoundingMode::Ceil) {
return ISO8601::RoundingMode::Floor;
}
// If roundingMode is floor, return ceil.
if (roundingMode == ISO8601::RoundingMode::Floor) {
return ISO8601::RoundingMode::Ceil;
}
// If roundingMode is half-ceil, return half-floor.
if (roundingMode == ISO8601::RoundingMode::HalfCeil) {
return ISO8601::RoundingMode::HalfFloor;
}
// If roundingMode is half-floor, return half-ceil.
if (roundingMode == ISO8601::RoundingMode::HalfFloor) {
return ISO8601::RoundingMode::HalfCeil;
}
// Return roundingMode
return roundingMode;
}
ISO8601::DateTimeUnit Temporal::largerOfTwoTemporalUnits(ISO8601::DateTimeUnit u1, ISO8601::DateTimeUnit u2)
{
// For each row of Table 21, except the header row, in table order, do
// Let unit be the value in the "Value" column of the row.
// If u1 is unit, return unit.
// If u2 is unit, return unit.
if (false) {}
#define DEFINE_COMPARE(name, Name, names, Names, index, category) \
else if (u1 == ISO8601::DateTimeUnit::Name) \
{ \
return u1; \
} \
else if (u2 == ISO8601::DateTimeUnit::Name) \
{ \
return u2; \
}
PLAIN_DATETIME_UNITS(DEFINE_COMPARE)
#undef DEFINE_COMPARE
else
{
ASSERT_NOT_REACHED();
return ISO8601::DateTimeUnit::Year;
}
}
Optional<unsigned> Temporal::maximumTemporalDurationRoundingIncrement(ISO8601::DateTimeUnit unit)
{
// Return the value from the "Maximum duration rounding increment" column of the row of Table 21 in which unit is in the "Value" column.
if (unit == ISO8601::DateTimeUnit::Hour) {
return 24;
} else if (unit == ISO8601::DateTimeUnit::Minute) {
return 60;
} else if (unit == ISO8601::DateTimeUnit::Second) {
return 60;
} else if (unit == ISO8601::DateTimeUnit::Millisecond) {
return 1000;
} else if (unit == ISO8601::DateTimeUnit::Microsecond) {
return 1000;
} else if (unit == ISO8601::DateTimeUnit::Nanosecond) {
return 1000;
}
return NullOption;
}
Int128 Temporal::timeDurationFromEpochNanosecondsDifference(Int128 one, Int128 two)
{
// Let result be (one) - (two).
auto result = one - two;
// Assert: abs(result) ≤ maxTimeDuration.
ASSERT(std::abs(result) < ISO8601::InternalDuration::maxTimeDuration);
// Return result.
return result;
}
TemporalObject::TemporalObject(ExecutionState& state)
: TemporalObject(state, state.context()->globalObject()->objectPrototype())
{

View file

@ -215,6 +215,27 @@ struct CalendarDate {
bool inLeapYear = false;
};
enum class TemporalUnit : uint8_t {
#define DEFINE_TYPE(name, Name, names, Names, index, category) Name,
PLAIN_DATETIME_UNITS(DEFINE_TYPE)
#undef DEFINE_TYPE
Auto
};
inline ISO8601::DateTimeUnit toDateTimeUnit(TemporalUnit u)
{
ASSERT(u != TemporalUnit::Auto);
return static_cast<ISO8601::DateTimeUnit>(u);
}
inline Optional<ISO8601::DateTimeUnit> toDateTimeUnit(Optional<TemporalUnit> u)
{
if (u) {
return toDateTimeUnit(u.value());
}
return NullOption;
}
class Temporal {
public:
/* TODO ParseISODateTime
@ -287,6 +308,8 @@ public:
static Value createTemporalDate(ExecutionState& state, const ISODate& isoDate, String* calendar, Optional<Object*> newTarget);
static Value toTemporalDate(ExecutionState& state, Value item, Value options = Value());
static void formatSecondsStringFraction(StringBuilder& builder, Int128 fraction, Value precision);
// https://tc39.es/proposal-temporal/#sec-temporal-systemutcepochnanoseconds
static Int128 systemUTCEpochNanoseconds();
// https://tc39.es/proposal-temporal/#sec-temporal-isvalidepochnanoseconds
@ -303,13 +326,13 @@ public:
static Optional<unsigned> getTemporalFractionalSecondDigitsOption(ExecutionState& state, Optional<Object*> resolvedOptions);
// https://tc39.es/proposal-temporal/#sec-temporal-getroundingmodeoption
static String* getRoundingModeOption(ExecutionState& state, Optional<Object*> resolvedOptions, String* fallback);
static ISO8601::RoundingMode getRoundingModeOption(ExecutionState& state, Optional<Object*> resolvedOptions, String* fallback);
// https://tc39.es/proposal-temporal/#sec-temporal-gettemporalunitvaluedoption
static Optional<String*> getTemporalUnitValuedOption(ExecutionState& state, Optional<Object*> resolvedOptions, String* key, Optional<Value> defaultValue /* give DefaultValue to EmptyValue means Required = true*/);
static Optional<TemporalUnit> getTemporalUnitValuedOption(ExecutionState& state, Optional<Object*> resolvedOptions, String* key, Optional<Value> defaultValue /* give DefaultValue to EmptyValue means Required = true*/);
// https://tc39.es/proposal-temporal/#sec-temporal-validatetemporalunitvaluedoption
static void validateTemporalUnitValue(ExecutionState& state, Optional<String*> value, ISO8601::DateTimeUnitCategory unitGroup, Optional<String*> extraValues, size_t extraValueSize);
static void validateTemporalUnitValue(ExecutionState& state, Optional<TemporalUnit> value, ISO8601::DateTimeUnitCategory unitGroup, Optional<TemporalUnit*> extraValues, size_t extraValueSize);
// https://tc39.es/proposal-temporal/#sec-temporal-totemporaltimezoneidentifier
static TimeZone toTemporalTimezoneIdentifier(ExecutionState& state, const Value& temporalTimeZoneLike);
@ -317,16 +340,38 @@ public:
// https://tc39.es/proposal-temporal/#sec-temporal-tosecondsstringprecisionrecord
struct StringPrecisionRecord {
Value precision;
String* unit;
ISO8601::DateTimeUnit unit;
unsigned increment;
};
static StringPrecisionRecord toSecondsStringPrecisionRecord(ExecutionState& state, Optional<String*> smallestUnit, Optional<unsigned> fractionalDigitCount);
static StringPrecisionRecord toSecondsStringPrecisionRecord(ExecutionState& state, Optional<ISO8601::DateTimeUnit> smallestUnit, Optional<unsigned> fractionalDigitCount);
// https://tc39.es/proposal-temporal/#sec-validatetemporalroundingincrement
static void validateTemporalRoundingIncrement(ExecutionState& state, unsigned increment, Int128 dividend, bool inclusive);
// https://tc39.es/proposal-temporal/#sec-temporal-getroundingincrementoption
static int32_t getRoundingIncrementOption(ExecutionState& state, Object* options);
static unsigned getRoundingIncrementOption(ExecutionState& state, Optional<Object*> options);
// https://tc39.es/proposal-temporal/#sec-temporal-getdifferencesettings
struct DifferenceSettingsRecord {
ISO8601::DateTimeUnit smallestUnit;
ISO8601::DateTimeUnit largestUnit;
ISO8601::RoundingMode roundingMode;
unsigned roundingIncrement;
};
static DifferenceSettingsRecord getDifferenceSettings(ExecutionState& state, bool isSinceOperation, Optional<Object*> options, ISO8601::DateTimeUnitCategory unitGroup,
Optional<TemporalUnit*> disallowedUnits, size_t disallowedUnitsLength, TemporalUnit fallbackSmallestUnit, TemporalUnit smallestLargestDefaultUnit);
// https://tc39.es/proposal-temporal/#sec-temporal-negateroundingmode
static ISO8601::RoundingMode negateRoundingMode(ExecutionState& state, ISO8601::RoundingMode roundingMode);
// https://tc39.es/proposal-temporal/#sec-temporal-largeroftwotemporalunits
static ISO8601::DateTimeUnit largerOfTwoTemporalUnits(ISO8601::DateTimeUnit u1, ISO8601::DateTimeUnit u2);
// https://tc39.es/proposal-temporal/#sec-temporal-maximumtemporaldurationroundingincrement
static Optional<unsigned> maximumTemporalDurationRoundingIncrement(ISO8601::DateTimeUnit unit);
// https://tc39.es/proposal-temporal/#sec-temporal-timedurationfromepochnanosecondsdifference
static Int128 timeDurationFromEpochNanosecondsDifference(Int128 one, Int128 two);
};
class TemporalObject : public DerivedObject {

View file

@ -68,6 +68,8 @@ constexpr Int128 ExactTime::nsPerDay;
constexpr Int128 ExactTime::minValue;
constexpr Int128 ExactTime::maxValue;
constexpr Int128 InternalDuration::maxTimeDuration;
static constexpr int64_t nsPerHour = 1000LL * 1000 * 1000 * 60 * 60;
static constexpr int64_t nsPerMinute = 1000LL * 1000 * 1000 * 60;
static constexpr int64_t nsPerSecond = 1000LL * 1000 * 1000;
@ -117,7 +119,7 @@ static int32_t parseDecimalInt32(const std::string& characters)
// DurationHandleFractions ( fHours, minutes, fMinutes, seconds, fSeconds, milliseconds, fMilliseconds, microseconds, fMicroseconds, nanoseconds, fNanoseconds )
// https://tc39.es/proposal-temporal/#sec-temporal-durationhandlefractions
static void handleFraction(Duration& duration, int factor, std::string fractionString, Duration::Type fractionType)
static void handleFraction(Duration& duration, int factor, std::string fractionString, ISO8601::DateTimeUnit fractionType)
{
auto fractionLength = fractionString.length();
std::string padded("000000000");
@ -131,7 +133,7 @@ static void handleFraction(Duration& duration, int factor, std::string fractionS
}
static constexpr int64_t divisor = 1000000000LL;
if (fractionType == Duration::Type::Hours) {
if (fractionType == ISO8601::DateTimeUnit::Hour) {
fraction *= 60;
duration.setMinutes(fraction / divisor);
fraction %= divisor;
@ -139,7 +141,7 @@ static void handleFraction(Duration& duration, int factor, std::string fractionS
return;
}
if (fractionType != Duration::Type::Seconds) {
if (fractionType != ISO8601::DateTimeUnit::Second) {
fraction *= 60;
duration.setSeconds(fraction / divisor);
fraction %= divisor;
@ -157,6 +159,36 @@ static double parseInt(std::string src)
return std::stoi(src);
}
DateTimeUnitCategory toDateTimeCategory(DateTimeUnit u)
{
if (false) {}
#define DEFINE_TYPE(name, Name, names, Names, index, category) \
else if (u == DateTimeUnit::Name) \
{ \
return category; \
}
PLAIN_DATETIME_UNITS(DEFINE_TYPE)
#undef DEFINE_TYPE
ASSERT_NOT_REACHED();
return DateTimeUnitCategory::Date;
}
DateTimeUnit toDateTimeUnit(String* unit)
{
if (false) {}
#define DEFINE_TYPE(name, Name, names, Names, index, category) \
else if (unit->equals(#name)) \
{ \
return DateTimeUnit::Name; \
}
PLAIN_DATETIME_UNITS(DEFINE_TYPE)
#undef DEFINE_TYPE
ASSERT_NOT_REACHED();
return DateTimeUnit::Year;
}
Optional<Duration> Duration::parseDurationString(String* input)
{
// ISO 8601 duration strings are like "-P1Y2M3W4DT5H6M7.123456789S". Notes:
@ -272,7 +304,7 @@ Optional<Duration> Duration::parseDurationString(String* input)
return NullOption;
result.setHours(integer);
if (fractionalPart.size()) {
handleFraction(result, factor, fractionalPart, Duration::Type::Hours);
handleFraction(result, factor, fractionalPart, ISO8601::DateTimeUnit::Hour);
timePartIndex = 3;
} else {
timePartIndex = 1;
@ -283,7 +315,7 @@ Optional<Duration> Duration::parseDurationString(String* input)
return NullOption;
result.setMinutes(integer);
if (fractionalPart.size()) {
handleFraction(result, factor, fractionalPart, Duration::Type::Minutes);
handleFraction(result, factor, fractionalPart, ISO8601::DateTimeUnit::Minute);
timePartIndex = 3;
} else {
timePartIndex = 2;
@ -292,7 +324,7 @@ Optional<Duration> Duration::parseDurationString(String* input)
case 'S':
result.setSeconds(integer);
if (fractionalPart.size()) {
handleFraction(result, factor, fractionalPart, Duration::Type::Seconds);
handleFraction(result, factor, fractionalPart, ISO8601::DateTimeUnit::Second);
}
timePartIndex = 3;
break;
@ -308,11 +340,11 @@ Optional<Duration> Duration::parseDurationString(String* input)
return result;
}
String* Duration::typeName(ExecutionState& state, Type t)
String* Duration::typeName(ExecutionState& state, ISO8601::DateTimeUnit t)
{
switch (t) {
#define DEFINE_GETTER(name, Name, names, Names, index, category) \
case Type::Names: \
case ISO8601::DateTimeUnit::Name: \
return state.context()->staticStrings().lazy##Names().string();
PLAIN_DATETIME_UNITS(DEFINE_GETTER)
#undef DEFINE_GETTER
@ -323,11 +355,11 @@ String* Duration::typeName(ExecutionState& state, Type t)
return String::emptyString();
}
Int128 Duration::totalNanoseconds(Duration::Type unit) const
Int128 Duration::totalNanoseconds(ISO8601::DateTimeUnit unit) const
{
ASSERT(unit != Duration::Type::Years);
ASSERT(unit != Duration::Type::Months);
ASSERT(unit != Duration::Type::Weeks);
ASSERT(unit != ISO8601::DateTimeUnit::Year);
ASSERT(unit != ISO8601::DateTimeUnit::Month);
ASSERT(unit != ISO8601::DateTimeUnit::Week);
Int128 resultNs = 0;
@ -335,40 +367,40 @@ Int128 Duration::totalNanoseconds(Duration::Type unit) const
constexpr int64_t milliMultiplier = 1000000ULL;
constexpr int64_t microMultiplier = 1000ULL;
if (unit <= Duration::Type::Days) {
if (unit <= ISO8601::DateTimeUnit::Day) {
Int128 s(days());
s *= 86400;
s *= nanoMultiplier;
resultNs += s;
}
if (unit <= Duration::Type::Hours) {
if (unit <= ISO8601::DateTimeUnit::Hour) {
Int128 s(hours());
s *= 3600;
s *= nanoMultiplier;
resultNs += s;
}
if (unit <= Duration::Type::Minutes) {
if (unit <= ISO8601::DateTimeUnit::Minute) {
Int128 s(minutes());
s *= 60;
s *= nanoMultiplier;
resultNs += s;
}
if (unit <= Duration::Type::Seconds) {
if (unit <= ISO8601::DateTimeUnit::Second) {
Int128 s(seconds());
s *= nanoMultiplier;
resultNs += s;
}
if (unit <= Duration::Type::Milliseconds) {
if (unit <= ISO8601::DateTimeUnit::Millisecond) {
Int128 s(milliseconds());
s *= milliMultiplier;
resultNs += s;
}
if (unit <= Duration::Type::Microseconds) {
if (unit <= ISO8601::DateTimeUnit::Microsecond) {
Int128 s(microseconds());
s *= microMultiplier;
resultNs += s;
}
if (unit <= Duration::Type::Nanoseconds) {
if (unit <= ISO8601::DateTimeUnit::Nanosecond) {
Int128 s(nanoseconds());
resultNs += s;
}
@ -1303,27 +1335,103 @@ ExactTime ExactTime::fromISOPartsAndOffset(int32_t year, uint8_t month, uint8_t
return ExactTime{ utcNanoseconds - offset };
}
static Int128 lengthInNanoseconds(String* unit)
Int128 lengthInNanoseconds(DateTimeUnit unit)
{
if (unit->equals("nanosecond")) {
if (unit == DateTimeUnit::Nanosecond) {
return 1;
} else if (unit->equals("microsecond")) {
} else if (unit == DateTimeUnit::Microsecond) {
return 1000;
} else if (unit->equals("millisecond")) {
} else if (unit == DateTimeUnit::Millisecond) {
return 1000 * 1000;
} else if (unit->equals("second")) {
} else if (unit == DateTimeUnit::Second) {
return Int128(1000 * 1000) * Int128(1000);
} else if (unit->equals("minute")) {
} else if (unit == DateTimeUnit::Minute) {
return Int128(1000 * 1000) * Int128(1000) * Int128(60);
} else if (unit->equals("hour")) {
} else if (unit == DateTimeUnit::Hour) {
return Int128(1000 * 1000) * Int128(1000) * Int128(60) * Int128(60);
} else {
ASSERT(unit->equals("day"));
ASSERT(unit == DateTimeUnit::Day);
return Int128(1000 * 1000) * Int128(1000) * Int128(60) * Int128(60) * Int128(24);
}
}
Int128 roundNumberToIncrementAsIfPositive(Int128 x, Int128 increment, String* roundingMode)
double roundNumberToIncrement(double x, double increment, RoundingMode roundingMode)
{
auto quotient = x / increment;
auto truncatedQuotient = std::trunc(quotient);
if (truncatedQuotient == quotient)
return truncatedQuotient * increment;
auto isNegative = quotient < 0;
auto expandedQuotient = isNegative ? truncatedQuotient - 1 : truncatedQuotient + 1;
if (roundingMode >= RoundingMode::HalfCeil) {
auto unsignedFractionalPart = std::abs(quotient - truncatedQuotient);
if (unsignedFractionalPart < 0.5)
return truncatedQuotient * increment;
if (unsignedFractionalPart > 0.5)
return expandedQuotient * increment;
}
if (roundingMode == RoundingMode::Ceil) {
return (isNegative ? truncatedQuotient : expandedQuotient) * increment;
} else if (roundingMode == RoundingMode::Floor) {
return (isNegative ? expandedQuotient : truncatedQuotient) * increment;
} else if (roundingMode == RoundingMode::Expand) {
return expandedQuotient * increment;
} else if (roundingMode == RoundingMode::Trunc) {
return truncatedQuotient * increment;
} else if (roundingMode == RoundingMode::HalfCeil) {
return (isNegative ? truncatedQuotient : expandedQuotient) * increment;
} else if (roundingMode == RoundingMode::HalfFloor) {
return (isNegative ? expandedQuotient : truncatedQuotient) * increment;
} else if (roundingMode == RoundingMode::HalfExpand) {
return expandedQuotient * increment;
} else if (roundingMode == RoundingMode::HalfTrunc) {
return truncatedQuotient * increment;
} else if (roundingMode == RoundingMode::HalfEven) {
return (!std::fmod(truncatedQuotient, 2) ? truncatedQuotient : expandedQuotient) * increment;
}
ASSERT_NOT_REACHED();
return 0;
}
Int128 roundNumberToIncrement(Int128 x, Int128 increment, RoundingMode roundingMode)
{
// This follows the polyfill code rather than the spec, in order to work around
// being unable to apply floating-point division in x / increment.
// See https://github.com/tc39/proposal-temporal/blob/main/polyfill/lib/ecmascript.mjs#L4043
Int128 quotient = x / increment;
Int128 remainder = x % increment;
bool isNegative = x < 0;
Int128 r1 = std::abs(quotient);
Int128 r2 = r1 + 1;
Int128 even = r1 % 2;
auto unsignedRoundingMode = getUnsignedRoundingMode(roundingMode, isNegative);
Int128 rounded = 0;
if (std::abs(x) == r1 * increment)
rounded = r1;
else if (unsignedRoundingMode == UnsignedRoundingMode::Zero)
rounded = r1;
else if (unsignedRoundingMode == UnsignedRoundingMode::Infinity)
rounded = r2;
else if (std::abs(remainder * 2) < increment)
rounded = r1;
else if (std::abs(remainder * 2) > increment)
rounded = r2;
else if (unsignedRoundingMode == UnsignedRoundingMode::HalfZero)
rounded = r1;
else if (unsignedRoundingMode == UnsignedRoundingMode::HalfInfinity)
rounded = r2;
else
rounded = !even ? r1 : r2;
if (isNegative)
rounded = -rounded;
return rounded * increment;
}
Int128 roundNumberToIncrementAsIfPositive(Int128 x, Int128 increment, RoundingMode roundingMode)
{
// The following code follows the polyfill rather than the spec, because we don't have float128.
// ApplyUnsignedRoundingMode is inlined here to mirror the polyfill's implementation of it,
@ -1357,41 +1465,81 @@ Int128 roundNumberToIncrementAsIfPositive(Int128 x, Int128 increment, String* ro
return !even ? r1 * increment : r2 * increment;
}
UnsignedRoundingMode getUnsignedRoundingMode(String* roundingMode, bool isNegative)
UnsignedRoundingMode getUnsignedRoundingMode(RoundingMode roundingMode, bool isNegative)
{
if (roundingMode->equals("ceil")) {
if (roundingMode == RoundingMode::Ceil) {
return isNegative ? UnsignedRoundingMode::Zero : UnsignedRoundingMode::Infinity;
} else if (roundingMode->equals("floor")) {
} else if (roundingMode == RoundingMode::Floor) {
return isNegative ? UnsignedRoundingMode::Infinity : UnsignedRoundingMode::Zero;
} else if (roundingMode->equals("expand")) {
} else if (roundingMode == RoundingMode::Expand) {
return UnsignedRoundingMode::Infinity;
} else if (roundingMode->equals("trunc")) {
} else if (roundingMode == RoundingMode::Trunc) {
return UnsignedRoundingMode::Zero;
} else if (roundingMode->equals("halfCeil")) {
} else if (roundingMode == RoundingMode::HalfCeil) {
return isNegative ? UnsignedRoundingMode::HalfZero : UnsignedRoundingMode::HalfInfinity;
} else if (roundingMode->equals("halfFloor")) {
} else if (roundingMode == RoundingMode::HalfFloor) {
return isNegative ? UnsignedRoundingMode::HalfInfinity : UnsignedRoundingMode::HalfZero;
} else if (roundingMode->equals("halfExpand")) {
} else if (roundingMode == RoundingMode::HalfExpand) {
return UnsignedRoundingMode::HalfInfinity;
} else if (roundingMode->equals("halfTrunc")) {
} else if (roundingMode == RoundingMode::HalfTrunc) {
return UnsignedRoundingMode::HalfZero;
}
return UnsignedRoundingMode::HalfEven;
}
static Int128 roundTemporalInstant(Int128 ns, unsigned increment, String* unit, String* roundingMode)
static Int128 roundTemporalInstant(Int128 ns, unsigned increment, DateTimeUnit unit, RoundingMode roundingMode)
{
auto unitLength = lengthInNanoseconds(unit);
auto incrementNs = increment * unitLength;
return roundNumberToIncrementAsIfPositive(ns, incrementNs, roundingMode);
}
ExactTime ExactTime::round(ExecutionState& state, unsigned increment, String* unit, String* roundingMode)
ExactTime ExactTime::round(ExecutionState& state, unsigned increment, DateTimeUnit unit, RoundingMode roundingMode)
{
auto roundedNs = roundTemporalInstant(m_epochNanoseconds, increment, unit, roundingMode);
return ExactTime{ roundedNs };
}
// https://tc39.es/proposal-temporal/#sec-temporal-datedurationsign
static int32_t dateDurationSign(const Duration& d)
{
if (d.years() > 0)
return 1;
if (d.years() < 0)
return -1;
if (d.months() > 0)
return 1;
if (d.months() < 0)
return -1;
if (d.weeks() > 0)
return 1;
if (d.weeks() < 0)
return -1;
if (d.days() > 0)
return 1;
if (d.days() < 0)
return -1;
return 0;
}
// https://tc39.es/proposal-temporal/#sec-temporal-internaldurationsign
int32_t ISO8601::InternalDuration::sign() const
{
int32_t sign = dateDurationSign(m_dateDuration);
if (sign)
return sign;
return timeDurationSign();
}
// https://tc39.es/proposal-temporal/#sec-temporal-combinedateandtimeduration
InternalDuration InternalDuration::combineDateAndTimeDuration(Duration dateDuration, Int128 timeDuration)
{
int32_t dateSign = dateDurationSign(dateDuration);
int32_t timeSign = timeDuration < 0 ? -1 : timeDuration > 0 ? 1
: 0;
return InternalDuration{ std::move(dateDuration), timeDuration };
}
using CheckedInt128 = Checked<Int128, RecordOverflow>;
static CheckedInt128 checkedCastDoubleToInt128(double n)
@ -1486,6 +1634,31 @@ Optional<TimeZoneID> parseTimeZoneName(String* string)
return ret;
}
Int128 resolveNanosecondsValueByUnit(DateTimeUnit unit)
{
Int128 maximum = 0;
constexpr int64_t hoursPerDay = 24;
constexpr int64_t minutesPerHour = 60;
constexpr int64_t secondsPerMinute = 60;
constexpr int64_t msPerDay = hoursPerDay * minutesPerHour * secondsPerMinute * 1000;
if (unit == DateTimeUnit::Hour) {
maximum = static_cast<Int128>(hoursPerDay);
} else if (unit == DateTimeUnit::Minute) {
maximum = static_cast<Int128>(minutesPerHour * hoursPerDay);
} else if (unit == DateTimeUnit::Second) {
maximum = static_cast<Int128>(secondsPerMinute * minutesPerHour * hoursPerDay);
} else if (unit == DateTimeUnit::Millisecond) {
maximum = static_cast<Int128>(msPerDay);
} else if (unit == DateTimeUnit::Microsecond) {
maximum = static_cast<Int128>(msPerDay * 1000);
} else if (unit == DateTimeUnit::Nanosecond) {
maximum = ISO8601::ExactTime::nsPerDay;
}
return maximum;
}
} // namespace ISO8601
} // namespace Escargot

View file

@ -77,6 +77,23 @@ enum class DateTimeUnitCategory {
F(microsecond, Microsecond, microseconds, Microseconds, 8, ISO8601::DateTimeUnitCategory::Time) \
F(nanosecond, Nanosecond, nanoseconds, Nanoseconds, 9, ISO8601::DateTimeUnitCategory::Time)
enum class DateTimeUnit : uint8_t {
#define DEFINE_TYPE(name, Name, names, Names, index, category) Name,
PLAIN_DATETIME_UNITS(DEFINE_TYPE)
#undef DEFINE_TYPE
};
DateTimeUnitCategory toDateTimeCategory(DateTimeUnit u);
DateTimeUnit toDateTimeUnit(String*);
inline Optional<DateTimeUnit> toDateTimeUnit(Optional<String*> s)
{
if (s) {
return toDateTimeUnit(s.value());
}
return NullOption;
}
#define PLAIN_DATE_UNITS(macro) \
macro(year, Year) \
macro(month, Month) \
@ -90,6 +107,26 @@ enum class DateTimeUnitCategory {
macro(microsecond, Microsecond) \
macro(nanosecond, Nanosecond)
enum class RoundingMode : uint8_t {
Ceil,
Floor,
Expand,
Trunc,
HalfCeil,
HalfFloor,
HalfExpand,
HalfTrunc,
HalfEven,
};
enum class UnsignedRoundingMode : uint8_t {
Infinity,
Zero,
HalfInfinity,
HalfZero,
HalfEven
};
class ExactTime {
public:
static constexpr Int128 dayRangeSeconds{ 8640000000000 }; // 1e8 days
@ -144,7 +181,7 @@ public:
return m_epochNanoseconds >= ExactTime::minValue && m_epochNanoseconds <= ExactTime::maxValue;
}
ExactTime round(ExecutionState& state, unsigned increment, String* unit, String* roundingMode);
ExactTime round(ExecutionState& state, unsigned increment, DateTimeUnit unit, RoundingMode roundingMode);
private:
Int128 m_epochNanoseconds{};
@ -154,12 +191,6 @@ class Duration {
std::array<double, 10> m_data;
public:
enum class Type : uint8_t {
#define DEFINE_TYPE(name, Name, names, Names, index, category) Names,
PLAIN_DATETIME_UNITS(DEFINE_TYPE)
#undef DEFINE_TYPE
};
// https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldurationstring
static Optional<Duration> parseDurationString(String* input);
@ -173,15 +204,49 @@ public:
{
}
static String* typeName(ExecutionState& state, Type t);
Int128 totalNanoseconds(Duration::Type type) const;
Duration(std::initializer_list<double> list)
{
size_t idx = 0;
for (auto n : list) {
m_data[idx++] = n;
}
}
int sign() const
{
for (const auto& v : m_data) {
if (v < 0) {
return -1;
}
if (v > 0) {
return 1;
}
}
return 0;
}
// https://tc39.es/proposal-temporal/#sec-temporal-defaulttemporallargestunit
DateTimeUnit defaultTemporalLargestUnit() const
{
size_t idx = 0;
for (auto n : m_data) {
if (n) {
return static_cast<DateTimeUnit>(idx);
}
idx++;
}
return DateTimeUnit::Nanosecond;
}
static String* typeName(ExecutionState& state, ISO8601::DateTimeUnit t);
Int128 totalNanoseconds(ISO8601::DateTimeUnit type) const;
double operator[](size_t idx) const
{
return m_data[static_cast<unsigned>(idx)];
}
double operator[](Duration::Type idx) const
double operator[](ISO8601::DateTimeUnit idx) const
{
return operator[](static_cast<size_t>(idx));
}
@ -191,7 +256,7 @@ public:
return m_data[static_cast<unsigned>(idx)];
}
double& operator[](Duration::Type idx)
double& operator[](ISO8601::DateTimeUnit idx)
{
return operator[](static_cast<size_t>(idx));
}
@ -222,7 +287,7 @@ public:
return m_data[static_cast<unsigned>(idx)];
}
Optional<double> operator[](Duration::Type idx) const
Optional<double> operator[](ISO8601::DateTimeUnit idx) const
{
return operator[](static_cast<size_t>(idx));
}
@ -232,7 +297,7 @@ public:
return m_data[static_cast<unsigned>(idx)];
}
Optional<double>& operator[](Duration::Type idx)
Optional<double>& operator[](ISO8601::DateTimeUnit idx)
{
return operator[](static_cast<size_t>(idx));
}
@ -251,6 +316,50 @@ public:
#undef DEFINE_SETTER
};
// https://tc39.es/proposal-temporal/#sec-temporal-internal-duration-records
// Represents a duration as an ISO8601::Duration (in which all time fields
// are ignored) along with an Int128 time duration that represents the sum
// of all time fields. Used to avoid losing precision in intermediate calculations.
class InternalDuration final {
public:
InternalDuration(Duration d, Int128 t)
: m_dateDuration(d)
, m_time(t)
{
}
InternalDuration()
: m_dateDuration(Duration())
, m_time(0)
{
}
static constexpr Int128 maxTimeDuration = 9007199254740992 * ExactTime::nsPerSecond - 1;
int32_t sign() const;
int32_t timeDurationSign() const
{
return m_time < 0 ? -1 : m_time > 0 ? 1
: 0;
}
Int128 time() const { return m_time; }
Duration dateDuration() const { return m_dateDuration; }
static InternalDuration combineDateAndTimeDuration(Duration, Int128);
private:
// Time fields are ignored
Duration m_dateDuration;
// A time duration is an integer in the inclusive interval from -maxTimeDuration
// to maxTimeDuration, where
// maxTimeDuration = 2**53 × 10**9 - 1 = 9,007,199,254,740,991,999,999,999.
// It represents the portion of a Temporal.Duration object that deals with time
// units, but as a combined value of total nanoseconds.
Int128 m_time;
};
class PlainTime {
public:
PlainTime()
@ -351,18 +460,18 @@ Optional<int64_t> parseUTCOffset(String* string, bool parseSubMinutePrecision =
Optional<TimeZoneID> parseTimeZoneName(String* string);
Optional<std::tuple<PlainDate, Optional<PlainTime>, Optional<TimeZoneRecord>, Optional<CalendarID>>> parseCalendarDateTime(String* input, bool parseSubMinutePrecisionForTimeZone = true);
// https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement
double roundNumberToIncrement(double x, double increment, RoundingMode roundingMode);
Int128 roundNumberToIncrement(Int128 x, Int128 increment, RoundingMode roundingMode);
// https://tc39.es/proposal-temporal/#sec-roundNumbertoincrementasifpositive
Int128 roundNumberToIncrementAsIfPositive(Int128 x, Int128 increment, String* roundingMode);
Int128 roundNumberToIncrementAsIfPositive(Int128 x, Int128 increment, RoundingMode roundingMode);
// https://tc39.es/proposal-temporal/#sec-getunsignedroundingmode
enum class UnsignedRoundingMode : uint8_t {
Infinity,
Zero,
HalfInfinity,
HalfZero,
HalfEven
};
UnsignedRoundingMode getUnsignedRoundingMode(String* roundingMode, bool isNegative);
UnsignedRoundingMode getUnsignedRoundingMode(RoundingMode roundingMode, bool isNegative);
Int128 lengthInNanoseconds(DateTimeUnit unit);
Int128 resolveNanosecondsValueByUnit(DateTimeUnit unit);
class TimeConstants {
public:

View file

@ -232,14 +232,6 @@
<test id="built-ins/Temporal/Duration/prototype/add/result-out-of-range-2"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/add/result-out-of-range-3"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/add/subclassing-ignored"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/negated/basic"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/negated/branding"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/negated/builtin"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/negated/length"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/negated/name"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/negated/not-a-constructor"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/negated/prop-desc"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/negated/subclassing-ignored"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/round/accepts-datetime-strings-for-relative-to"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/round/balance-negative-result"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/round/balance-subseconds"><reason>TODO</reason></test>
@ -389,43 +381,9 @@
<test id="built-ins/Temporal/Duration/prototype/toJSON/prop-desc"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toLocaleString/branding"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toLocaleString/prop-desc"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/balance"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/balance-subseconds"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/blank-duration-precision"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/branding"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/fractionalseconddigits-auto"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/fractionalseconddigits-exact-number-of-digits"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/fractionalseconddigits-invalid-string"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/fractionalseconddigits-nan"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/fractionalseconddigits-non-integer"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/fractionalseconddigits-number"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/fractionalseconddigits-out-of-range"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/fractionalseconddigits-undefined"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/fractionalseconddigits-wrong-type"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/max-value"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/negative-components"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/no-precision-loss"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/options-object"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/options-undefined"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/options-wrong-type"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/order-of-operations"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/precision"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/prop-desc"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/round-cross-unit-boundary"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/roundingmode-ceil"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/roundingmode-floor"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/roundingmode-halfExpand"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/roundingmode-invalid-string"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/roundingmode-trunc"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/roundingmode-undefined"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/roundingmode-wrong-type"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/smallestunit-fractionalseconddigits"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/smallestunit-invalid-string"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/smallestunit-plurals-accepted"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/smallestunit-undefined"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/smallestunit-valid-units"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/smallestunit-wrong-type"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/throws-when-rounded-duration-is-invalid"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/toString/total-of-duration-time-units-out-of-range"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/total/balance-negative-result"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Duration/prototype/total/balance-subseconds"><reason>TODO</reason></test>
@ -566,70 +524,8 @@
<test id="built-ins/Temporal/Instant/prototype/epochMilliseconds/basic"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/equals/argument-zoneddatetime"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/add-subtract"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/argument-object-tostring"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/argument-string-calendar-annotation"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/argument-string-calendar-annotation-invalid-key"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/argument-string-critical-unknown-annotation"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/argument-string-date-with-utc-offset"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/argument-string-invalid"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/argument-string-limits"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/argument-string-minus-sign"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/argument-string-multiple-calendar"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/argument-string-multiple-time-zone"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/argument-string-time-separators"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/argument-string-time-zone-annotation"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/argument-string-unknown-annotation"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/argument-wrong-type"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/argument-zoneddatetime"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/branding"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/builtin"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/instant-string"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/instant-string-multiple-offsets"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/instant-string-sub-minute-offset"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/invalid-increments"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/largest-unit-default"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/largestunit"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/largestunit-invalid-string"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/largestunit-plurals-accepted"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/largestunit-smallestunit-mismatch"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/largestunit-undefined"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/largestunit-wrong-type"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/leap-second"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/length"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/minutes-and-hours"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/name"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/not-a-constructor"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/options-may-be-function"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/options-object"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/options-undefined"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/order-of-operations"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/prop-desc"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/round-cross-unit-boundary"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/rounding-increments"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/roundingincrement-nan"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/roundingincrement-non-integer"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/roundingincrement-out-of-range"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/roundingincrement-undefined"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/roundingincrement-wrong-type"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/roundingmode-ceil"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/roundingmode-expand"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/roundingmode-floor"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/roundingmode-halfCeil"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/roundingmode-halfEven"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/roundingmode-halfExpand"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/roundingmode-halfFloor"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/roundingmode-halfTrunc"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/roundingmode-invalid-string"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/roundingmode-trunc"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/roundingmode-undefined"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/roundingmode-wrong-type"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/smallestunit-invalid-string"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/smallestunit-plurals-accepted"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/smallestunit-undefined"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/smallestunit-wrong-type"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/subseconds"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/valid-increments"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/since/year-zero"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/subtract/argument-duration-max"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/subtract/argument-duration-out-of-range"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/subtract/argument-mixed-sign"><reason>TODO</reason></test>
@ -670,69 +566,8 @@
<test id="built-ins/Temporal/Instant/prototype/toZonedDateTimeISO/timezone-wrong-type"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/toZonedDateTimeISO/to-zoned-date-time-iso"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/add-subtract"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/argument-object-tostring"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/argument-string-calendar-annotation"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/argument-string-calendar-annotation-invalid-key"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/argument-string-critical-unknown-annotation"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/argument-string-date-with-utc-offset"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/argument-string-invalid"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/argument-string-limits"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/argument-string-minus-sign"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/argument-string-multiple-calendar"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/argument-string-multiple-time-zone"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/argument-string-time-separators"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/argument-string-time-zone-annotation"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/argument-string-unknown-annotation"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/argument-wrong-type"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/argument-zoneddatetime"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/branding"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/builtin"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/instant-string"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/instant-string-multiple-offsets"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/instant-string-sub-minute-offset"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/invalid-increments"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/largestunit-default"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/largestunit-invalid-string"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/largestunit-plurals-accepted"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/largestunit-smallestunit-mismatch"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/largestunit-undefined"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/largestunit-wrong-type"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/leap-second"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/length"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/minutes-and-hours"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/name"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/not-a-constructor"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/options-may-be-function"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/options-object"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/options-undefined"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/order-of-operations"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/prop-desc"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/round-cross-unit-boundary"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/rounding-increments"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/roundingincrement-nan"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/roundingincrement-non-integer"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/roundingincrement-out-of-range"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/roundingincrement-undefined"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/roundingincrement-wrong-type"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/roundingmode-ceil"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/roundingmode-expand"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/roundingmode-floor"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/roundingmode-halfCeil"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/roundingmode-halfEven"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/roundingmode-halfExpand"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/roundingmode-halfFloor"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/roundingmode-halfTrunc"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/roundingmode-invalid-string"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/roundingmode-trunc"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/roundingmode-undefined"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/roundingmode-wrong-type"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/smallestunit-invalid-string"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/smallestunit-plurals-accepted"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/smallestunit-undefined"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/smallestunit-wrong-type"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/subseconds"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/valid-increments"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Instant/prototype/until/year-zero"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Now/plainDateISO/length"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Now/plainDateISO/prop-desc"><reason>TODO</reason></test>
<test id="built-ins/Temporal/Now/plainDateISO/return-value"><reason>TODO</reason></test>