escargot/src/runtime/TemporalObject.cpp
Seonghyun Kim 8d140c3c0f Implement Temporal.Duration.{ add, subtract, with, toLocalString, toJSON }
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-09-09 11:38:59 +09:00

1065 lines
45 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#if defined(ENABLE_TEMPORAL)
/*
* Copyright (c) 2022-present Samsung Electronics Co., Ltd
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#include "Escargot.h"
#include "TemporalObject.h"
#include "runtime/ArrayObject.h"
#include "runtime/BigInt.h"
#include "runtime/DateObject.h"
#include "runtime/TemporalDurationObject.h"
#include "runtime/TemporalInstantObject.h"
#include "intl/Intl.h"
namespace Escargot {
//////////////////
// helper function
//////////////////
static Value getOption(ExecutionState& state, Object* options, Value property, bool isBool, Value* values, size_t valuesLength, const Value& defaultValue)
{
Value value = options->get(state, ObjectPropertyName(state, property)).value(state, options);
if (value.isUndefined()) {
// TODO handle REQUIRED in default value
return defaultValue;
}
if (isBool) {
value = Value(value.toBoolean());
} else {
value = Value(value.toString(state));
}
if (valuesLength) {
bool contains = false;
for (size_t i = 0; i < valuesLength; i++) {
if (values[i].equalsTo(state, value)) {
contains = true;
}
}
if (!contains) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, ErrorObject::Messages::TemporalError);
}
}
return value;
}
static Object* getOptionsObject(ExecutionState& state, Value options)
{
if (options.isUndefined()) {
return new Object(state, Object::PrototypeIsNull);
}
if (options.isObject()) {
return options.asObject();
}
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, ErrorObject::Messages::TemporalError);
return nullptr;
}
static bool getTemporalOverflowOptions(ExecutionState& state, Object* options)
{
StaticStrings* strings = &state.context()->staticStrings();
Value stringConstrain = Value(strings->lazyConstrain().string());
Value values[2] = { stringConstrain, strings->reject.string() };
Value stringValue = getOption(state, options, Value(strings->lazyOverflow().string()), false, values, 2, stringConstrain);
if (stringValue.equalsTo(state, stringConstrain)) {
// return CONSTRAIN
return true;
}
// return REJECT
return false;
}
static int64_t countModZerosInRange(int64_t mod, int64_t start, int64_t end)
{
int64_t divEnd = end - 1;
int negative = divEnd < 0;
int64_t endPos = (divEnd + negative) / mod - negative;
divEnd = start - 1;
negative = divEnd < 0;
int64_t startPos = (divEnd + negative) / mod - negative;
return endPos - startPos;
}
static int countDaysOfYear(int year, int month, int day)
{
if (month < 1 || month > 12) {
return 0;
}
static const int seekTable[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
int daysOfYear = seekTable[month - 1] + day - 1;
if ((year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) && month >= 3) {
daysOfYear++;
}
return daysOfYear;
}
static double makeDay(double year, double month, double date)
{
if (!std::isfinite(year) || !std::isfinite(month) || !std::isfinite(date)) {
return std::numeric_limits<double>::quiet_NaN();
}
double ym = year + std::floor(month / TimeConstant::MonthsPerYear);
if (!std::isfinite(ym)) {
return std::numeric_limits<double>::quiet_NaN();
}
double mn = ((int)month % TimeConstant::MonthsPerYear);
if ((ym > std::numeric_limits<int>::max()) || (ym < std::numeric_limits<int>::min())) {
return std::numeric_limits<double>::quiet_NaN();
}
if ((mn + 1 > std::numeric_limits<int>::max()) || (mn + 1 < std::numeric_limits<int>::min())) {
return std::numeric_limits<double>::quiet_NaN();
}
int beginYear, endYear, leapSign;
if (ym < 1970) {
beginYear = static_cast<int>(ym);
endYear = 1970;
leapSign = -1;
} else {
beginYear = 1970;
endYear = static_cast<int>(ym);
leapSign = 1;
}
int64_t days = 365 * (static_cast<int64_t>(ym) - 1970);
int64_t extraLeapDays = 0;
extraLeapDays += countModZerosInRange(4, beginYear, endYear);
extraLeapDays += countModZerosInRange(100, beginYear, endYear);
extraLeapDays += countModZerosInRange(400, beginYear, endYear);
int64_t yearsToDays = days + extraLeapDays * leapSign;
int64_t daysOfYear = countDaysOfYear(static_cast<int>(ym), static_cast<int>(mn) + 1, 1);
int64_t t = (yearsToDays + daysOfYear) * TimeConstant::MsPerDay;
return (std::floor(t / TimeConstant::MsPerDay) + date - 1);
}
static double makeTime(double hour, double minute, double sec, double ms)
{
if (!std::isfinite(hour) || !std::isfinite(minute) || !std::isfinite(sec) || !std::isfinite(ms)) {
return std::numeric_limits<double>::quiet_NaN();
}
return (hour * TimeConstant::MsPerHour + minute * TimeConstant::MsPerMinute + sec * TimeConstant::MsPerSecond + ms);
}
static double makeDate(double day, double time)
{
if (!std::isfinite(day) || !std::isfinite(time)) {
return std::numeric_limits<double>::quiet_NaN();
}
double tv = day * TimeConstant::MsPerDay + time;
if (!std::isfinite(tv)) {
return std::numeric_limits<double>::quiet_NaN();
}
return tv;
}
////////////////////////////
// Temporal helper functions
////////////////////////////
double Temporal::epochDayNumberForYear(const double year)
{
return 365.0 * (year - 1970.0) + std::floor((year - 1969.0) / 4.0) - std::floor((year - 1901.0) / 100.0) + std::floor((year - 1601.0) / 400.0);
}
int Temporal::epochTimeToEpochYear(const double time)
{
return DateObject::yearFromTime(time);
}
double Temporal::epochTimeForYear(const double year)
{
return TimeConstant::MsPerDay * epochDayNumberForYear(year);
}
int Temporal::mathematicalInLeapYear(const double time)
{
int daysInYear = mathematicalDaysInYear(epochTimeToEpochYear(time));
if (daysInYear == 365) {
return 0;
} else {
ASSERT(daysInYear == 366);
return 1;
}
}
int Temporal::mathematicalDaysInYear(const int year)
{
if ((year % 4) != 0) {
return 365;
}
if ((year % 100) != 0) {
return 366;
}
if ((year % 400) != 0) {
return 365;
}
return 366;
}
bool Temporal::isValidISODate(ExecutionState& state, const int year, const int month, const int day)
{
if (month < 1 || month > 12) {
return false;
}
int daysInMonth = Temporal::ISODaysInMonth(year, month);
if (day < 1 || day > daysInMonth) {
return false;
}
return true;
}
int Temporal::ISODaysInMonth(const int year, const int month)
{
if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) {
return 31;
}
if (month == 4 || month == 6 || month == 9 || month == 11) {
return 30;
}
ASSERT(month == 2);
// Return 28 + MathematicalInLeapYear(EpochTimeForYear(year)).
return 28 + mathematicalInLeapYear(epochTimeForYear(year));
}
int Temporal::ISODayOfWeek(const ISODate& date)
{
// Let epochDays be ISODateToEpochDays(isoDate.[[Year]], isoDate.[[Month]] - 1, isoDate.[[Day]]).
double epochDays = makeDay(date.year, date.month - 1, date.day);
int dayOfWeek = static_cast<int>(std::floor((epochDays * TimeConstant::MsPerDay) / TimeConstant::MsPerDay) + 4) % 7;
if (dayOfWeek == 0) {
return 7;
}
return dayOfWeek;
}
int Temporal::ISODayOfYear(const ISODate& date)
{
// Let epochDays be ISODateToEpochDays(isoDate.[[Year]], isoDate.[[Month]] - 1, isoDate.[[Day]]).
double epochDays = makeDay(date.year, date.month - 1, date.day);
double t = epochDays * TimeConstant::MsPerDay;
return static_cast<int>(std::floor(t / TimeConstant::MsPerDay) - epochDayNumberForYear(DateObject::yearFromTime(t))) + 1;
}
YearWeek Temporal::ISOWeekOfYear(const ISODate& date)
{
const int year = date.year;
const int wednesday = 3;
const int thursday = 4;
const int friday = 5;
const int saturday = 6;
const int daysInWeek = 7;
const int maxWeekNumber = 53;
const int dayOfYear = ISODayOfYear(date);
const int dayOfWeek = ISODayOfWeek(date);
const int week = static_cast<int>(std::floor((double)(dayOfYear + daysInWeek - dayOfWeek + wednesday) / daysInWeek));
if (week < 1) {
ISODate jan1st(year, 1, 1);
int dayOfJan1st = ISODayOfWeek(jan1st);
if (dayOfJan1st == friday) {
return YearWeek(maxWeekNumber, year - 1);
}
if ((dayOfJan1st == saturday) && (mathematicalInLeapYear(epochTimeForYear(year - 1)) == 1)) {
return YearWeek(maxWeekNumber, year - 1);
}
return YearWeek(maxWeekNumber - 1, year - 1);
}
if (week == maxWeekNumber) {
int daysInYear = mathematicalDaysInYear(year);
int daysLaterInYear = daysInYear - dayOfYear;
int daysAfterThursday = thursday - dayOfWeek;
if (daysLaterInYear < daysAfterThursday) {
return YearWeek(1, year + 1);
}
}
return YearWeek(week, year);
}
bool Temporal::ISODateWithinLimits(ExecutionState& state, const ISODate& date)
{
// NoonTimeRecord
// { [[Days]]: 0, [[Hour]]: 12, [[Minute]]: 0, [[Second]]: 0, [[Millisecond]]: 0, [[Microsecond]]: 0, [[Nanosecond]]: 0 }
TimeRecord record = TimeRecord::noonTimeRecord();
ISODateTime dateTime = ISODateTime(date, record);
return Temporal::ISODateTimeWithinLimits(state, dateTime);
}
bool Temporal::ISODateTimeWithinLimits(ExecutionState& state, const ISODateTime& dateTime)
{
double date = makeDay(dateTime.date.year, dateTime.date.month - 1, dateTime.date.day);
if (std::abs(date) > 100000001) {
return false;
}
double time = makeTime(dateTime.time.hour, dateTime.time.minute, dateTime.time.second, dateTime.time.millisecond);
double ms = makeDate(date, time);
// Z(R(ms) × 10**6 + isoDateTime.[[Time]].[[Microsecond]] × 10**3 + isoDateTime.[[Time]].[[Nanosecond]]).
int64_t ns = static_cast<int64_t>(ms) * 1000000 + dateTime.time.microsecond * 1000 + dateTime.time.nanosecond;
if (ns <= Temporal::nsMinConstant()) {
return false;
}
if (ns >= Temporal::nsMaxConstant()) {
return false;
}
return true;
}
int64_t Temporal::nsPerDay()
{
return 86400000000000;
}
Value Temporal::createTemporalDate(ExecutionState& state, const ISODate& isoDate, String* calendar, Optional<Object*> newTarget)
{
if (!Temporal::ISODateWithinLimits(state, isoDate)) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, ErrorObject::Messages::TemporalError);
}
Object* proto = Object::getPrototypeFromConstructor(state, newTarget.hasValue() ? newTarget.value() : state.context()->globalObject()->temporalPlainDate(), [](ExecutionState& state, Context* constructorRealm) -> Object* {
return constructorRealm->globalObject()->temporalPlainDatePrototype();
});
return new TemporalPlainDateObject(state, proto, isoDate, calendar);
}
Value Temporal::toTemporalDate(ExecutionState& state, Value item, Value options)
{
if (item.isObject()) {
if (item.asObject()->isTemporalPlainDateObject()) {
Object* resolvedOptions = getOptionsObject(state, options);
getTemporalOverflowOptions(state, resolvedOptions);
return Temporal::createTemporalDate(state, item.asObject()->asTemporalPlainDateObject()->date(), item.asObject()->asTemporalPlainDateObject()->calendar(), nullptr);
}
// TODO
return Value();
}
if (!item.isString()) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, ErrorObject::Messages::TemporalError);
}
// TODO ParseISODateTime
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();
auto microSecondsSinceEpoch = std::chrono::duration_cast<std::chrono::microseconds>(d).count();
unsigned long nano = std::chrono::duration_cast<std::chrono::nanoseconds>(d - std::chrono::duration_cast<std::chrono::microseconds>(d)).count();
Int128 ret = Int128(microSecondsSinceEpoch);
ret *= 1000;
ret += nano;
return ret;
}
bool Temporal::isValidEpochNanoseconds(Int128 epochNanoseconds)
{
// If (epochNanoseconds) < nsMinInstant or (epochNanoseconds) > nsMaxInstant, then
if (epochNanoseconds < Temporal::nsMinInstant() || epochNanoseconds > Temporal::nsMaxInstant()) {
// Return false.
return false;
}
// Return true.
return true;
}
TemporalDurationObject* Temporal::toTemporalDuration(ExecutionState& state, const Value& item)
{
// If item is an Object and item has an [[InitializedTemporalDuration]] internal slot, then
if (item.isObject() && item.asObject()->isTemporalDurationObject()) {
// Return ! CreateTemporalDuration(item.[[Years]], item.[[Months]], item.[[Weeks]], item.[[Days]], item.[[Hours]], item.[[Minutes]], item.[[Seconds]], item.[[Milliseconds]], item.[[Microseconds]], item.[[Nanoseconds]]).
return new TemporalDurationObject(state, item.asPointerValue()->asTemporalDurationObject()->duration());
}
constexpr auto msg = "The value you gave for ToTemporalDuration is invalid";
if (!item.isObject()) {
// If item is not an Object, then
// If item is not a String, throw a TypeError exception.
if (!item.isString()) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, msg);
}
// Return ? ParseTemporalDurationString(item).
auto duration = ISO8601::Duration::parseDurationString(item.asString());
if (!duration) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
}
return new TemporalDurationObject(state, duration.value());
}
// Let result be a new Partial Duration Record with each field set to 0.
// Let partial be ? ToTemporalPartialDurationRecord(item).
auto partial = TemporalDurationObject::toTemporalPartialDurationRecord(state, item);
// If partial.[[Years]] is not undefined, set result.[[Years]] to partial.[[Years]].
// If partial.[[Months]] is not undefined, set result.[[Months]] to partial.[[Months]].
// If partial.[[Weeks]] is not undefined, set result.[[Weeks]] to partial.[[Weeks]].
// If partial.[[Days]] is not undefined, set result.[[Days]] to partial.[[Days]].
// If partial.[[Hours]] is not undefined, set result.[[Hours]] to partial.[[Hours]].
// If partial.[[Minutes]] is not undefined, set result.[[Minutes]] to partial.[[Minutes]].
// If partial.[[Seconds]] is not undefined, set result.[[Seconds]] to partial.[[Seconds]].
// If partial.[[Milliseconds]] is not undefined, set result.[[Milliseconds]] to partial.[[Milliseconds]].
// If partial.[[Microseconds]] is not undefined, set result.[[Microseconds]] to partial.[[Microseconds]].
// If partial.[[Nanoseconds]] is not undefined, set result.[[Nanoseconds]] to partial.[[Nanoseconds]].
ISO8601::Duration duration;
size_t idx = 0;
for (const auto& s : partial) {
if (s.hasValue()) {
duration[idx] = s.value();
}
idx++;
}
if (!duration.isValid()) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
}
// Return ? CreateTemporalDuration(result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]).
return new TemporalDurationObject(state, duration);
}
// https://tc39.es/proposal-temporal/#sec-temporal-totemporalinstant
TemporalInstantObject* Temporal::toTemporalInstant(ExecutionState& state, Value item)
{
// If item is an Object, then
if (item.isObject()) {
// If item has an [[InitializedTemporalInstant]] or [[InitializedTemporalZonedDateTime]] internal slot, then
// TODO [[InitializedTemporalZonedDateTime]]
if (item.asObject()->isTemporalInstantObject()) {
// Return ! CreateTemporalInstant(item.[[EpochNanoseconds]]).
return new TemporalInstantObject(state, state.context()->globalObject()->temporalInstantPrototype(),
item.asObject()->asTemporalInstantObject()->epochNanoseconds());
}
// NOTE: This use of ToPrimitive allows Instant-like objects to be converted.
// Set item to ? ToPrimitive(item, string).
item = item.toPrimitive(state, Value::PrimitiveTypeHint::PreferString);
}
constexpr auto msg = "The value you gave for ToTemporalInstant is invalid";
if (!item.isString()) {
// If item is not a String, throw a TypeError exception.
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, msg);
}
// Let parsed be ? ParseISODateTime(item, « TemporalInstantString »).
// Assert: Either parsed.[[TimeZone]].[[OffsetString]] is not empty or parsed.[[TimeZone]].[[Z]] is true, but not both.
// If parsed.[[TimeZone]].[[Z]] is true, let offsetNanoseconds be 0; otherwise, let offsetNanoseconds be ! ParseDateTimeUTCOffset(parsed.[[TimeZone]].[[OffsetString]]).
// If parsed.[[Time]] is start-of-day, let time be MidnightTimeRecord(); else let time be parsed.[[Time]].
// Let balanced be BalanceISODateTime(parsed.[[Year]], parsed.[[Month]], parsed.[[Day]], time.[[Hour]], time.[[Minute]], time.[[Second]], time.[[Millisecond]], time.[[Microsecond]], time.[[Nanosecond]] - offsetNanoseconds).
// Perform ? CheckISODaysRange(balanced.[[ISODate]]).
// Let epochNanoseconds be GetUTCEpochNanoseconds(balanced).
// If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
auto parsed = ISO8601::parseISODateTimeWithInstantFormat(item.asString());
if (!parsed || !parsed.value().isValid()) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
}
// Return ! CreateTemporalInstant(epochNanoseconds).
return new TemporalInstantObject(state, state.context()->globalObject()->temporalInstantPrototype(), parsed.value().epochNanoseconds());
}
Optional<unsigned> Temporal::getTemporalFractionalSecondDigitsOption(ExecutionState& state, Optional<Object*> resolvedOptions)
{
constexpr auto msg = "The value you gave for GetTemporalFractionalSecondDigitsOption is invalid";
// Let digitsValue be ? Get(options, "fractionalSecondDigits").
if (!resolvedOptions) {
return NullOption;
}
Value digitsValue = resolvedOptions->get(state, state.context()->staticStrings().lazyFractionalSecondDigits()).value(state, resolvedOptions.value());
// If digitsValue is undefined, return auto.
if (digitsValue.isUndefined()) {
return NullOption;
}
// If digitsValue is not a Number, then
if (!digitsValue.isNumber()) {
// If ? ToString(digitsValue) is not "auto", throw a RangeError exception.
String* dv = digitsValue.toString(state);
if (!dv->equals("auto")) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
}
// Return auto.
return NullOption;
}
double dv = digitsValue.asNumber();
// If digitsValue is NaN, +∞𝔽, or -∞𝔽, throw a RangeError exception.
if (std::isnan(dv) || std::isinf(dv)) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
}
// Let digitCount be floor((digitsValue)).
double digitCount = floor(dv);
// If digitCount < 0 or digitCount > 9, throw a RangeError exception.
if (digitCount < 0 || digitCount > 9) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
}
// Return digitCount.
return digitCount;
}
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 toRoundingMode(fallback);
}
Value values[] = {
state.context()->staticStrings().ceil.string(),
state.context()->staticStrings().floor.string(),
state.context()->staticStrings().lazyExpand().string(),
state.context()->staticStrings().trunc.string(),
state.context()->staticStrings().lazyHalfCeil().string(),
state.context()->staticStrings().lazyHalfFloor().string(),
state.context()->staticStrings().lazyHalfExpand().string(),
state.context()->staticStrings().lazyHalfTrunc().string(),
state.context()->staticStrings().lazyHalfEven().string()
};
String* roundingMode = Intl::getOption(state, resolvedOptions.value(), state.context()->staticStrings().lazyRoundingMode().string(), Intl::OptionValueType::StringValue, values, 9, fallback).asString();
return toRoundingMode(roundingMode);
}
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.
// 3. NOTE: For each singular Temporal unit name that is contained within allowedStrings, the corresponding plural name is also contained within it.
Value allowedStrings[] = {
#define DEFINE_STRING(name, Name, names, Names, index, category) \
state.context()->staticStrings().lazy##Name().string(), \
state.context()->staticStrings().lazy##Names().string(),
PLAIN_DATETIME_UNITS(DEFINE_STRING)
state.context()
->staticStrings()
.lazyAuto()
.string()
};
#undef DEFINE_STRING
// If default is unset, then
if (!defaultValue) {
// Let defaultValue be undefined.
defaultValue = Value();
} else {
// Else,
// Let defaultValue be default.
}
// Let value be ? GetOption(options, key, string, allowedStrings, defaultValue).
Value value;
if (resolvedOptions) {
value = Intl::getOption(state, resolvedOptions.value(), key, Intl::StringValue, allowedStrings, sizeof(allowedStrings) / sizeof(Value), defaultValue.value());
if (value.isEmpty()) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Required options value is missing");
}
} else {
if (defaultValue && defaultValue.value().isEmpty()) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Required options value is missing");
}
value = defaultValue.value();
}
// If value is undefined, return unset.
if (value.isUndefined()) {
return NullOption;
}
String* stringValue = value.asString();
// If value is "auto", return auto.
if (stringValue->equals("auto")) {
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 TemporalUnit::Name; \
} \
else if (stringValue->equals(#names)) \
{ \
return TemporalUnit::Name; \
}
PLAIN_DATETIME_UNITS(DEFINE_COMPARE)
#undef DEFINE_COMPARE
ASSERT_NOT_REACHED();
return TemporalUnit::Auto;
}
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) {
return;
}
// 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] == value.value()) {
return;
}
}
}
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 (toDateTimeUnit(value.value()) == ISO8601::DateTimeUnit::Name) \
{ \
categoryValue = category; \
}
PLAIN_DATETIME_UNITS(DEFINE_COMPARE)
#undef DEFINE_COMPARE
else
{
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
}
// If category is date and unitGroup is date or datetime, return unused.
if (categoryValue == ISO8601::DateTimeUnitCategory::Date && (unitGroup == ISO8601::DateTimeUnitCategory::Date || unitGroup == ISO8601::DateTimeUnitCategory::DateTime)) {
return;
}
// If category is time and unitGroup is time or datetime, return unused.
if (categoryValue == ISO8601::DateTimeUnitCategory::Time && (unitGroup == ISO8601::DateTimeUnitCategory::Time || unitGroup == ISO8601::DateTimeUnitCategory::DateTime)) {
return;
}
// Throw a RangeError exception.
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
}
TimeZone Temporal::toTemporalTimezoneIdentifier(ExecutionState& state, const Value& temporalTimeZoneLike)
{
// TODO If temporalTimeZoneLike is an Object, then
// TODO If temporalTimeZoneLike has an [[InitializedTemporalZonedDateTime]] internal slot, then
// TODO Return temporalTimeZoneLike.[[TimeZone]].
// If temporalTimeZoneLike is not a String, throw a TypeError exception.
if (!temporalTimeZoneLike.isString()) {
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "temporalTimeZoneLike is not String");
}
String* temporalTimeZoneLikeString = temporalTimeZoneLike.asString();
// Let parseResult be ? ParseTemporalTimeZoneString(temporalTimeZoneLike).
// Let offsetMinutes be parseResult.[[OffsetMinutes]].
// If offsetMinutes is not empty, return FormatOffsetTimeZoneIdentifier(offsetMinutes).
// Let name be parseResult.[[Name]].
// Let timeZoneIdentifierRecord be GetAvailableNamedTimeZoneIdentifier(name).
// If timeZoneIdentifierRecord is empty, throw a RangeError exception.
// Return timeZoneIdentifierRecord.[[Identifier]].
Optional<int64_t> utcOffset = ISO8601::parseUTCOffset(temporalTimeZoneLikeString, false);
if (utcOffset) {
return TimeZone(utcOffset.value());
}
Optional<ISO8601::TimeZoneID> identifier = ISO8601::parseTimeZoneName(temporalTimeZoneLikeString);
if (identifier) {
return TimeZone(identifier.value());
}
// Optional<std::tuple<PlainDate, Optional<PlainTime>, Optional<TimeZoneRecord>, Optional<CalendarID>>>
auto complexTimeZone = ISO8601::parseCalendarDateTime(temporalTimeZoneLikeString, false);
if (complexTimeZone && std::get<2>(complexTimeZone.value())) {
ISO8601::TimeZoneRecord record = std::get<2>(complexTimeZone.value()).value();
if (record.m_z) {
return TimeZone(int64_t(0));
} else if (record.m_nameOrOffset && record.m_nameOrOffset.id().value() == 0) {
const std::string& s = record.m_nameOrOffset.get<0>();
return TimeZone(String::fromUTF8(s.data(), s.length()));
} else if (record.m_nameOrOffset && record.m_nameOrOffset.id().value() == 1) {
return TimeZone(record.m_nameOrOffset.get<1>());
} else if (record.m_offset) {
return TimeZone(record.m_offset.value());
}
}
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid timeZone string");
return TimeZone(String::emptyString());
}
Temporal::StringPrecisionRecord Temporal::toSecondsStringPrecisionRecord(ExecutionState& state, Optional<ISO8601::DateTimeUnit> smallestUnit, Optional<unsigned> fractionalDigitCount)
{
if (smallestUnit) {
// If smallestUnit is minute, then
if (smallestUnit.value() == ISO8601::DateTimeUnit::Minute) {
// Return the Record { [[Precision]]: minute, [[Unit]]: minute, [[Increment]]: 1 }.
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), 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), 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), 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), ISO8601::DateTimeUnit::Nanosecond, 1 };
}
}
// Assert: smallestUnit is unset.
ASSERT(!smallestUnit.hasValue());
// If fractionalDigitCount is auto, then
if (!fractionalDigitCount.hasValue()) {
// Return the Record { [[Precision]]: auto, [[Unit]]: nanosecond, [[Increment]]: 1 }.
return { state.context()->staticStrings().lazyAuto().string(), ISO8601::DateTimeUnit::Nanosecond, 1 };
}
auto pow10Unsigned = [](unsigned n) -> unsigned {
unsigned result = 1;
for (unsigned i = 0; i < n; ++i)
result *= 10;
return result;
};
// If fractionalDigitCount = 0, then
if (fractionalDigitCount && fractionalDigitCount.value() == 0) {
// Return the Record { [[Precision]]: 0, [[Unit]]: second, [[Increment]]: 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()), 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()), 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()), ISO8601::DateTimeUnit::Nanosecond, pow10Unsigned(9 - fractionalDigitCount.value()) };
}
void Temporal::validateTemporalRoundingIncrement(ExecutionState& state, unsigned increment, Int128 dividend, bool inclusive)
{
// If inclusive is true, then
Int128 maximum;
if (inclusive) {
// Let maximum be dividend.
maximum = dividend;
} else {
// Else,
// Assert: dividend > 1.
// Let maximum be dividend - 1.
maximum = dividend - 1;
}
// If increment > maximum, throw a RangeError exception.
if (increment > maximum) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid increment value");
}
// If dividend modulo increment ≠ 0, then
if (dividend % increment) {
// Throw a RangeError exception.
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid dividend value");
}
// Return unused.
}
unsigned Temporal::getRoundingIncrementOption(ExecutionState& state, Optional<Object*> options)
{
if (!options) {
return 1;
}
// Let value be ? Get(options, "roundingIncrement").
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;
}
// Let integerIncrement be ? ToIntegerWithTruncation(value).
auto integerIncrement = value.toIntegerWithTruncation(state);
// If integerIncrement < 1 or integerIncrement > 10**9, throw a RangeError exception.
if (integerIncrement < 1 || integerIncrement > 1000000000) {
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid roundingIncrement value");
}
// Return integerIncrement.
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;
}
bool Temporal::isCalendarUnit(ISO8601::DateTimeUnit unit)
{
if (unit == ISO8601::DateTimeUnit::Year || unit == ISO8601::DateTimeUnit::Month || unit == ISO8601::DateTimeUnit::Week) {
return true;
}
return false;
}
TemporalObject::TemporalObject(ExecutionState& state)
: TemporalObject(state, state.context()->globalObject()->objectPrototype())
{
}
TemporalObject::TemporalObject(ExecutionState& state, Object* proto)
: DerivedObject(state, proto)
{
}
TemporalPlainDateObject::TemporalPlainDateObject(ExecutionState& state, Object* proto, const ISODate& date, String* calendar)
: TemporalObject(state, proto)
, m_calendar(calendar)
, m_date(date)
{
}
void* TemporalPlainDateObject::operator new(size_t size)
{
static MAY_THREAD_LOCAL bool typeInited = false;
static MAY_THREAD_LOCAL GC_descr descr;
if (!typeInited) {
GC_word obj_bitmap[GC_BITMAP_SIZE(TemporalPlainDateObject)] = { 0 };
Object::fillGCDescriptor(obj_bitmap);
GC_set_bit(obj_bitmap, GC_WORD_OFFSET(TemporalPlainDateObject, m_calendar));
descr = GC_make_descriptor(obj_bitmap, GC_WORD_LEN(TemporalPlainDateObject));
typeInited = true;
}
return GC_MALLOC_EXPLICITLY_TYPED(size, descr);
}
} // namespace Escargot
#endif