mirror of
https://github.com/Samsung/escargot.git
synced 2026-06-22 10:01:50 +00:00
2389 lines
109 KiB
C++
2389 lines
109 KiB
C++
#if defined(ENABLE_TEMPORAL)
|
||
/*
|
||
* Copyright (C) 2021 Apple Inc. All rights reserved.
|
||
* Copyright (C) 2022 Sony Interactive Entertainment Inc.
|
||
*
|
||
* Redistribution and use in source and binary forms, with or without
|
||
* modification, are permitted provided that the following conditions
|
||
* are met:
|
||
* 1. Redistributions of source code must retain the above copyright
|
||
* notice, this list of conditions and the following disclaimer.
|
||
* 2. Redistributions in binary form must reproduce the above copyright
|
||
* notice, this list of conditions and the following disclaimer in the
|
||
* documentation and/or other materials provided with the distribution.
|
||
*
|
||
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
|
||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
|
||
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||
* THE POSSIBILITY OF SUCH DAMAGE.
|
||
*/
|
||
/*
|
||
* 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 "runtime/TemporalPlainTimeObject.h"
|
||
#include "runtime/TemporalPlainDateObject.h"
|
||
#include "runtime/TemporalPlainYearMonthObject.h"
|
||
#include "intl/Intl.h"
|
||
|
||
namespace Escargot {
|
||
|
||
void Calendar::lookupICUEra(ExecutionState& state, const std::function<bool(size_t idx, const std::string& icuEra)>& fn) const
|
||
{
|
||
std::string s = "root/calendar/";
|
||
s += toICUString();
|
||
s += "/eras/abbreviated";
|
||
UErrorCode status = U_ZERO_ERROR;
|
||
UResourceBundle* t = nullptr;
|
||
t = ures_findResource(s.data(), t, &status);
|
||
|
||
if (U_FAILURE(status)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Internal ICU error");
|
||
}
|
||
|
||
size_t siz = ures_getSize(t);
|
||
for (size_t i = 0; i < siz; i++) {
|
||
int32_t len = 0;
|
||
auto u16 = ures_getStringByIndex(t, i, &len, &status);
|
||
if (U_FAILURE(status)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Internal ICU error");
|
||
}
|
||
UTF16StringFromExternalMemory u16Str(u16, len);
|
||
const UNormalizer2* normalizer = unorm2_getNFDInstance(&status);
|
||
if (!normalizer || U_FAILURE(status)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "normalization fails");
|
||
}
|
||
int32_t normalizedStringLength = unorm2_normalize(normalizer, u16, len, nullptr, 0, &status);
|
||
|
||
if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) {
|
||
// when normalize fails.
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "normalization fails");
|
||
}
|
||
UTF16StringData ret;
|
||
ret.resizeWithUninitializedValues(normalizedStringLength);
|
||
status = U_ZERO_ERROR;
|
||
unorm2_normalize(normalizer, u16, len, (UChar*)ret.data(), normalizedStringLength, &status);
|
||
|
||
std::string icuString;
|
||
for (int32_t i = 0; i < normalizedStringLength; i++) {
|
||
if (ret[i] < 128) {
|
||
icuString.push_back(tolower(ret[i]));
|
||
}
|
||
if (ret[i] == ' ') {
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (fn(i, icuString)) {
|
||
break;
|
||
}
|
||
}
|
||
ures_close(t);
|
||
}
|
||
|
||
bool Calendar::isEraRelated() const
|
||
{
|
||
switch (m_id) {
|
||
case ID::Gregorian:
|
||
case ID::Islamic:
|
||
case ID::IslamicCivil:
|
||
case ID::IslamicCivilLegacy:
|
||
case ID::IslamicRGSA:
|
||
case ID::IslamicTabular:
|
||
case ID::IslamicUmmAlQura:
|
||
case ID::ROC:
|
||
return true;
|
||
case ID::Japanese:
|
||
return true;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
bool Calendar::shouldUseICUExtendedYear() const
|
||
{
|
||
if (id() == Calendar::ID::Dangi || id() == Calendar::ID::Japanese || id() == Calendar::ID::Chinese) {
|
||
return true;
|
||
}
|
||
if (isEraRelated()) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
Optional<Calendar> Calendar::fromString(ISO8601::CalendarID id)
|
||
{
|
||
auto u = id;
|
||
std::transform(u.begin(), u.end(), u.begin(), tolower);
|
||
if (false) {}
|
||
#define DEFINE_FIELD(name, string, icuString, fullName) \
|
||
else if (u == string || u == icuString) \
|
||
{ \
|
||
return Calendar(ID::name); \
|
||
}
|
||
CALENDAR_ID_RECORDS(DEFINE_FIELD)
|
||
#undef DEFINE_FIELD
|
||
|
||
return NullOption;
|
||
}
|
||
|
||
Optional<Calendar> Calendar::fromString(String* str)
|
||
{
|
||
return fromString(str->toNonGCUTF8StringData());
|
||
}
|
||
|
||
String* Calendar::toString() const
|
||
{
|
||
switch (m_id) {
|
||
#define DEFINE_FIELD(name, string, icuString, fullName) \
|
||
case ID::name: \
|
||
return new ASCIIStringFromExternalMemory(fullName);
|
||
CALENDAR_ID_RECORDS(DEFINE_FIELD)
|
||
#undef DEFINE_FIELD
|
||
default:
|
||
ASSERT_NOT_REACHED();
|
||
}
|
||
return new ASCIIStringFromExternalMemory("iso8601");
|
||
}
|
||
|
||
std::string Calendar::toICUString() const
|
||
{
|
||
switch (m_id) {
|
||
#define DEFINE_FIELD(name, string, icuString, fullName) \
|
||
case ID::name: \
|
||
return icuString;
|
||
CALENDAR_ID_RECORDS(DEFINE_FIELD)
|
||
#undef DEFINE_FIELD
|
||
default:
|
||
ASSERT_NOT_REACHED();
|
||
}
|
||
return "iso8601";
|
||
}
|
||
|
||
UCalendar* Calendar::createICUCalendar(ExecutionState& state)
|
||
{
|
||
std::string calName = "en@calendar=" + toICUString();
|
||
UErrorCode status = U_ZERO_ERROR;
|
||
auto icuCalendar = ucal_open(u"GMT", 3, calName.data(), UCalendarType::UCAL_DEFAULT, &status);
|
||
if (U_FAILURE(status)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "failed to initialize ICU calendar");
|
||
}
|
||
ucal_setMillis(icuCalendar, 0, &status);
|
||
return icuCalendar;
|
||
}
|
||
|
||
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()));
|
||
}
|
||
}
|
||
|
||
int32_t Temporal::computeTimeZoneOffset(ExecutionState& state, String* name, int64_t epoch)
|
||
{
|
||
auto u16 = name->toUTF16StringData();
|
||
UErrorCode status = U_ZERO_ERROR;
|
||
const char* msg = "Failed to get timeZone offset from ICU";
|
||
auto ucalendar = ucal_open(u16.data(), u16.length(), "en", UCAL_GREGORIAN, &status);
|
||
if (!ucalendar) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
|
||
return 0;
|
||
}
|
||
|
||
ucal_setMillis(ucalendar, epoch, &status);
|
||
if (U_FAILURE(status)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
|
||
}
|
||
auto zoneOffset = ucal_get(ucalendar, UCAL_ZONE_OFFSET, &status);
|
||
if (U_FAILURE(status)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
|
||
}
|
||
auto dstOffset = ucal_get(ucalendar, UCAL_DST_OFFSET, &status);
|
||
if (U_FAILURE(status)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
|
||
}
|
||
ucal_close(ucalendar);
|
||
return zoneOffset + dstOffset;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
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());
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#sec-temporal-totemporaltime
|
||
TemporalPlainTimeObject* Temporal::toTemporalTime(ExecutionState& state, Value item, Value options)
|
||
{
|
||
ISO8601::PlainTime result;
|
||
// If options is not present, set options to undefined.
|
||
// If item is an Object, then
|
||
if (item.isObject()) {
|
||
// If item has an [[InitializedTemporalTime]] internal slot, then
|
||
if (item.asObject()->isTemporalPlainTimeObject()) {
|
||
// Let resolvedOptions be ? GetOptionsObject(options).
|
||
auto resolvedOptions = Intl::getOptionsObject(state, options);
|
||
// Perform ? GetTemporalOverflowOption(resolvedOptions).
|
||
Temporal::getTemporalOverflowOption(state, resolvedOptions);
|
||
// Return ! CreateTemporalTime(item.[[Time]]).
|
||
return new TemporalPlainTimeObject(state, state.context()->globalObject()->temporalPlainTimePrototype(),
|
||
item.asObject()->asTemporalPlainTimeObject()->plainTime());
|
||
}
|
||
// TODO If item has an [[InitializedTemporalZonedDateTime]] internal slot, then...
|
||
// TODO If item has an [[InitializedTemporalDateTime]] internal slot, then...
|
||
// Let result be ? ToTemporalTimeRecord(item).
|
||
auto resultRecord = TemporalPlainTimeObject::toTemporalTimeRecord(state, item, NullOption);
|
||
// Let resolvedOptions be ? GetOptionsObject(options).
|
||
auto resolvedOptions = Intl::getOptionsObject(state, options);
|
||
// Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
|
||
auto overflow = Temporal::getTemporalOverflowOption(state, resolvedOptions);
|
||
// Set result to ? RegulateTime(result.[[Hour]], result.[[Minute]], result.[[Second]], result.[[Millisecond]], result.[[Microsecond]], result.[[Nanosecond]], overflow).
|
||
result = TemporalPlainTimeObject::regulateTime(state, resultRecord.hour().value(), resultRecord.minute().value(), resultRecord.second().value(), resultRecord.millisecond().value(),
|
||
resultRecord.microsecond().value(), resultRecord.nanosecond().value(), overflow);
|
||
} else {
|
||
// Else,
|
||
// If item is not a String, throw a TypeError exception.
|
||
if (!item.isString()) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "ToTemporalTime needs Object or String");
|
||
}
|
||
// Let parseResult be ? ParseISODateTime(item, « TemporalTimeString »).
|
||
auto parseDateTimeResult = ISO8601::parseCalendarDateTime(item.asString());
|
||
auto parseTimeResult = ISO8601::parseTime(item.asString());
|
||
// If ParseText(StringToCodePoints(item), AmbiguousTemporalTimeString) is a Parse Node, throw a RangeError exception.
|
||
// Assert: parseResult.[[Time]] is not start-of-day.
|
||
if (!parseTimeResult && (!parseDateTimeResult || !std::get<1>(parseDateTimeResult.value()))) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "ToTemporalTime needs ISO Time string");
|
||
}
|
||
if ((parseDateTimeResult && std::get<2>(parseDateTimeResult.value()) && std::get<2>(parseDateTimeResult.value()).value().m_z) || (parseTimeResult && std::get<1>(parseTimeResult.value()) && std::get<1>(parseTimeResult.value()).value().m_z)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "ToTemporalTime needs ISO Time string without UTC designator");
|
||
}
|
||
// Set result to parseResult.[[Time]].
|
||
if (parseDateTimeResult && std::get<1>(parseDateTimeResult.value())) {
|
||
result = std::get<1>(parseDateTimeResult.value()).value();
|
||
} else {
|
||
result = std::get<0>(parseTimeResult.value());
|
||
}
|
||
// Let resolvedOptions be ? GetOptionsObject(options).
|
||
auto resolvedOptions = Intl::getOptionsObject(state, options);
|
||
// Perform ? GetTemporalOverflowOption(resolvedOptions).
|
||
Temporal::getTemporalOverflowOption(state, resolvedOptions);
|
||
}
|
||
return new TemporalPlainTimeObject(state, state.context()->globalObject()->temporalPlainTimePrototype(), result);
|
||
}
|
||
|
||
TemporalPlainDateObject* Temporal::toTemporalDate(ExecutionState& state, Value item, Value options)
|
||
{
|
||
// If options is not present, set options to undefined.
|
||
// If item is an Object, then
|
||
if (item.isObject()) {
|
||
// If item has an [[InitializedTemporalDate]] internal slot, then
|
||
if (item.asObject()->isTemporalPlainDateObject()) {
|
||
// Let resolvedOptions be ? GetOptionsObject(options).
|
||
auto resolvedOptions = Intl::getOptionsObject(state, options);
|
||
// Perform ? GetTemporalOverflowOption(resolvedOptions).
|
||
Temporal::getTemporalOverflowOption(state, resolvedOptions);
|
||
// Return ! CreateTemporalDate(item.[[ISODate]], item.[[Calendar]]).
|
||
return new TemporalPlainDateObject(state, state.context()->globalObject()->temporalPlainDatePrototype(),
|
||
item.asObject()->asTemporalPlainDateObject()->plainDate(), item.asObject()->asTemporalPlainDateObject()->calendarID());
|
||
}
|
||
// TODO If item has an [[InitializedTemporalZonedDateTime]] internal slot, then...
|
||
// TODO If item has an [[InitializedTemporalDateTime]] internal slot, then...
|
||
|
||
// Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(item).
|
||
auto calendar = Temporal::getTemporalCalendarIdentifierWithISODefault(state, item);
|
||
// Let fields be ? PrepareCalendarFields(calendar, item, « year, month, month-code, day », «», «»).
|
||
CalendarField f[4] = { CalendarField::Year, CalendarField::Month, CalendarField::MonthCode, CalendarField::Day };
|
||
auto fields = prepareCalendarFields(state, calendar, item.asObject(), f, 4, nullptr, 0, nullptr, 0);
|
||
// Let resolvedOptions be ? GetOptionsObject(options).
|
||
auto resolvedOptions = Intl::getOptionsObject(state, options);
|
||
// Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
|
||
auto overflow = Temporal::getTemporalOverflowOption(state, resolvedOptions);
|
||
// Let isoDate be ? CalendarDateFromFields(calendar, fields, overflow).
|
||
auto isoDate = calendarDateFromFields(state, calendar, fields, overflow);
|
||
// Return ! CreateTemporalDate(isoDate, calendar).
|
||
return new TemporalPlainDateObject(state, state.context()->globalObject()->temporalPlainDatePrototype(),
|
||
isoDate, calendar);
|
||
}
|
||
|
||
// If item is not a String, throw a TypeError exception.
|
||
if (!item.isString()) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "ToTemporalDate needs Object or String");
|
||
}
|
||
// Let result be ? ParseISODateTime(item, « TemporalDateTimeString[~Zoned] »).
|
||
ISO8601::DateTimeParseOption option;
|
||
option.allowTimeZoneTimeWithoutTime = true;
|
||
auto result = ISO8601::parseCalendarDateTime(item.asString(), option);
|
||
if (!result) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "ToTemporalDate needs ISO Date string");
|
||
}
|
||
if (std::get<2>(result.value()) && std::get<2>(result.value()).value().m_z) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "ToTemporalDate needs ISO Time string without UTC designator");
|
||
}
|
||
if (!std::get<1>(result.value()) && std::get<2>(result.value()) && std::get<2>(result.value()).value().m_offset.hasValue() && !std::get<3>(result.value())) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "ToTemporalDate needs ISO Date string");
|
||
}
|
||
if (!ISO8601::isDateTimeWithinLimits(std::get<0>(result.value()).year(), std::get<0>(result.value()).month(), std::get<0>(result.value()).day(), 12, 0, 0, 0, 0, 0)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "ToTemporalDate needs ISO Date string in valid range");
|
||
}
|
||
|
||
// Let calendar be result.[[Calendar]].
|
||
// If calendar is empty, set calendar to "iso8601".
|
||
String* calendar = state.context()->staticStrings().lazyISO8601().string();
|
||
if (std::get<3>(result.value())) {
|
||
calendar = String::fromUTF8(std::get<3>(result.value()).value().data(), std::get<3>(result.value()).value().length());
|
||
}
|
||
// Set calendar to ? CanonicalizeCalendar(calendar).
|
||
auto mayID = Calendar::fromString(calendar);
|
||
if (!mayID) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid CalendarID");
|
||
}
|
||
// Let resolvedOptions be ? GetOptionsObject(options).
|
||
auto resolvedOptions = Intl::getOptionsObject(state, options);
|
||
// Perform ? GetTemporalOverflowOption(resolvedOptions).
|
||
Temporal::getTemporalOverflowOption(state, resolvedOptions);
|
||
// Let isoDate be CreateISODateRecord(result.[[Year]], result.[[Month]], result.[[Day]]).
|
||
// Return ? CreateTemporalDate(isoDate, calendar).
|
||
return new TemporalPlainDateObject(state, state.context()->globalObject()->temporalPlainDatePrototype(),
|
||
std::get<0>(result.value()), mayID.value());
|
||
}
|
||
|
||
TemporalPlainYearMonthObject* Temporal::toTemporalYearMonth(ExecutionState& state, Value item, Value options)
|
||
{
|
||
// If options is not present, set options to undefined.
|
||
// If item is an Object, then
|
||
if (item.isObject()) {
|
||
// If item has an [[InitializedTemporalDate]] internal slot, then
|
||
if (item.asObject()->isTemporalPlainYearMonthObject()) {
|
||
// Let resolvedOptions be ? GetOptionsObject(options).
|
||
auto resolvedOptions = Intl::getOptionsObject(state, options);
|
||
// Perform ? GetTemporalOverflowOption(resolvedOptions).
|
||
Temporal::getTemporalOverflowOption(state, resolvedOptions);
|
||
// Return ! CreateTemporalYearMonth(item.[[ISODate]], item.[[Calendar]]).
|
||
return new TemporalPlainYearMonthObject(state, state.context()->globalObject()->temporalPlainYearMonthPrototype(),
|
||
item.asObject()->asTemporalPlainYearMonthObject()->plainDate(), item.asObject()->asTemporalPlainYearMonthObject()->calendarID());
|
||
}
|
||
|
||
// Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(item).
|
||
auto calendar = Temporal::getTemporalCalendarIdentifierWithISODefault(state, item);
|
||
// Let fields be ? PrepareCalendarFields(calendar, item, « year, month, month-code », «», «»).
|
||
CalendarField f[3] = { CalendarField::Year, CalendarField::Month, CalendarField::MonthCode };
|
||
auto fields = prepareCalendarFields(state, calendar, item.asObject(), f, 3, nullptr, 0, nullptr, 0);
|
||
fields.day = 1;
|
||
// Let resolvedOptions be ? GetOptionsObject(options).
|
||
auto resolvedOptions = Intl::getOptionsObject(state, options);
|
||
// Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
|
||
auto overflow = Temporal::getTemporalOverflowOption(state, resolvedOptions);
|
||
// Let isoDate be ? CalendarDateFromFields(calendar, fields, overflow).
|
||
auto isoDate = calendarDateFromFields(state, calendar, fields, overflow);
|
||
// Return ! CreateTemporalYearMonth(isoDate, calendar).
|
||
return new TemporalPlainYearMonthObject(state, state.context()->globalObject()->temporalPlainYearMonthPrototype(),
|
||
isoDate, calendar);
|
||
}
|
||
|
||
// If item is not a String, throw a TypeError exception.
|
||
if (!item.isString()) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "ToTemporalYearMonth needs Object or String");
|
||
}
|
||
// Let result be ? ParseISODateTime(item, « TemporalYearMonthString »).
|
||
ISO8601::DateTimeParseOption option;
|
||
option.allowTimeZoneTimeWithoutTime = true;
|
||
auto result = ISO8601::parseCalendarDateTime(item.asString(), option);
|
||
auto parseResultYearMonth = ISO8601::parseCalendarYearMonth(item.asString(), option);
|
||
if (!result && !parseResultYearMonth) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "ToTemporalYearMonth needs ISO Date string");
|
||
}
|
||
if (result && std::get<2>(result.value()) && std::get<2>(result.value()).value().m_z) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "ToTemporalYearMonth needs ISO Time string without UTC designator");
|
||
}
|
||
if (result && !std::get<1>(result.value()) && std::get<2>(result.value()) && std::get<2>(result.value()).value().m_offset.hasValue() && !std::get<3>(result.value())) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "ToTemporalYearMonth needs ISO Date string");
|
||
}
|
||
|
||
ISO8601::PlainDate plainDate;
|
||
ISO8601::CalendarID calendarID;
|
||
if (result) {
|
||
plainDate = std::get<0>(result.value());
|
||
plainDate = ISO8601::PlainDate(plainDate.year(), plainDate.month(), 1);
|
||
if (std::get<3>(result.value())) {
|
||
calendarID = std::get<3>(result.value()).value();
|
||
}
|
||
} else {
|
||
plainDate = std::get<0>(parseResultYearMonth.value());
|
||
if (std::get<1>(parseResultYearMonth.value())) {
|
||
calendarID = std::get<3>(result.value()).value();
|
||
}
|
||
}
|
||
|
||
if (!ISO8601::isDateTimeWithinLimits(plainDate.year(), plainDate.month(), plainDate.day(), 12, 0, 0, 0, 0, 0)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "ToTemporalYearMonth needs ISO Date string in valid range");
|
||
}
|
||
|
||
// Let calendar be result.[[Calendar]].
|
||
// If calendar is empty, set calendar to "iso8601".
|
||
String* calendar = state.context()->staticStrings().lazyISO8601().string();
|
||
if (calendarID.size()) {
|
||
calendar = String::fromUTF8(calendarID.data(), calendarID.length());
|
||
}
|
||
// Set calendar to ? CanonicalizeCalendar(calendar).
|
||
auto mayID = Calendar::fromString(calendar);
|
||
if (!mayID) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid CalendarID");
|
||
}
|
||
// Let resolvedOptions be ? GetOptionsObject(options).
|
||
auto resolvedOptions = Intl::getOptionsObject(state, options);
|
||
// Perform ? GetTemporalOverflowOption(resolvedOptions).
|
||
Temporal::getTemporalOverflowOption(state, resolvedOptions);
|
||
// Let isoDate be CreateISODateRecord(result.[[Year]], result.[[Month]], result.[[Day]]).
|
||
// Return ? CreateTemporalDate(isoDate, calendar).
|
||
return new TemporalPlainYearMonthObject(state, state.context()->globalObject()->temporalPlainYearMonthPrototype(),
|
||
plainDate, mayID.value());
|
||
}
|
||
|
||
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]].
|
||
ISO8601::DateTimeParseOption option;
|
||
option.parseSubMinutePrecisionForTimeZone = false;
|
||
Optional<int64_t> utcOffset = ISO8601::parseUTCOffset(temporalTimeZoneLikeString, option);
|
||
if (utcOffset) {
|
||
return TimeZone(utcOffset.value());
|
||
}
|
||
|
||
Optional<ISO8601::TimeZoneID> identifier = ISO8601::parseTimeZoneName(temporalTimeZoneLikeString);
|
||
if (identifier) {
|
||
return TimeZone(identifier.value());
|
||
}
|
||
|
||
auto complexTimeZone = ISO8601::parseCalendarDateTime(temporalTimeZoneLikeString, option);
|
||
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;
|
||
}
|
||
|
||
bool Temporal::isValidTime(int64_t hour, int64_t minute, int64_t second, int64_t millisecond, int64_t microsecond, int64_t nanosecond)
|
||
{
|
||
if (hour < 0 || hour > 23) {
|
||
return false;
|
||
}
|
||
if (minute < 0 || minute > 59) {
|
||
return false;
|
||
}
|
||
if (second < 0 || second > 59) {
|
||
return false;
|
||
}
|
||
if (millisecond < 0 || millisecond > 999) {
|
||
return false;
|
||
}
|
||
if (microsecond < 0 || microsecond > 999) {
|
||
return false;
|
||
}
|
||
if (nanosecond < 0 || nanosecond > 999) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
TemporalOverflowOption Temporal::getTemporalOverflowOption(ExecutionState& state, Optional<Object*> options)
|
||
{
|
||
// Let stringValue be ? GetOption(options, "overflow", string, « "constrain", "reject" », "constrain").
|
||
String* stringValue = state.context()->staticStrings().lazyConstrain().string();
|
||
if (options) {
|
||
Value values[2] = { state.context()->staticStrings().lazyConstrain().string(),
|
||
state.context()->staticStrings().lazyReject().string() };
|
||
stringValue = Intl::getOption(state, options.value(), state.context()->staticStrings().lazyOverflow().string(), Intl::StringValue,
|
||
values, 2, state.context()->staticStrings().lazyConstrain().string())
|
||
.asString();
|
||
}
|
||
// If stringValue is "constrain", return constrain.
|
||
if (stringValue->equals("constrain")) {
|
||
return TemporalOverflowOption::Constrain;
|
||
}
|
||
// Return reject.
|
||
return TemporalOverflowOption::Reject;
|
||
}
|
||
|
||
bool Temporal::isPartialTemporalObject(ExecutionState& state, Value value)
|
||
{
|
||
// If value is not an Object, return false.
|
||
if (!value.isObject()) {
|
||
return false;
|
||
}
|
||
// TODO If value has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalTime]],
|
||
// [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, return false.
|
||
if (value.asObject()->isTemporalPlainTimeObject()) {
|
||
return false;
|
||
}
|
||
// Let calendarProperty be ? Get(value, "calendar").
|
||
Value calendarProperty = value.asObject()->get(state, state.context()->staticStrings().lazyCalendar()).value(state, value);
|
||
// If calendarProperty is not undefined, return false.
|
||
if (!calendarProperty.isUndefined()) {
|
||
return false;
|
||
}
|
||
// Let timeZoneProperty be ? Get(value, "timeZone").
|
||
Value timeZoneProperty = value.asObject()->get(state, state.context()->staticStrings().lazyTimeZone()).value(state, value);
|
||
// If timeZoneProperty is not undefined, return false.
|
||
if (!timeZoneProperty.isUndefined()) {
|
||
return false;
|
||
}
|
||
// Return true.
|
||
return true;
|
||
}
|
||
|
||
static double nonNegativeModulo(double x, double y)
|
||
{
|
||
double result = std::fmod(x, y);
|
||
if (!result)
|
||
return 0;
|
||
if (result < 0)
|
||
result += y;
|
||
return result;
|
||
}
|
||
|
||
static double nonNegativeModulo(int64_t x, int64_t y)
|
||
{
|
||
int64_t result = x % y;
|
||
if (!result)
|
||
return 0;
|
||
if (result < 0)
|
||
result += y;
|
||
return result;
|
||
}
|
||
|
||
static int64_t int64Floor(int64_t x, int64_t y)
|
||
{
|
||
if (x > 0) {
|
||
return x / y;
|
||
}
|
||
if (x % y) {
|
||
return x / y - 1;
|
||
} else {
|
||
return x / y;
|
||
}
|
||
}
|
||
|
||
ISO8601::Duration Temporal::balanceTime(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond)
|
||
{
|
||
microsecond += std::floor(nanosecond / 1000);
|
||
nanosecond = nonNegativeModulo(nanosecond, 1000);
|
||
millisecond += std::floor(microsecond / 1000);
|
||
microsecond = nonNegativeModulo(microsecond, 1000);
|
||
second += std::floor(millisecond / 1000);
|
||
millisecond = nonNegativeModulo(millisecond, 1000);
|
||
minute += std::floor(second / 60);
|
||
second = nonNegativeModulo(second, 60);
|
||
hour += std::floor(minute / 60);
|
||
minute = nonNegativeModulo(minute, 60);
|
||
double days = std::floor(hour / 24);
|
||
hour = nonNegativeModulo(hour, 24);
|
||
return ISO8601::Duration({ 0.0, 0.0, 0.0, days, hour, minute, second, millisecond, microsecond, nanosecond });
|
||
}
|
||
|
||
ISO8601::Duration Temporal::balanceTime(int64_t hour, int64_t minute, int64_t second, int64_t millisecond, int64_t microsecond, int64_t nanosecond)
|
||
{
|
||
microsecond += int64Floor(nanosecond, 1000);
|
||
nanosecond = nonNegativeModulo(nanosecond, 1000);
|
||
millisecond += int64Floor(microsecond, 1000);
|
||
microsecond = nonNegativeModulo(microsecond, 1000);
|
||
second += int64Floor(millisecond, 1000);
|
||
millisecond = nonNegativeModulo(millisecond, 1000);
|
||
minute += int64Floor(second, 60);
|
||
second = nonNegativeModulo(second, 60);
|
||
hour += int64Floor(minute, 60);
|
||
minute = nonNegativeModulo(minute, 60);
|
||
int64_t days = int64Floor(hour, 24);
|
||
hour = nonNegativeModulo(hour, 24);
|
||
return ISO8601::Duration({ int64_t(0), int64_t(0), int64_t(0), days, hour, minute, second, millisecond, microsecond, nanosecond });
|
||
}
|
||
|
||
Int128 Temporal::differenceTime(ISO8601::PlainTime time1, ISO8601::PlainTime time2)
|
||
{
|
||
// Let hours be time2.[[Hour]] - time1.[[Hour]].
|
||
// Let minutes be time2.[[Minute]] - time1.[[Minute]].
|
||
// Let seconds be time2.[[Second]] - time1.[[Second]].
|
||
// Let milliseconds be time2.[[Millisecond]] - time1.[[Millisecond]].
|
||
// Let microseconds be time2.[[Microsecond]] - time1.[[Microsecond]].
|
||
// Let nanoseconds be time2.[[Nanosecond]] - time1.[[Nanosecond]].
|
||
#define DEFINE_DIFF(name, capitalizedName) \
|
||
int32_t name##s = time2.name() - time1.name();
|
||
PLAIN_TIME_UNITS(DEFINE_DIFF)
|
||
#undef DEFINE_DIFF
|
||
// Let timeDuration be TimeDurationFromComponents(hours, minutes, seconds, milliseconds, microseconds, nanoseconds).
|
||
// Assert: abs(timeDuration) < nsPerDay.
|
||
// Return timeDuration.
|
||
return timeDurationFromComponents(hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
|
||
}
|
||
|
||
Int128 Temporal::timeDurationFromComponents(double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds)
|
||
{
|
||
// Set minutes to minutes + hours × 60.
|
||
// Set seconds to seconds + minutes × 60.
|
||
// Set milliseconds to milliseconds + seconds × 1000.
|
||
// Set microseconds to microseconds + milliseconds × 1000.
|
||
// Set nanoseconds to nanoseconds + microseconds × 1000.
|
||
// Assert: abs(nanoseconds) ≤ maxTimeDuration.
|
||
// Return nanoseconds.
|
||
return ISO8601::Duration({ 0.0, 0.0, 0.0, 0.0, hours, minutes, seconds, milliseconds, microseconds, nanoseconds }).totalNanoseconds(ISO8601::DateTimeUnit::Hour);
|
||
}
|
||
|
||
Calendar Temporal::getTemporalCalendarIdentifierWithISODefault(ExecutionState& state, Value item)
|
||
{
|
||
// TODO [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]]
|
||
// If item has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then
|
||
// Return item.[[Calendar]].
|
||
if (item.isObject()) {
|
||
if (item.asObject()->isTemporalPlainDateObject()) {
|
||
return item.asObject()->asTemporalPlainDateObject()->calendarID();
|
||
}
|
||
}
|
||
|
||
// Let calendarLike be ? Get(item, "calendar").
|
||
Value calendarLike = Object::getV(state, item, state.context()->staticStrings().lazyCalendar());
|
||
// If calendarLike is undefined, then
|
||
if (calendarLike.isUndefined()) {
|
||
// Return "iso8601".
|
||
return Calendar();
|
||
}
|
||
// Return ? ToTemporalCalendarIdentifier(calendarLike).
|
||
return toTemporalCalendarIdentifier(state, calendarLike);
|
||
}
|
||
|
||
Calendar Temporal::toTemporalCalendarIdentifier(ExecutionState& state, Value temporalCalendarLike)
|
||
{
|
||
// TODO [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]]
|
||
// If temporalCalendarLike is an Object, then
|
||
// If temporalCalendarLike has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then
|
||
// Return temporalCalendarLike.[[Calendar]].
|
||
if (temporalCalendarLike.isObject()) {
|
||
if (temporalCalendarLike.asObject()->isTemporalPlainDateObject()) {
|
||
return temporalCalendarLike.asObject()->asTemporalPlainDateObject()->calendarID();
|
||
}
|
||
}
|
||
|
||
// If temporalCalendarLike is not a String, throw a TypeError exception.
|
||
if (!temporalCalendarLike.isString()) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "ToTemporalCalendarIdentifier needs Temporal date-like Object or String");
|
||
}
|
||
// Let identifier be ? ParseTemporalCalendarString(temporalCalendarLike).
|
||
// Return ? CanonicalizeCalendar(identifier).
|
||
auto mayCalendar = Calendar::fromString(temporalCalendarLike.asString());
|
||
auto parseResult = ISO8601::parseCalendarDateTime(temporalCalendarLike.asString());
|
||
auto parseResultYearMonth = ISO8601::parseCalendarYearMonth(temporalCalendarLike.asString());
|
||
auto parseResultMonthDay = ISO8601::parseCalendarMonthDay(temporalCalendarLike.asString());
|
||
if (!mayCalendar && !parseResult && !parseResultYearMonth && !parseResultMonthDay) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid calendar");
|
||
}
|
||
|
||
if (mayCalendar) {
|
||
return mayCalendar.value();
|
||
}
|
||
auto tryOnce = [](ExecutionState& state, Optional<ISO8601::CalendarID> cid) -> Optional<Calendar> {
|
||
if (cid) {
|
||
auto mayCalendar = Calendar::fromString(cid.value());
|
||
if (!mayCalendar) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid calendar");
|
||
}
|
||
return mayCalendar.value();
|
||
}
|
||
return NullOption;
|
||
};
|
||
if (parseResult) {
|
||
auto cid = tryOnce(state, std::get<3>(parseResult.value()));
|
||
if (cid) {
|
||
return cid.value();
|
||
}
|
||
}
|
||
if (parseResultYearMonth) {
|
||
auto cid = tryOnce(state, std::get<1>(parseResultYearMonth.value()));
|
||
if (cid) {
|
||
return cid.value();
|
||
}
|
||
}
|
||
if (parseResultMonthDay) {
|
||
auto cid = tryOnce(state, std::get<1>(parseResultMonthDay.value()));
|
||
if (cid) {
|
||
return cid.value();
|
||
}
|
||
}
|
||
return Calendar();
|
||
}
|
||
|
||
static AtomicString calendarFieldsRecordToString(ExecutionState& state, CalendarField f)
|
||
{
|
||
if (false) {}
|
||
#define DEFINE_FIELD(name, Name, type) \
|
||
else if (f == CalendarField::Name) \
|
||
{ \
|
||
return state.context()->staticStrings().lazy##Name(); \
|
||
}
|
||
CALENDAR_FIELD_RECORDS(DEFINE_FIELD)
|
||
#undef DEFINE_FIELD
|
||
|
||
ASSERT_NOT_REACHED();
|
||
return AtomicString();
|
||
}
|
||
|
||
static CalendarField stringToCalendarFieldsRecord(ExecutionState& state, AtomicString f)
|
||
{
|
||
if (false) {}
|
||
#define DEFINE_FIELD(name, Name, type) \
|
||
else if (f == state.context()->staticStrings().lazy##Name()) \
|
||
{ \
|
||
return CalendarField::Name; \
|
||
}
|
||
CALENDAR_FIELD_RECORDS(DEFINE_FIELD)
|
||
#undef DEFINE_FIELD
|
||
|
||
ASSERT_NOT_REACHED();
|
||
return CalendarField::Year;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#sec-temporal-parsemonthcode
|
||
static MonthCode parseMonthCode(ExecutionState& state, Value input)
|
||
{
|
||
constexpr auto msg = "Failed to parse month code";
|
||
// Let monthCode be ? ToPrimitive(argument, string).
|
||
Value monthCode = input.toPrimitive(state, Value::PrimitiveTypeHint::PreferString);
|
||
// If monthCode is not a String, throw a TypeError exception.
|
||
if (!monthCode.isString()) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, msg);
|
||
}
|
||
// If ParseText(StringToCodePoints(monthCode), MonthCode) is a List of errors, throw a RangeError exception.
|
||
// Let isLeapMonth be false.
|
||
// If the length of monthCode is 4, then
|
||
// Assert: The fourth code unit of monthCode is 0x004C (LATIN CAPITAL LETTER L).
|
||
// Set isLeapMonth to true.
|
||
// Let monthCodeDigits be the substring of monthCode from 1 to 3.
|
||
// Let monthNumber be ℝ(StringToNumber(monthCodeDigits)).
|
||
ParserString buffer(monthCode.asString());
|
||
|
||
if (buffer.atEnd() || *buffer != 'M') {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
|
||
}
|
||
buffer.advance();
|
||
|
||
bool isLeapMonth = false;
|
||
unsigned code = 0;
|
||
if (!buffer.atEnd() && *buffer == '0') {
|
||
buffer.advance();
|
||
if (!buffer.atEnd() && isASCIIDigit(*buffer) && *buffer != '0') {
|
||
code = *buffer - '0';
|
||
buffer.advance();
|
||
} else {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
|
||
}
|
||
} else if (!buffer.atEnd() && isASCIIDigit(*buffer)) {
|
||
code = (*buffer - '0') * 10;
|
||
buffer.advance();
|
||
if (!buffer.atEnd() && isASCIIDigit(*buffer)) {
|
||
code += *buffer - '0';
|
||
buffer.advance();
|
||
} else {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
|
||
}
|
||
}
|
||
|
||
// process 'L'
|
||
if (!buffer.atEnd() && *buffer == 'L') {
|
||
buffer.advance();
|
||
isLeapMonth = true;
|
||
}
|
||
|
||
if (!buffer.atEnd()) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
|
||
}
|
||
|
||
// If monthNumber is 0 and isLeapMonth is false, throw a RangeError exception.
|
||
if (code == 0 && !isLeapMonth) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, msg);
|
||
}
|
||
|
||
// Return the Record { [[MonthNumber]]: monthNumber, [[IsLeapMonth]]: isLeapMonth }.
|
||
MonthCode record;
|
||
record.monthNumber = code;
|
||
record.isLeapMonth = isLeapMonth;
|
||
return record;
|
||
}
|
||
|
||
void CalendarFieldsRecord::setValue(ExecutionState& state, CalendarField f, Value value)
|
||
{
|
||
if (f == CalendarField::Era) {
|
||
era = value.toString(state);
|
||
} else if (f == CalendarField::EraYear) {
|
||
eraYear = value.toIntegerWithTruncation(state);
|
||
} else if (f == CalendarField::Year) {
|
||
year = value.toIntegerWithTruncation(state);
|
||
} else if (f == CalendarField::Month) {
|
||
month = value.toPostiveIntegerWithTruncation(state);
|
||
} else if (f == CalendarField::MonthCode) {
|
||
monthCode = parseMonthCode(state, value);
|
||
} else if (f == CalendarField::Day) {
|
||
day = value.toPostiveIntegerWithTruncation(state);
|
||
} else if (f == CalendarField::Hour) {
|
||
hour = value.toIntegerWithTruncation(state);
|
||
} else if (f == CalendarField::Minute) {
|
||
minute = value.toIntegerWithTruncation(state);
|
||
} else if (f == CalendarField::Second) {
|
||
second = value.toIntegerWithTruncation(state);
|
||
} else if (f == CalendarField::Millisecond) {
|
||
millisecond = value.toIntegerWithTruncation(state);
|
||
} else if (f == CalendarField::Microsecond) {
|
||
microsecond = value.toIntegerWithTruncation(state);
|
||
} else if (f == CalendarField::Nanosecond) {
|
||
nanosecond = value.toIntegerWithTruncation(state);
|
||
} else {
|
||
// TODO
|
||
ASSERT_NOT_REACHED();
|
||
}
|
||
}
|
||
|
||
CalendarFieldsRecord Temporal::prepareCalendarFields(ExecutionState& state, Calendar calendar, Object* fields, CalendarField* calendarFieldNames, size_t calendarFieldNamesLength,
|
||
CalendarField* nonCalendarFieldNames, size_t nonCalendarFieldNamesLength, CalendarField* requiredFieldNames, size_t requiredFieldNamesLength)
|
||
{
|
||
// Let fieldNames be the list-concatenation of calendarFieldNames and nonCalendarFieldNames.
|
||
std::vector<CalendarField> fieldName;
|
||
for (size_t i = 0; i < calendarFieldNamesLength; i++) {
|
||
fieldName.push_back(calendarFieldNames[i]);
|
||
}
|
||
for (size_t i = 0; i < nonCalendarFieldNamesLength; i++) {
|
||
fieldName.push_back(nonCalendarFieldNames[i]);
|
||
}
|
||
// Let extraFieldNames be CalendarExtraFields(calendar, calendarFieldNames).
|
||
// Set fieldNames to the list-concatenation of fieldNames and extraFieldNames.
|
||
if (calendar.isEraRelated()) {
|
||
fieldName.push_back(CalendarField::Era);
|
||
fieldName.push_back(CalendarField::EraYear);
|
||
}
|
||
|
||
// Assert: fieldNames contains no duplicate elements.
|
||
// Let any be false.
|
||
bool any = false;
|
||
// Let result be a Calendar Fields Record with all fields equal to unset.
|
||
CalendarFieldsRecord result;
|
||
std::vector<AtomicString> sortedPropertyNames;
|
||
for (auto n : fieldName) {
|
||
sortedPropertyNames.push_back(calendarFieldsRecordToString(state, n));
|
||
}
|
||
|
||
std::sort(sortedPropertyNames.begin(), sortedPropertyNames.end(), [](AtomicString lhs, AtomicString rhs) {
|
||
return *lhs.string() < *rhs.string();
|
||
});
|
||
|
||
// For each property name property of sortedPropertyNames, do
|
||
for (auto property : sortedPropertyNames) {
|
||
// Let key be the value in the Enumeration Key column of Table 19 corresponding to the row whose Property Key value is property.
|
||
// Let value be ? Get(fields, property).
|
||
Value value = fields->get(state, ObjectPropertyName(property)).value(state, fields);
|
||
// If value is not undefined, then
|
||
if (!value.isUndefined()) {
|
||
// Step i...ix
|
||
result.setValue(state, stringToCalendarFieldsRecord(state, property), value);
|
||
any = true;
|
||
} else if (requiredFieldNamesLength && requiredFieldNamesLength != SIZE_MAX) {
|
||
// Else if requiredFieldNames is a List, then
|
||
// If requiredFieldNames contains key, then
|
||
for (size_t i = 0; i < requiredFieldNamesLength; i++) {
|
||
// Throw a TypeError exception.
|
||
if (requiredFieldNames[i] == stringToCalendarFieldsRecord(state, property)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Missing required field");
|
||
}
|
||
}
|
||
// TODO Set result's field whose name is given in the Field Name column of the same row to the corresponding Default value of the same row.
|
||
}
|
||
}
|
||
|
||
// If requiredFieldNames is partial and any is false, then
|
||
if (requiredFieldNamesLength == SIZE_MAX && !any) {
|
||
// Throw a TypeError exception.
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Missing required field");
|
||
}
|
||
|
||
// Return result.
|
||
return result;
|
||
}
|
||
|
||
UCalendar* Temporal::calendarDateFromFields(ExecutionState& state, Calendar calendar, CalendarFieldsRecord fields, TemporalOverflowOption overflow)
|
||
{
|
||
// CalendarResolveFields steps
|
||
if (calendar.isISO8601() || !calendar.isEraRelated()) {
|
||
if (!fields.year || !fields.day) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Missing required field");
|
||
}
|
||
} else {
|
||
if (!fields.day) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Missing required field");
|
||
}
|
||
if (!(fields.era && fields.eraYear) && !fields.year) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Missing required field");
|
||
}
|
||
if ((fields.era && !fields.eraYear) || (!fields.era && fields.eraYear)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Missing required field");
|
||
}
|
||
}
|
||
|
||
if (!fields.monthCode && !fields.month) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Missing required field");
|
||
}
|
||
if (fields.monthCode && (fields.monthCode.value().isLeapMonth || fields.monthCode.value().monthNumber > 12)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Wrong month code");
|
||
}
|
||
if (fields.month && fields.monthCode && fields.month.value() != fields.monthCode.value().monthNumber) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Wrong month code or month");
|
||
}
|
||
if (fields.monthCode) {
|
||
fields.month = fields.monthCode.value().monthNumber;
|
||
}
|
||
|
||
auto icuCalendar = calendar.createICUCalendar(state);
|
||
|
||
if (calendar.isISO8601()) {
|
||
// CalendarDateToISO steps
|
||
if (overflow == TemporalOverflowOption::Constrain) {
|
||
fields.month = std::min<unsigned>(fields.month.value(), 12);
|
||
fields.day = std::min<unsigned>(fields.day.value(), ISO8601::daysInMonth(fields.year.value(), fields.month.value()));
|
||
}
|
||
auto plainDate = ISO8601::toPlainDate(ISO8601::Duration({ static_cast<double>(fields.year.value()), static_cast<double>(fields.month.value()), 0.0, static_cast<double>(fields.day.value()), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }));
|
||
if (!plainDate || !ISO8601::isDateTimeWithinLimits(plainDate.value().year(), plainDate.value().month(), plainDate.value().day(), 12, 0, 0, 0, 0, 0)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Out of range date");
|
||
}
|
||
|
||
ucal_set(icuCalendar, UCAL_YEAR, fields.year.value());
|
||
ucal_set(icuCalendar, UCAL_MONTH, fields.month.value() - 1);
|
||
ucal_set(icuCalendar, UCAL_DAY_OF_MONTH, fields.day.value());
|
||
|
||
} else if (calendar.isEraRelated() && (fields.era && fields.eraYear)) {
|
||
Optional<int32_t> eraIdx;
|
||
|
||
auto fieldEraValue = fields.era.value()->toNonGCUTF8StringData();
|
||
calendar.lookupICUEra(state, [&](size_t idx, const std::string& s) -> bool {
|
||
if (s == fieldEraValue) {
|
||
eraIdx = idx;
|
||
return true;
|
||
}
|
||
return false;
|
||
});
|
||
|
||
if (!eraIdx) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid era value");
|
||
}
|
||
|
||
ucal_set(icuCalendar, UCAL_ERA, eraIdx.value());
|
||
ucal_set(icuCalendar, UCAL_YEAR, fields.eraYear.value());
|
||
ucal_set(icuCalendar, UCAL_MONTH, fields.month.value() - 1);
|
||
ucal_set(icuCalendar, UCAL_DAY_OF_MONTH, fields.day.value());
|
||
|
||
if (fields.year) {
|
||
UErrorCode status = U_ZERO_ERROR;
|
||
auto epochTime = ucal_getMillis(icuCalendar, &status);
|
||
DateObject::DateTimeInfo timeInfo;
|
||
DateObject::computeTimeInfoFromEpoch(epochTime, timeInfo);
|
||
if (timeInfo.year != fields.year.value()) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "'year' and computed 'year' calendar fields are inconsistent");
|
||
}
|
||
}
|
||
} else {
|
||
if (calendar.shouldUseICUExtendedYear()) {
|
||
ucal_set(icuCalendar, UCAL_EXTENDED_YEAR, fields.year.value());
|
||
} else {
|
||
ucal_set(icuCalendar, UCAL_YEAR, fields.year.value());
|
||
}
|
||
ucal_set(icuCalendar, UCAL_MONTH, fields.month.value() - 1);
|
||
ucal_set(icuCalendar, UCAL_DAY_OF_MONTH, fields.day.value());
|
||
}
|
||
|
||
return icuCalendar;
|
||
}
|
||
|
||
CalendarFieldsRecord Temporal::calendarMergeFields(ExecutionState& state, Calendar calendar, const CalendarFieldsRecord& fields, const CalendarFieldsRecord& additionalFields)
|
||
{
|
||
CalendarFieldsRecord merged;
|
||
|
||
CalendarFieldsRecord copiedAdditionalFields = additionalFields;
|
||
|
||
if (copiedAdditionalFields.month && !copiedAdditionalFields.monthCode) {
|
||
MonthCode mc;
|
||
mc.monthNumber = copiedAdditionalFields.month.value();
|
||
copiedAdditionalFields.monthCode = mc;
|
||
} else if (!copiedAdditionalFields.month && copiedAdditionalFields.monthCode) {
|
||
copiedAdditionalFields.month = copiedAdditionalFields.monthCode.value().monthNumber;
|
||
}
|
||
|
||
#define COPY_FIELD(name, Name, type) \
|
||
if (fields.name) { \
|
||
merged.name = fields.name; \
|
||
} \
|
||
if (copiedAdditionalFields.name) { \
|
||
merged.name = copiedAdditionalFields.name; \
|
||
}
|
||
CALENDAR_FIELD_RECORDS(COPY_FIELD)
|
||
#undef COPY_FIELD
|
||
|
||
if (copiedAdditionalFields.era || copiedAdditionalFields.eraYear) {
|
||
merged.year = NullOption;
|
||
}
|
||
|
||
return merged;
|
||
}
|
||
|
||
TemporalShowCalendarNameOption Temporal::getTemporalShowCalendarNameOption(ExecutionState& state, Optional<Object*> options)
|
||
{
|
||
if (!options) {
|
||
return TemporalShowCalendarNameOption::Auto;
|
||
}
|
||
Value values[4] = { state.context()->staticStrings().lazyAuto().string(), state.context()->staticStrings().lazyAlways().string(),
|
||
state.context()->staticStrings().lazyNever().string(), state.context()->staticStrings().lazyCritical().string() };
|
||
// Let stringValue be ? GetOption(options, "calendarName", string, « "auto", "always", "never", "critical" », "auto").
|
||
auto stringValue = Intl::getOption(state, options.value(), state.context()->staticStrings().lazyCalendarName().string(), Intl::StringValue,
|
||
values, 4, state.context()->staticStrings().lazyAuto().string())
|
||
.asString();
|
||
// If stringValue is "always", return always.
|
||
if (stringValue->equals("always")) {
|
||
return TemporalShowCalendarNameOption::Always;
|
||
}
|
||
// If stringValue is "never", return never.
|
||
if (stringValue->equals("never")) {
|
||
return TemporalShowCalendarNameOption::Never;
|
||
}
|
||
// If stringValue is "critical", return critical.
|
||
if (stringValue->equals("critical")) {
|
||
return TemporalShowCalendarNameOption::Critical;
|
||
}
|
||
// Return auto.
|
||
return TemporalShowCalendarNameOption::Auto;
|
||
}
|
||
|
||
inline int daysInYear(int year)
|
||
{
|
||
return 365 + ISO8601::isLeapYear(year);
|
||
}
|
||
|
||
static bool balanceISODate(double& year, double& month, double& day)
|
||
{
|
||
ASSERT(month >= 1 && month <= 12);
|
||
if (!ISO8601::isYearWithinLimits(year))
|
||
return false;
|
||
|
||
double daysFrom1970 = day + ISO8601::dateToDaysFrom1970(static_cast<int>(year), static_cast<int>(month - 1), 1) - 1;
|
||
|
||
double balancedYear = std::floor(daysFrom1970 / 365.2425) + 1970;
|
||
if (!ISO8601::isYearWithinLimits(balancedYear))
|
||
return false;
|
||
|
||
double daysUntilYear = ISO8601::daysFrom1970ToYear(static_cast<int>(balancedYear));
|
||
if (daysUntilYear > daysFrom1970) {
|
||
balancedYear--;
|
||
daysUntilYear -= daysInYear(static_cast<int>(balancedYear));
|
||
} else {
|
||
double daysUntilFollowingYear = daysUntilYear + daysInYear(static_cast<int>(balancedYear));
|
||
if (daysUntilFollowingYear <= daysFrom1970) {
|
||
daysUntilYear = daysUntilFollowingYear;
|
||
balancedYear++;
|
||
}
|
||
}
|
||
|
||
ASSERT(daysFrom1970 - daysUntilYear >= 0);
|
||
auto dayInYear = static_cast<unsigned>(daysFrom1970 - daysUntilYear + 1);
|
||
|
||
unsigned daysUntilMonth = 0;
|
||
unsigned balancedMonth = 1;
|
||
for (; balancedMonth < 12; balancedMonth++) {
|
||
auto monthDays = balancedMonth != 2 ? ISO8601::daysInMonth(balancedMonth) : ISO8601::daysInMonth(static_cast<int>(balancedYear), balancedMonth);
|
||
if (daysUntilMonth + monthDays >= dayInYear)
|
||
break;
|
||
daysUntilMonth += monthDays;
|
||
}
|
||
|
||
year = balancedYear;
|
||
month = balancedMonth;
|
||
day = dayInYear - daysUntilMonth;
|
||
return true;
|
||
}
|
||
|
||
static ISO8601::PlainDate balanceISODate(ExecutionState& state, double year, double month, double day)
|
||
{
|
||
if (!balanceISODate(year, month, day)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Out of range date-time");
|
||
}
|
||
return ISO8601::PlainDate(year, month, day);
|
||
}
|
||
|
||
#define CHECK_ICU_CALENDAR() \
|
||
if (U_FAILURE(status)) { \
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "failed to get value from ICU calendar"); \
|
||
}
|
||
|
||
UCalendar* Temporal::calendarDateAdd(ExecutionState& state, Calendar calendar, ISO8601::PlainDate isoDate, UCalendar* icuDate, const ISO8601::Duration& duration, TemporalOverflowOption overflow)
|
||
{
|
||
UErrorCode status = U_ZERO_ERROR;
|
||
auto newCal = ucal_clone(icuDate, &status);
|
||
CHECK_ICU_CALENDAR()
|
||
if (calendar.isISO8601()) {
|
||
int32_t oldY = isoDate.year();
|
||
int32_t oldM = isoDate.month();
|
||
int32_t oldD = isoDate.day();
|
||
|
||
double year = oldY + duration.years();
|
||
double month = oldM + duration.months();
|
||
if (month < 1 || month > 12) {
|
||
year += std::floor((month - 1) / 12);
|
||
month = nonNegativeModulo((month - 1), 12) + 1;
|
||
}
|
||
|
||
double daysInMonth = ISO8601::daysInMonth(year, month);
|
||
double day = oldD;
|
||
if (overflow == TemporalOverflowOption::Constrain)
|
||
day = std::min<double>(day, daysInMonth);
|
||
else if (day > daysInMonth) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Out of range date-time");
|
||
}
|
||
|
||
day += duration.days() + 7 * duration.weeks();
|
||
if (day < 1 || day > daysInMonth) {
|
||
if (!balanceISODate(year, month, day)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Out of range date-time");
|
||
}
|
||
}
|
||
|
||
ucal_set(newCal, UCAL_YEAR, year);
|
||
ucal_set(newCal, UCAL_MONTH, month - 1);
|
||
ucal_set(newCal, UCAL_DAY_OF_MONTH, day);
|
||
|
||
if (!ISO8601::isDateTimeWithinLimits(year, month, day, 12, 0, 0, 0, 0, 0)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Out of range date-time");
|
||
}
|
||
|
||
return newCal;
|
||
} else {
|
||
int32_t o, check;
|
||
|
||
o = ucal_get(newCal, UCAL_YEAR, &status);
|
||
CHECK_ICU_CALENDAR()
|
||
ucal_set(newCal, UCAL_YEAR, o + duration.years());
|
||
check = ucal_get(newCal, UCAL_YEAR, &status);
|
||
CHECK_ICU_CALENDAR()
|
||
if (check != (o + duration.years())) {
|
||
if (overflow == TemporalOverflowOption::Reject) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Out of range date-time");
|
||
}
|
||
}
|
||
|
||
o = ucal_get(newCal, UCAL_MONTH, &status);
|
||
CHECK_ICU_CALENDAR()
|
||
ucal_set(newCal, UCAL_MONTH, o + duration.months() - 1);
|
||
check = ucal_get(newCal, UCAL_MONTH, &status);
|
||
CHECK_ICU_CALENDAR()
|
||
if (check != (o + duration.months() - 1)) {
|
||
if (overflow == TemporalOverflowOption::Reject) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Out of range date-time");
|
||
}
|
||
}
|
||
|
||
o = ucal_get(newCal, UCAL_DAY_OF_MONTH, &status);
|
||
CHECK_ICU_CALENDAR()
|
||
ucal_set(newCal, UCAL_DAY_OF_MONTH, o + duration.days());
|
||
check = ucal_get(newCal, UCAL_DAY_OF_MONTH, &status);
|
||
CHECK_ICU_CALENDAR()
|
||
if (check != (o + duration.days())) {
|
||
if (overflow == TemporalOverflowOption::Reject) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Out of range date-time");
|
||
}
|
||
}
|
||
|
||
if (duration.weeks()) {
|
||
auto epochTime = ucal_getMillis(newCal, &status);
|
||
int32_t w = ucal_get(newCal, UCAL_WEEK_OF_YEAR, &status);
|
||
CHECK_ICU_CALENDAR()
|
||
w += duration.weeks();
|
||
ucal_set(newCal, UCAL_WEEK_OF_YEAR, w);
|
||
}
|
||
|
||
auto epoch = ucal_getMillis(newCal, &status);
|
||
CHECK_ICU_CALENDAR()
|
||
if (!ISO8601::isoDateTimeWithinLimits(Int128(epoch))) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Out of range date-time");
|
||
}
|
||
}
|
||
return newCal;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#sec-temporal-isodatesurpasses
|
||
static bool isoDateSurpasses(int32_t sign, double y1, double m1, double d1, const ISO8601::PlainDate& isoDate2)
|
||
{
|
||
if (y1 != isoDate2.year()) {
|
||
if (sign * (y1 - isoDate2.year()) > 0)
|
||
return true;
|
||
} else if (m1 != isoDate2.month()) {
|
||
if (sign * (m1 - isoDate2.month()) > 0)
|
||
return true;
|
||
} else if (d1 != isoDate2.day()) {
|
||
if (sign * (d1 - isoDate2.day()) > 0)
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#sec-temporal-balanceisoyearmonth
|
||
ISO8601::PlainYearMonth Temporal::balanceISOYearMonth(double year, double month)
|
||
{
|
||
year += std::floor((month - 1) / 12);
|
||
// ECMA modulo operator always results in same sign as y in x mod y
|
||
month = std::fmod(month - 1, 12) + 1;
|
||
if (month < 1)
|
||
month += 12;
|
||
return ISO8601::PlainYearMonth(year, month);
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#sec-temporal-regulateisodate
|
||
Optional<ISO8601::PlainDate> Temporal::regulateISODate(double year, double month, double day, TemporalOverflowOption overflow)
|
||
{
|
||
if (overflow == TemporalOverflowOption::Constrain) {
|
||
if (month < 1)
|
||
month = 1;
|
||
if (month > 12)
|
||
month = 12;
|
||
auto daysInMonth = ISO8601::daysInMonth(year, month);
|
||
if (day < 1)
|
||
day = 1;
|
||
if (day > daysInMonth)
|
||
day = daysInMonth;
|
||
} else if (!ISO8601::isDateTimeWithinLimits(year, month, day, 12, 0, 0, 0, 0, 0))
|
||
return NullOption;
|
||
return ISO8601::PlainDate(year, month, day);
|
||
}
|
||
|
||
static inline double makeDay(double year, double month, double date)
|
||
{
|
||
double additionalYears = std::floor(month / 12);
|
||
double ym = year + additionalYears;
|
||
if (!std::isfinite(ym))
|
||
return std::numeric_limits<double>::quiet_NaN();
|
||
double mm = month - additionalYears * 12;
|
||
int32_t yearInt32 = ym;
|
||
int32_t monthInt32 = mm;
|
||
if (yearInt32 != ym || monthInt32 != mm)
|
||
return std::numeric_limits<double>::quiet_NaN();
|
||
double days = ISO8601::dateToDaysFrom1970(yearInt32, monthInt32, 1);
|
||
return days + date - 1;
|
||
}
|
||
|
||
ISO8601::Duration Temporal::calendarDateUntil(Calendar calendar, ISO8601::PlainDate one, ISO8601::PlainDate two, TemporalUnit largestUnit)
|
||
{
|
||
// TODO non-iso8601 calendar
|
||
auto sign = -1 * one.compare(two);
|
||
if (!sign)
|
||
return {};
|
||
|
||
double years = 0;
|
||
double months = 0;
|
||
|
||
if (largestUnit == TemporalUnit::Year || largestUnit == TemporalUnit::Month) {
|
||
auto candidateYears = two.year() - one.year();
|
||
if (candidateYears)
|
||
candidateYears -= sign;
|
||
while (!isoDateSurpasses(sign, one.year() + candidateYears, one.month(), one.day(), two)) {
|
||
years = candidateYears;
|
||
candidateYears += sign;
|
||
}
|
||
|
||
auto candidateMonths = sign;
|
||
auto intermediate = balanceISOYearMonth(one.year() + years, one.month() + candidateMonths);
|
||
while (!isoDateSurpasses(sign, intermediate.year(), intermediate.month(), one.day(), two)) {
|
||
months = candidateMonths;
|
||
candidateMonths += sign;
|
||
intermediate = balanceISOYearMonth(intermediate.year(), intermediate.month() + sign);
|
||
}
|
||
|
||
if (largestUnit == TemporalUnit::Month) {
|
||
months += years * 12;
|
||
years = 0;
|
||
}
|
||
}
|
||
|
||
auto intermediate = balanceISOYearMonth(one.year() + years, one.month() + months);
|
||
auto constrained = regulateISODate(intermediate.year(), intermediate.month(), one.day(), TemporalOverflowOption::Constrain);
|
||
|
||
double weeks = 0;
|
||
double days = makeDay(two.year(), two.month() - 1, two.day()) - makeDay(constrained->year(), constrained->month() - 1, constrained->day());
|
||
|
||
if (largestUnit == TemporalUnit::Week) {
|
||
weeks = std::trunc(std::abs(days) / 7.0);
|
||
days = std::trunc((double)(((Int128)std::trunc(days)) % 7));
|
||
if (weeks)
|
||
weeks *= sign; // Avoid -0
|
||
}
|
||
|
||
return { years, months, weeks, days, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
|
||
}
|
||
|
||
class NudgeResult {
|
||
public:
|
||
ISO8601::InternalDuration m_duration;
|
||
Int128 m_nudgedEpochNs;
|
||
bool m_didExpandCalendarUnit;
|
||
NudgeResult() {}
|
||
NudgeResult(ISO8601::InternalDuration d, Int128 ns, bool expanded)
|
||
: m_duration(d)
|
||
, m_nudgedEpochNs(ns)
|
||
, m_didExpandCalendarUnit(expanded)
|
||
{
|
||
}
|
||
};
|
||
|
||
class Nudged {
|
||
public:
|
||
NudgeResult m_nudgeResult;
|
||
double m_total;
|
||
Nudged() {}
|
||
Nudged(NudgeResult n, double t)
|
||
: m_nudgeResult(n)
|
||
, m_total(t)
|
||
{
|
||
}
|
||
};
|
||
|
||
static Int128 add24HourDaysToTimeDuration(ExecutionState& state, Int128 d, double days)
|
||
{
|
||
Int128 daysInNanoseconds = ((Int128)days) * ISO8601::ExactTime::nsPerDay;
|
||
Int128 result = d + daysInNanoseconds;
|
||
if (std::abs(result) > ISO8601::InternalDuration::maxTimeDuration) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Total time in duration is out of range");
|
||
}
|
||
return result;
|
||
}
|
||
|
||
// There are two different versions of this method due to the lack
|
||
// of float128. The names are different (roundNumberToIncrementInt128() and
|
||
// roundNumberToIncrement()) to avoid confusion in the presence of
|
||
// implicit casts.
|
||
// https://tc39.es/proposal-temporal/#sec-temporal-roundnumbertoincrement
|
||
static Int128 roundNumberToIncrementInt128(Int128 x, Int128 increment, ISO8601::RoundingMode mode)
|
||
{
|
||
// 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(mode, isNegative);
|
||
Int128 rounded = 0;
|
||
if (std::abs(x) == r1 * increment)
|
||
rounded = r1;
|
||
else if (unsignedRoundingMode == ISO8601::UnsignedRoundingMode::Zero)
|
||
rounded = r1;
|
||
else if (unsignedRoundingMode == ISO8601::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 == ISO8601::UnsignedRoundingMode::HalfZero)
|
||
rounded = r1;
|
||
else if (unsignedRoundingMode == ISO8601::UnsignedRoundingMode::HalfInfinity)
|
||
rounded = r2;
|
||
else
|
||
rounded = !even ? r1 : r2;
|
||
if (isNegative)
|
||
rounded = -rounded;
|
||
return rounded * increment;
|
||
}
|
||
|
||
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 double totalTimeDuration(ExecutionState& state, Int128 timeDuration, TemporalUnit unit)
|
||
{
|
||
Int128 divisor = ISO8601::lengthInNanoseconds(toDateTimeUnit(unit));
|
||
Int128 quotient = timeDuration / divisor;
|
||
Int128 remainder = timeDuration % divisor;
|
||
// Perform long division to calculate the fractional part of the quotient
|
||
// remainder / n with more accuracy than 64-bit floating point division
|
||
size_t precision = 50;
|
||
size_t size = 0;
|
||
StringBuilder decimalDigits;
|
||
int32_t digit = 0;
|
||
int32_t sign = timeDuration < 0 ? -1 : 1;
|
||
while (remainder && size < precision) {
|
||
remainder *= 10;
|
||
digit = (int32_t)(remainder / divisor);
|
||
remainder = remainder % divisor;
|
||
appendInteger(decimalDigits, std::abs(digit));
|
||
size++;
|
||
}
|
||
StringBuilder result;
|
||
appendInteger(result, (double)std::abs(quotient));
|
||
result.appendChar('.');
|
||
result.appendString(decimalDigits.finalize());
|
||
// NOTE: if result.toString() == 9007199254740992.999,
|
||
// the result is rounded down to 9007199254740992.
|
||
// This causes the test262 test
|
||
// Temporal/Duration/prototype/total/precision-exact-mathematical-values-7.js
|
||
// to fail when unit=milliseconds, smallerUnit=microseconds, integer=2**53, fraction=1999.
|
||
return sign * Value(result.finalize()).toNumber(state);
|
||
}
|
||
|
||
static ISO8601::Duration adjustDateDurationRecord(ExecutionState& state, const ISO8601::Duration& dateDuration, double days, Optional<double> weeks, Optional<double> months)
|
||
{
|
||
auto result = ISO8601::Duration{ dateDuration.years(), months ? months.value() : dateDuration.months(), weeks ? weeks.value() : dateDuration.weeks(), days, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
|
||
if (!result.isValid()) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Duration properties must be valid and of consistent sign");
|
||
}
|
||
return result;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#sec-temporal-nudgetodayortime
|
||
static NudgeResult nudgeToDayOrTime(ExecutionState& state, ISO8601::InternalDuration duration, Int128 destEpochNs, TemporalUnit largestUnit,
|
||
double increment, TemporalUnit smallestUnit, ISO8601::RoundingMode roundingMode)
|
||
{
|
||
Int128 timeDuration = add24HourDaysToTimeDuration(state, duration.time(), duration.dateDuration().days());
|
||
Int128 unitLength = ISO8601::lengthInNanoseconds(toDateTimeUnit(smallestUnit));
|
||
Int128 roundedTime = roundNumberToIncrementInt128(timeDuration,
|
||
unitLength * (Int128)std::trunc(increment), roundingMode);
|
||
Int128 diffTime = roundedTime - timeDuration;
|
||
double wholeDays = totalTimeDuration(state, timeDuration, TemporalUnit::Day);
|
||
double roundedWholeDays = totalTimeDuration(state, roundedTime, TemporalUnit::Day);
|
||
auto dayDelta = roundedWholeDays - wholeDays;
|
||
auto dayDeltaSign = dayDelta < 0 ? -1 : dayDelta > 0 ? 1
|
||
: 0;
|
||
bool didExpandDays = dayDeltaSign == (timeDuration < 0 ? -1 : timeDuration > 0 ? 1
|
||
: 0);
|
||
auto nudgedEpochNs = diffTime + destEpochNs;
|
||
auto days = 0;
|
||
auto remainder = roundedTime;
|
||
if (largestUnit <= TemporalUnit::Day) {
|
||
days = roundedWholeDays;
|
||
remainder = roundedTime + Temporal::timeDurationFromComponents(-roundedWholeDays * 24, 0, 0, 0, 0, 0);
|
||
}
|
||
auto dateDuration = adjustDateDurationRecord(state, duration.dateDuration(), days, NullOption, NullOption);
|
||
auto resultDuration = ISO8601::InternalDuration::combineDateAndTimeDuration(dateDuration, remainder);
|
||
return NudgeResult(resultDuration, nudgedEpochNs, didExpandDays);
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#sec-temporal-addisodate
|
||
ISO8601::PlainDate Temporal::isoDateAdd(ExecutionState& state, const ISO8601::PlainDate& plainDate, const ISO8601::Duration& duration, TemporalOverflowOption overflow)
|
||
{
|
||
double years = plainDate.year() + duration.years();
|
||
double months = plainDate.month() + duration.months();
|
||
double days = plainDate.day();
|
||
ISO8601::PlainYearMonth intermediate = balanceISOYearMonth(years, months);
|
||
Optional<ISO8601::PlainDate> intermediate1 = regulateISODate(intermediate.year(), intermediate.month(), days, overflow);
|
||
if (!intermediate1) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "date time is out of range of ECMAScript representation");
|
||
}
|
||
auto d = intermediate1.value().day() + duration.days() + (7 * duration.weeks());
|
||
double yy = intermediate1.value().year(), mm = intermediate1.value().month(), dd = d;
|
||
balanceISODate(yy, mm, dd);
|
||
if (!ISO8601::isDateTimeWithinLimits(yy, mm, dd, 12, 0, 0, 0, 0, 0)) {
|
||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "date time is out of range of ECMAScript representation");
|
||
}
|
||
return ISO8601::PlainDate(yy, mm, dd);
|
||
}
|
||
|
||
Int128 Temporal::getEpochNanosecondsFor(ExecutionState& state, Optional<TimeZone> timeZone, ISO8601::PlainDateTime isoDateTime, TemporalDisambiguationOption disambiguation)
|
||
{
|
||
const auto& date = isoDateTime.plainDate();
|
||
const auto& time = isoDateTime.plainTime();
|
||
auto epochNanoValue = ISO8601::ExactTime::fromISOPartsAndOffset(date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second(), time.millisecond(), time.microsecond(), time.nanosecond(), 0).epochNanoseconds();
|
||
if (!timeZone) {
|
||
return epochNanoValue;
|
||
}
|
||
if (timeZone.hasValue()) {
|
||
if (timeZone.value().hasOffset()) {
|
||
return epochNanoValue + timeZone.value().offset();
|
||
}
|
||
}
|
||
// TODO https://tc39.es/proposal-temporal/#sec-temporal-disambiguatepossibleepochnanoseconds
|
||
auto offset = computeTimeZoneOffset(state, timeZone.value().timeZoneName(), ISO8601::ExactTime(epochNanoValue).epochMilliseconds());
|
||
return epochNanoValue + offset;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#sec-applyunsignedroundingmode
|
||
double applyUnsignedRoundingMode(double x, double r1, double r2, ISO8601::UnsignedRoundingMode unsignedRoundingMode)
|
||
{
|
||
if (x == r1)
|
||
return r1;
|
||
ASSERT(r1 < x && x < r2);
|
||
if (unsignedRoundingMode == ISO8601::UnsignedRoundingMode::Zero)
|
||
return r1;
|
||
if (unsignedRoundingMode == ISO8601::UnsignedRoundingMode::Infinity)
|
||
return r2;
|
||
double d1 = x - r1;
|
||
double d2 = r2 - x;
|
||
if (d1 < d2)
|
||
return r1;
|
||
if (d2 < d1)
|
||
return r2;
|
||
ASSERT(d1 == d2);
|
||
if (unsignedRoundingMode == ISO8601::UnsignedRoundingMode::HalfZero)
|
||
return r1;
|
||
if (unsignedRoundingMode == ISO8601::UnsignedRoundingMode::HalfInfinity)
|
||
return r2;
|
||
ASSERT(unsignedRoundingMode == ISO8601::UnsignedRoundingMode::HalfEven);
|
||
auto cardinality = std::fmod(r1 / (r2 - r1), 2);
|
||
return !cardinality ? r1 : r2;
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#sec-temporal-nudgetocalendarunit
|
||
static Nudged nudgeToCalendarUnit(ExecutionState& state, int32_t sign, const ISO8601::InternalDuration& duration, Int128 destEpochNs, ISO8601::PlainDateTime isoDateTime, Optional<TimeZone> timeZone, Calendar calendar, double increment, TemporalUnit unit, ISO8601::RoundingMode roundingMode)
|
||
{
|
||
double r1 = 0;
|
||
double r2 = 0;
|
||
ISO8601::Duration startDuration;
|
||
ISO8601::Duration endDuration;
|
||
switch (unit) {
|
||
case TemporalUnit::Year: {
|
||
Int128 years = roundNumberToIncrementInt128((Int128)duration.dateDuration().years(),
|
||
(Int128)increment, ISO8601::RoundingMode::Trunc);
|
||
r1 = (double)years;
|
||
r2 = (double)years + increment * sign;
|
||
startDuration = ISO8601::Duration{ r1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
|
||
endDuration = ISO8601::Duration{ r2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
|
||
break;
|
||
}
|
||
case TemporalUnit::Month: {
|
||
Int128 months = roundNumberToIncrementInt128((Int128)duration.dateDuration().months(),
|
||
(Int128)increment, ISO8601::RoundingMode::Trunc);
|
||
r1 = (double)months;
|
||
r2 = (double)months + increment * sign;
|
||
startDuration = adjustDateDurationRecord(state, duration.dateDuration(), 0, 0, r1);
|
||
endDuration = adjustDateDurationRecord(state, duration.dateDuration(), 0, 0, r2);
|
||
break;
|
||
}
|
||
case TemporalUnit::Week: {
|
||
auto yearsMonths = adjustDateDurationRecord(state, duration.dateDuration(), 0, 0, NullOption);
|
||
auto weeksStart = Temporal::isoDateAdd(state, isoDateTime.plainDate(), yearsMonths, TemporalOverflowOption::Constrain);
|
||
auto weeksEnd = balanceISODate(state, weeksStart.year(), weeksStart.month(), weeksStart.day() + duration.dateDuration().days());
|
||
auto untilResult = Temporal::calendarDateUntil(calendar, weeksStart, weeksEnd, TemporalUnit::Week);
|
||
Int128 weeks = roundNumberToIncrementInt128((Int128)(duration.dateDuration().weeks() + untilResult.weeks()),
|
||
(Int128)increment, ISO8601::RoundingMode::Trunc);
|
||
r1 = (double)weeks;
|
||
r2 = (double)weeks + increment * sign;
|
||
startDuration = adjustDateDurationRecord(state, duration.dateDuration(), 0, r1, NullOption);
|
||
endDuration = adjustDateDurationRecord(state, duration.dateDuration(), 0, r2, NullOption);
|
||
break;
|
||
}
|
||
default: {
|
||
ASSERT(unit == TemporalUnit::Day);
|
||
Int128 days = roundNumberToIncrementInt128((Int128)duration.dateDuration().days(),
|
||
(Int128)increment, ISO8601::RoundingMode::Trunc);
|
||
r1 = (double)days;
|
||
r2 = (double)days + increment * sign;
|
||
startDuration = adjustDateDurationRecord(state, duration.dateDuration(), r1, NullOption, NullOption);
|
||
endDuration = adjustDateDurationRecord(state, duration.dateDuration(), r2, NullOption, NullOption);
|
||
break;
|
||
}
|
||
}
|
||
ASSERT(sign != 1 || (r1 >= 0 && r1 < r2));
|
||
ASSERT(sign != -1 || (r1 <= 0 && r1 > r2));
|
||
auto start = Temporal::isoDateAdd(state, isoDateTime.plainDate(), startDuration, TemporalOverflowOption::Constrain);
|
||
auto end = Temporal::isoDateAdd(state, isoDateTime.plainDate(), endDuration, TemporalOverflowOption::Constrain);
|
||
auto startDateTime = ISO8601::PlainDateTime(start, isoDateTime.plainTime());
|
||
auto endDateTime = ISO8601::PlainDateTime(end, isoDateTime.plainTime());
|
||
Int128 startEpochNs = Temporal::getEpochNanosecondsFor(state, timeZone, startDateTime, TemporalDisambiguationOption::Compatible);
|
||
Int128 endEpochNs = Temporal::getEpochNanosecondsFor(state, timeZone, endDateTime, TemporalDisambiguationOption::Compatible);
|
||
ASSERT(sign != 1 || ((startEpochNs <= destEpochNs) && (destEpochNs <= endEpochNs)));
|
||
ASSERT(sign == 1 || ((endEpochNs <= destEpochNs) && (destEpochNs <= startEpochNs)));
|
||
ASSERT(startEpochNs != endEpochNs);
|
||
// See 18. NOTE
|
||
Int128 progressNumerator = destEpochNs - startEpochNs;
|
||
Int128 progressDenominator = endEpochNs - startEpochNs;
|
||
double total = r1 + (((double)progressNumerator) / ((double)progressDenominator)) * increment * sign;
|
||
Int128 progress = progressNumerator / progressDenominator;
|
||
ASSERT(0 <= progress && progress <= 1);
|
||
auto isNegative = sign < 0;
|
||
ISO8601::UnsignedRoundingMode unsignedRoundingMode = getUnsignedRoundingMode(roundingMode, isNegative);
|
||
double roundedUnit = std::abs(r2);
|
||
if (progress != 1) {
|
||
ASSERT(std::abs(r1) <= std::abs(total) && std::abs(total) < std::abs(r2));
|
||
roundedUnit = applyUnsignedRoundingMode(
|
||
std::abs(total), std::abs(r1), std::abs(r2), unsignedRoundingMode);
|
||
}
|
||
bool didExpandCalendarUnit = true;
|
||
ISO8601::Duration resultDuration = endDuration;
|
||
Int128 nudgedEpochNs = endEpochNs;
|
||
if (roundedUnit != std::abs(r2)) {
|
||
didExpandCalendarUnit = false;
|
||
resultDuration = startDuration;
|
||
nudgedEpochNs = startEpochNs;
|
||
}
|
||
ISO8601::InternalDuration resultDurationInternal = ISO8601::InternalDuration::combineDateAndTimeDuration(resultDuration, 0);
|
||
auto nudgeResult = NudgeResult(resultDurationInternal, nudgedEpochNs, didExpandCalendarUnit);
|
||
return Nudged(nudgeResult, total);
|
||
}
|
||
|
||
static int32_t unitIndexInTable(TemporalUnit unit)
|
||
{
|
||
switch (unit) {
|
||
case TemporalUnit::Year:
|
||
return 0;
|
||
case TemporalUnit::Month:
|
||
return 1;
|
||
case TemporalUnit::Week:
|
||
return 2;
|
||
case TemporalUnit::Day:
|
||
return 3;
|
||
case TemporalUnit::Hour:
|
||
return 4;
|
||
case TemporalUnit::Minute:
|
||
return 5;
|
||
case TemporalUnit::Second:
|
||
return 6;
|
||
case TemporalUnit::Millisecond:
|
||
return 7;
|
||
case TemporalUnit::Microsecond:
|
||
return 8;
|
||
case TemporalUnit::Nanosecond:
|
||
return 9;
|
||
default: {
|
||
RELEASE_ASSERT_NOT_REACHED();
|
||
}
|
||
}
|
||
}
|
||
|
||
static TemporalUnit unitInTable(int32_t i)
|
||
{
|
||
switch (i) {
|
||
case 0:
|
||
return TemporalUnit::Year;
|
||
case 1:
|
||
return TemporalUnit::Month;
|
||
case 2:
|
||
return TemporalUnit::Week;
|
||
case 3:
|
||
return TemporalUnit::Day;
|
||
case 4:
|
||
return TemporalUnit::Hour;
|
||
case 5:
|
||
return TemporalUnit::Minute;
|
||
case 6:
|
||
return TemporalUnit::Second;
|
||
case 7:
|
||
return TemporalUnit::Millisecond;
|
||
case 8:
|
||
return TemporalUnit::Microsecond;
|
||
case 9:
|
||
return TemporalUnit::Nanosecond;
|
||
default: {
|
||
RELEASE_ASSERT_NOT_REACHED();
|
||
}
|
||
}
|
||
}
|
||
|
||
// https://tc39.es/proposal-temporal/#sec-temporal-bubblerelativeduration
|
||
static ISO8601::InternalDuration bubbleRelativeDuration(ExecutionState& state, int32_t sign, ISO8601::InternalDuration duration, Int128 nudgedEpochNs, ISO8601::PlainDateTime isoDateTime, Optional<TimeZone> timeZone, TemporalUnit largestUnit, TemporalUnit smallestUnit)
|
||
{
|
||
if (smallestUnit == largestUnit)
|
||
return duration;
|
||
auto largestUnitIndex = unitIndexInTable(largestUnit);
|
||
auto smallestUnitIndex = unitIndexInTable(smallestUnit);
|
||
auto unitIndex = smallestUnitIndex - 1;
|
||
bool done = false;
|
||
ISO8601::Duration endDuration;
|
||
while (unitIndex >= largestUnitIndex && !done) {
|
||
auto unit = unitInTable(unitIndex);
|
||
if (unit != TemporalUnit::Week || largestUnit == TemporalUnit::Week) {
|
||
if (unit == TemporalUnit::Year) {
|
||
auto years = duration.dateDuration().years() + sign;
|
||
endDuration = ISO8601::Duration{ years, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
|
||
} else if (unit == TemporalUnit::Month) {
|
||
auto months = duration.dateDuration().months() + sign;
|
||
endDuration = adjustDateDurationRecord(state, duration.dateDuration(), 0, 0, months);
|
||
} else {
|
||
ASSERT(unit == TemporalUnit::Week);
|
||
auto weeks = duration.dateDuration().weeks() + sign;
|
||
endDuration = adjustDateDurationRecord(state, duration.dateDuration(), 0, weeks, NullOption);
|
||
}
|
||
auto end = Temporal::isoDateAdd(state, isoDateTime.plainDate(), endDuration, TemporalOverflowOption::Constrain);
|
||
auto endDateTime = ISO8601::PlainDateTime(end, isoDateTime.plainTime());
|
||
auto endEpochNs = Temporal::getEpochNanosecondsFor(state, timeZone, endDateTime, TemporalDisambiguationOption::Compatible);
|
||
auto beyondEnd = nudgedEpochNs - endEpochNs;
|
||
auto beyondEndSign = beyondEnd < 0 ? -1 : beyondEnd > 0 ? 1
|
||
: 0;
|
||
if (beyondEndSign != -sign) {
|
||
duration = ISO8601::InternalDuration::combineDateAndTimeDuration(endDuration, 0);
|
||
} else
|
||
done = true;
|
||
}
|
||
unitIndex--;
|
||
}
|
||
return duration;
|
||
}
|
||
|
||
ISO8601::InternalDuration Temporal::roundRelativeDuration(ExecutionState& state, ISO8601::InternalDuration duration, Int128 destEpochNs, ISO8601::PlainDateTime isoDateTime, Optional<TimeZone> timeZone,
|
||
Calendar calendar, TemporalUnit largestUnit, double increment, TemporalUnit smallestUnit, ISO8601::RoundingMode roundingMode)
|
||
{
|
||
bool irregularLengthUnit = smallestUnit <= TemporalUnit::Week;
|
||
int32_t sign = duration.sign() < 0 ? -1 : 1;
|
||
NudgeResult nudgeResult;
|
||
if (irregularLengthUnit) {
|
||
Nudged record = nudgeToCalendarUnit(state, sign, duration, destEpochNs, isoDateTime, timeZone, calendar, increment, smallestUnit, roundingMode);
|
||
nudgeResult = record.m_nudgeResult;
|
||
} else {
|
||
// 7
|
||
nudgeResult = nudgeToDayOrTime(state, duration, destEpochNs, largestUnit, increment, smallestUnit, roundingMode);
|
||
}
|
||
// 8.
|
||
duration = nudgeResult.m_duration;
|
||
// 9.
|
||
if (nudgeResult.m_didExpandCalendarUnit && smallestUnit != TemporalUnit::Week) {
|
||
auto startUnit = smallestUnit <= TemporalUnit::Day ? smallestUnit : TemporalUnit::Day;
|
||
duration = bubbleRelativeDuration(state, sign, duration, nudgeResult.m_nudgedEpochNs, isoDateTime, timeZone, largestUnit, startUnit);
|
||
}
|
||
return duration;
|
||
}
|
||
|
||
void Temporal::formatCalendarAnnotation(StringBuilder& builder, Calendar calendar, TemporalShowCalendarNameOption showCalendar)
|
||
{
|
||
if (showCalendar == TemporalShowCalendarNameOption::Never) {
|
||
} else if (showCalendar == TemporalShowCalendarNameOption::Auto && calendar.isISO8601()) {
|
||
} else {
|
||
builder.appendChar('[');
|
||
if (showCalendar == TemporalShowCalendarNameOption::Critical) {
|
||
builder.appendChar('!');
|
||
}
|
||
builder.appendString("u-ca=");
|
||
builder.appendString(calendar.toString());
|
||
builder.appendChar(']');
|
||
}
|
||
}
|
||
|
||
} // namespace Escargot
|
||
|
||
#endif
|