// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/objects/js-temporal-helpers.h"
#include "src/objects/js-objects-inl.h"
#include "src/objects/object-conversions-inl.h"
namespace v8::internal::temporal {
namespace {
// https://tc39.es/proposal-temporal/#sec-isintegralnumber
bool IsIntegralNumber(Isolate* isolate, DirectHandle<Object> argument) {
// 1. If Type(argument) is not Number, return false.
if (!IsNumber(*argument)) return false;
// 2. If argument is NaN, +∞𝔽, or -∞𝔽, return false.
double number = Object::NumberValue(Cast<Number>(*argument));
if (!std::isfinite(number)) return false;
// 3. If floor(abs(ℝ(argument))) ≠ abs(ℝ(argument)), return false.
if (std::floor(std::abs(number)) != std::abs(number)) return false;
// 4. Return true.
return true;
}
// https://tc39.es/proposal-temporal/#sec-temporal-tointegerwithoutrounding
Maybe<double> ToIntegerWithoutRounding(Isolate* isolate,
DirectHandle<Object> argument) {
// 1. Let number be ? ToNumber(argument).
DirectHandle<Number> number;
ASSIGN_RETURN_ON_EXCEPTION(isolate, number,
Object::ToNumber(isolate, argument));
// 2. If number is NaN, +0𝔽, or −0𝔽 return 0.
if (IsNaN(*number) || Object::NumberValue(*number) == 0) {
return Just(static_cast<double>(0));
}
// 3. If IsIntegralNumber(number) is false, throw a RangeError exception.
if (!IsIntegralNumber(isolate, number)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR("Number not integral."));
}
// 4. Return ℝ(number).
return Just(Object::NumberValue(*number));
}
// Helper function to loop through Table 8 Duration Record Fields
// This function implement
// "For each row of Table 8, except the header row, in table order, do"
// loop. It is designed to be used to implement the common part of
// ToPartialDuration, ToTemporalDurationRecord
Maybe<bool> IterateDurationRecordFieldsTable(
Isolate* isolate, DirectHandle<JSReceiver> temporal_duration_like,
Maybe<bool> (*RowFunction)(Isolate*,
DirectHandle<JSReceiver> temporal_duration_like,
DirectHandle<String>, double*),
DurationRecord* record) {
Factory* factory = isolate->factory();
std::array<std::pair<DirectHandle<String>, double*>, 10> table8 = {
{{factory->days_string(), &record->time_duration.days},
{factory->hours_string(), &record->time_duration.hours},
{factory->microseconds_string(), &record->time_duration.microseconds},
{factory->milliseconds_string(), &record->time_duration.milliseconds},
{factory->minutes_string(), &record->time_duration.minutes},
{factory->months_string(), &record->months},
{factory->nanoseconds_string(), &record->time_duration.nanoseconds},
{factory->seconds_string(), &record->time_duration.seconds},
{factory->weeks_string(), &record->weeks},
{factory->years_string(), &record->years}}};
// x. Let any be false.
bool any = false;
// x+1. For each row of Table 8, except the header row, in table order, do
for (const auto& row : table8) {
bool result;
// row.first is prop: the Property Name value of the current row
// row.second is the address of result's field whose name is the Field Name
// value of the current row
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result,
RowFunction(isolate, temporal_duration_like, row.first, row.second));
any |= result;
}
return Just(any);
}
} // namespace
// https://tc39.es/proposal-temporal/#sec-temporal-isvalidduration
bool IsValidDuration(Isolate* isolate, const DurationRecord& dur) {
// 1. Let sign be ! DurationSign(years, months, weeks, days, hours, minutes,
// seconds, milliseconds, microseconds, nanoseconds).
int32_t sign = DurationRecord::Sign(dur);
// 2. For each value v of « years, months, weeks, days, hours, minutes,
// seconds, milliseconds, microseconds, nanoseconds », do a. If v is not
// finite, return false. b. If v < 0 and sign > 0, return false. c. If v > 0
// and sign < 0, return false.
// 3. Return true.
const TimeDurationRecord& time = dur.time_duration;
if (!(std::isfinite(dur.years) && std::isfinite(dur.months) &&
std::isfinite(dur.weeks) && std::isfinite(time.days) &&
std::isfinite(time.hours) && std::isfinite(time.minutes) &&
std::isfinite(time.seconds) && std::isfinite(time.milliseconds) &&
std::isfinite(time.microseconds) && std::isfinite(time.nanoseconds))) {
return false;
}
if ((sign > 0 && (dur.years < 0 || dur.months < 0 || dur.weeks < 0 ||
time.days < 0 || time.hours < 0 || time.minutes < 0 ||
time.seconds < 0 || time.milliseconds < 0 ||
time.microseconds < 0 || time.nanoseconds < 0)) ||
(sign < 0 && (dur.years > 0 || dur.months > 0 || dur.weeks > 0 ||
time.days > 0 || time.hours > 0 || time.minutes > 0 ||
time.seconds > 0 || time.milliseconds > 0 ||
time.microseconds > 0 || time.nanoseconds > 0))) {
return false;
}
static const double kPower32Of2 = static_cast<double>(int64_t(1) << 32);
static const int64_t kPower53Of2 = int64_t(1) << 53;
// 3. If abs(years) ≥ 2**32, return false.
if (std::abs(dur.years) >= kPower32Of2) {
return false;
}
// 4. If abs(months) ≥ 2**32, return false.
if (std::abs(dur.months) >= kPower32Of2) {
return false;
}
// 5. If abs(weeks) ≥ 2**32, return false.
if (std::abs(dur.weeks) >= kPower32Of2) {
return false;
}
// 6. Let normalizedSeconds be days × 86,400 + hours × 3600 + minutes × 60 +
// seconds + ℝ(𝔽(milliseconds)) × 10**-3 + ℝ(𝔽(microseconds)) × 10**-6 +
// ℝ(𝔽(nanoseconds)) × 10**-9.
// 7. NOTE: The above step cannot be implemented directly using floating-point
// arithmetic. Multiplying by 10**-3, 10**-6, and 10**-9 respectively may be
// imprecise when milliseconds, microseconds, or nanoseconds is an unsafe
// integer. This multiplication can be implemented in C++ with an
// implementation of std::remquo() with sufficient bits in the quotient.
// String manipulation will also give an exact result, since the
// multiplication is by a power of 10.
// 8. If abs(normalizedSeconds) ≥ 2**53, return false.
int64_t allowed = kPower53Of2;
double in_seconds = std::abs(time.days * 86400.0 + time.hours * 3600.0 +
time.minutes * 60.0 + time.seconds);
if (in_seconds >= allowed) {
return false;
}
allowed -= in_seconds;
// Check the part > 1 seconds.
in_seconds = std::floor(std::abs(time.milliseconds / 1e3)) +
std::floor(std::abs(time.microseconds / 1e6)) +
std::floor(std::abs(time.nanoseconds / 1e9));
if (in_seconds >= allowed) {
return false;
}
allowed -= in_seconds;
// Sum of the three remainings will surely < 3
if (allowed > 3) {
return true;
}
allowed *= 1000000000; // convert to ns
int64_t remainders = std::abs(fmod(time.milliseconds, 1e3)) * 1000000 +
std::abs(fmod(time.microseconds, 1e6)) * 1000 +
std::abs(fmod(time.nanoseconds, 1e9));
if (remainders >= allowed) {
return false;
}
return true;
}
// https://tc39.es/proposal-temporal/#sec-temporal-durationsign
int32_t DurationRecord::Sign(const DurationRecord& dur) {
// 1. For each value v of « years, months, weeks, days, hours, minutes,
// seconds, milliseconds, microseconds, nanoseconds », do a. If v < 0, return
// −1. b. If v > 0, return 1.
// 2. Return 0.
if (dur.years < 0) return -1;
if (dur.years > 0) return 1;
if (dur.months < 0) return -1;
if (dur.months > 0) return 1;
if (dur.weeks < 0) return -1;
if (dur.weeks > 0) return 1;
const TimeDurationRecord& time = dur.time_duration;
if (time.days < 0) return -1;
if (time.days > 0) return 1;
if (time.hours < 0) return -1;
if (time.hours > 0) return 1;
if (time.minutes < 0) return -1;
if (time.minutes > 0) return 1;
if (time.seconds < 0) return -1;
if (time.seconds > 0) return 1;
if (time.milliseconds < 0) return -1;
if (time.milliseconds > 0) return 1;
if (time.microseconds < 0) return -1;
if (time.microseconds > 0) return 1;
if (time.nanoseconds < 0) return -1;
if (time.nanoseconds > 0) return 1;
return 0;
}
// https://tc39.es/proposal-temporal/#sec-temporal-createtimedurationrecord
Maybe<TimeDurationRecord> TimeDurationRecord::Create(
Isolate* isolate, double days, double hours, double minutes, double seconds,
double milliseconds, double microseconds, double nanoseconds) {
// 1. If ! IsValidDuration(0, 0, 0, days, hours, minutes, seconds,
// milliseconds, microseconds, nanoseconds) is false, throw a RangeError
// exception.
TimeDurationRecord record = {days, hours, minutes, seconds,
milliseconds, microseconds, nanoseconds};
if (!IsValidDuration(isolate, {0, 0, 0, record})) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR("Invalid duration."));
}
// 2. Return the Record { [[Days]]: ℝ(𝔽(days)), [[Hours]]: ℝ(𝔽(hours)),
// [[Minutes]]: ℝ(𝔽(minutes)), [[Seconds]]: ℝ(𝔽(seconds)), [[Milliseconds]]:
// ℝ(𝔽(milliseconds)), [[Microseconds]]: ℝ(𝔽(microseconds)), [[Nanoseconds]]:
// ℝ(𝔽(nanoseconds)) }.
return Just(record);
}
// https://tc39.es/proposal-temporal/#sec-temporal-createdurationrecord
Maybe<DurationRecord> DurationRecord::Create(
Isolate* isolate, double years, double months, double weeks, double days,
double hours, double minutes, double seconds, double milliseconds,
double microseconds, double nanoseconds) {
// 1. If ! IsValidDuration(years, months, weeks, days, hours, minutes,
// seconds, milliseconds, microseconds, nanoseconds) is false, throw a
// RangeError exception.
DurationRecord record = {
years,
months,
weeks,
{days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds}};
if (!IsValidDuration(isolate, record)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR("Invalid duration."));
}
// 2. Return the Record { [[Years]]: ℝ(𝔽(years)), [[Months]]: ℝ(𝔽(months)),
// [[Weeks]]: ℝ(𝔽(weeks)), [[Days]]: ℝ(𝔽(days)), [[Hours]]: ℝ(𝔽(hours)),
// [[Minutes]]: ℝ(𝔽(minutes)), [[Seconds]]: ℝ(𝔽(seconds)), [[Milliseconds]]:
// ℝ(𝔽(milliseconds)), [[Microseconds]]: ℝ(𝔽(microseconds)), [[Nanoseconds]]:
// ℝ(𝔽(nanoseconds)) }.
return Just(record);
}
// https://tc39.es/proposal-temporal/#sec-todurationrecord-deleted
Maybe<DurationRecord> ToDurationRecord(
Isolate* isolate, DirectHandle<Object> temporal_duration_like_obj,
const DurationRecord& input) {
// 1. If Type(temporalDurationLike) is not Object, then
if (!IsJSReceiver(*temporal_duration_like_obj)) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(
"Duration argument must be an object."));
}
DirectHandle<JSReceiver> temporal_duration_like =
Cast<JSReceiver>(temporal_duration_like_obj);
// 2. Let result be a new partial Duration Record with each field set to
// undefined.
DurationRecord result = input;
// 3. Let any be false.
bool any = false;
// Table 8: Duration Record Fields
// #table-temporal-duration-record-fields
// 4. For each row of Table 8, except the header row, in table order, do
ASSIGN_RETURN_ON_EXCEPTION(
isolate, any,
IterateDurationRecordFieldsTable(
isolate, temporal_duration_like,
[](Isolate* isolate, DirectHandle<JSReceiver> temporal_duration_like,
DirectHandle<String> prop, double* field) -> Maybe<bool> {
bool not_undefined = false;
// a. Let prop be the Property value of the current row.
DirectHandle<Object> val;
// b. Let val be ? Get(temporalDurationLike, prop).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, val,
JSReceiver::GetProperty(isolate, temporal_duration_like, prop));
// c. If val is not undefined, then
if (!IsUndefined(*val)) {
// i. Set any to true.
not_undefined = true;
// ii. Let val be 𝔽(? ToIntegerWithoutRounding(val)).
// iii. Set result's field whose name is the Field Name value of
// the current row to val.
ASSIGN_RETURN_ON_EXCEPTION(
isolate, *field, ToIntegerWithoutRounding(isolate, val));
}
return Just(not_undefined);
},
&result));
// 5. If any is false, then
if (!any) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(
isolate,
NEW_TEMPORAL_TYPE_ERROR(
"Duration argument must contain at least one date/time field."));
}
// 6. Return result.
return Just(result);
}
} // namespace v8::internal::temporal