blob: 8b2b0a7a39d3c77520e5f695b0088a52456addea [file]
// 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-objects.h"
#include <algorithm>
#include <optional>
#include <set>
#include "include/v8config.h"
#include "src/api/api-inl.h"
#include "src/base/numerics/safe_conversions.h"
#include "src/common/globals.h"
#include "src/date/date.h"
#include "src/execution/isolate.h"
#include "src/heap/factory.h"
#include "src/numbers/conversions-inl.h"
#include "src/objects/js-objects-inl.h"
#include "src/objects/js-objects.h"
#include "src/objects/js-temporal-helpers.h"
#include "src/objects/js-temporal-objects-inl.h"
#include "src/objects/js-temporal-zoneinfo64.h"
#include "src/objects/managed-inl.h"
#include "src/objects/object-conversions-inl.h"
#include "src/objects/objects-inl.h"
#include "src/objects/option-utils.h"
#include "src/objects/property-descriptor.h"
#include "src/objects/string-set.h"
#include "src/strings/string-builder-inl.h"
#include "temporal_rs/I128Nanoseconds.hpp"
#include "temporal_rs/OwnedRelativeTo.hpp"
#include "temporal_rs/Unit.hpp"
#ifdef V8_INTL_SUPPORT
#include "src/objects/intl-objects.h"
#include "src/objects/js-date-time-format.h"
#include "src/objects/js-duration-format.h"
#include "unicode/calendar.h"
#include "unicode/unistr.h"
#endif // V8_INTL_SUPPORT
namespace v8::internal {
namespace {
// Shorten enums with `using`
using temporal_rs::RoundingMode;
using temporal_rs::Unit;
/**
* This header declare the Abstract Operations defined in the
* Temporal spec with the enum and struct for them.
*/
template <typename T>
using TemporalResult =
temporal_rs::diplomat::result<T, temporal_rs::TemporalError>;
template <typename T>
using TemporalAllocatedResult = TemporalResult<std::unique_ptr<T>>;
using temporal::DurationRecord;
using temporal::TimeDurationRecord;
// Options
// https://tc39.es/proposal-temporal/#sec-temporal-totemporaldisambiguation
enum class Disambiguation { kCompatible, kEarlier, kLater, kReject };
// https://tc39.es/proposal-temporal/#sec-temporal-toshowcalendaroption
enum class ShowCalendar { kAuto, kAlways, kNever };
// https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes
enum class UnsignedRoundingMode {
kInfinity,
kZero,
kHalfInfinity,
kHalfZero,
kHalfEven
};
// https://tc39.es/proposal-temporal/#sec-temporal-GetTemporalUnit
enum class UnitGroup {
kDate,
kTime,
kDateTime,
};
// Default value as used by GetTemporalUnitValuedOption
enum class DefaultValue {
kUnset,
kRequired,
};
// https://tc39.es/proposal-temporal/#sec-temporal-totemporaltimerecord
enum Completeness {
kComplete,
kPartial,
};
// Convenience method for getting the timezone provider
temporal_rs::Provider& TimeZoneProvider() {
return ZoneInfo64Provider::Singleton().Provider();
}
// Common error strings
static constexpr char kInvalidIsoDate[] = "Invalid ISO date.";
static constexpr char kInvalidTime[] = "Invalid time";
static constexpr char kFiniteInteger[] = "Expected finite integer.";
static constexpr char kIntegerOutOfRange[] = "Integer out of range.";
static constexpr char kOptionMustBeObject[] = "Option must be object:";
static constexpr char kCalendarMustBeString[] = "Calendar must be string.";
static constexpr char kRoundToMissing[] = "Must specify a roundTo parameter.";
static constexpr char kRoundToMustBeObject[] = "roundTo must be an object.";
static constexpr char kYearMustBeObject[] = "year argument must be an object.";
static constexpr char kTimeZoneMissing[] = "Must specify time zone.";
static constexpr char kWithNoPartial[] =
"Argument to with() must contain some date/time fields.";
#define ORDINARY_CREATE_FROM_CONSTRUCTOR(obj, target, new_target, T) \
DirectHandle<JSReceiver> new_target_receiver = Cast<JSReceiver>(new_target); \
DirectHandle<Map> map; \
ASSIGN_RETURN_ON_EXCEPTION( \
isolate, map, \
JSFunction::GetDerivedMap(isolate, target, new_target_receiver)); \
DirectHandle<T> object = \
Cast<T>(isolate->factory()->NewFastOrSlowJSObjectFromMap(map));
// Helper function to split the string into its two constituent encodings
// and call two different functions depending on the encoding
template <typename RetVal>
RetVal HandleStringEncodings(
Isolate* isolate, DirectHandle<String> string,
std::function<RetVal(std::string_view)> utf8_fn,
std::function<RetVal(std::u16string_view)> utf16_fn) {
string = String::Flatten(isolate, string);
DisallowGarbageCollection no_gc;
auto flat = string->GetFlatContent(no_gc);
if (flat.IsOneByte()) {
auto content = flat.ToOneByteVector();
// reinterpret_cast since std string types accept signed chars
std::string_view view(reinterpret_cast<const char*>(content.data()),
content.size());
return utf8_fn(view);
} else {
auto content = flat.ToUC16Vector();
std::u16string_view view(reinterpret_cast<const char16_t*>(content.data()),
content.size());
return utf16_fn(view);
}
}
// Take a Rust Result and turn it into a Maybe, suitable for use
// with error handling macros.
//
// Note that a lot of the types returned by Rust code prefer
// move semantics, try using MOVE_RETURN_ON_EXCEPTION
template <typename ContainedValue>
Maybe<ContainedValue> ExtractRustResult(
Isolate* isolate, TemporalResult<ContainedValue>&& rust_result) {
if (rust_result.is_err()) {
auto err = std::move(rust_result).err().value();
Handle<String> msg;
if (err.msg.has_value()) {
bool success =
isolate->factory()->NewStringFromUtf8(err.msg.value()).ToHandle(&msg);
if (!success) {
msg = isolate->factory()->NewStringFromStaticChars("(utf8 error)");
}
} else {
// We need to have *some* message here even if Rust doesn't have one
msg = isolate->factory()->NewStringFromStaticChars("Unspecified error.");
}
switch (err.kind) {
case temporal_rs::ErrorKind::Type:
THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kTemporal, msg));
break;
case temporal_rs::ErrorKind::Range:
THROW_NEW_ERROR(isolate,
NewRangeError(MessageTemplate::kTemporal, msg));
break;
case temporal_rs::ErrorKind::Syntax:
THROW_NEW_ERROR(isolate,
NewSyntaxError(MessageTemplate::kTemporal, msg));
case temporal_rs::ErrorKind::Assert:
case temporal_rs::ErrorKind::Generic:
default:
// These cases shouldn't happen; the spec doesn't currently trigger
// these errors
THROW_NEW_ERROR(isolate,
NewError(MessageTemplate::kTemporalWithArg,
isolate->factory()->NewStringFromStaticChars(
"Internal error:"),
msg));
}
return Nothing<ContainedValue>();
}
return Just(std::move(rust_result).ok().value());
}
// Helper function to construct a JSType that wraps a RustType (infallible)
template <typename JSType>
MaybeDirectHandle<JSType> ConstructRustWrappingType(
Isolate* isolate, DirectHandle<JSFunction> target,
DirectHandle<HeapObject> new_target,
std::unique_ptr<typename JSType::RustType>&& rust_value) {
// CppGCManaged requires shared ownership
std::shared_ptr<typename JSType::RustType> rust_shared =
std::move(rust_value);
DirectHandle<CppGCManaged<typename JSType::RustType>> managed =
CppGCManaged<typename JSType::RustType>::Create(isolate, 0, rust_shared);
ORDINARY_CREATE_FROM_CONSTRUCTOR(object, target, new_target, JSType)
object->initialize_with_wrapped_rust_value(*managed);
return object;
}
// Helper function to construct a JSType that wraps a RustType (fallible)
template <typename JSType>
MaybeDirectHandle<JSType> ConstructRustWrappingType(
Isolate* isolate, DirectHandle<JSFunction> target,
DirectHandle<HeapObject> new_target,
TemporalResult<std::unique_ptr<typename JSType::RustType>>&& rust_result) {
std::unique_ptr<typename JSType::RustType> rust_value = nullptr;
MOVE_RETURN_ON_EXCEPTION(isolate, rust_value,
ExtractRustResult(isolate, std::move(rust_result)));
return ConstructRustWrappingType<JSType>(isolate, target, new_target,
std::move(rust_value));
}
// Most of the time you just need JSType's constructor as the target,
// these helpers let you skip passing the argument
template <typename JSType>
MaybeDirectHandle<JSType> ConstructRustWrappingType(
Isolate* isolate, std::unique_ptr<typename JSType::RustType>&& rust_value) {
auto ctor = JSType::GetConstructorTarget(isolate);
return ConstructRustWrappingType<JSType>(isolate, ctor, ctor,
std::move(rust_value));
}
template <typename JSType>
MaybeDirectHandle<JSType> ConstructRustWrappingType(
Isolate* isolate,
TemporalResult<std::unique_ptr<typename JSType::RustType>>&& rust_result) {
auto ctor = JSType::GetConstructorTarget(isolate);
return ConstructRustWrappingType<JSType>(isolate, ctor, ctor,
std::move(rust_result));
}
} // namespace
namespace temporal {
// ====== Numeric conversions ======
// Note: All of these IntegralDouble functions MUST
// be given an integral number, typically obtained via
// ToIntegerIfIntegral or ToIntegerWithTruncation.
template <typename IntegerType>
IntegerType CastIntegralDouble(double d) {
DCHECK((base::IsValueInRangeForNumericType<IntegerType, double>(d)));
DCHECK_EQ(nearbyint(d), d);
return static_cast<IntegerType>(d);
}
template <typename IntegerType>
IntegerType ClampIntegralDouble(double d, IntegerType min, IntegerType max) {
DCHECK_EQ(nearbyint(d), d);
double clamped =
std::clamp(d, static_cast<double>(min), static_cast<double>(max));
return CastIntegralDouble<IntegerType>(clamped);
}
template <typename IntegerType>
IntegerType ClampIntegralDoubleToRange(double d) {
return ClampIntegralDouble<IntegerType>(
d, std::numeric_limits<IntegerType>::min(),
std::numeric_limits<IntegerType>::max());
}
template <typename IntegerType>
Maybe<IntegerType> CheckDoubleInRange(Isolate* isolate, double d) {
if (!base::IsValueInRangeForNumericType<IntegerType, double>(d)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kIntegerOutOfRange));
}
return Just(CastIntegralDouble<IntegerType>(d));
}
// https://tc39.es/proposal-temporal/#sec-temporal-tointegerifintegral
Maybe<double> ToIntegerIfIntegral(Isolate* isolate,
DirectHandle<Object> argument) {
// 1. Let number be ? ToNumber(argument).
DirectHandle<Number> number;
ASSIGN_RETURN_ON_EXCEPTION(isolate, number,
Object::ToNumber(isolate, argument));
double number_double = Object::NumberValue(*number);
// 2. If number is not an integral Number, throw a RangeError exception.
if (!std::isfinite(number_double) ||
nearbyint(number_double) != number_double) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kFiniteInteger));
}
// 3. Return ℝ(number).
return Just(number_double);
}
// temporal_rs currently accepts integer types in cases where
// the spec uses a double (and bounds-checks later). This helper
// allows safely converting objects to some known integer type.
//
// TODO(manishearth) This helper should be removed when it is unnecessary.
// Tracked in https://github.com/boa-dev/temporal/issues/334
template <typename IntegerType>
Maybe<IntegerType> ToIntegerTypeIfIntegral(Isolate* isolate,
DirectHandle<Object> argument) {
double d;
ASSIGN_RETURN_ON_EXCEPTION(isolate, d,
ToIntegerIfIntegral(isolate, argument));
if (!base::IsValueInRangeForNumericType<IntegerType, double>(d)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kIntegerOutOfRange));
}
return Just(static_cast<IntegerType>(d));
}
// https://tc39.es/proposal-temporal/#sec-temporal-tointegerwithtruncation
Maybe<double> ToIntegerWithTruncation(Isolate* isolate,
DirectHandle<Object> argument) {
// 1. Let number be ? ToNumber(argument).
DirectHandle<Number> number;
ASSIGN_RETURN_ON_EXCEPTION(isolate, number,
Object::ToNumber(isolate, argument));
double number_double = Object::NumberValue(*number);
// 2. If number is NaN, +∞𝔽 or -∞𝔽, throw a RangeError exception.
if (!std::isfinite(number_double)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kFiniteInteger));
}
// 3. Return truncate(number).
return Just(std::trunc(number_double));
}
// Same as ToIntegerWithTruncation, but returns 0 when undefined
Maybe<double> ToIntegerWithTruncationOrZero(Isolate* isolate,
DirectHandle<Object> argument) {
if (IsUndefined(*argument)) {
return Just(0.0);
}
return ToIntegerWithTruncation(isolate, argument);
}
// https://tc39.es/proposal-temporal/#sec-temporal-topositiveintegerwithtruncation
Maybe<double> ToPositiveIntegerWithTruncation(Isolate* isolate,
DirectHandle<Object> argument) {
// 1. Let integer be ?ToIntegerWithTruncation(argument).
double integer;
ASSIGN_RETURN_ON_EXCEPTION(isolate, integer,
ToIntegerWithTruncation(isolate, argument));
// 2. If integer is ≤ 0, throw a RangeError exception
if (integer <= 0) {
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_RANGE_ERROR("Expected positive integer."));
}
// 3. Return integer.
return Just(integer);
}
static constexpr uint64_t kU64HighBitMask = uint64_t{1} << 63;
// Throws RangeErrors for out-of-range BigInts, checking IsValidEpochNanoseconds
Maybe<temporal_rs::I128Nanoseconds> GetI128FromBigInt(
Isolate* isolate, DirectHandle<BigInt> bigint) {
static constexpr char kNSOutOfRange[] = "Nanoseconds out of range.";
if (bigint->Words64Count() > 2) {
// 3. ! IsValidEpochNanoseconds(epochNanoseconds) is false, throw a
// RangeError exception.
//
// This only performs part of the check, the rest of it is done below with
// is_valid()
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kNSOutOfRange));
}
uint64_t words[2] = {0, 0};
uint32_t word_count = 2;
int sign_bit = 0;
bigint->ToWordsArray64(&sign_bit, &word_count, words);
if ((words[1] & kU64HighBitMask) != 0) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kNSOutOfRange));
}
uint64_t high = words[1];
if (sign_bit == 1) {
high |= kU64HighBitMask;
}
temporal_rs::I128Nanoseconds ns;
ns.high = high;
ns.low = words[0];
if (!ns.is_valid()) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kNSOutOfRange));
}
return Just(ns);
#undef NANOSECONDS_RANGE_ERROR
}
MaybeDirectHandle<BigInt> I128ToBigInt(Isolate* isolate,
temporal_rs::I128Nanoseconds ns) {
uint64_t words[2];
bool sign_bit;
if ((ns.high & temporal::kU64HighBitMask) != 0) {
sign_bit = true;
words[1] = ns.high & ~temporal::kU64HighBitMask;
} else {
sign_bit = false;
words[1] = ns.high;
}
words[0] = ns.low;
return BigInt::FromWords64(isolate, sign_bit, 2, words);
}
bool IsValidTime(double hour, double minute, double second, double millisecond,
double microsecond, double nanosecond) {
// 1. If hour < 0 or hour > 23, then
// a. Return false.
if (hour < 0 || hour > 23) {
return false;
}
// 2. If minute < 0 or minute > 59, then
// a. Return false.
if (minute < 0 || minute > 59) {
return false;
}
// 3. If second < 0 or second > 59, then
// a. Return false.
if (second < 0 || second > 59) {
return false;
}
// 4. If millisecond < 0 or millisecond > 999, then
// a. Return false.
if (millisecond < 0 || millisecond > 999) {
return false;
}
// 5. If microsecond < 0 or microsecond > 999, then
// a. Return false.
if (microsecond < 0 || microsecond > 999) {
return false;
}
// 6. If nanosecond < 0 or nanosecond > 999, then
// a. Return false.
if (nanosecond < 0 || nanosecond > 999) {
return false;
}
// 7. Return true.
return true;
}
// https://tc39.es/proposal-temporal/#sec-temporal-isodaysinmonth
int8_t ISODaysInMonth(int32_t year, uint8_t month) {
switch (month) {
// 1. If month is 1, 3, 5, 7, 8, 10, or 12, return 31.
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
return 31;
// 4. Return 28 + MathematicalInLeapYear(EpochTimeForYear(year)).
case 2:
if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) {
return 29;
} else {
return 28;
}
// 2. If month is 4, 6, 9, or 11, return 30.
default:
return 30;
}
}
// https://tc39.es/proposal-temporal/#sec-temporal-isvalidisodate
bool IsValidIsoDate(double year, double month, double day) {
// 1. If month < 1 or month > 12, then
if (month < 1 || month > 12) {
// a. Return false.
return false;
}
// This check is technically needed later when we check if things are in the
// Temporal range, but we do it now to ensure we can safely cast to int32_t
// before passing to Rust See https://github.com/boa-dev/temporal/issues/334.
if (!base::IsValueInRangeForNumericType<int32_t, double>(year)) {
return false;
}
// IsValidIsoDate does not care about years that are "out of Temporal range",
// that gets handled later.
int32_t year_int = static_cast<int32_t>(year);
uint8_t month_int = static_cast<uint8_t>(month);
// 2. Let daysInMonth be ISODaysInMonth(year, month).
// 3. If day < 1 or day > daysInMonth, then
if (day < 1 || day > ISODaysInMonth(year_int, month_int)) {
// a. Return false.
return false;
}
// 4. Return true.
return true;
}
// ====== Options getters ======
// https://tc39.es/proposal-temporal/#sec-temporal-tomonthcode
Maybe<std::string> ToMonthCode(Isolate* isolate,
DirectHandle<Object> argument) {
static constexpr char kMonthCodeOutOfRange[] = "Month code out of range.";
// 1. Let monthCode be ?ToPrimitive(argument, string).
DirectHandle<Object> mc_prim;
if (IsJSReceiver(*argument)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, mc_prim,
JSReceiver::ToPrimitive(isolate, Cast<JSReceiver>(argument),
ToPrimitiveHint::kString));
} else {
mc_prim = argument;
}
// 2. If monthCode is not a String, throw a TypeError exception.
if (!IsString(*mc_prim)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kMonthCodeOutOfRange));
}
auto month_code = Cast<String>(*mc_prim)->ToStdString();
// 3. If the length of monthCode is not 3 or 4, throw a RangeError exception.
if (month_code.size() != 3 && month_code.size() != 4) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kMonthCodeOutOfRange));
}
// 4. If the first code unit of monthCode is not 0x004D (LATIN CAPITAL LETTER
// M), throw a RangeError exception.
if (month_code[0] != 'M') {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kMonthCodeOutOfRange));
}
// 5. If the second code unit of monthCode is not in the inclusive interval
// from 0x0030 (DIGIT ZERO) to 0x0039 (DIGIT NINE), throw a RangeError
// exception.
if (month_code[1] < '0' || month_code[1] > '9') {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kMonthCodeOutOfRange));
}
// 6. If the third code unit of monthCode is not in the inclusive interval
// from 0x0030 (DIGIT ZERO) to 0x0039 (DIGIT NINE), throw a RangeError
// exception.
if (month_code[2] < '0' || month_code[2] > '9') {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kMonthCodeOutOfRange));
}
// 7. If the length of monthCode is 4 and the fourth code unit of monthCode is
// not 0x004C (LATIN CAPITAL LETTER L), throw a RangeError exception.
if (month_code.size() == 4 && month_code[3] != 'L') {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kMonthCodeOutOfRange));
}
// 8. Let monthCodeDigits be the substring of monthCode from 1 to 3.
// 9. Let monthCodeInteger be ℝ(StringToNumber(monthCodeDigits)).
// 10. If monthCodeInteger is 0 and the length of monthCode is not 4, throw a
// RangeError exception.
if (month_code[1] == '0' && month_code[2] == '0' && month_code.size() != 4) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kMonthCodeOutOfRange));
}
// 11. Return monthCode.
return Just(month_code);
#undef MONTHCODE_RANGE_ERROR
}
// https://tc39.es/proposal-temporal/#sec-temporal-totemporaloverflow
// Also handles the undefined check from GetOptionsObject
Maybe<temporal_rs::ArithmeticOverflow> ToTemporalOverflowHandleUndefined(
Isolate* isolate, MaybeDirectHandle<Object> maybe_options,
const char* method_name) {
DirectHandle<Object> options;
// Default is "constrain"
if (!maybe_options.ToHandle(&options) || IsUndefined(*options)) {
return Just(temporal_rs::ArithmeticOverflow(
temporal_rs::ArithmeticOverflow::Constrain));
}
auto overflow_ident = isolate->factory()->overflow_string();
if (!IsJSReceiver(*options)) {
// (GetOptionsObject) 3. Throw a TypeError exception.
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR_WITH_ARG(
kOptionMustBeObject, overflow_ident));
}
// 2. Return ? GetOption(options, "overflow", « String », « "constrain",
// "reject" », "constrain").
return GetStringOption<temporal_rs::ArithmeticOverflow>(
isolate, Cast<JSReceiver>(options), overflow_ident, method_name,
std::to_array<const std::string_view>({"constrain", "reject"}),
std::to_array<temporal_rs::ArithmeticOverflow>(
{temporal_rs::ArithmeticOverflow::Constrain,
temporal_rs::ArithmeticOverflow::Reject}),
temporal_rs::ArithmeticOverflow::Constrain);
}
// https://tc39.es/proposal-temporal/#sec-temporal-getdirectionoption
Maybe<temporal_rs::TransitionDirection> GetDirectionOption(
Isolate* isolate, DirectHandle<JSReceiver> options,
const char* method_name) {
// 1. Let stringValue be ? GetOption(options, "direction", string, « "next",
// "previous" », required).
temporal_rs::TransitionDirection dir;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, dir,
GetStringOption<temporal_rs::TransitionDirection>(
isolate, Cast<JSReceiver>(options),
isolate->factory()->direction_string(), method_name,
std::to_array<const std::string_view>({"next", "previous"}),
std::to_array<temporal_rs::TransitionDirection>(
{temporal_rs::TransitionDirection::Next,
temporal_rs::TransitionDirection::Previous}),
std::nullopt));
return Just(dir);
}
// https://tc39.es/proposal-temporal/#sec-temporal-gettemporaldisambiguationoption
// Also handles the undefined check from GetOptionsObject
Maybe<temporal_rs::Disambiguation>
GetTemporalDisambiguationOptionHandleUndefined(Isolate* isolate,
DirectHandle<Object> options,
const char* method_name) {
// Default is "compatible"
if (IsUndefined(*options)) {
return Just(
temporal_rs::Disambiguation(temporal_rs::Disambiguation::Compatible));
}
auto disambiguation_ident = isolate->factory()->disambiguation_string();
if (!IsJSReceiver(*options)) {
// (GetOptionsObject) 3. Throw a TypeError exception.
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR_WITH_ARG(
kOptionMustBeObject, disambiguation_ident));
}
// 1. Let stringValue be ?GetOption(options, "disambiguation", string, «
// "compatible", "earlier", "later", "reject" », "compatible").
return GetStringOption<temporal_rs::Disambiguation>(
isolate, Cast<JSReceiver>(options), disambiguation_ident, method_name,
std::to_array<const std::string_view>(
{"compatible", "earlier", "later", "reject"}),
std::to_array<temporal_rs::Disambiguation>({
temporal_rs::Disambiguation::Compatible,
temporal_rs::Disambiguation::Earlier,
temporal_rs::Disambiguation::Later,
temporal_rs::Disambiguation::Reject,
}),
temporal_rs::Disambiguation::Compatible);
}
// https://tc39.es/proposal-temporal/#sec-temporal-gettemporaloffsetoption
// Also handles the undefined check from GetOptionsObject
Maybe<temporal_rs::OffsetDisambiguation> GetTemporalOffsetOptionHandleUndefined(
Isolate* isolate, DirectHandle<Object> options,
temporal_rs::OffsetDisambiguation fallback, const char* method_name) {
// Default is fallback
if (IsUndefined(*options)) {
return Just(fallback);
}
auto offset_ident = isolate->factory()->offset_string();
if (!IsJSReceiver(*options)) {
// (GetOptionsObject) 3. Throw a TypeError exception.
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR_WITH_ARG(
kOptionMustBeObject, offset_ident));
}
// 5. Let stringValue be ? GetOption(options, "offset", string, « "prefer",
// "use", "ignore", "reject" », stringFallback).
return GetStringOption<temporal_rs::OffsetDisambiguation>(
isolate, Cast<JSReceiver>(options), offset_ident, method_name,
std::to_array<const std::string_view>(
{"prefer", "use", "ignore", "reject"}),
std::to_array<temporal_rs::OffsetDisambiguation>({
temporal_rs::OffsetDisambiguation::Prefer,
temporal_rs::OffsetDisambiguation::Use,
temporal_rs::OffsetDisambiguation::Ignore,
temporal_rs::OffsetDisambiguation::Reject,
}),
fallback);
}
// https://tc39.es/proposal-temporal/#sec-temporal-gettemporalfractionalseconddigitsoption
Maybe<temporal_rs::Precision> GetTemporalFractionalSecondDigitsOption(
Isolate* isolate, DirectHandle<JSReceiver> normalized_options,
const char* method_name) {
auto auto_val =
temporal_rs::Precision{.is_minute = false, .precision = std::nullopt};
Factory* factory = isolate->factory();
// 1. Let digitsValue be ?Get(options, "fractionalSecondDigits").
DirectHandle<Object> digits_val;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, digits_val,
JSReceiver::GetProperty(isolate, normalized_options,
factory->fractionalSecondDigits_string()));
// 2. If digitsValue is undefined, return auto.
if (IsUndefined(*digits_val)) {
return Just(auto_val);
}
// 3. If digitsValue is not a Number, then
if (!IsNumber(*digits_val)) {
DirectHandle<String> string;
ASSIGN_RETURN_ON_EXCEPTION(isolate, string,
Object::ToString(isolate, digits_val));
// a. If ? ToString(digitsValue) is not "auto", throw a RangeError
// exception.
if (!String::Equals(isolate, string, factory->auto_string())) {
THROW_NEW_ERROR(isolate,
NewRangeError(MessageTemplate::kPropertyValueOutOfRange,
factory->fractionalSecondDigits_string()));
}
// b. Return auto.
return Just(auto_val);
}
// 4. If digitsValue is NaN, +∞𝔽, or -∞𝔽, throw a RangeError exception.
auto digits_num = Cast<Number>(*digits_val);
auto digits_float = Object::NumberValue(digits_num);
if (std::isnan(digits_float) || std::isinf(digits_float)) {
THROW_NEW_ERROR(isolate,
NewRangeError(MessageTemplate::kPropertyValueOutOfRange,
factory->fractionalSecondDigits_string()));
}
// 5. Let digitCount be floor(ℝ(digitsValue)).
double digit_count = std::floor(digits_float);
// 6. If digitCount < 0 or digitCount > 9, throw a RangeError exception.
if (digit_count < 0 || digit_count > 9) {
THROW_NEW_ERROR(isolate,
NewRangeError(MessageTemplate::kPropertyValueOutOfRange,
factory->fractionalSecondDigits_string()));
}
uint8_t clamped = CastIntegralDouble<uint8_t>(digit_count);
return Just(
temporal_rs::Precision{.is_minute = false, .precision = clamped});
}
// https://tc39.es/proposal-temporal/#sec-temporal-gettemporalunitvaluedoption
Maybe<std::optional<Unit>> GetTemporalUnitValuedOption(
Isolate* isolate, DirectHandle<JSReceiver> normalized_options,
DirectHandle<String> key, DefaultValue default_value,
const char* method_name) {
// 1. 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.
// 2. 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.
constexpr auto strs = std::to_array<const std::string_view>(
{"year", "month", "week", "day", "hour",
"minute", "second", "millisecond", "microsecond", "nanosecond",
"auto", "years", "months", "weeks", "days",
"hours", "minutes", "seconds", "milliseconds", "microseconds",
"nanoseconds"});
constexpr auto enums = std::to_array<const std::optional<Unit::Value>>(
{Unit::Year, Unit::Month, Unit::Week,
Unit::Day, Unit::Hour, Unit::Minute,
Unit::Second, Unit::Millisecond, Unit::Microsecond,
Unit::Nanosecond, Unit::Auto, Unit::Year,
Unit::Month, Unit::Week, Unit::Day,
Unit::Hour, Unit::Minute, Unit::Second,
Unit::Millisecond, Unit::Microsecond, Unit::Nanosecond});
// 4. If default is unset, then
// a. Let defaultValue be undefined.
std::optional<std::optional<Unit>> wrapped_default = std::nullopt;
if (default_value == DefaultValue::kUnset) {
// GetStringOption treats a null default as REQUIRED
// however, we also wish to handle undefined/UNSET here, which we
// represent as a None value in the inner optional
wrapped_default = std::make_optional(std::optional<Unit>(std::nullopt));
}
// 6. Let value be ? GetOption(options, key, string, allowedStrings,
// defaultValue).
std::optional<Unit::Value> value;
ASSIGN_RETURN_ON_EXCEPTION(isolate, value,
GetStringOption<std::optional<Unit::Value>>(
isolate, normalized_options, key, method_name,
strs, enums, wrapped_default));
// 7. If value is undefined, return unset.
// 8. If value is "auto", return auto.
// 9. 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 (value.has_value()) {
return Just<std::optional<Unit>>((Unit)value.value());
} else {
return Just<std::optional<Unit>>(std::nullopt);
}
}
Maybe<void> ValidateTemporalUnitValue(
Isolate* isolate, std::optional<Unit> value_or_unset, UnitGroup unit_group,
std::optional<Unit> extra_values = std::nullopt) {
// 1. If value is unset, return unused.
if (!value_or_unset.has_value()) {
return JustVoid();
}
auto value = value_or_unset.value();
// 2. If extraValues is present and extraValues contains value, return unused.
if (extra_values == value) {
return JustVoid();
}
// 3. 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.
// 4. If category is date and unitGroup is date or datetime, return unused.
// 5. If category is time and unitGroup is time or datetime, return unused.
switch (value) {
case Unit::Auto:
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_RANGE_ERROR("Auto unit not allowed here"));
case Unit::Year:
case Unit::Month:
case Unit::Week:
case Unit::Day:
if (unit_group == UnitGroup::kDate ||
unit_group == UnitGroup::kDateTime) {
return JustVoid();
} else {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(
"Found date unit, expect time unit"));
}
default:
if (unit_group == UnitGroup::kTime ||
unit_group == UnitGroup::kDateTime) {
return JustVoid();
}
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(
"Found date unit, expect time unit"));
}
// 6. Throw a RangeError exception.
// (done in branches above)
}
// https://tc39.es/proposal-temporal/#sec-temporal-canonicalizecalendar
Maybe<temporal_rs::AnyCalendarKind> CanonicalizeCalendar(
Isolate* isolate, DirectHandle<String> calendar) {
std::string s = calendar->ToStdString();
// 2. If calendars does not contain the ASCII-lowercase of id, throw a
// RangeError exception.
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
auto cal = temporal_rs::AnyCalendarKind::get_for_str(s);
if (!cal.has_value()) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR_WITH_ARG(
"Unknown calendar type", calendar));
}
// Other steps unnecessary, we're not storing these as -u- values but rather
// as enums.
return Just(cal.value());
}
// https://tc39.es/proposal-temporal/#sec-temporal-getroundingincrementoption
Maybe<uint32_t> GetRoundingIncrementOption(
Isolate* isolate, DirectHandle<JSReceiver> normalized_options) {
DirectHandle<Object> value;
// 1. Let value be ? Get(options, "roundingIncrement").
ASSIGN_RETURN_ON_EXCEPTION(
isolate, value,
JSReceiver::GetProperty(isolate, normalized_options,
isolate->factory()->roundingIncrement_string()));
// 2. If value is undefined, return 1𝔽.
if (IsUndefined(*value)) {
return Just(static_cast<uint32_t>(1));
}
// 3. Let integerIncrement be ? ToIntegerWithTruncation(value).
double integer_increment;
ASSIGN_RETURN_ON_EXCEPTION(isolate, integer_increment,
ToIntegerWithTruncation(isolate, value));
// 4. If integerIncrement < 1 or integerIncrement > 10**9, throw a RangeError
// exception.
if (integer_increment < 1 || integer_increment > 1e9) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kIntegerOutOfRange));
}
return Just(static_cast<uint32_t>(integer_increment));
}
// sec-temporal-getroundingmodeoption
Maybe<RoundingMode> GetRoundingModeOption(Isolate* isolate,
DirectHandle<JSReceiver> options,
RoundingMode fallback,
const char* method_name) {
// 1. Return ? GetOption(normalizedOptions, "roundingMode", "string", «
// "ceil", "floor", "expand", "trunc", "halfCeil", "halfFloor", "halfExpand",
// "halfTrunc", "halfEven" », fallback).
static const auto values = std::to_array<RoundingMode>(
{RoundingMode::Ceil, RoundingMode::Floor, RoundingMode::Expand,
RoundingMode::Trunc, RoundingMode::HalfCeil, RoundingMode::HalfFloor,
RoundingMode::HalfExpand, RoundingMode::HalfTrunc,
RoundingMode::HalfEven});
return GetStringOption<RoundingMode>(
isolate, options, isolate->factory()->roundingMode_string(), method_name,
std::to_array<const std::string_view>(
{"ceil", "floor", "expand", "trunc", "halfCeil", "halfFloor",
"halfExpand", "halfTrunc", "halfEven"}),
values, fallback);
}
// https://tc39.es/proposal-temporal/#sec-temporal-getshowoffsetoption
Maybe<temporal_rs::DisplayOffset> GetTemporalShowOffsetOption(
Isolate* isolate, DirectHandle<JSReceiver> options,
const char* method_name) {
// 1. Let stringValue be ?GetOption(options, "offset", string, « "auto",
// "never" », "auto").
// 2. If stringValue is "never", return never.
// 3. Return auto.
static const auto values = std::to_array<temporal_rs::DisplayOffset>(
{temporal_rs::DisplayOffset::Auto, temporal_rs::DisplayOffset::Never});
return GetStringOption<temporal_rs::DisplayOffset>(
isolate, options, isolate->factory()->offset_string(), method_name,
std::to_array<const std::string_view>({"auto", "never"}), values,
temporal_rs::DisplayOffset::Auto);
}
// https://tc39.es/proposal-temporal/#sec-temporal-gettemporalshowtimezonenameoption
Maybe<temporal_rs::DisplayTimeZone> GetTemporalShowTimeZoneNameOption(
Isolate* isolate, DirectHandle<JSReceiver> options,
const char* method_name) {
// 1. Let stringValue be ? GetOption(options, "timeZoneName", string, «
// "auto", "never", "critical" », "auto").
// 2. If stringValue is "never", return never.
// 3. If stringValue is "critical", return critical.
static const auto values = std::to_array<temporal_rs::DisplayTimeZone>(
{temporal_rs::DisplayTimeZone::Auto, temporal_rs::DisplayTimeZone::Never,
temporal_rs::DisplayTimeZone::Critical});
return GetStringOption<temporal_rs::DisplayTimeZone>(
isolate, options, isolate->factory()->timeZoneName_string(), method_name,
std::to_array<const std::string_view>({"auto", "never", "critical"}),
values, temporal_rs::DisplayTimeZone::Auto);
}
// https://tc39.es/proposal-temporal/#sec-temporal-getdifferencesettings
//
// This does not perform any validity checks, it only does the minimum needed
// to construct a DifferenceSettings object. temporal_rs handles the rest
Maybe<temporal_rs::DifferenceSettings> GetDifferenceSettingsWithoutChecks(
Isolate* isolate, DirectHandle<Object> options_obj, UnitGroup unit_group,
std::optional<Unit> fallback_smallest_unit, const char* method_name) {
DirectHandle<JSReceiver> options;
// 1. Set options to ? GetOptionsObject(options).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options, GetOptionsObject(isolate, options_obj, method_name));
// 2. Let largestUnit be ? GetTemporalUnitValuedOption(options, "largestUnit",
// unset).
std::optional<Unit> largest_unit;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, largest_unit,
temporal::GetTemporalUnitValuedOption(
isolate, options, isolate->factory()->largestUnit_string(),
DefaultValue::kUnset, method_name));
// 4. Let roundingIncrement be ?GetRoundingIncrementOption(options).
uint32_t rounding_increment;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, rounding_increment,
temporal::GetRoundingIncrementOption(isolate, options));
// 5. Let roundingMode be ?GetRoundingModeOption(options, trunc).
RoundingMode rounding_mode;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, rounding_mode,
temporal::GetRoundingModeOption(isolate, options, RoundingMode::Trunc,
method_name));
// 9. Let smallestUnit be ? GetTemporalUnitValuedOption(options,
// "smallestUnit", unset).
std::optional<Unit> smallest_unit;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, smallest_unit,
temporal::GetTemporalUnitValuedOption(
isolate, options, isolate->factory()->smallestUnit_string(),
DefaultValue::kUnset, method_name));
// remaining steps are validation, to be performed later
return Just(temporal_rs::DifferenceSettings{.largest_unit = largest_unit,
.smallest_unit = smallest_unit,
.rounding_mode = rounding_mode,
.increment = rounding_increment});
}
// https://tc39.es/proposal-temporal/#sec-temporal-gettemporalshowcalendarnameoption
Maybe<temporal_rs::DisplayCalendar> GetTemporalShowCalendarNameOption(
Isolate* isolate, DirectHandle<JSReceiver> options,
const char* method_name) {
// 1. Return ? GetOption(normalizedOptions, "calendarName", « String », «
// "auto", "always", "never" », "auto").
return GetStringOption<temporal_rs::DisplayCalendar>(
isolate, options, isolate->factory()->calendarName_string(), method_name,
std::to_array<const std::string_view>(
{"auto", "always", "never", "critical"}),
std::to_array<temporal_rs::DisplayCalendar>(
{temporal_rs::DisplayCalendar::Auto,
temporal_rs::DisplayCalendar::Always,
temporal_rs::DisplayCalendar::Never,
temporal_rs::DisplayCalendar::Critical}),
temporal_rs::DisplayCalendar::Auto);
}
// convenience method for getting the "calendar" field off of a Temporal object
std::optional<temporal_rs::AnyCalendarKind> ExtractCalendarFrom(
Isolate* isolate, Tagged<HeapObject> calendar_like) {
InstanceType instance_type = calendar_like->map()->instance_type();
// a. If temporalCalendarLike has an [[InitializedTemporalDate]],
// [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]],
// [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]]
// internal slot, then
//
// i. Return temporalCalendarLike.[[Calendar]].
if (InstanceTypeChecker::IsJSTemporalPlainDate(instance_type)) {
return Cast<JSTemporalPlainDate>(calendar_like)
->wrapped_rust()
->calendar()
.kind();
} else if (InstanceTypeChecker::IsJSTemporalPlainDateTime(instance_type)) {
return Cast<JSTemporalPlainDateTime>(calendar_like)
->wrapped_rust()
->calendar()
.kind();
} else if (InstanceTypeChecker::IsJSTemporalPlainMonthDay(instance_type)) {
return Cast<JSTemporalPlainMonthDay>(calendar_like)
->wrapped_rust()
->calendar()
.kind();
} else if (InstanceTypeChecker::IsJSTemporalPlainYearMonth(instance_type)) {
return Cast<JSTemporalPlainYearMonth>(calendar_like)
->wrapped_rust()
->calendar()
.kind();
} else if (InstanceTypeChecker::IsJSTemporalZonedDateTime(instance_type)) {
return Cast<JSTemporalZonedDateTime>(calendar_like)
->wrapped_rust()
->calendar()
.kind();
}
return std::nullopt;
}
// https://tc39.es/proposal-temporal/#sec-temporal-gettemporalcalendarslotvaluewithisodefault
Maybe<temporal_rs::AnyCalendarKind> ToTemporalCalendarIdentifier(
Isolate* isolate, DirectHandle<Object> calendar_like) {
// 1. If temporalCalendarLike is an Object, then
if (IsHeapObject(*calendar_like)) {
// a. If temporalCalendarLike has an [[InitializedTemporalDate]],
// [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]],
// [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]]
// internal slot, then
//
// i. Return temporalCalendarLike.[[Calendar]].
auto cal_field =
ExtractCalendarFrom(isolate, Cast<HeapObject>(*calendar_like));
if (cal_field.has_value()) {
return Just(cal_field.value());
}
}
// 2. If temporalCalendarLike is not a String, throw a TypeError exception.
if (!IsString(*calendar_like)) {
THROW_NEW_ERROR(
isolate, NEW_TEMPORAL_TYPE_ERROR(
"Calendar must be string or calendared Temporal object."));
}
auto stdstr = Cast<String>(calendar_like)->ToStdString();
// 3. Let identifier be ?ParseTemporalCalendarString(temporalCalendarLike).
// 4. Return ?CanonicalizeCalendar(identifier).
auto kind =
temporal_rs::AnyCalendarKind::parse_temporal_calendar_string(stdstr);
if (kind.has_value()) {
return Just(kind.value());
} else {
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_RANGE_ERROR("Invalid calendar string"));
}
}
// https://tc39.es/proposal-temporal/#sec-temporal-gettemporalcalendarslotvaluewithisodefault
Maybe<temporal_rs::AnyCalendarKind> GetTemporalCalendarIdentifierWithISODefault(
Isolate* isolate, DirectHandle<JSReceiver> options) {
// 1. If item has an [[InitializedTemporalDate]],
// [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]],
// [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]]
// internal slot, then
//
// a. Return item.[[Calendar]].
if (IsHeapObject(*options)) {
auto cal_field = ExtractCalendarFrom(isolate, Cast<HeapObject>(*options));
if (cal_field.has_value()) {
return Just(cal_field.value());
}
}
// 2. Let calendarLike be ?Get(item, "calendar").
DirectHandle<Object> calendar;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, calendar,
JSReceiver::GetProperty(isolate, options,
isolate->factory()->calendar_string()));
// 3. If calendarLike is undefined, then
if (IsUndefined(*calendar)) {
// a. Return "iso8601".
return Just(
temporal_rs::AnyCalendarKind(temporal_rs::AnyCalendarKind::Iso));
}
return ToTemporalCalendarIdentifier(isolate, calendar);
}
constexpr temporal_rs::ToStringRoundingOptions kToStringAuto =
temporal_rs::ToStringRoundingOptions{
.precision = temporal_rs::Precision{.is_minute = false,
.precision = std::nullopt},
.smallest_unit = std::nullopt,
.rounding_mode = std::nullopt,
};
// ====== Stringification operations ======
// Generic code for calling to_string()
//
// It has two varargs since there may be some implicit conversions between
// the args passed to this method and the args expected by Rust.
//
// https://tc39.es/proposal-temporal/#sec-temporal-temporaldurationtostring
// https://tc39.es/proposal-temporal/#sec-temporal-temporalinstanttostring
// https://tc39.es/proposal-temporal/#sec-temporal-temporalplaindatetostring
// https://tc39.es/proposal-temporal/#sec-temporal-temporaltimerecordtostring
// https://tc39.es/proposal-temporal/#sec-temporal-temporalisodatetimetostring
// https://tc39.es/proposal-temporal/#sec-temporal-temporalplainyearmonthtostring
// https://tc39.es/proposal-temporal/#sec-temporal-temporalplainmonthdaytostring
// https://tc39.es/proposal-temporal/#sec-temporal-temporalplainzoneddatetimetostring
template <typename JSType, typename... Args, typename... Args2>
MaybeDirectHandle<String> GenericTemporalToString(
Isolate* isolate, DirectHandle<JSType> val,
std::string (JSType::RustType::*method)(Args2...) const, Args... args) {
auto rust_object = val->wrapped_rust();
// This is currently inefficient, can be improved after
// https://github.com/rust-diplomat/diplomat/issues/866 is fixed
auto output = (rust_object.raw()->*method)(args...);
IncrementalStringBuilder builder(isolate);
builder.AppendString(output);
return builder.Finish().ToHandleChecked();
}
// Same as the above, but bubbles out Rust errors
template <typename JSType, typename... Args, typename... Args2>
MaybeDirectHandle<String> GenericTemporalToString(
Isolate* isolate, DirectHandle<JSType> val,
TemporalResult<std::string> (JSType::RustType::*method)(Args2...) const,
Args... args) {
auto rust_object = val->wrapped_rust();
std::string output;
// This is currently inefficient, can be improved after
// https://github.com/rust-diplomat/diplomat/issues/866 is fixed
MOVE_RETURN_ON_EXCEPTION(
isolate, output,
ExtractRustResult(isolate, (rust_object.raw()->*method)(args...)));
IncrementalStringBuilder builder(isolate);
builder.AppendString(output);
return builder.Finish().ToHandleChecked();
}
// ====== Record operations ======
constexpr temporal_rs::PartialDate kNullPartialDate = temporal_rs::PartialDate{
.year = std::nullopt,
.month = std::nullopt,
.month_code = "",
.day = std::nullopt,
.era = "",
.era_year = std::nullopt,
.calendar = temporal_rs::AnyCalendarKind::Iso,
};
constexpr temporal_rs::PartialTime kNullPartialTime = temporal_rs::PartialTime{
.hour = std::nullopt,
.minute = std::nullopt,
.second = std::nullopt,
.millisecond = std::nullopt,
.microsecond = std::nullopt,
.nanosecond = std::nullopt,
};
constexpr temporal_rs::PartialZonedDateTime kNullPartialZonedDateTime =
temporal_rs::PartialZonedDateTime{
.date = kNullPartialDate,
.time = kNullPartialTime,
.offset = std::nullopt,
.timezone = std::nullopt,
};
constexpr temporal_rs::PartialDateTime kNullPartialDateTime =
temporal_rs::PartialDateTime{.date = kNullPartialDate,
.time = kNullPartialTime};
struct TimeRecord {
std::optional<double> hour = std::nullopt;
std::optional<double> minute = std::nullopt;
std::optional<double> second = std::nullopt;
std::optional<double> millisecond = std::nullopt;
std::optional<double> microsecond = std::nullopt;
std::optional<double> nanosecond = std::nullopt;
Maybe<temporal_rs::PartialTime> Regulate(
Isolate* isolate, temporal_rs::ArithmeticOverflow overflow);
};
// https://tc39.es/proposal-temporal/#sec-temporal-regulatetime
//
// N.B. The spec implicitly assumes all fields are set; however
// we plan to call this in contexts where they are not (e.g. during
// .with(). Fortunately, )
Maybe<temporal_rs::PartialTime> TimeRecord::Regulate(
Isolate* isolate, temporal_rs::ArithmeticOverflow overflow) {
temporal_rs::PartialTime partial = kNullPartialTime;
// 1. If overflow is constrain, then
if (overflow == temporal_rs::ArithmeticOverflow::Constrain) {
// a. Set hour to the result of clamping hour between 0 and 23.
if (hour.has_value()) {
partial.hour = ClampIntegralDouble<uint8_t>(hour.value(), 0, 23);
}
// b. Set minute to the result of clamping minute between 0 and 59.
if (minute.has_value()) {
partial.minute = ClampIntegralDouble<uint8_t>(minute.value(), 0, 59);
}
// c. Set second to the result of clamping second between 0 and 59.
if (second.has_value()) {
partial.second = ClampIntegralDouble<uint8_t>(second.value(), 0, 59);
}
// d. Set millisecond to the result of clamping millisecond between 0 and
// 999.
if (millisecond.has_value()) {
partial.millisecond =
ClampIntegralDouble<uint16_t>(millisecond.value(), 0, 999);
}
// e. Set microsecond to the result of clamping microsecond between 0 and
// 999.
if (microsecond.has_value()) {
partial.microsecond =
ClampIntegralDouble<uint16_t>(microsecond.value(), 0, 999);
}
// f. Set nanosecond to the result of clamping nanosecond between 0 and 999.
if (nanosecond.has_value()) {
partial.nanosecond =
ClampIntegralDouble<uint16_t>(nanosecond.value(), 0, 999);
}
} else {
// b. If IsValidTime(hour, minute, second, millisecond, microsecond,
// nanosecond) is false, throw a RangeError exception.
if (!IsValidTime(hour.value_or(0), minute.value_or(0), second.value_or(0),
millisecond.value_or(0), microsecond.value_or(0),
nanosecond.value_or(0))) {
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_RANGE_ERROR("Invalid time provided"));
}
if (hour.has_value()) {
partial.hour = CastIntegralDouble<uint8_t>(hour.value());
}
if (minute.has_value()) {
partial.minute = CastIntegralDouble<uint8_t>(minute.value());
}
if (second.has_value()) {
partial.second = CastIntegralDouble<uint8_t>(second.value());
}
if (millisecond.has_value()) {
partial.millisecond = CastIntegralDouble<uint16_t>(millisecond.value());
}
if (microsecond.has_value()) {
partial.microsecond = CastIntegralDouble<uint16_t>(microsecond.value());
}
if (nanosecond.has_value()) {
partial.nanosecond = CastIntegralDouble<uint16_t>(nanosecond.value());
}
}
return Just(partial);
}
template <typename RustObject>
temporal_rs::PartialTime GetPartialTimeFromRust(RustObject& rust_object) {
return temporal_rs::PartialTime{
.hour = rust_object->hour(),
.minute = rust_object->minute(),
.second = rust_object->second(),
.millisecond = rust_object->millisecond(),
.microsecond = rust_object->microsecond(),
.nanosecond = rust_object->nanosecond(),
};
}
// These can eventually be replaced with methods upstream
temporal_rs::PartialTime GetPartialTime(
DirectHandle<JSTemporalPlainTime> plain_time) {
auto rust_object = plain_time->wrapped_rust();
return GetPartialTimeFromRust(rust_object);
}
temporal_rs::PartialTime GetPartialTime(
DirectHandle<JSTemporalPlainDateTime> date_time) {
auto rust_object = date_time->wrapped_rust();
return GetPartialTimeFromRust(rust_object);
}
temporal_rs::PartialTime GetPartialTime(
DirectHandle<JSTemporalZonedDateTime> zoned_date_time) {
auto rust_object = zoned_date_time->wrapped_rust();
return GetPartialTimeFromRust(rust_object);
}
template <typename RustObject>
temporal_rs::PartialDate GetPartialDateFromRust(RustObject& rust_object) {
return temporal_rs::PartialDate{
.year = rust_object->year(),
.month = rust_object->month(),
.month_code = "",
.day = rust_object->day(),
.era = "",
.era_year = std::nullopt,
.calendar = rust_object->calendar().kind(),
};
}
temporal_rs::PartialDate GetPartialDate(
DirectHandle<JSTemporalPlainDate> plain_date) {
auto rust_object = plain_date->wrapped_rust();
return GetPartialDateFromRust(rust_object);
}
temporal_rs::PartialDate GetPartialDate(
DirectHandle<JSTemporalPlainDateTime> date_time) {
auto rust_object = date_time->wrapped_rust();
return GetPartialDateFromRust(rust_object);
}
temporal_rs::PartialDate GetPartialDate(
DirectHandle<JSTemporalZonedDateTime> zoned_date_time) {
auto rust_object = zoned_date_time->wrapped_rust();
return GetPartialDateFromRust(rust_object);
}
temporal_rs::PartialDateTime GetPartialDateTime(
DirectHandle<JSTemporalPlainDate> plain_date) {
auto rust_object = plain_date->wrapped_rust();
return temporal_rs::PartialDateTime{
.date = GetPartialDateFromRust(rust_object),
.time = kNullPartialTime,
};
}
temporal_rs::PartialDateTime GetPartialDateTime(
DirectHandle<JSTemporalPlainDateTime> date_time) {
auto rust_object = date_time->wrapped_rust();
return temporal_rs::PartialDateTime{
.date = GetPartialDateFromRust(rust_object),
.time = GetPartialTimeFromRust(rust_object),
};
}
temporal_rs::PartialDateTime GetPartialDateTime(
DirectHandle<JSTemporalZonedDateTime> zoned_date_time) {
auto rust_object = zoned_date_time->wrapped_rust();
return temporal_rs::PartialDateTime{
.date = GetPartialDateFromRust(rust_object),
.time = GetPartialTimeFromRust(rust_object),
};
}
// Helper for ToTemporalPartialDurationRecord
// Maybe<std::optional> since the Maybe handles errors and the optional handles
// missing fields
Maybe<std::optional<double>> GetSingleDurationField(
Isolate* isolate, DirectHandle<JSReceiver> duration_like,
DirectHandle<String> field_name) {
DirectHandle<Object> val;
// Let val be ? Get(temporalDurationLike, fieldName).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, val,
JSReceiver::GetProperty(isolate, duration_like, field_name));
// c. If val is not undefined, then
if (IsUndefined(*val)) {
return Just((std::optional<double>)std::nullopt);
} else {
double field;
// 5. If val is not undefined, set result.[[val]] to
// ?ToIntegerIfIntegral(val).
ASSIGN_RETURN_ON_EXCEPTION(isolate, field,
temporal::ToIntegerIfIntegral(isolate, val));
return Just(std::optional(field));
}
}
// GetSingleDurationField, but also casts into an integer, erroring if out of
// range.
//
// Can be removed once https://github.com/boa-dev/temporal/issues/189 has been
// fixed.
Maybe<std::optional<int64_t>> GetSingleDurationFieldInteger(
Isolate* isolate, DirectHandle<JSReceiver> duration_like,
DirectHandle<String> field_name) {
std::optional<double> ret_opt;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, ret_opt,
GetSingleDurationField(isolate, duration_like, field_name));
if (ret_opt.has_value()) {
double ret = ret_opt.value();
if (!base::IsValueInRangeForNumericType<int64_t, double>(ret)) {
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_RANGE_ERROR("Duration field out of range."));
}
return Just(std::optional(static_cast<int64_t>(ret)));
} else {
return Just((std::optional<int64_t>)std::nullopt);
}
}
// https://tc39.es/proposal-temporal/#sec-temporal-tooffsetstring
Maybe<std::string> ToOffsetString(Isolate* isolate,
DirectHandle<Object> argument) {
// 1. Let offset be ?ToPrimitive(argument, string).
DirectHandle<Object> offset_prim;
if (IsJSReceiver(*argument)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, offset_prim,
JSReceiver::ToPrimitive(isolate, Cast<JSReceiver>(argument),
ToPrimitiveHint::kString));
} else {
offset_prim = argument;
}
// 2. If offset is not a String, throw a TypeError exception.
if (!IsString(*offset_prim)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR("Offset must be string."));
}
// 3. Perform ?ParseDateTimeUTCOffset(offset).
auto offset_str = Cast<String>(offset_prim);
auto offset = offset_str->ToStdString();
// TODO(manishearth) this has a minor unnecessary cost of allocating a
// TimeZone, but it can be obviated once
// https://github.com/boa-dev/temporal/issues/330 is fixed.
RETURN_ON_EXCEPTION(
isolate,
ExtractRustResult(isolate,
temporal_rs::TimeZone::try_from_offset_str(offset)));
return Just(std::move(offset));
}
Maybe<temporal_rs::TimeZone> ToTemporalTimeZoneIdentifier(
Isolate* isolate, DirectHandle<Object> tz_like) {
// 1. If temporalTimeZoneLike is an Object, then
// a. If temporalTimeZoneLike has an [[InitializedTemporalZonedDateTime]]
// internal slot, then
if (IsJSTemporalZonedDateTime(*tz_like)) {
// i. Return temporalTimeZoneLike.[[TimeZone]].
return Just(
Cast<JSTemporalZonedDateTime>(tz_like)->wrapped_rust()->timezone());
}
// 2. If temporalTimeZoneLike is not a String, throw a TypeError exception.
if (!IsString(*tz_like)) {
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_TYPE_ERROR(
"Time zone must be string or ZonedDateTime object."));
}
DirectHandle<String> str = Cast<String>(tz_like);
auto std_str = str->ToStdString();
return ExtractRustResult(isolate,
temporal_rs::TimeZone::try_from_str_with_provider(
std_str, TimeZoneProvider()));
}
// https://tc39.es/proposal-temporal/#sec-temporal-totemporalpartialdurationrecord
Maybe<temporal_rs::PartialDuration> ToTemporalPartialDurationRecord(
Isolate* isolate, DirectHandle<Object> duration_like_obj) {
Factory* factory = isolate->factory();
// 1. If temporalDurationLike is not an Object, then
if (!IsJSReceiver(*duration_like_obj)) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_TYPE_ERROR("Must provide a duration."));
}
DirectHandle<JSReceiver> duration_like = Cast<JSReceiver>(duration_like_obj);
// 2. Let result be a new partial Duration Record with each field set to
// undefined.
auto result = temporal_rs::PartialDuration{
.years = std::nullopt,
.months = std::nullopt,
.weeks = std::nullopt,
.days = std::nullopt,
.hours = std::nullopt,
.minutes = std::nullopt,
.seconds = std::nullopt,
.milliseconds = std::nullopt,
.microseconds = std::nullopt,
.nanoseconds = std::nullopt,
};
// Steps 3-14: get each field in alphabetical order
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result.days,
temporal::GetSingleDurationFieldInteger(isolate, duration_like,
factory->days_string()));
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result.hours,
temporal::GetSingleDurationFieldInteger(isolate, duration_like,
factory->hours_string()));
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result.microseconds,
temporal::GetSingleDurationField(isolate, duration_like,
factory->microseconds_string()));
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result.milliseconds,
temporal::GetSingleDurationFieldInteger(isolate, duration_like,
factory->milliseconds_string()));
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result.minutes,
temporal::GetSingleDurationFieldInteger(isolate, duration_like,
factory->minutes_string()));
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result.months,
temporal::GetSingleDurationFieldInteger(isolate, duration_like,
factory->months_string()));
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result.nanoseconds,
temporal::GetSingleDurationField(isolate, duration_like,
factory->nanoseconds_string()));
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result.seconds,
temporal::GetSingleDurationFieldInteger(isolate, duration_like,
factory->seconds_string()));
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result.weeks,
temporal::GetSingleDurationFieldInteger(isolate, duration_like,
factory->weeks_string()));
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result.years,
temporal::GetSingleDurationFieldInteger(isolate, duration_like,
factory->years_string()));
// 24. If years is undefined, and months is undefined, and weeks is undefined,
// and days is undefined, and hours is undefined, and minutes is undefined,
// and seconds is undefined, and milliseconds is undefined, and microseconds
// is undefined, and nanoseconds is undefined, throw a TypeError exception.
if (!result.years.has_value() && !result.months.has_value() &&
!result.weeks.has_value() && !result.days.has_value() &&
!result.hours.has_value() && !result.minutes.has_value() &&
!result.seconds.has_value() && !result.milliseconds.has_value() &&
!result.microseconds.has_value() && !result.nanoseconds.has_value()) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(
"Did not provide any valid Duration fields."));
}
return Just(result);
}
// Helper for ToTemporalTimeRecord
// Maybe<std::optional> since the Maybe handles errors and the optional handles
// missing fields
Maybe<std::optional<double>> GetSingleTimeRecordField(
Isolate* isolate, DirectHandle<JSReceiver> time_like,
DirectHandle<String> field_name, bool* any) {
DirectHandle<Object> val;
// Let v be ?Get(temporalTimeLike, field_name).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, val, JSReceiver::GetProperty(isolate, time_like, field_name));
// If val is not undefined, then
if (!IsUndefined(*val)) {
double field;
// 5. a. Set result.[[Hour]] to ?ToIntegerWithTruncation(hour).
ASSIGN_RETURN_ON_EXCEPTION(isolate, field,
temporal::ToIntegerWithTruncation(isolate, val));
// b. Set any to true.
*any = true;
return Just(std::optional(field));
} else {
return Just((std::optional<double>)std::nullopt);
}
}
// https://tc39.es/proposal-temporal/#sec-temporal-ispartialtemporalobject
Maybe<bool> IsPartialTemporalObject(Isolate* isolate,
DirectHandle<Object> value) {
// 1. If value is not an Object, return false.
if (!IsHeapObject(*value)) {
return Just(false);
}
InstanceType instance_type = Cast<HeapObject>(*value)->map()->instance_type();
if (!InstanceTypeChecker::IsJSReceiver(instance_type)) {
return Just(false);
}
auto value_recvr = Cast<JSReceiver>(value);
// 2. If value has an [[InitializedTemporalDate]],
// [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]],
// [[InitializedTemporalTime]], [[InitializedTemporalYearMonth]], or
// [[InitializedTemporalZonedDateTime]] internal slot, return false.
if (InstanceTypeChecker::IsJSTemporalPlainDate(instance_type) ||
InstanceTypeChecker::IsJSTemporalPlainDateTime(instance_type) ||
InstanceTypeChecker::IsJSTemporalPlainMonthDay(instance_type) ||
InstanceTypeChecker::IsJSTemporalPlainTime(instance_type) ||
InstanceTypeChecker::IsJSTemporalPlainYearMonth(instance_type) ||
InstanceTypeChecker::IsJSTemporalZonedDateTime(instance_type)) {
return Just(false);
}
// 3. Let calendarProperty be ? Get(value, "calendar").
DirectHandle<Object> cal;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, cal,
JSReceiver::GetProperty(isolate, value_recvr,
isolate->factory()->calendar_string()));
// 4. If calendarProperty is not undefined, return false.
if (!IsUndefined(*cal)) {
return Just(false);
}
// 5. Let timeZoneProperty be ? Get(value, "timeZone").
DirectHandle<Object> tz;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, tz,
JSReceiver::GetProperty(isolate, value_recvr,
isolate->factory()->timeZone_string()));
// 6. If timeZoneProperty is not undefined, return false.
if (!IsUndefined(*tz)) {
return Just(false);
}
// 7. return true
return Just(true);
}
// https://tc39.es/proposal-temporal/#sec-temporal-totemporaltimerecord
Maybe<TimeRecord> ToTemporalTimeRecord(Isolate* isolate,
DirectHandle<JSReceiver> time_like,
const char* method_name,
Completeness completeness = kComplete) {
Factory* factory = isolate->factory();
// 2. If completeness is complete, then
// a. Let result be a new TemporalTimeLike Record with each field set to 0.
// 3. Else,
// a. Let result be a new TemporalTimeLike Record with each field set to
// unset.
auto result = completeness == kPartial ? TimeRecord {
.hour = std::nullopt,
.minute = std::nullopt,
.second = std::nullopt,
.millisecond = std::nullopt,
.microsecond = std::nullopt,
.nanosecond = std::nullopt,
} : TimeRecord {
.hour = 0,
.minute = 0,
.second = 0,
.millisecond = 0,
.microsecond = 0,
.nanosecond = 0,
};
bool any = false;
// Steps 3-14: get each field in alphabetical order
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result.hour,
temporal::GetSingleTimeRecordField(isolate, time_like,
factory->hour_string(), &any));
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result.microsecond,
temporal::GetSingleTimeRecordField(isolate, time_like,
factory->microsecond_string(), &any));
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result.millisecond,
temporal::GetSingleTimeRecordField(isolate, time_like,
factory->millisecond_string(), &any));
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result.minute,
temporal::GetSingleTimeRecordField(isolate, time_like,
factory->minute_string(), &any));
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result.nanosecond,
temporal::GetSingleTimeRecordField(isolate, time_like,
factory->nanosecond_string(), &any));
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result.second,
temporal::GetSingleTimeRecordField(isolate, time_like,
factory->second_string(), &any));
if (!any) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(
"Must specify at least one time field."));
}
return Just(result);
}
struct DateRecord {
std::optional<double> year;
std::optional<double> month;
std::optional<std::string> month_code;
std::optional<double> day;
std::optional<std::string> era;
std::optional<double> era_year;
temporal_rs::AnyCalendarKind calendar = temporal_rs::AnyCalendarKind::Iso;
Maybe<temporal_rs::PartialDate> Regulate(
Isolate* isolate,
temporal_rs::ArithmeticOverflow overflow) V8_LIFETIME_BOUND;
};
// https://tc39.es/proposal-temporal/#sec-temporal-regulatetime
//
// N.B. The spec implicitly assumes all fields are set; however
// we plan to call this in contexts where they are not (e.g. during
// .with(). Fortunately, )
Maybe<temporal_rs::PartialDate> DateRecord::Regulate(
Isolate* isolate, temporal_rs::ArithmeticOverflow overflow) {
temporal_rs::PartialDate partial = kNullPartialDate;
if (month_code.has_value()) {
partial.month_code = month_code.value();
}
if (era.has_value()) {
partial.era = era.value();
}
partial.calendar = calendar;
// 1. If overflow is constrain, then
if (overflow == temporal_rs::ArithmeticOverflow::Constrain) {
if (year.has_value()) {
partial.year = ClampIntegralDoubleToRange<int32_t>(year.value());
}
if (month.has_value()) {
partial.month = ClampIntegralDoubleToRange<int8_t>(month.value());
}
if (day.has_value()) {
partial.day = ClampIntegralDoubleToRange<int8_t>(day.value());
}
if (era_year.has_value()) {
partial.era_year = ClampIntegralDoubleToRange<int32_t>(era_year.value());
}
} else {
if (year.has_value()) {
int32_t result;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result, CheckDoubleInRange<int32_t>(isolate, year.value()));
partial.year = result;
}
if (month.has_value()) {
uint8_t result;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result, CheckDoubleInRange<uint8_t>(isolate, month.value()));
partial.month = result;
}
if (day.has_value()) {
uint8_t result;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result, CheckDoubleInRange<uint8_t>(isolate, day.value()));
partial.day = result;
}
if (era_year.has_value()) {
int32_t result;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result,
CheckDoubleInRange<int32_t>(isolate, era_year.value()));
partial.era_year = result;
}
}
return Just(partial);
}
// Returned by PrepareCalendarFields
struct CombinedRecord {
DateRecord date;
TimeRecord time;
std::optional<std::string> offset;
std::optional<temporal_rs::TimeZone> time_zone;
// For use in generic contexts
template <typename Ret>
Maybe<Ret> Regulate(Isolate* isolate,
temporal_rs::ArithmeticOverflow overflow)
V8_LIFETIME_BOUND;
};
template <>
Maybe<temporal_rs::PartialDate> CombinedRecord::Regulate(
Isolate* isolate, temporal_rs::ArithmeticOverflow overflow) {
DCHECK(!offset.has_value() && !time_zone.has_value());
DCHECK(!time.hour.has_value() && !time.minute.has_value() &&
!time.second.has_value() && !time.millisecond.has_value() &&
!time.microsecond.has_value() && !time.nanosecond.has_value());
return date.Regulate(isolate, overflow);
}
template <>
Maybe<temporal_rs::PartialTime> CombinedRecord::Regulate(
Isolate* isolate, temporal_rs::ArithmeticOverflow overflow) {
DCHECK(!offset.has_value() && !time_zone.has_value());
DCHECK(!date.year.has_value() && !date.month.has_value() &&
date.month_code == "" && !date.day.has_value() && date.era == "" &&
!date.era_year.has_value() &&
date.calendar == temporal_rs::AnyCalendarKind::Iso);
return time.Regulate(isolate, overflow);
}
template <>
Maybe<temporal_rs::PartialDateTime> CombinedRecord::Regulate(
Isolate* isolate, temporal_rs::ArithmeticOverflow overflow) {
DCHECK(!offset.has_value() && !time_zone.has_value());
temporal_rs::PartialDate regulated_date = kNullPartialDate;
ASSIGN_RETURN_ON_EXCEPTION(isolate, regulated_date,
date.Regulate(isolate, overflow));
temporal_rs::PartialTime regulated_time = kNullPartialTime;
ASSIGN_RETURN_ON_EXCEPTION(isolate, regulated_time,
time.Regulate(isolate, overflow));
return Just(temporal_rs::PartialDateTime{
.date = regulated_date,
.time = regulated_time,
});
}
template <>
Maybe<temporal_rs::PartialZonedDateTime> CombinedRecord::Regulate(
Isolate* isolate, temporal_rs::ArithmeticOverflow overflow) {
temporal_rs::PartialDate regulated_date = kNullPartialDate;
ASSIGN_RETURN_ON_EXCEPTION(isolate, regulated_date,
date.Regulate(isolate, overflow));
temporal_rs::PartialTime regulated_time = kNullPartialTime;
ASSIGN_RETURN_ON_EXCEPTION(isolate, regulated_time,
time.Regulate(isolate, overflow));
auto record = temporal_rs::PartialZonedDateTime{
.date = regulated_date,
.time = regulated_time,
.offset = std::nullopt,
.timezone = std::nullopt,
};
if (time_zone.has_value()) {
record.timezone = time_zone.value();
}
if (offset.has_value()) {
record.offset = offset.value();
}
return Just(record);
}
enum class CalendarFieldsFlag : uint8_t {
kDay = 1 << 0,
// Month and MonthCode
kMonthFields = 1 << 1,
// year, era, eraYear
kYearFields = 1 << 2,
// hour, minute, second, millisecond, microsecont, nanosecond
kTimeFields = 1 << 3,
kOffset = 1 << 4,
kTimeZone = 1 << 5,
};
using CalendarFieldsFlags = base::Flags<CalendarFieldsFlag>;
DEFINE_OPERATORS_FOR_FLAGS(CalendarFieldsFlags)
constexpr CalendarFieldsFlags kAllDateFlags = CalendarFieldsFlag::kDay |
CalendarFieldsFlag::kMonthFields |
CalendarFieldsFlag::kYearFields;
enum class RequiredFields {
kNone,
kPartial,
kTimeZone,
};
// A single run of the PrepareCalendarFields iteration (Step 9, substeps a-c,
// NOT d) Returns whether or not the field was found. Does not handle the case
// when the field is not found.
template <typename OutType>
Maybe<bool> GetSingleCalendarField(
Isolate* isolate, DirectHandle<JSReceiver> fields,
DirectHandle<String> field_name, bool& any, OutType& output,
Maybe<OutType> (*conversion_func)(Isolate*, DirectHandle<Object>)) {
// b. Let value be ?Get(fields, property).
DirectHandle<Object> value;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, value, JSReceiver::GetProperty(isolate, fields, field_name));
// c. If value is not undefined, then
if (!IsUndefined(*value)) {
// i. Set any to true.
any = true;
// ii. Let Conversion be the Conversion value of the same row.
// (perform conversion)
// ix. Set result's field whose name is given in the Field Name column of
// the same row to value.
MOVE_RETURN_ON_EXCEPTION(isolate, output, conversion_func(isolate, value));
return Just(true);
}
return Just(false);
}
// Same as above but for DirectHandles
template <typename OutType>
Maybe<bool> GetSingleCalendarField(
Isolate* isolate, DirectHandle<JSReceiver> fields,
DirectHandle<String> field_name, bool& any, DirectHandle<OutType>& output,
MaybeDirectHandle<OutType> (*conversion_func)(Isolate*,
DirectHandle<Object>)) {
// b. Let value be ?Get(fields, property).
DirectHandle<Object> value;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, value, JSReceiver::GetProperty(isolate, fields, field_name));
// c. If value is not undefined, then
if (!IsUndefined(*value)) {
// i. Set any to true.
any = true;
// ii. Let Conversion be the Conversion value of the same row.
// (perform conversion)
// ix. Set result's field whose name is given in the Field Name column of
// the same row to value.
ASSIGN_RETURN_ON_EXCEPTION(isolate, output,
conversion_func(isolate, value));
return Just(true);
}
return Just(false);
}
// Setters take `resultField` (a field expression of CombinedRecords)
// and `field` (a variable holding a value, and the name of the property),
// and set resultField to field, performing additional work if necessary.
#define SIMPLE_SETTER(resultField, field) resultField = field;
#define MOVING_SETTER(resultField, field) resultField = std::move(field);
#define STR_CONVERSION_SETTER(resultField, field) \
resultField = field->ToStdString();
// Conditions take a boolean expression and wrap it with additional checks
#define SIMPLE_CONDITION(cond) cond
#define ERA_CONDITION(cond) calendarUsesEras && (cond)
#define NOOP_REQUIRED_CHECK
#define TIMEZONE_REQUIRED_CHECK \
if (!found && required_fields == RequiredFields::kTimeZone) { \
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kTimeZoneMissing)); \
}
// https://tc39.es/proposal-temporal/#sec-temporal-calendar-fields-records
// V(CalendarFieldsFlags, propertyName, resultField, Type, Conversion,
// CONDITION, SETTER, REQUIRED_CHECK, AssignOrMove)
#define CALENDAR_FIELDS(V) \
V(kDay, day, result.date.day, double, ToPositiveIntegerWithTruncation, \
SIMPLE_CONDITION, SIMPLE_SETTER, NOOP_REQUIRED_CHECK, ASSIGN) \
V(kYearFields, era, result.date.era, DirectHandle<String>, Object::ToString, \
ERA_CONDITION, STR_CONVERSION_SETTER, NOOP_REQUIRED_CHECK, ASSIGN) \
V(kYearFields, eraYear, result.date.era_year, double, \
ToIntegerWithTruncation, ERA_CONDITION, SIMPLE_SETTER, \
NOOP_REQUIRED_CHECK, ASSIGN) \
V(kTimeFields, hour, result.time.hour, double, ToIntegerWithTruncation, \
SIMPLE_CONDITION, SIMPLE_SETTER, NOOP_REQUIRED_CHECK, ASSIGN) \
V(kTimeFields, microsecond, result.time.microsecond, double, \
ToIntegerWithTruncation, SIMPLE_CONDITION, SIMPLE_SETTER, \
NOOP_REQUIRED_CHECK, ASSIGN) \
V(kTimeFields, millisecond, result.time.millisecond, double, \
ToIntegerWithTruncation, SIMPLE_CONDITION, SIMPLE_SETTER, \
NOOP_REQUIRED_CHECK, ASSIGN) \
V(kTimeFields, minute, result.time.minute, double, ToIntegerWithTruncation, \
SIMPLE_CONDITION, SIMPLE_SETTER, NOOP_REQUIRED_CHECK, ASSIGN) \
V(kMonthFields, month, result.date.month, double, \
ToPositiveIntegerWithTruncation, SIMPLE_CONDITION, SIMPLE_SETTER, \
NOOP_REQUIRED_CHECK, ASSIGN) \
V(kMonthFields, monthCode, result.date.month_code, std::string, ToMonthCode, \
SIMPLE_CONDITION, MOVING_SETTER, NOOP_REQUIRED_CHECK, ASSIGN) \
V(kTimeFields, nanosecond, result.time.nanosecond, double, \
ToIntegerWithTruncation, SIMPLE_CONDITION, SIMPLE_SETTER, \
NOOP_REQUIRED_CHECK, ASSIGN) \
V(kOffset, offset, result.offset, std::string, ToOffsetString, \
SIMPLE_CONDITION, MOVING_SETTER, NOOP_REQUIRED_CHECK, ASSIGN) \
V(kTimeFields, second, result.time.second, double, ToIntegerWithTruncation, \
SIMPLE_CONDITION, SIMPLE_SETTER, NOOP_REQUIRED_CHECK, ASSIGN) \
V(kTimeZone, timeZone, result.time_zone, temporal_rs::TimeZone, \
ToTemporalTimeZoneIdentifier, SIMPLE_CONDITION, MOVING_SETTER, \
TIMEZONE_REQUIRED_CHECK, MOVE) \
V(kYearFields, year, result.date.year, double, ToIntegerWithTruncation, \
SIMPLE_CONDITION, SIMPLE_SETTER, NOOP_REQUIRED_CHECK, ASSIGN)
// https://tc39.es/proposal-temporal/#sec-temporal-preparecalendarfields
Maybe<CombinedRecord> PrepareCalendarFields(Isolate* isolate,
temporal_rs::AnyCalendarKind kind,
DirectHandle<JSReceiver> fields,
CalendarFieldsFlags which_fields,
RequiredFields required_fields) {
// 1. Assert: If requiredFieldNames is a List, requiredFieldNames contains
// zero or one of each of the elements of calendarFieldNames and
// nonCalendarFieldNames.
// 2. Let fieldNames be the list-concatenation of calendarFieldNames and
// nonCalendarFieldNames.
// 3. Let extraFieldNames be CalendarExtraFields(calendar,
// calendarFieldNames).
// Currently al
// 4. Set fieldNames to the list-concatenation of fieldNames and
// extraFieldNames.
// 5. Assert: fieldNames contains no duplicate elements.
// All steps handled by RequiredFields/CalendarFieldsFlag being enums, and
// CalendarExtraFields is handled by calendarUsesEras below.
// Currently all calendars except for iso, chinese, and dangi support eras.
// This may change, but is unlikely to.
//
// https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-calendarsupportsera
bool calendarUsesEras = kind != temporal_rs::AnyCalendarKind::Iso &&
kind != temporal_rs::AnyCalendarKind::Chinese &&
kind != temporal_rs::AnyCalendarKind::Dangi;
// 6. Let result be a Calendar Fields Record with all fields equal to unset.
CombinedRecord result;
// This is not explicitly specced, but CombinedRecord contains the calendar
// kind unlike the spec, and no caller of PrepareCalendarFields does anything
// other than pair `fields` with `calendar` when passing to subsequent
// algorithms.
result.date.calendar = kind;
// 7. Let any be false.
bool any = false;
// 8. Let sortedPropertyNames be a List whose elements are the values in the
// Property Key column of Table 19 corresponding to the elements of
// fieldNames, sorted according to lexicographic code unit order. (handled by
// sorting)
// 9. For each property name property of sortedPropertyNames, do
// a. Let key be the value in the Enumeration Key column of Table 19
// corresponding to the row whose Property Key value is property.
//
// b. Let value be ?Get(fields, property).
//
// c. If value is not undefined, then
// i. Set any to true.
// ii. Let Conversion be the Conversion value of the same row.
// iii. If Conversion is to-integer-with-truncation, then
// 1. Set value to ? ToIntegerWithTruncation(value).
// 2. Set value to 𝔽(value).
// iv. Else if Conversion is to-positive-integer-with-truncation, then
// 1. Set value to ? ToPositiveIntegerWithTruncation(value).
// 2. Set value to 𝔽(value).
// v. Else if Conversion is to-string, then
// 1. Set value to ? ToString(value).
// vi. Else if Conversion is to-temporal-time-zone-identifier, then
// 1. Set value to ? ToTemporalTimeZoneIdentifier(value).
// vii. Else if Conversion is to-month-code, then
// 1. Set value to ? ToMonthCode(value).
// viii. Else,
// 1. Assert: Conversion is to-offset-string.
// 2. Set value to ? ToOffsetString(value).
// ix. Set result's field whose name is given in the Field Name column of
// the same row to value.
// d. Else if requiredFieldNames is a List, then
// i. If requiredFieldNames contains key, then
// 1. Throw a TypeError exception.
// ii. 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.
#define GET_SINGLE_CALENDAR_FIELD(fieldsFlag, propertyName, resultField, Type, \
Conversion, CONDITION, SETTER, \
REQUIRED_CHECK, AssignOrMove) \
if (CONDITION(which_fields & CalendarFieldsFlag::fieldsFlag)) { \
Type propertyName; \
bool found = 0; \
AssignOrMove##_RETURN_ON_EXCEPTION( \
isolate, found, \
GetSingleCalendarField(isolate, fields, \
isolate->factory()->propertyName##_string(), \
any, propertyName, Conversion)); \
if (found) { \
SETTER(resultField, propertyName); \
} \
REQUIRED_CHECK \
}
CALENDAR_FIELDS(GET_SINGLE_CALENDAR_FIELD);
#undef GET_SINGLE_CALENDAR_FIELD
// 10. If requiredFieldNames is partial and any is false, then
if (required_fields == RequiredFields::kPartial && !any) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(
"Must specify at least one calendar field."));
}
return Just(std::move(result));
}
#undef SIMPLE_SETTER
#undef MOVING_SETTER
#undef ANCHORED_SETTER_WITH_MOVE
#undef ANCHORED_SETTER_WITH_STR_CONVERSION
#undef SIMPLE_CONDITION
#undef ERA_CONDITION
#undef NOOP_REQUIRED_CHECK
#undef TIMEZONE_REQUIRED_CHECK
#undef CALENDAR_FIELDS
// ====== System time ======
// https://tc39.es/proposal-temporal/#sec-systemtimezoneidentifier
temporal_rs::TimeZone UTCTimeZoneInner() {
auto result = temporal_rs::TimeZone::utc_with_provider(TimeZoneProvider());
if (result.is_ok()) {
return std::move(result).ok().value();
}
// TODO(Manishearth) use https://github.com/boa-dev/temporal/pull/554 instead
// when we can
return temporal_rs::TimeZone::zero();
}
// https://tc39.es/proposal-temporal/#sec-systemtimezoneidentifier
temporal_rs::TimeZone UTCTimeZone() {
static temporal_rs::TimeZone UTC_TZ = UTCTimeZoneInner();
return UTC_TZ;
}
// https://tc39.es/proposal-temporal/#sec-systemtimezoneidentifier
#ifdef V8_INTL_SUPPORT
temporal_rs::TimeZone SystemTimeZoneIdentifier() {
auto tz_str = Intl::DefaultTimeZone();
auto tz = temporal_rs::TimeZone::try_from_identifier_str_with_provider(
tz_str, TimeZoneProvider())
.ok();
if (tz.has_value()) {
return std::move(tz).value();
}
return UTCTimeZone();
}
#else // V8_INTL_SUPPORT
temporal_rs::TimeZone SystemTimeZoneIdentifier() { return UTCTimeZone(); }
#endif // V8_INTL_SUPPORT
// https://tc39.es/proposal-temporal/#sec-temporal-systemutcepochnanoseconds
temporal_rs::I128Nanoseconds SystemUTCEpochNanoseconds(Isolate* isolate) {
DirectHandle<NativeContext> context = isolate->native_context();
if (!IsUndefined(context->temporal_get_epoch_nanoseconds_callback())) {
v8::TemporalHostSystemUTCEpochNanosecondsCallback callback =
v8::ToCData<v8::TemporalHostSystemUTCEpochNanosecondsCallback,
kApiTemporalHostSystemUTCEpochNanosecondsCallbackTag>(
isolate, context->temporal_get_epoch_nanoseconds_callback());
v8::Local<v8::Context> api_context = v8::Utils::ToLocal(context);
int64_t ns_int = callback(api_context);
absl::int128 ns = ns_int;
return temporal_rs::I128Nanoseconds{.high = absl::Uint128High64(ns),
.low = absl::Uint128Low64(ns)};
}
double ms =
V8::GetCurrentPlatform()->CurrentClockTimeMillisecondsHighResolution();
double intPart;
double fracPart = std::modf(ms, &intPart);
absl::int128 ns = absl::int128(static_cast<int64_t>(intPart)) * 1'000'000;
ns += static_cast<int64_t>(std::round(fracPart * 1'000'000));
return temporal_rs::I128Nanoseconds{.high = absl::Uint128High64(ns),
.low = absl::Uint128Low64(ns)};
}
// Gets a ZonedDateTime in the ISO calendar representing system time, using
// either a passed in timezone or the system timezone.
//
// Primarily patterned off of Now.zonedDateTimeISO(), all other callers can
// convert to specific types.
//
// https://tc39.es/proposal-temporal/#sec-temporal.now.zoneddatetimeiso
// https://tc39.es/proposal-temporal/#sec-temporal.now.plaindatetimeiso
// https://tc39.es/proposal-temporal/#sec-temporal.now.plaindateiso
// https://tc39.es/proposal-temporal/#sec-temporal.now.plaintimeiso
Maybe<std::unique_ptr<temporal_rs::ZonedDateTime>> GenericTemporalNowISO(
Isolate* isolate, DirectHandle<Object> temporal_time_zone_like) {
temporal_rs::TimeZone time_zone;
// 1. If temporalTimeZoneLike is undefined, then
if (IsUndefined(*temporal_time_zone_like)) {
// a. Let timeZone be SystemTimeZoneIdentifier().
time_zone = SystemTimeZoneIdentifier();
// 2. Else,
} else {
// a. Let timeZone be ? ToTemporalTimeZoneIdentifier(temporalTimeZoneLike).
MOVE_RETURN_ON_EXCEPTION(isolate, time_zone,
temporal::ToTemporalTimeZoneIdentifier(
isolate, temporal_time_zone_like));
}
// 3. Let ns be SystemUTCEpochNanoseconds().
auto ns = SystemUTCEpochNanoseconds(isolate);
// 4. Return ! CreateTemporalZonedDateTime(ns, timeZone, "iso8601").
// TODO(manishearth) we can avoid the multiple layers of allocation here
// once https://github.com/boa-dev/temporal/pull/359 lands.
std::unique_ptr<temporal_rs::Instant> instant;
MOVE_RETURN_ON_EXCEPTION(
isolate, instant,
ExtractRustResult(isolate, temporal_rs::Instant::try_new(ns)));
std::unique_ptr<temporal_rs::ZonedDateTime> zdt;
MOVE_RETURN_ON_EXCEPTION(
isolate, zdt,
ExtractRustResult(isolate, instant->to_zoned_date_time_iso_with_provider(
time_zone, TimeZoneProvider())));
return Just(std::move(zdt));
}
// ====== Construction operations ======
// Implementing val.toFoo() for different Temporal types Foo
// (this is distinct from the ToTemporalFoo abstract operations below)
template <typename DstType, typename SrcType>
MaybeDirectHandle<DstType> GenericToTemporalMethod(
Isolate* isolate, DirectHandle<SrcType> val,
TemporalAllocatedResult<typename DstType::RustType> (
SrcType::RustType::*method)() const) {
return ConstructRustWrappingType<DstType>(
isolate, (val->wrapped_rust().raw()->*method)());
}
// Same as above, but for infallible conversions
template <typename DstType, typename SrcType>
MaybeDirectHandle<DstType> GenericToTemporalMethod(
Isolate* isolate, DirectHandle<SrcType> val,
std::unique_ptr<typename DstType::RustType> (SrcType::RustType::*method)()
const) {
return ConstructRustWrappingType<DstType>(
isolate, (val->wrapped_rust().raw()->*method)());
}
// https://tc39.es/proposal-temporal/#sec-temporal-totemporalduration
//
// We split this out since Temporal is lazy-initialized; so we do not wish
// to call ConstructRustWrappingType in a context where Temporal has not yet
// been initialized (e.g. DurationFormat)
Maybe<std::unique_ptr<temporal_rs::Duration>> ToTemporalDurationRust(
Isolate* isolate, DirectHandle<Object> item, const char* method_name) {
// 1. If item is an Object and item has an [[InitializedTemporalDuration]]
// internal slot, then a. a. Return ! CreateTemporalDuration(item.[[Years]],
// item.[[Months]], item.[[Weeks]], item.[[Days]], item.[[Hours]],
// item.[[Minutes]], item.[[Seconds]], item.[[Milliseconds]],
// item.[[Microseconds]], item.[[Nanoseconds]]).
if (IsJSTemporalDuration(*item)) {
auto duration = Cast<JSTemporalDuration>(item);
// i. Return !CreateTemporalInstant(item.[[EpochNanoseconds]]).
return Just(duration->wrapped_rust()->clone());
}
// 2. If item is not an Object, then
if (!IsJSReceiver(*item)) {
// a. If item is not a String, throw a TypeError exception.
if (!IsString(*item)) {
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_TYPE_ERROR(
"Duration argument must be Duration or string."));
}
DirectHandle<String> str = Cast<String>(item);
// b. Let result be ? ParseTemporalDurationString(string).
DirectHandle<JSTemporalInstant> result;
auto rust_result =
HandleStringEncodings<TemporalAllocatedResult<temporal_rs::Duration>>(
isolate, str,
[](std::string_view view)
-> TemporalAllocatedResult<temporal_rs::Duration> {
return temporal_rs::Duration::from_utf8(view);
},
[](std::u16string_view view)
-> TemporalAllocatedResult<temporal_rs::Duration> {
return temporal_rs::Duration::from_utf16(view);
});
return ExtractRustResult(isolate, std::move(rust_result));
}
temporal_rs::PartialDuration partial;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, partial,
temporal::ToTemporalPartialDurationRecord(isolate, item));
return ExtractRustResult(
isolate, temporal_rs::Duration::from_partial_duration(partial));
}
// https://tc39.es/proposal-temporal/#sec-temporal-totemporalduration
MaybeDirectHandle<JSTemporalDuration> ToTemporalDuration(
Isolate* isolate, DirectHandle<Object> item, const char* method_name) {
std::unique_ptr<temporal_rs::Duration> duration;
MOVE_RETURN_ON_EXCEPTION(isolate, duration,
ToTemporalDurationRust(isolate, item, method_name));
return ConstructRustWrappingType<JSTemporalDuration>(isolate,
std::move(duration));
}
// https://tc39.es/proposal-temporal/#sec-temporal-totemporalduration
//
// Used by DurationFormat
Maybe<DurationRecord> ToTemporalDurationAsRecord(Isolate* isolate,
DirectHandle<Object> item,
const char* method_name) {
std::unique_ptr<temporal_rs::Duration> duration;
MOVE_RETURN_ON_EXCEPTION(isolate, duration,
ToTemporalDurationRust(isolate, item, method_name));
return Just(temporal::DurationRecord{
.years = static_cast<double>(duration->years()),
.months = static_cast<double>(duration->months()),
.weeks = static_cast<double>(duration->weeks()),
.time_duration = {
.days = static_cast<double>(duration->days()),
.hours = static_cast<double>(duration->hours()),
.minutes = static_cast<double>(duration->minutes()),
.seconds = static_cast<double>(duration->seconds()),
.milliseconds = static_cast<double>(duration->milliseconds()),
.microseconds = static_cast<double>(duration->microseconds()),
.nanoseconds = static_cast<double>(duration->nanoseconds()),
}});
}
// https://tc39.es/proposal-temporal/#sec-temporal-totemporalinstant
MaybeDirectHandle<JSTemporalInstant> ToTemporalInstant(
Isolate* isolate, DirectHandle<Object> item, const char* method_name) {
// 1. If item is an Object, then
// a. If item has an [[InitializedTemporalInstant]] or
// [[InitializedTemporalZonedDateTime]] internal slot, then
if (IsJSTemporalInstant(*item)) {
auto instant = Cast<JSTemporalInstant>(item);
// i. Return !CreateTemporalInstant(item.[[EpochNanoseconds]]).
return ConstructRustWrappingType<JSTemporalInstant>(
isolate, instant->wrapped_rust()->clone());
// ... or [[InitializedTemporalZonedDateTime]] internal slot
} else if (IsJSTemporalZonedDateTime(*item)) {
auto zdt = Cast<JSTemporalZonedDateTime>(item);
auto ns = zdt->wrapped_rust()->epoch_nanoseconds();
// i. Return !CreateTemporalInstant(item.[[EpochNanoseconds]]).
return ConstructRustWrappingType<JSTemporalInstant>(
isolate, temporal_rs::Instant::try_new(ns));
}
// c. Set item to ?ToPrimitive(item, STRING).
DirectHandle<Object> item_prim;
if (IsJSReceiver(*item)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, item_prim,
JSReceiver::ToPrimitive(isolate, Cast<JSReceiver>(item),
ToPrimitiveHint::kString));
} else {
item_prim = item;
}
if (!IsString(*item_prim)) {
THROW_NEW_ERROR(
isolate,
NEW_TEMPORAL_TYPE_ERROR("Instant argument must be Instant or string."));
}
DirectHandle<String> item_string = Cast<String>(item_prim);
auto rust_result =
HandleStringEncodings<TemporalAllocatedResult<temporal_rs::Instant>>(
isolate, item_string,
[](std::string_view view)
-> TemporalAllocatedResult<temporal_rs::Instant> {
return temporal_rs::Instant::from_utf8(view);
},
[](std::u16string_view view)
-> TemporalAllocatedResult<temporal_rs::Instant> {
return temporal_rs::Instant::from_utf16(view);
});
return ConstructRustWrappingType<JSTemporalInstant>(isolate,
std::move(rust_result));
}
// A lot of branches in the ToTemporalFoo functions read the overflow object and
// do nothing with it
#define READ_AND_DISCARD_OVERFLOW(options_obj) \
RETURN_ON_EXCEPTION(isolate, temporal::ToTemporalOverflowHandleUndefined( \
isolate, options_obj, method_name))
// https://tc39.es/proposal-temporal/#sec-temporal-totemporaltime
MaybeDirectHandle<JSTemporalPlainTime> ToTemporalTime(
Isolate* isolate, DirectHandle<Object> item,
MaybeDirectHandle<Object> options_obj, const char* method_name) {
// 1. If options is not present, set options to undefined.
// (handled by caller)
// This error is eventually thrown by step 3; we perform a check early
// so that we can optimize with InstanceType. Step 1 and 2 are unobservable.
if (!IsHeapObject(*item)) {
THROW_NEW_ERROR(
isolate,
NEW_TEMPORAL_TYPE_ERROR("Time-like argument must be object or string"));
}
InstanceType instance_type = Cast<HeapObject>(*item)->map()->instance_type();
// 2. If item is an Object, then
if (InstanceTypeChecker::IsJSReceiver(instance_type)) {
auto partial = temporal::kNullPartialTime;
// a. If item has an [[InitializedTemporalTime]] internal slot, then
if (InstanceTypeChecker::IsJSTemporalPlainTime(instance_type)) {
auto cast = Cast<JSTemporalPlainTime>(item);
// ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
READ_AND_DISCARD_OVERFLOW(options_obj);
// iii. Return !CreateTemporalTime(item.[[Time]]).
return ConstructRustWrappingType<JSTemporalPlainTime>(
isolate, cast->wrapped_rust()->clone());
// b. If item has an [[InitializedTemporalDateTime]] internal slot, then
} else if (InstanceTypeChecker::IsJSTemporalPlainDateTime(instance_type)) {
// iii. Return ! CreateTemporalTime(item.[[ISODateTime]].[[Time]]).
partial = GetPartialTime(Cast<JSTemporalPlainDateTime>(item));
// c. If item has an [[InitializedTemporalZonedDateTime]] internal slot,
// then
} else if (InstanceTypeChecker::IsJSTemporalZonedDateTime(instance_type)) {
// i. Let isoDateTime be GetISODateTimeFor(item.[[TimeZone]],
// item.[[EpochNanoseconds]]).
partial = GetPartialTime(Cast<JSTemporalZonedDateTime>(item));
// iv. Return !CreateTemporalTime(isoDateTime.[[Time]]).
} else {
// d. Let result be ?ToTemporalTimeRecord(item).
DirectHandle<JSReceiver> item_recvr = Cast<JSReceiver>(item);
temporal::TimeRecord record;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, record,
temporal::ToTemporalTimeRecord(isolate, item_recvr, method_name));
// e. Let resolvedOptions be ? GetOptionsObject(options).
// f. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
temporal_rs::ArithmeticOverflow overflow;
ASSIGN_RETURN_ON_EXCEPTION(isolate, overflow,
temporal::ToTemporalOverflowHandleUndefined(
isolate, options_obj, method_name));
// g. Set result to ? RegulateTime(result.[[Hour]], result.[[Minute]],
// result.[[Second]], result.[[Millisecond]], result.[[Microsecond]],
// result.[[Nanosecond]], overflow).
ASSIGN_RETURN_ON_EXCEPTION(isolate, partial,
record.Regulate(isolate, overflow));
return ConstructRustWrappingType<JSTemporalPlainTime>(
isolate, temporal_rs::PlainTime::from_partial(partial, overflow));
}
// (found in each branch above)
temporal_rs::ArithmeticOverflow overflow;
// e. Let resolvedOptions be ?GetOptionsObject(options).
// f. f. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
ASSIGN_RETURN_ON_EXCEPTION(isolate, overflow,
temporal::ToTemporalOverflowHandleUndefined(
isolate, options_obj, method_name));
return ConstructRustWrappingType<JSTemporalPlainTime>(
isolate, temporal_rs::PlainTime::from_partial(partial, overflow));
// 3. Else,
} else {
// a. If item is not a String, throw a TypeError exception.
if (!InstanceTypeChecker::IsString(instance_type)) {
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_TYPE_ERROR(
"Time-like argument must be object or string"));
}
DirectHandle<String> str = Cast<String>(item);
auto rust_result =
HandleStringEncodings<TemporalAllocatedResult<temporal_rs::PlainTime>>(
isolate, str,
[](std::string_view view)
-> TemporalAllocatedResult<temporal_rs::PlainTime> {
return temporal_rs::PlainTime::from_utf8(view);
},
[](std::u16string_view view)
-> TemporalAllocatedResult<temporal_rs::PlainTime> {
return temporal_rs::PlainTime::from_utf16(view);
});
std::unique_ptr<temporal_rs::PlainTime> time;
MOVE_RETURN_ON_EXCEPTION(
isolate, time, ExtractRustResult(isolate, std::move(rust_result)));
READ_AND_DISCARD_OVERFLOW(options_obj);
return ConstructRustWrappingType<JSTemporalPlainTime>(isolate,
std::move(time));
}
}
// Instead of returning Midnight, just returns null, since temporal_rs
// handles the midnight-setting
//
// https://tc39.es/proposal-temporal/#sec-temporal-totimerecordormidnight
Maybe<CppGCManaged<temporal_rs::PlainTime>::Ptr> ToTimeRecordOrMidnight(
Isolate* isolate, DirectHandle<Object> item,
DirectHandle<JSTemporalPlainTime>& output_time, const char* method_name) {
// 1. If item is undefined, return MidnightTimeRecord().
if (IsUndefined(*item)) {
return Just(CppGCManaged<temporal_rs::PlainTime>::Ptr());
}
// 2. Let plainTime be ? ToTemporalTime(item).
ASSIGN_RETURN_ON_EXCEPTION(isolate, output_time,
ToTemporalTime(isolate, item, {}, method_name));
// 3. Return plainTime.[[Time]].
CppGCManaged<temporal_rs::PlainTime>::Ptr empty = output_time->wrapped_rust();
return Just(std::move(empty));
}
// https://tc39.es/proposal-temporal/#sec-temporal-totemporaldate
MaybeDirectHandle<JSTemporalPlainDate> ToTemporalDate(
Isolate* isolate, DirectHandle<Object> item,
MaybeDirectHandle<Object> options_obj, const char* method_name) {
// 1. If options is not present, set options to undefined.
// (handled by caller)
// This error is eventually thrown by step 3a; we perform a check early
// so that we can optimize with InstanceType. Step 1 and 2 are unobservable.
if (!IsHeapObject(*item)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(
"Date argument must be object or string."));
}
InstanceType instance_type = Cast<HeapObject>(*item)->map()->instance_type();
// 2. If item is an Object, then
if (InstanceTypeChecker::IsJSReceiver(instance_type)) {
auto partial = temporal::kNullPartialDate;
// a. If item has an [[InitializedTemporalDate]] internal slot, then
if (InstanceTypeChecker::IsJSTemporalPlainDate(instance_type)) {
auto cast = Cast<JSTemporalPlainDate>(item);
// ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
READ_AND_DISCARD_OVERFLOW(options_obj);
// iii. Return !CreateTemporalDate(item.[[Date]], item.[[Calendar]]).
return ConstructRustWrappingType<JSTemporalPlainDate>(
isolate, cast->wrapped_rust()->clone());
// b. If item has an [[InitializedTemporalZonedDateTime]] internal slot,
// then
} else if (InstanceTypeChecker::IsJSTemporalZonedDateTime(instance_type)) {
// i. Let isoDateTime be GetISODateTimeFor(item.[[TimeZone]],
// item.[[EpochNanoseconds]]).
//
// iv. Return !CreateTemporalDate(isoDateTime.[[ISODate]],
// item.[[Calendar]]).
partial = GetPartialDate(Cast<JSTemporalZonedDateTime>(item));
// c. If item has an [[InitializedTemporalDateTime]] internal slot, then
} else if (InstanceTypeChecker::IsJSTemporalPlainDateTime(instance_type)) {
// iii. Return !CreateTemporalDate(item.[[ISODateTime]].[[ISODate]],
// item.[[Calendar]]).
partial = GetPartialDate(Cast<JSTemporalPlainDateTime>(item));
} else {
// d. Let calendar be ?GetTemporalCalendarIdentifierWithISODefault(item).
temporal_rs::AnyCalendarKind kind = temporal_rs::AnyCalendarKind::Iso;
DirectHandle<JSReceiver> item_recvr = Cast<JSReceiver>(item);
ASSIGN_RETURN_ON_EXCEPTION(
isolate, kind,
temporal::GetTemporalCalendarIdentifierWithISODefault(isolate,
item_recvr));
// e. Let fields be ?PrepareCalendarFields(calendar, item, « year, month,
// month-code, day», «», «»).
CombinedRecord fields;
MOVE_RETURN_ON_EXCEPTION(
isolate, fields,
PrepareCalendarFields(isolate, kind, item_recvr, kAllDateFlags,
RequiredFields::kNone));
temporal_rs::ArithmeticOverflow overflow;
// f. Let resolvedOptions be ?GetOptionsObject(options).
// g. f. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
ASSIGN_RETURN_ON_EXCEPTION(isolate, overflow,
temporal::ToTemporalOverflowHandleUndefined(
isolate, options_obj, method_name));
ASSIGN_RETURN_ON_EXCEPTION(
isolate, partial,
fields.Regulate<temporal_rs::PartialDate>(isolate, overflow));
return ConstructRustWrappingType<JSTemporalPlainDate>(
isolate, temporal_rs::PlainDate::from_partial(partial, overflow));
}
// (from each branch above)
// i. Let resolvedOptions be ? GetOptionsObject(options).
// ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
READ_AND_DISCARD_OVERFLOW(options_obj);
return ConstructRustWrappingType<JSTemporalPlainDate>(
isolate, temporal_rs::PlainDate::from_partial(partial, std::nullopt));
// 3. Else,
} else {
// a. If item is not a String, throw a TypeError exception.
if (!InstanceTypeChecker::IsString(instance_type)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(
"Date argument must be object or string."));
}
DirectHandle<String> str = Cast<String>(item);
// Rest of the steps handled in Rust.
std::unique_ptr<temporal_rs::ParsedDate> date;
auto rust_result =
HandleStringEncodings<TemporalAllocatedResult<temporal_rs::ParsedDate>>(
isolate, str,
[](std::string_view view)
-> TemporalAllocatedResult<temporal_rs::ParsedDate> {
return temporal_rs::ParsedDate::from_utf8(view);
},
[](std::u16string_view view)
-> TemporalAllocatedResult<temporal_rs::ParsedDate> {
return temporal_rs::ParsedDate::from_utf16(view);
});
MOVE_RETURN_ON_EXCEPTION(
isolate, date, ExtractRustResult(isolate, std::move(rust_result)));
// 9. Perform ? GetTemporalOverflowOption(resolvedOptions).
READ_AND_DISCARD_OVERFLOW(options_obj);
return ConstructRustWrappingType<JSTemporalPlainDate>(
isolate, temporal_rs::PlainDate::from_parsed(*date));
}
}
// https://tc39.es/proposal-temporal/#sec-temporal-totemporaldatetime
MaybeDirectHandle<JSTemporalPlainDateTime> ToTemporalDateTime(
Isolate* isolate, DirectHandle<Object> item,
MaybeDirectHandle<Object> options_obj, const char* method_name) {
// 1. If options is not present, set options to undefined.
// (handled by caller)
// This error is eventually thrown by step 3; we perform a check early
// so that we can optimize with InstanceType. Step 1 and 2 are unobservable.
if (!IsHeapObject(*item)) {
THROW_NEW_ERROR(
isolate,
NEW_TEMPORAL_TYPE_ERROR("DateTime argument must be object or string."));
}
InstanceType instance_type = Cast<HeapObject>(*item)->map()->instance_type();
// 2. If item is an Object, then
if (InstanceTypeChecker::IsJSReceiver(instance_type)) {
auto partial = temporal::kNullPartialDateTime;
// a. If item has an [[InitializedTemporalDateTime]] internal slot, then
if (InstanceTypeChecker::IsJSTemporalPlainDateTime(instance_type)) {
auto cast = Cast<JSTemporalPlainDateTime>(item);
// ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
READ_AND_DISCARD_OVERFLOW(options_obj);
// iii. Return !CreateTemporalDate(item.[[Date]], item.[[Calendar]]).
return ConstructRustWrappingType<JSTemporalPlainDateTime>(
isolate, cast->wrapped_rust()->clone());
// b. If item has an [[InitializedTemporalZonedDateTime]] internal slot,
// then
} else if (InstanceTypeChecker::IsJSTemporalZonedDateTime(instance_type)) {
// i. Let isoDateTime be GetISODateTimeFor(item.[[TimeZone]],
// item.[[EpochNanoseconds]]).
//
// iv. Return !CreateTemporalDateTime(isoDateTime, item.[[Calendar]]).
partial = GetPartialDateTime(Cast<JSTemporalZonedDateTime>(item));
// c. If item has an [[InitializedTemporalDate]] internal slot, then
} else if (InstanceTypeChecker::IsJSTemporalPlainDate(instance_type)) {
// iii. Return !CreateTemporalDate(item.[[ISODateTime]].[[ISODate]],
// item.[[Calendar]]).
partial = GetPartialDateTime(Cast<JSTemporalPlainDate>(item));
} else {
// d. Let calendar be ?GetTemporalCalendarIdentifierWithISODefault(item).
temporal_rs::AnyCalendarKind kind = temporal_rs::AnyCalendarKind::Iso;
DirectHandle<JSReceiver> item_recvr = Cast<JSReceiver>(item);
ASSIGN_RETURN_ON_EXCEPTION(
isolate, kind,
temporal::GetTemporalCalendarIdentifierWithISODefault(isolate,
item_recvr));
// e. Let fields be ?PrepareCalendarFields(calendar, item, « year, month,
// month-code, day», « hour, minute, second, millisecond, microsecond,
// nanosecond», «»).
CombinedRecord fields;
using enum CalendarFieldsFlag;
MOVE_RETURN_ON_EXCEPTION(
isolate, fields,
PrepareCalendarFields(isolate, kind, item_recvr,
kAllDateFlags | kTimeFields,
RequiredFields::kNone));
temporal_rs::ArithmeticOverflow overflow;
// f. Let resolvedOptions be ?GetOptionsObject(options).
// g. f. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
ASSIGN_RETURN_ON_EXCEPTION(isolate, overflow,
temporal::ToTemporalOverflowHandleUndefined(
isolate, options_obj, method_name));
ASSIGN_RETURN_ON_EXCEPTION(
isolate, partial,
fields.Regulate<temporal_rs::PartialDateTime>(isolate, overflow));
return ConstructRustWrappingType<JSTemporalPlainDateTime>(
isolate, temporal_rs::PlainDateTime::from_partial(partial, overflow));
}
temporal_rs::ArithmeticOverflow overflow;
// f. Let resolvedOptions be ?GetOptionsObject(options).
// g. f. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
ASSIGN_RETURN_ON_EXCEPTION(isolate, overflow,
temporal::ToTemporalOverflowHandleUndefined(
isolate, options_obj, method_name));
return ConstructRustWrappingType<JSTemporalPlainDateTime>(
isolate, temporal_rs::PlainDateTime::from_partial(partial, overflow));
} else {
// 3. If item is not a String, throw a TypeError exception.
if (!InstanceTypeChecker::IsString(instance_type)) {
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_TYPE_ERROR(
"DateTime argument must be object or string."));
}
DirectHandle<String> str = Cast<String>(item);
// Rest of the steps handled in Rust
std::unique_ptr<temporal_rs::ParsedDateTime> date;
auto rust_result = HandleStringEncodings<
TemporalAllocatedResult<temporal_rs::ParsedDateTime>>(
isolate, str,
[](std::string_view view)
-> TemporalAllocatedResult<temporal_rs::ParsedDateTime> {
return temporal_rs::ParsedDateTime::from_utf8(view);
},
[](std::u16string_view view)
-> TemporalAllocatedResult<temporal_rs::ParsedDateTime> {
return temporal_rs::ParsedDateTime::from_utf16(view);
});
MOVE_RETURN_ON_EXCEPTION(
isolate, date, ExtractRustResult(isolate, std::move(rust_result)));
// 9. Perform ? GetTemporalOverflowOption(resolvedOptions).
READ_AND_DISCARD_OVERFLOW(options_obj);
return ConstructRustWrappingType<JSTemporalPlainDateTime>(
isolate, temporal_rs::PlainDateTime::from_parsed(*date));
}
}
// https://tc39.es/proposal-temporal/#sec-temporal-totemporalyearmonth
MaybeDirectHandle<JSTemporalPlainYearMonth> ToTemporalYearMonth(
Isolate* isolate, DirectHandle<Object> item,
MaybeDirectHandle<Object> options_obj, const char* method_name) {
// 1. If options is not present, set options to undefined.
// (handled by caller)
// This error is eventually thrown by step 3; we perform a check early
// so that we can optimize with InstanceType. Step 1 and 2 are unobservable.
if (!IsHeapObject(*item)) {
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_TYPE_ERROR(
"YearMonth argument must be object or string."));
}
InstanceType instance_type = Cast<HeapObject>(*item)->map()->instance_type();
// 2. If item is an Object, then
if (InstanceTypeChecker::IsJSReceiver(instance_type)) {
// a. If item has an [[InitializedTemporalYearMonth]] internal slot, then
if (InstanceTypeChecker::IsJSTemporalPlainYearMonth(instance_type)) {
auto cast = Cast<JSTemporalPlainYearMonth>(item);
// ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
READ_AND_DISCARD_OVERFLOW(options_obj);
// iii. Return ! CreateTemporalYearMonth(item.[[ISODate]],
// item.[[Calendar]]).
return ConstructRustWrappingType<JSTemporalPlainYearMonth>(
isolate, cast->wrapped_rust()->clone());
} else {
// b. Let calendar be ?GetTemporalCalendarIdentifierWithISODefault(item).
DirectHandle<JSReceiver> item_recvr = Cast<JSReceiver>(item);
temporal_rs::AnyCalendarKind kind;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, kind,
temporal::GetTemporalCalendarIdentifierWithISODefault(isolate,
item_recvr));
// c. Let fields be ?PrepareCalendarFields(calendar, item, « year, month,
// month-code», «», «»).
CombinedRecord fields;
using enum CalendarFieldsFlag;
MOVE_RETURN_ON_EXCEPTION(isolate, fields,
PrepareCalendarFields(isolate, kind, item_recvr,
kYearFields | kMonthFields,
RequiredFields::kNone));
// e. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
temporal_rs::ArithmeticOverflow overflow;
ASSIGN_RETURN_ON_EXCEPTION(isolate, overflow,
temporal::ToTemporalOverflowHandleUndefined(
isolate, options_obj, method_name));
temporal_rs::PartialDate partial = temporal::kNullPartialDate;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, partial,
fields.Regulate<temporal_rs::PartialDate>(isolate, overflow));
// f. Let isoDate be ? CalendarYearMonthFromFields(calendar, fields,
// overflow).
//
// g. Return !CreateTemporalYearMonth(isoDate, calendar).
return ConstructRustWrappingType<JSTemporalPlainYearMonth>(
isolate,
temporal_rs::PlainYearMonth::from_partial(partial, overflow));
}
} else {
// 3. If item is not a String, throw a TypeError exception.
if (!InstanceTypeChecker::IsString(instance_type)) {
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_TYPE_ERROR(
"YearMonth argument must be object or string."));
}
DirectHandle<String> str = Cast<String>(item);
// Rest of the steps handled in Rust
std::unique_ptr<temporal_rs::ParsedDate> date;
auto rust_result =
HandleStringEncodings<TemporalAllocatedResult<temporal_rs::ParsedDate>>(
isolate, str,
[](std::string_view view)
-> TemporalAllocatedResult<temporal_rs::ParsedDate> {
return temporal_rs::ParsedDate::year_month_from_utf8(view);
},
[](std::u16string_view view)
-> TemporalAllocatedResult<temporal_rs::ParsedDate> {
return temporal_rs::ParsedDate::year_month_from_utf16(view);
});
MOVE_RETURN_ON_EXCEPTION(
isolate, date, ExtractRustResult(isolate, std::move(rust_result)));
// 9. Perform ? GetTemporalOverflowOption(resolvedOptions).
READ_AND_DISCARD_OVERFLOW(options_obj);
return ConstructRustWrappingType<JSTemporalPlainYearMonth>(
isolate, temporal_rs::PlainYearMonth::from_parsed(*date));
}
}
struct ZDTOptions {
temporal_rs::Disambiguation disambiguation;
temporal_rs::OffsetDisambiguation offset_option;
temporal_rs::ArithmeticOverflow overflow;
};
Maybe<ZDTOptions> GetZDTOptions(Isolate* isolate,
MaybeDirectHandle<Object> maybe_options_obj,
const char* method_name) {
ZDTOptions options =
ZDTOptions{.disambiguation = temporal_rs::Disambiguation::Compatible,
.offset_option = temporal_rs::OffsetDisambiguation::Reject,
.overflow = temporal_rs::ArithmeticOverflow::Constrain};
DirectHandle<Object> options_obj;
if (!maybe_options_obj.ToHandle(&options_obj)) {
return Just(options);
}
// (ToTemporalZonedDateTime) g. Let resolvedOptions be
// ?GetOptionsObject(options).
//
// (ToTemporalZonedDateTime) h. Let disambiguation be
// ?GetTemporalDisambiguationOption(resolvedOptions).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options.disambiguation,
temporal::GetTemporalDisambiguationOptionHandleUndefined(
isolate, options_obj, method_name));
// (ToTemporalZonedDateTime) i. Let offsetOption be
// ?GetTemporalOffsetOption(resolvedOptions, reject).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options.offset_option,
temporal::GetTemporalOffsetOptionHandleUndefined(
isolate, options_obj, temporal_rs::OffsetDisambiguation::Reject,
method_name));
// (ToTemporalZonedDateTime) ii. Perform
// ?GetTemporalOverflowOption(resolvedOptions).
ASSIGN_RETURN_ON_EXCEPTION(isolate, options.overflow,
temporal::ToTemporalOverflowHandleUndefined(
isolate, options_obj, method_name));
return Just(options);
}
// https://tc39.es/proposal-temporal/#sec-temporal-totemporalzoneddatetime
// Note this skips the options-parsing steps and instead asks the caller to pass
// it in
MaybeDirectHandle<JSTemporalZonedDateTime> ToTemporalZonedDateTime(
Isolate* isolate, DirectHandle<Object> item,
MaybeDirectHandle<Object> options_obj, const char* method_name) {
// 1. If options is not present, set options to undefined.
// (handled by caller)
// This error is eventually thrown by step 3; we perform a check early
// so that we can optimize with InstanceType. Step 1 and 2 are unobservable.
if (!IsHeapObject(*item)) {
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_TYPE_ERROR(
"ZonedDateTime argument must be object or string."));
}
InstanceType instance_type = Cast<HeapObject>(*item)->map()->instance_type();
// 2. Let offsetBehaviour be option.
// 3. Let matchBehaviour be match-exactly.
// (handled in Rust)
// 4. If item is an Object, then
if (InstanceTypeChecker::IsJSReceiver(instance_type)) {
// a. If item has an [[InitializedTemporalZonedDateTime]] internal slot,
// then
if (InstanceTypeChecker::IsJSTemporalZonedDateTime(instance_type)) {
auto cast = Cast<JSTemporalZonedDateTime>(item);
// iii. Perform ? GetTemporalDisambiguationOption(resolvedOptions).
// iv. Perform ? GetTemporalOffsetOption(resolvedOptions, reject).
// v. Perform ? GetTemporalOverflowOption(resolvedOptions).
ZDTOptions options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options, GetZDTOptions(isolate, options_obj, method_name));
// vi. Return !CreateTemporalZonedDateTime(item.[[EpochNanoseconds]],
// item.[[TimeZone]], item.[[Calendar]]).
return ConstructRustWrappingType<JSTemporalZonedDateTime>(
isolate, cast->wrapped_rust()->clone());
} else {
// b. Let calendar be ?GetTemporalCalendarIdentifierWithISODefault(item).
temporal_rs::AnyCalendarKind kind = temporal_rs::AnyCalendarKind::Iso;
DirectHandle<JSReceiver> item_recvr = Cast<JSReceiver>(item);
ASSIGN_RETURN_ON_EXCEPTION(
isolate, kind,
temporal::GetTemporalCalendarIdentifierWithISODefault(isolate,
item_recvr));
// c. Let fields be ? PrepareCalendarFields(calendar, item, « year,
// month, month-code, day», « hour, minute, second, millisecond,
// microsecond, nanosecond, offset, time-zone», « time-zone»).
CombinedRecord fields;
using enum CalendarFieldsFlag;
MOVE_RETURN_ON_EXCEPTION(
isolate, fields,
PrepareCalendarFields(
isolate, kind, item_recvr,
kAllDateFlags | kTimeFields | kOffset | kTimeZone,
RequiredFields::kTimeZone));
// h. Perform ? GetTemporalDisambiguationOption(resolvedOptions).
// i. Perform ? GetTemporalOffsetOption(resolvedOptions, reject).
// j. Perform ? GetTemporalOverflowOption(resolvedOptions).
ZDTOptions options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options, GetZDTOptions(isolate, options_obj, method_name));
temporal_rs::PartialZonedDateTime partial = kNullPartialZonedDateTime;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, partial,
fields.Regulate<temporal_rs::PartialZonedDateTime>(isolate,
options.overflow));
return ConstructRustWrappingType<JSTemporalZonedDateTime>(
isolate, temporal_rs::ZonedDateTime::from_partial_with_provider(
partial, options.overflow, options.disambiguation,
options.offset_option, TimeZoneProvider()));
}
} else {
// 3. If item is not a String, throw a TypeError exception.
if (!InstanceTypeChecker::IsString(instance_type)) {
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_TYPE_ERROR(
"ZonedDateTime argument must be object or string."));
}
DirectHandle<String> str = Cast<String>(item);
// b. Let result be ? ParseISODateTime(item, «
// TemporalDateTimeString[+Zoned] »).
//
// Steps b-l handled in Rust
std::unique_ptr<temporal_rs::ParsedZonedDateTime> parsed;
auto rust_result = HandleStringEncodings<
TemporalAllocatedResult<temporal_rs::ParsedZonedDateTime>>(
isolate, str,
[](std::string_view view)
-> TemporalAllocatedResult<temporal_rs::ParsedZonedDateTime> {
return temporal_rs::ParsedZonedDateTime::from_utf8_with_provider(
view, TimeZoneProvider());
},
[](std::u16string_view view)
-> TemporalAllocatedResult<temporal_rs::ParsedZonedDateTime> {
return temporal_rs::ParsedZonedDateTime::from_utf16_with_provider(
view, TimeZoneProvider());
});
MOVE_RETURN_ON_EXCEPTION(
isolate, parsed, ExtractRustResult(isolate, std::move(rust_result)));
// o. Perform ? GetTemporalDisambiguationOption(resolvedOptions).
// p. Perform ? GetTemporalOffsetOption(resolvedOptions, reject).
// q. Perform ? GetTemporalOverflowOption(resolvedOptions).
ZDTOptions options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options, GetZDTOptions(isolate, options_obj, method_name));
// Rest of the steps handled in Rust
return ConstructRustWrappingType<JSTemporalZonedDateTime>(
isolate, temporal_rs::ZonedDateTime::from_parsed_with_provider(
*parsed, options.disambiguation, options.offset_option,
TimeZoneProvider()));
}
}
// https://tc39.es/proposal-temporal/#sec-temporal-totemporalmonthday
MaybeDirectHandle<JSTemporalPlainMonthDay> ToTemporalMonthDay(
Isolate* isolate, DirectHandle<Object> item,
MaybeDirectHandle<Object> options_obj, const char* method_name) {
// 1. If options is not present, set options to undefined.
// (handled by caller)
// This error is eventually thrown by step 3; we perform a check early
// so that we can optimize with InstanceType. Step 1 and 2 are unobservable.
if (!IsHeapObject(*item)) {
THROW_NEW_ERROR(
isolate,
NEW_TEMPORAL_TYPE_ERROR("MonthDay argument must be object or string."));
}
InstanceType instance_type = Cast<HeapObject>(*item)->map()->instance_type();
// 2. If item is an Object, then
if (InstanceTypeChecker::IsJSReceiver(instance_type)) {
// a. If item has an [[InitializedTemporalMonthDay]] internal slot, then
if (InstanceTypeChecker::IsJSTemporalPlainMonthDay(instance_type)) {
auto cast = Cast<JSTemporalPlainMonthDay>(item);
// ii. Perform ? GetTemporalOverflowOption(resolvedOptions).
READ_AND_DISCARD_OVERFLOW(options_obj);
// iii. Return ! CreateTemporalMonthDay(item.[[ISODate]],
// item.[[Calendar]]).
return ConstructRustWrappingType<JSTemporalPlainMonthDay>(
isolate, cast->wrapped_rust()->clone());
} else {
// b. Let calendar be ?GetTemporalCalendarIdentifierWithISODefault(item).
DirectHandle<JSReceiver> item_recvr = Cast<JSReceiver>(item);
temporal_rs::AnyCalendarKind kind;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, kind,
temporal::GetTemporalCalendarIdentifierWithISODefault(isolate,
item_recvr));
// c. Let fields be ?PrepareCalendarFields(calendar, item, « year, month,
// month-code, day», «», «»).
CombinedRecord fields;
using enum CalendarFieldsFlag;
MOVE_RETURN_ON_EXCEPTION(
isolate, fields,
PrepareCalendarFields(isolate, kind, item_recvr,
kYearFields | kMonthFields | kDay,
RequiredFields::kNone));
// Remaining steps handled in Rust
// e. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
temporal_rs::ArithmeticOverflow overflow;
ASSIGN_RETURN_ON_EXCEPTION(isolate, overflow,
temporal::ToTemporalOverflowHandleUndefined(
isolate, options_obj, method_name));
// f. Let isoDate be ? CalendarMonthDayFromFields(calendar, fields,
// overflow).
//
// g. Return ! CreateTemporalMonthDay(isoDate, calendar).
temporal_rs::PartialDate partial = temporal::kNullPartialDate;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, partial,
fields.Regulate<temporal_rs::PartialDate>(isolate, overflow));
return ConstructRustWrappingType<JSTemporalPlainMonthDay>(
isolate, temporal_rs::PlainMonthDay::from_partial(partial, overflow));
}
} else {
// 3. If item is not a String, throw a TypeError exception.
if (!InstanceTypeChecker::IsString(instance_type)) {
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_TYPE_ERROR(
"MonthDay argument must be object or string."));
}
DirectHandle<String> str = Cast<String>(item);
DirectHandle<JSTemporalPlainDate> result;
// Rest of the steps handled in Rust
std::unique_ptr<temporal_rs::ParsedDate> date;
auto rust_result =
HandleStringEncodings<TemporalAllocatedResult<temporal_rs::ParsedDate>>(
isolate, str,
[](std::string_view view)
-> TemporalAllocatedResult<temporal_rs::ParsedDate> {
return temporal_rs::ParsedDate::month_day_from_utf8(view);
},
[](std::u16string_view view)
-> TemporalAllocatedResult<temporal_rs::ParsedDate> {
return temporal_rs::ParsedDate::month_day_from_utf16(view);
});
MOVE_RETURN_ON_EXCEPTION(
isolate, date, ExtractRustResult(isolate, std::move(rust_result)));
// 9. Perform ? GetTemporalOverflowOption(resolvedOptions).
READ_AND_DISCARD_OVERFLOW(options_obj);
return ConstructRustWrappingType<JSTemporalPlainMonthDay>(
isolate, temporal_rs::PlainMonthDay::from_parsed(*date));
}
}
// For calling ToTemporalFoo in generic contexts.
// Note that this ignores all options `overflow`.
template <typename JSType>
MaybeDirectHandle<JSType> ToTemporalGeneric(
Isolate* isolate, DirectHandle<Object> item,
const char* method_name);
#define DEFINE_TO_TEMPORAL_GENERIC(Type) \
template <> \
MaybeDirectHandle<JSTemporalPlain##Type> \
ToTemporalGeneric<JSTemporalPlain##Type>( \
Isolate * isolate, DirectHandle<Object> item, const char* method_name) { \
return ToTemporal##Type(isolate, item, {}, method_name); \
}
DEFINE_TO_TEMPORAL_GENERIC(Time)
DEFINE_TO_TEMPORAL_GENERIC(Date)
DEFINE_TO_TEMPORAL_GENERIC(DateTime)
DEFINE_TO_TEMPORAL_GENERIC(YearMonth)
DEFINE_TO_TEMPORAL_GENERIC(MonthDay)
#undef DEFINE_TO_TEMPORAL_GENERIC
// These two have a different number of arguments
template <>
MaybeDirectHandle<JSTemporalInstant> ToTemporalGeneric<JSTemporalInstant>(
Isolate* isolate, DirectHandle<Object> item,
const char* method_name) {
return ToTemporalInstant(isolate, item, method_name);
}
template <>
MaybeDirectHandle<JSTemporalZonedDateTime>
ToTemporalGeneric<JSTemporalZonedDateTime>(
Isolate* isolate, DirectHandle<Object> item,
const char* method_name) {
return ToTemporalZonedDateTime(isolate, item, {}, method_name);
}
// A class that wraps a PlainDate or a ZonedDateTime that allows
// them to be either owned in a unique_ptr or borrowed.
//
// Setters should only be called once (this is not a safety
// invariant, but the spec should not be setting things multiple
// times)
class RelativeTo {
public:
RelativeTo() = default;
explicit RelativeTo(std::shared_ptr<const temporal_rs::PlainDate> val)
: date_(std::move(val)), zoned_() {}
explicit RelativeTo(std::shared_ptr<const temporal_rs::ZonedDateTime> val)
: date_(), zoned_(std::move(val)) {}
explicit RelativeTo(temporal_rs::OwnedRelativeTo&& owned) {
date_ = std::move(owned.date);
zoned_ = std::move(owned.zoned);
}
temporal_rs::RelativeTo ToRust() const V8_LIFETIME_BOUND {
return temporal_rs::RelativeTo{
.date = date_.get(),
.zoned = zoned_.get(),
};
}
private:
std::shared_ptr<const temporal_rs::PlainDate> date_;
std::shared_ptr<const temporal_rs::ZonedDateTime> zoned_;
};
// https://tc39.es/proposal-temporal/#sec-temporal-gettemporalrelativetooption
// Also handles the undefined case from GetOptionsObject
Maybe<RelativeTo> GetTemporalRelativeToOptionHandleUndefined(
Isolate* isolate, DirectHandle<Object> options) {
RelativeTo ret;
// Default is empty
if (IsUndefined(*options)) {
return Just(RelativeTo());
}
auto relativeto_ident = isolate->factory()->relativeTo_string();
if (!IsJSReceiver(*options)) {
// (GetOptionsObject) 3. Throw a TypeError exception.
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR_WITH_ARG(
kOptionMustBeObject, relativeto_ident));
}
// 1. Let value be ?Get(options, "relativeTo").
DirectHandle<Object> value;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, value,
JSReceiver::GetProperty(isolate, Cast<JSReceiver>(options),
relativeto_ident));
// 2. If value is undefined, return the Record { [[PlainRelativeTo]]:
// undefined, [[ZonedRelativeTo]]: undefined }.
if (IsUndefined(*value)) {
return Just(RelativeTo());
}
// 3. Let offsetBehaviour be option.
// 4. Let matchBehaviour be match-exactly.
// This error is eventually thrown by step 6a; we perform a check early
// so that we can optimize with InstanceType. Step 5-6 are unobservable
// in this case.
if (!IsHeapObject(*value)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(
"relativeTo must be object or string."));
}
InstanceType instance_type = Cast<HeapObject>(*value)->map()->instance_type();
// 5. If value is an Object, then
if (InstanceTypeChecker::IsJSReceiver(instance_type)) {
// a. If value has an [[InitializedTemporalZonedDateTime]] internal slot,
// then
if (InstanceTypeChecker::IsJSTemporalZonedDateTime(instance_type)) {
// i. Return the Record { [[PlainRelativeTo]]: undefined,
// [[ZonedRelativeTo]]: value }.
return Just(RelativeTo(Cast<JSTemporalZonedDateTime>(value)
->wrapped_rust()
.as_shared_ptr()));
}
// b. If value has an [[InitializedTemporalDate]] internal slot, then
if (InstanceTypeChecker::IsJSTemporalPlainDate(instance_type)) {
// i. Return the Record { [[PlainRelativeTo]]: value, [[ZonedRelativeTo]]:
// undefined }.
return Just(RelativeTo(
Cast<JSTemporalPlainDate>(value)->wrapped_rust().as_shared_ptr()));
}
// c. If value has an [[InitializedTemporalDateTime]] internal slot, then
if (InstanceTypeChecker::IsJSTemporalPlainDateTime(instance_type)) {
// i. Let plainDate be
// !CreateTemporalDate(value.[[ISODateTime]].[[ISODate]],
// value.[[Calendar]]).
auto date_record = GetPartialDate(Cast<JSTemporalPlainDateTime>(value));
std::unique_ptr<temporal_rs::PlainDate> plain_date = nullptr;
MOVE_RETURN_ON_EXCEPTION(
isolate, plain_date,
ExtractRustResult(isolate, temporal_rs::PlainDate::from_partial(
date_record, std::nullopt)));
// ii. Return the Record { [[PlainRelativeTo]]: plainDate,
// [[ZonedRelativeTo]]: undefined }.
return Just(RelativeTo(std::move(plain_date)));
}
// d. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(value).
temporal_rs::AnyCalendarKind kind = temporal_rs::AnyCalendarKind::Iso;
auto value_recvr = Cast<JSReceiver>(value);
ASSIGN_RETURN_ON_EXCEPTION(
isolate, kind,
temporal::GetTemporalCalendarIdentifierWithISODefault(isolate,
value_recvr));
// e. Let fields be ? PrepareCalendarFields(calendar, value, « year, month,
// month-code, day », « hour, minute, second, millisecond, microsecond,
// nanosecond, offset, time-zone », «»).
CombinedRecord fields;
using enum CalendarFieldsFlag;
MOVE_RETURN_ON_EXCEPTION(
isolate, fields,
PrepareCalendarFields(isolate, kind, value_recvr,
kAllDateFlags | kTimeFields | kOffset | kTimeZone,
RequiredFields::kNone));
auto partial = kNullPartialZonedDateTime;
// f. Let result be ? InterpretTemporalDateTimeFields(calendar, fields,
// constrain).
// g. Let timeZone be fields.[[TimeZone]].
// h. Let offsetString be fields.[[OffsetString]].
// j. Let isoDate be result.[[ISODate]].
// k. Let time be result.[[Time]].
auto overflow = temporal_rs::ArithmeticOverflow::Constrain;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, partial,
fields.Regulate<temporal_rs::PartialZonedDateTime>(isolate, overflow));
// We use different construction methods for ZonedDateTime in these two
// branches, so we've pulled steps 7-12 into this branch
// 7. If timeZone is unset, then
if (!partial.timezone) {
// a. Let plainDate be ? CreateTemporalDate(isoDate, calendar).
std::unique_ptr<temporal_rs::PlainDate> plain_relative_to;
MOVE_RETURN_ON_EXCEPTION(
isolate, plain_relative_to,
ExtractRustResult(isolate, temporal_rs::PlainDate::from_partial(
partial.date, overflow)));
// b. Return the Record { [[PlainRelativeTo]]: plainDate,
// [[ZonedRelativeTo]]: undefined }.
return Just(RelativeTo(std::move(plain_relative_to)));
}
// 8. If offsetBehaviour is option, then
// a. Let offsetNs be ! ParseDateTimeUTCOffset(offsetString).
// 9. Else,
// a. Let offsetNs be 0.
// 10. Let epochNanoseconds be ? InterpretISODateTimeOffset(isoDate, time,
// offsetBehaviour, offsetNs, timeZone, compatible, reject, matchBehaviour).
// 11. Let zonedRelativeTo be !
// CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar).
// (All these steps handled in Rust)
std::unique_ptr<temporal_rs::ZonedDateTime> zoned_relative_to;
MOVE_RETURN_ON_EXCEPTION(
isolate, zoned_relative_to,
ExtractRustResult(
isolate,
temporal_rs::ZonedDateTime::from_partial_with_provider(
partial, overflow, temporal_rs::Disambiguation::Compatible,
temporal_rs::OffsetDisambiguation::Reject,
TimeZoneProvider())));
// 12. Return the Record { [[PlainRelativeTo]]: undefined,
// [[ZonedRelativeTo]]: zonedRelativeTo }.
return Just(RelativeTo(std::move(zoned_relative_to)));
// 6. Else,
} else {
// a. If value is not a String, throw a TypeError exception.
if (!InstanceTypeChecker::IsString(instance_type)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(
"relativeTo must be object or string."));
}
DirectHandle<String> str = Cast<String>(value);
// b. Let result be ? ParseISODateTime(value, «
// TemporalDateTimeString[+Zoned], TemporalDateTimeString[~Zoned] »). Rest
// of the steps handled in Rust
temporal_rs::OwnedRelativeTo relative_to;
auto rust_result =
HandleStringEncodings<TemporalResult<temporal_rs::OwnedRelativeTo>>(
isolate, str,
[](std::string_view view)
-> TemporalResult<temporal_rs::OwnedRelativeTo> {
return temporal_rs::OwnedRelativeTo::from_utf8_with_provider(
view, TimeZoneProvider());
},
[](std::u16string_view view)
-> TemporalResult<temporal_rs::OwnedRelativeTo> {
return temporal_rs::OwnedRelativeTo::from_utf16_with_provider(
view, TimeZoneProvider());
});
MOVE_RETURN_ON_EXCEPTION(
isolate, relative_to,
ExtractRustResult(isolate, std::move(rust_result)));
// 12. Return the Record { [[PlainRelativeTo]]: undefined,
// [[ZonedRelativeTo]]: zonedRelativeTo }.
return Just(RelativeTo(std::move(relative_to)));
}
}
// ====== Difference operations ======
// ProviderArg is only for wrapping temporal_rs::Provider&
template <typename RustType, typename... ProviderArg>
using DifferenceOperation = TemporalAllocatedResult<temporal_rs::Duration> (
RustType::*)(const RustType&, temporal_rs::DifferenceSettings,
const ProviderArg&...) const;
// Generic function for all DifferenceTemporalFoo operations
//
// https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalinstant
// https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalplaindate
// https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalplaindatetime
// https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalplainyearmonth
// https://tc39.es/proposal-temporal/#sec-temporal-differencetemporalzoneddatetime
template <typename JSType, typename... ProviderArg>
MaybeDirectHandle<JSTemporalDuration> GenericDifferenceTemporal(
Isolate* isolate,
DifferenceOperation<typename JSType::RustType, ProviderArg...> operation,
UnitGroup group, Unit fallback_smallest_unit, DirectHandle<JSType> handle,
DirectHandle<Object> other_obj, DirectHandle<Object> options,
const char* method_name, const ProviderArg&... provider) {
// Steps are written for PlainDate, but are similar for other types
// 1. Set other to ?ToTemporalDate(other).
DirectHandle<JSType> other;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, other,
temporal::ToTemporalGeneric<JSType>(isolate, other_obj, method_name));
auto settings = temporal_rs::DifferenceSettings{.largest_unit = std::nullopt,
.smallest_unit = std::nullopt,
.rounding_mode = std::nullopt,
.increment = std::nullopt};
auto this_rust = handle->wrapped_rust();
auto other_rust = other->wrapped_rust();
// This step only exists for calendared types (everything other than PlainTime
// and Instant)
if constexpr (JSType::kTypeContainsCalendar) {
// 2. If CalendarEquals(temporalDate.[[Calendar]], other.[[Calendar]]) is
// false, throw a RangeError exception.
if (this_rust->calendar().kind() != other_rust->calendar().kind()) {
THROW_NEW_ERROR(isolate,
NewRangeError(MessageTemplate::kMismatchedCalendars));
}
}
// 3. Let resolvedOptions be ?GetOptionsObject(options).
// 4. Let settings be ? GetDifferenceSettings(operation, resolvedOptions,
// date, « », day, day).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, settings,
temporal::GetDifferenceSettingsWithoutChecks(
isolate, options, group, fallback_smallest_unit, method_name));
// Remaining steps handled by temporal_rs
// operation negation (step 6) is also handled in temporal_rs, we should not
// negate again here.
auto diff = (this_rust.raw()->*operation)(*other_rust, settings, provider...);
return ConstructRustWrappingType<JSTemporalDuration>(isolate,
std::move(diff));
}
// ====== Add operations ======
// Wrapping Rust-side ::add and ::subtract methods
// OverflowArgument is generic since some of these
// methods take std::optional.
//
// ProviderArg is only for wrapping temporal_rs::Provider&
template <typename RustType, typename OverflowArgument, typename... ProviderArg>
using BinaryOperation = TemporalAllocatedResult<RustType> (RustType::*)(
const temporal_rs::Duration&, OverflowArgument,
const ProviderArg&...) const;
// https://tc39.es/proposal-temporal/#sec-temporal-adddurationtodate
// https://tc39.es/proposal-temporal/#sec-temporal-adddurationtodatetime
// https://tc39.es/proposal-temporal/#sec-temporal-adddurationtoyearmonth
// https://tc39.es/proposal-temporal/#sec-temporal-adddurationtozoneddatetime
template <
typename JSType,
typename OverflowArgument = std::optional<temporal_rs::ArithmeticOverflow>,
typename... ProviderArg>
MaybeDirectHandle<JSType> AddDurationToGeneric(
Isolate* isolate,
BinaryOperation<typename JSType::RustType, OverflowArgument, ProviderArg...>
operation,
DirectHandle<JSType> temporal_js_type,
DirectHandle<Object> temporal_duration_like,
DirectHandle<Object> options_obj, const char* method_name,
const ProviderArg&... provider) {
// 1. Let duration be ? ToTemporalDuration(temporalDurationLike).
DirectHandle<JSTemporalDuration> other_duration;
ASSIGN_RETURN_ON_EXCEPTION(isolate, other_duration,
temporal::ToTemporalDuration(
isolate, temporal_duration_like, method_name));
// 2. If operation is subtract, set duration to
// CreateNegatedTemporalDuration(duration).
// (handled by Rust, we should not double-negate)
// 3. Let resolvedOptions be ?GetOptionsObject(options).
// 4. Let overflow be ?GetTemporalOverflowOption(resolvedOptions).
temporal_rs::ArithmeticOverflow overflow;
ASSIGN_RETURN_ON_EXCEPTION(isolate, overflow,
temporal::ToTemporalOverflowHandleUndefined(
isolate, options_obj, method_name));
auto rust_object = temporal_js_type->wrapped_rust();
// Remaining steps handled in Rust.
auto added = (rust_object.raw()->*operation)(*other_duration->wrapped_rust(),
overflow, provider...);
return ConstructRustWrappingType<JSType>(isolate, std::move(added));
}
// ====== .with() methods ======
// Helper for GenericWith: extract relevant options from options_obj, call
// .with() on Rust type.
//
// Steps 8 and later from
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.with
template <typename JSType, typename PartialType>
MaybeDirectHandle<JSType> GenericWithHelper(
Isolate* isolate,
const typename CppGCManaged<typename JSType::RustType>::Ptr& rust_object,
CombinedRecord& fields, DirectHandle<Object> options_obj,
const char* method_name) {
// 8. Let resolvedOptions be ? GetOptionsObject(options).
// 9. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
temporal_rs::ArithmeticOverflow overflow;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, overflow,
ToTemporalOverflowHandleUndefined(isolate, options_obj, method_name));
PartialType partial;
ASSIGN_RETURN_ON_EXCEPTION(isolate, partial,
fields.Regulate<PartialType>(isolate, overflow));
// Rest handled by Rust.
return ConstructRustWrappingType<JSType>(
isolate, rust_object->with(partial, overflow));
}
// ZonedDateTime needs to extract extra options
//
// Steps 19 and later in
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.with
template <>
MaybeDirectHandle<JSTemporalZonedDateTime>
GenericWithHelper<JSTemporalZonedDateTime, temporal_rs::PartialZonedDateTime>(
Isolate* isolate,
const CppGCManaged<JSTemporalZonedDateTime::RustType>::Ptr& rust_object,
CombinedRecord& fields, DirectHandle<Object> options_obj,
const char* method_name) {
// 19. Let resolvedOptions be ? GetOptionsObject(options).
// (Not explicitly needed since we use HandleUndefined methods)
temporal_rs::Disambiguation disambiguation;
temporal_rs::OffsetDisambiguation offset_option;
temporal_rs::ArithmeticOverflow overflow;
// 20. Let disambiguation be
// ? GetTemporalDisambiguationOption(resolvedOptions).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, disambiguation,
temporal::GetTemporalDisambiguationOptionHandleUndefined(
isolate, options_obj, method_name));
// 21. Let offset be ? GetTemporalOffsetOption(resolvedOptions, prefer).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, offset_option,
temporal::GetTemporalOffsetOptionHandleUndefined(
isolate, options_obj, temporal_rs::OffsetDisambiguation::Prefer,
method_name));
// 22. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, overflow,
ToTemporalOverflowHandleUndefined(isolate, options_obj, method_name));
temporal_rs::PartialZonedDateTime partial = kNullPartialZonedDateTime;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, partial,
fields.Regulate<temporal_rs::PartialZonedDateTime>(isolate, overflow));
// Rest handled by Rust.
return ConstructRustWrappingType<JSTemporalZonedDateTime>(
isolate,
rust_object->with_with_provider(partial, disambiguation, offset_option,
overflow, TimeZoneProvider()));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.with
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.with
// https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.with
// https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.with
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.with
//
// The function has comments for PlainDate, but the non-Rust-side code for the
// others is identical.
template <typename JSType, typename PartialType>
MaybeDirectHandle<JSType> GenericWith(Isolate* isolate,
DirectHandle<JSType> this_obj,
DirectHandle<Object> temporal_like_obj,
DirectHandle<Object> options_obj,
CalendarFieldsFlags flags,
const char* method_name) {
// 1. Let temporalDate be the this value.
// 2. Perform ? RequireInternalSlot(temporalDate,
// [[InitializedTemporalDate]]).
// (handled by type system)
// 3. If ? IsPartialTemporalObject(temporalDateLike) is false, throw a
// TypeError exception.
bool is_partial = false;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, is_partial, IsPartialTemporalObject(isolate, temporal_like_obj));
if (!is_partial) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kWithNoPartial));
}
// IsPartialTemporalObject only returns true for valid JSReceivers.
auto options_recvr = Cast<JSReceiver>(temporal_like_obj);
auto rust_object = this_obj->wrapped_rust();
// 4. Let calendar be temporalDate.[[Calendar]].
auto kind = rust_object->calendar().kind();
// 5. Let fields be ISODateToFields(calendar, temporalDate.[[ISODate]], date).
// (handled in Rust code, not observable)
// 6. Let partialDate be ? PrepareCalendarFields(calendar, temporalDateLike, «
// year, month, month-code, day », « », partial).
CombinedRecord fields;
MOVE_RETURN_ON_EXCEPTION(
isolate, fields,
PrepareCalendarFields(isolate, kind, options_recvr, flags,
RequiredFields::kPartial));
// 7. Set fields to CalendarMergeFields(calendar, fields, partialDate).
// (handled in Rust code, not observable)
// Fetching options handled by GenericWithHelper.
// Remaining steps handled by Rust code called by GenericWithHelper.
return GenericWithHelper<JSType, PartialType>(isolate, rust_object, fields,
options_obj, method_name);
}
// ====== Misc ======
V8_WARN_UNUSED_RESULT Maybe<temporal_rs::TimeZone> ToRustTimeZone(
Isolate* isolate, std::string_view tz) {
return ExtractRustResult(isolate,
temporal_rs::TimeZone::try_from_str_with_provider(
tz, TimeZoneProvider()));
}
} // namespace temporal
// https://tc39.es/proposal-temporal/#sec-temporal.duration
MaybeDirectHandle<JSTemporalDuration> JSTemporalDuration::Constructor(
Isolate* isolate, DirectHandle<JSFunction> target,
DirectHandle<HeapObject> new_target, DirectHandle<Object> years,
DirectHandle<Object> months, DirectHandle<Object> weeks,
DirectHandle<Object> days, DirectHandle<Object> hours,
DirectHandle<Object> minutes, DirectHandle<Object> seconds,
DirectHandle<Object> milliseconds, DirectHandle<Object> microseconds,
DirectHandle<Object> nanoseconds) {
// 1. If NewTarget is undefined, then
if (IsUndefined(*new_target)) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
isolate->factory()->NewStringFromAsciiChecked(
"Temporal.Duration")));
}
// 2. If years is undefined, let y be 0; else let y be
// ? ToIntegerIfIntegral(years).
int64_t y = 0;
if (!IsUndefined(*years)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, y, temporal::ToIntegerTypeIfIntegral<int64_t>(isolate, years));
}
// 3. If months is undefined, let mo be 0; else let mo be
// ? ToIntegerIfIntegral(months).
int64_t mo = 0;
if (!IsUndefined(*months)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, mo,
temporal::ToIntegerTypeIfIntegral<int64_t>(isolate, months));
}
// 4. If weeks is undefined, let w be 0; else let w be
// ? ToIntegerIfIntegral(weeks).
int64_t w = 0;
if (!IsUndefined(*weeks)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, w, temporal::ToIntegerTypeIfIntegral<int64_t>(isolate, weeks));
}
// 5. If days is undefined, let d be 0; else let d be
// ? ToIntegerIfIntegral(days).
int64_t d = 0;
if (!IsUndefined(*days)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, d, temporal::ToIntegerTypeIfIntegral<int64_t>(isolate, days));
}
// 6. If hours is undefined, let h be 0; else let h be
// ? ToIntegerIfIntegral(hours).
int64_t h = 0;
if (!IsUndefined(*hours)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, h, temporal::ToIntegerTypeIfIntegral<int64_t>(isolate, hours));
}
// 7. If minutes is undefined, let m be 0; else let m be
// ? ToIntegerIfIntegral(minutes).
int64_t m = 0;
if (!IsUndefined(*minutes)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, m,
temporal::ToIntegerTypeIfIntegral<int64_t>(isolate, minutes));
}
// 8. If seconds is undefined, let s be 0; else let s be
// ? ToIntegerIfIntegral(seconds).
int64_t s = 0;
if (!IsUndefined(*seconds)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, s,
temporal::ToIntegerTypeIfIntegral<int64_t>(isolate, seconds));
}
// 9. If milliseconds is undefined, let ms be 0; else let ms be
// ? ToIntegerIfIntegral(milliseconds).
int64_t ms = 0;
if (!IsUndefined(*milliseconds)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, ms,
temporal::ToIntegerTypeIfIntegral<int64_t>(isolate, milliseconds));
}
// 10. If microseconds is undefined, let mis be 0; else let mis be
// ? ToIntegerIfIntegral(microseconds).
double mis = 0;
if (!IsUndefined(*microseconds)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, mis, temporal::ToIntegerIfIntegral(isolate, microseconds));
}
// 11. If nanoseconds is undefined, let ns be 0; else let ns be
// ? ToIntegerIfIntegral(nanoseconds).
double ns = 0;
if (!IsUndefined(*nanoseconds)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, ns, temporal::ToIntegerIfIntegral(isolate, nanoseconds));
}
// 12. Return ?CreateTemporalDuration(y, mo, w, d, h, m, s, ms, mis, ns,
// NewTarget).
return ConstructRustWrappingType<JSTemporalDuration>(
isolate, target, new_target,
temporal_rs::Duration::create(y, mo, w, d, h, m, s, ms, mis, ns));
}
// https://tc39.es/proposal-temporal/#sec-temporal.duration.compare
MaybeDirectHandle<Smi> JSTemporalDuration::Compare(
Isolate* isolate, DirectHandle<Object> one_obj,
DirectHandle<Object> two_obj, DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.Duration.compare";
DirectHandle<JSTemporalDuration> one;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, one,
temporal::ToTemporalDuration(isolate, one_obj, method_name));
DirectHandle<JSTemporalDuration> two;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, two,
temporal::ToTemporalDuration(isolate, two_obj, method_name));
temporal::RelativeTo relative_to;
MOVE_RETURN_ON_EXCEPTION(isolate, relative_to,
temporal::GetTemporalRelativeToOptionHandleUndefined(
isolate, options_obj));
int8_t comparison = 0;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, comparison,
ExtractRustResult(isolate, one->wrapped_rust()->compare_with_provider(
*two->wrapped_rust(), relative_to.ToRust(),
TimeZoneProvider())));
return direct_handle(Smi::FromInt(comparison), isolate);
}
// https://tc39.es/proposal-temporal/#sec-temporal.duration.from
MaybeDirectHandle<JSTemporalDuration> JSTemporalDuration::From(
Isolate* isolate, DirectHandle<Object> item) {
static const char method_name[] = "Temporal.Duration.from";
return temporal::ToTemporalDuration(isolate, item, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.round
MaybeDirectHandle<JSTemporalDuration> JSTemporalDuration::Round(
Isolate* isolate, DirectHandle<JSTemporalDuration> duration,
DirectHandle<Object> round_to_obj) {
static const char method_name[] = "Temporal.Duration.prototype.round";
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration,
// [[InitializedTemporalDuration]]).
// (handled by type system)
// 3. If roundTo is undefined, then
if (IsUndefined(*round_to_obj)) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kRoundToMissing));
}
DirectHandle<JSReceiver> round_to;
auto factory = isolate->factory();
// 4. If roundTo is a String, then
if (IsString(*round_to_obj)) {
// a. Let paramString be roundTo.
DirectHandle<String> param_string = Cast<String>(round_to_obj);
// b. Set roundTo to ! OrdinaryObjectCreate(null).
round_to = factory->NewJSObjectWithNullProto();
// TODO(manishearth) Ideally we don't have to perform this allocation
// c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit",
// paramString).
CHECK(JSReceiver::CreateDataProperty(isolate, round_to,
factory->smallestUnit_string(),
param_string, Just(kThrowOnError))
.FromJust());
// 5. Else,
} else {
// a. Set roundTo to ? GetOptionsObject(roundTo).
// We have already checked for undefined, we can hoist the JSReceiver
// check out and just cast
if (!IsJSReceiver(*round_to_obj)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kRoundToMustBeObject));
}
round_to = Cast<JSReceiver>(round_to_obj);
}
// 6. Let smallestUnitPresent be true.
// 7. Let largestUnitPresent be true.
// (handled by Rust)
// 8. NOTE: (...)
// 9. Let largestUnit be ? GetTemporalUnitValuedOption(roundTo, "largestUnit",
// unset).
std::optional<Unit> largest_unit;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, largest_unit,
temporal::GetTemporalUnitValuedOption(
isolate, round_to, isolate->factory()->largestUnit_string(),
DefaultValue::kUnset, method_name));
// 10. Let relativeToRecord be ? GetTemporalRelativeToOption(roundTo).
// 11. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]].
// 12. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]].
temporal::RelativeTo relative_to;
MOVE_RETURN_ON_EXCEPTION(
isolate, relative_to,
temporal::GetTemporalRelativeToOptionHandleUndefined(isolate, round_to));
// 13. Let roundingIncrement be ? GetRoundingIncrementOption(roundTo).
uint32_t rounding_increment;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, rounding_increment,
temporal::GetRoundingIncrementOption(isolate, round_to));
// 14. Let roundingMode be ? GetRoundingModeOption(roundTo, half-expand).
RoundingMode rounding_mode;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, rounding_mode,
temporal::GetRoundingModeOption(isolate, round_to,
RoundingMode::HalfExpand, method_name));
// 15. Let smallestUnit be ? GetTemporalUnitValuedOption(roundTo,
// "smallestUnit", unset).
std::optional<Unit> smallest_unit;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, smallest_unit,
temporal::GetTemporalUnitValuedOption(
isolate, round_to, isolate->factory()->smallestUnit_string(),
DefaultValue::kUnset, method_name));
// 16. Perform ? ValidateTemporalUnitValue(smallestUnit, datetime).
RETURN_ON_EXCEPTION(
isolate, temporal::ValidateTemporalUnitValue(isolate, smallest_unit,
UnitGroup::kDateTime));
// Rest of the steps handled in Rust
auto options = temporal_rs::RoundingOptions{.largest_unit = largest_unit,
.smallest_unit = smallest_unit,
.rounding_mode = rounding_mode,
.increment = rounding_increment};
auto rounded = duration->wrapped_rust()->round_with_provider(
options, relative_to.ToRust(), TimeZoneProvider());
return ConstructRustWrappingType<JSTemporalDuration>(isolate,
std::move(rounded));
}
// https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.total
// https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.total
MaybeDirectHandle<Number> JSTemporalDuration::Total(
Isolate* isolate, DirectHandle<JSTemporalDuration> duration,
DirectHandle<Object> total_of_obj) {
static const char method_name[] = "Temporal.Duration.prototype.total";
// 1. Let duration be the this value.
// 2. Perform ? RequireInternalSlot(duration,
// [[InitializedTemporalDuration]]).
// (handled by type system)
// 3. If totalOf is undefined, throw a TypeError exception.
if (IsUndefined(*total_of_obj)) {
THROW_NEW_ERROR(
isolate, NEW_TEMPORAL_TYPE_ERROR("Must specify a totalOf parameter"));
}
DirectHandle<JSReceiver> total_of;
auto factory = isolate->factory();
// 4. If totalOf is a String, then
if (IsString(*total_of_obj)) {
// a. Let paramString be totalOf.
DirectHandle<String> param_string = Cast<String>(total_of_obj);
// b. Set totalOf to ! OrdinaryObjectCreate(null).
total_of = factory->NewJSObjectWithNullProto();
// c. Perform ! CreateDataPropertyOrThrow(total_of, "unit", paramString).
CHECK(JSReceiver::CreateDataProperty(isolate, total_of,
factory->unit_string(), param_string,
Just(kThrowOnError))
.FromJust());
// 5. Else,
} else {
// a. Set totalOf to ? GetOptionsObject(totalOf).
// We have already checked for undefined, we can hoist the JSReceiver
// check out and just cast
if (!IsJSReceiver(*total_of_obj)) {
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_TYPE_ERROR("totalOf must be an object."));
}
total_of = Cast<JSReceiver>(total_of_obj);
}
// 6. NOTE (...)
// 7. Let relativeToRecord be ? GetTemporalRelativeToOption(totalOf).
// 8. Let zonedRelativeTo be relativeToRecord.[[ZonedRelativeTo]].
// 9. Let plainRelativeTo be relativeToRecord.[[PlainRelativeTo]].
temporal::RelativeTo relative_to;
MOVE_RETURN_ON_EXCEPTION(
isolate, relative_to,
temporal::GetTemporalRelativeToOptionHandleUndefined(isolate, total_of));
// 10. Let unit be ? GetTemporalUnitValuedOption(totalOf, "unit", required).
std::optional<Unit> unit;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, unit,
temporal::GetTemporalUnitValuedOption(
isolate, total_of, isolate->factory()->unit_string(),
DefaultValue::kRequired, method_name));
// 11. Perform ? ValidateTemporalUnitValue(unit, datetime).
RETURN_ON_EXCEPTION(isolate, temporal::ValidateTemporalUnitValue(
isolate, unit, UnitGroup::kDateTime));
// We set required to true.
DCHECK(unit.has_value());
// Remaining steps handled in Rust
double ret;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, ret,
ExtractRustResult(isolate, duration->wrapped_rust()->total_with_provider(
unit.value(), relative_to.ToRust(),
TimeZoneProvider())));
return factory->NewNumber(ret);
}
// https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.with
MaybeDirectHandle<JSTemporalDuration> JSTemporalDuration::With(
Isolate* isolate, DirectHandle<JSTemporalDuration> duration,
DirectHandle<Object> temporal_duration_like) {
temporal_rs::PartialDuration partial;
ASSIGN_RETURN_ON_EXCEPTION(isolate, partial,
temporal::ToTemporalPartialDurationRecord(
isolate, temporal_duration_like));
if (!partial.years.has_value()) {
partial.years = duration->wrapped_rust()->years();
}
if (!partial.months.has_value()) {
partial.months = duration->wrapped_rust()->months();
}
if (!partial.months.has_value()) {
partial.months = duration->wrapped_rust()->months();
}
if (!partial.weeks.has_value()) {
partial.weeks = duration->wrapped_rust()->weeks();
}
if (!partial.days.has_value()) {
partial.days = duration->wrapped_rust()->days();
}
if (!partial.hours.has_value()) {
partial.hours = duration->wrapped_rust()->hours();
}
if (!partial.minutes.has_value()) {
partial.minutes = duration->wrapped_rust()->minutes();
}
if (!partial.seconds.has_value()) {
partial.seconds = duration->wrapped_rust()->seconds();
}
if (!partial.milliseconds.has_value()) {
partial.milliseconds = duration->wrapped_rust()->milliseconds();
}
if (!partial.microseconds.has_value()) {
partial.microseconds = duration->wrapped_rust()->microseconds();
}
if (!partial.nanoseconds.has_value()) {
partial.nanoseconds = duration->wrapped_rust()->nanoseconds();
}
return ConstructRustWrappingType<JSTemporalDuration>(
isolate, temporal_rs::Duration::from_partial_duration(partial));
}
// https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.sign
MaybeDirectHandle<Smi> JSTemporalDuration::Sign(
Isolate* isolate, DirectHandle<JSTemporalDuration> duration) {
auto sign = duration->wrapped_rust()->sign();
return direct_handle(Smi::FromInt((temporal_rs::Sign::Value)sign), isolate);
}
// https://tc39.es/proposal-temporal/#sec-get-temporal.duration.prototype.blank
MaybeDirectHandle<Oddball> JSTemporalDuration::Blank(
Isolate* isolate, DirectHandle<JSTemporalDuration> duration) {
return isolate->factory()->ToBoolean(duration->wrapped_rust()->sign() ==
temporal_rs::Sign::Zero);
}
// https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.negated
MaybeDirectHandle<JSTemporalDuration> JSTemporalDuration::Negated(
Isolate* isolate, DirectHandle<JSTemporalDuration> duration) {
return ConstructRustWrappingType<JSTemporalDuration>(
isolate, duration->wrapped_rust()->negated());
}
// https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.abs
MaybeDirectHandle<JSTemporalDuration> JSTemporalDuration::Abs(
Isolate* isolate, DirectHandle<JSTemporalDuration> duration) {
return ConstructRustWrappingType<JSTemporalDuration>(
isolate, duration->wrapped_rust()->abs());
}
// https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.add
MaybeDirectHandle<JSTemporalDuration> JSTemporalDuration::Add(
Isolate* isolate, DirectHandle<JSTemporalDuration> duration,
DirectHandle<Object> other, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.Duration.prototype.add";
DirectHandle<JSTemporalDuration> other_duration;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, other_duration,
temporal::ToTemporalDuration(isolate, other, method_name));
auto result = duration->wrapped_rust()->add(*other_duration->wrapped_rust());
return ConstructRustWrappingType<JSTemporalDuration>(isolate,
std::move(result));
}
// https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.subtract
MaybeDirectHandle<JSTemporalDuration> JSTemporalDuration::Subtract(
Isolate* isolate, DirectHandle<JSTemporalDuration> duration,
DirectHandle<Object> other, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.Duration.prototype.subtract";
DirectHandle<JSTemporalDuration> other_duration;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, other_duration,
temporal::ToTemporalDuration(isolate, other, method_name));
auto result =
duration->wrapped_rust()->subtract(*other_duration->wrapped_rust());
return ConstructRustWrappingType<JSTemporalDuration>(isolate,
std::move(result));
}
// https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tostring
MaybeDirectHandle<String> JSTemporalDuration::ToString(
Isolate* isolate, DirectHandle<JSTemporalDuration> duration,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.Duration.prototype.toString";
// 3. Let resolvedOptions be ?GetOptionsObject(options).
DirectHandle<JSReceiver> options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options, GetOptionsObject(isolate, options_obj, method_name));
// 5. Let digits be ?GetTemporalFractionalSecondDigitsOption(resolvedOptions).
temporal_rs::Precision digits;
ASSIGN_RETURN_ON_EXCEPTION(isolate, digits,
temporal::GetTemporalFractionalSecondDigitsOption(
isolate, options, method_name));
// 6. Let roundingMode be ? GetRoundingModeOption(resolvedOptions, trunc).
RoundingMode rounding_mode;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, rounding_mode,
temporal::GetRoundingModeOption(isolate, options, RoundingMode::Trunc,
method_name));
// 7. Let smallestUnit be ? GetTemporalUnitValuedOption(resolvedOptions,
// "smallestUnit", unset).
std::optional<Unit> smallest_unit;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, smallest_unit,
temporal::GetTemporalUnitValuedOption(
isolate, options, isolate->factory()->smallestUnit_string(),
DefaultValue::kUnset, method_name));
// 8. Perform ? ValidateTemporalUnitValue(smallestUnit, time).
RETURN_ON_EXCEPTION(isolate, temporal::ValidateTemporalUnitValue(
isolate, smallest_unit, UnitGroup::kTime));
// 8-17 performed by Rust
auto rust_options = temporal_rs::ToStringRoundingOptions{
.precision = digits,
.smallest_unit = smallest_unit,
.rounding_mode = rounding_mode,
};
return temporal::GenericTemporalToString(isolate, duration,
&temporal_rs::Duration::to_string,
std::move(rust_options));
}
// https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tojson
MaybeDirectHandle<String> JSTemporalDuration::ToJSON(
Isolate* isolate, DirectHandle<JSTemporalDuration> duration) {
return temporal::GenericTemporalToString(isolate, duration,
&temporal_rs::Duration::to_string,
std::move(temporal::kToStringAuto));
}
MaybeDirectHandle<String> JSTemporalDuration::ToLocaleString(
Isolate* isolate, DirectHandle<JSTemporalDuration> duration,
DirectHandle<Object> locales, DirectHandle<Object> options) {
#ifdef V8_INTL_SUPPORT
// https://tc39.es/proposal-temporal/#sup-temporal.duration.prototype.tolocalestring
return JSDurationFormat::TemporalToLocaleString(isolate, duration, locales,
options);
#else // V8_INTL_SUPPORT
// https://tc39.es/proposal-temporal/#sec-temporal.duration.prototype.tolocalestring
return temporal::GenericTemporalToString(isolate, duration,
&temporal_rs::Duration::to_string,
std::move(temporal::kToStringAuto));
#endif // V8_INTL_SUPPORT
}
MaybeDirectHandle<JSTemporalPlainDate> JSTemporalPlainDate::Constructor(
Isolate* isolate, DirectHandle<JSFunction> target,
DirectHandle<HeapObject> new_target, DirectHandle<Object> iso_year_obj,
DirectHandle<Object> iso_month_obj, DirectHandle<Object> iso_day_obj,
DirectHandle<Object> calendar_like) {
// 1. If NewTarget is undefined, then
if (IsUndefined(*new_target)) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
isolate->factory()->NewStringFromAsciiChecked(
"Temporal.PlainDate")));
}
// 2. Let y be ? ToIntegerWithTruncation(isoYear).
double y = 0;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, y, temporal::ToIntegerWithTruncation(isolate, iso_year_obj));
// 3. Let m be ? ToIntegerWithTruncation(isoMonth).
double m = 0;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, m, temporal::ToIntegerWithTruncation(isolate, iso_month_obj));
// 4. Let d be ? ToIntegerWithTruncation(isoDay).
double d = 0;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, d, temporal::ToIntegerWithTruncation(isolate, iso_day_obj));
// 5. If calendar is undefined, set calendar to "iso8601".
temporal_rs::AnyCalendarKind calendar = temporal_rs::AnyCalendarKind::Iso;
if (!IsUndefined(*calendar_like)) {
// 6. If calendar is not a String, throw a TypeError exception.
if (!IsString(*calendar_like)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kCalendarMustBeString));
}
// 7. Set calendar to ?CanonicalizeCalendar(calendar).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, calendar,
temporal::CanonicalizeCalendar(isolate, Cast<String>(calendar_like)));
}
// 8. If IsValidISODate(y, m, d) is false, throw a RangeError exception.
if (!temporal::IsValidIsoDate(y, m, d)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kInvalidIsoDate));
}
// Rest of the steps handled in Rust
// These static_casts are fine to perform since IsValid* will have constrained
// these values to range already.
// https://github.com/boa-dev/temporal/issues/334 for moving this logic into
// the Rust code.
auto rust_object = temporal_rs::PlainDate::try_new(
static_cast<int32_t>(y), static_cast<uint8_t>(m), static_cast<uint8_t>(d),
calendar);
return ConstructRustWrappingType<JSTemporalPlainDate>(
isolate, target, new_target, std::move(rust_object));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.compare
MaybeDirectHandle<Smi> JSTemporalPlainDate::Compare(
Isolate* isolate, DirectHandle<Object> one_obj,
DirectHandle<Object> two_obj) {
static const char method_name[] = "Temporal.PlainDate.compare";
DirectHandle<JSTemporalPlainDate> one;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, one,
temporal::ToTemporalDate(isolate, one_obj, {}, method_name));
DirectHandle<JSTemporalPlainDate> two;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, two,
temporal::ToTemporalDate(isolate, two_obj, {}, method_name));
return direct_handle(Smi::FromInt(temporal_rs::PlainDate::compare(
*one->wrapped_rust(), *two->wrapped_rust())),
isolate);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.equals
MaybeDirectHandle<Oddball> JSTemporalPlainDate::Equals(
Isolate* isolate, DirectHandle<JSTemporalPlainDate> temporal_date,
DirectHandle<Object> other_obj) {
static const char method_name[] = "Temporal.PlainDate.prototype.equals";
DirectHandle<JSTemporalPlainDate> other;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, other,
temporal::ToTemporalDate(isolate, other_obj, {}, method_name));
auto equals = temporal_date->wrapped_rust()->equals(*other->wrapped_rust());
return isolate->factory()->ToBoolean(equals);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.toplainyearmonth
MaybeDirectHandle<JSTemporalPlainYearMonth>
JSTemporalPlainDate::ToPlainYearMonth(
Isolate* isolate, DirectHandle<JSTemporalPlainDate> temporal_date) {
return temporal::GenericToTemporalMethod<JSTemporalPlainYearMonth>(
isolate, temporal_date, &temporal_rs::PlainDate::to_plain_year_month);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.toplainmonthday
MaybeDirectHandle<JSTemporalPlainMonthDay> JSTemporalPlainDate::ToPlainMonthDay(
Isolate* isolate, DirectHandle<JSTemporalPlainDate> temporal_date) {
return temporal::GenericToTemporalMethod<JSTemporalPlainMonthDay>(
isolate, temporal_date, &temporal_rs::PlainDate::to_plain_month_day);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.toplaindatetime
MaybeDirectHandle<JSTemporalPlainDateTime> JSTemporalPlainDate::ToPlainDateTime(
Isolate* isolate, DirectHandle<JSTemporalPlainDate> temporal_date,
DirectHandle<Object> temporal_time_obj) {
static const char method_name[] = "Temporal.PlainDate.toPlainDateTime";
// 3. Let time be ? ToTimeRecordOrMidnight(temporalTime).
CppGCManaged<temporal_rs::PlainTime>::Ptr maybe_time;
DirectHandle<JSTemporalPlainTime> time_output;
MOVE_RETURN_ON_EXCEPTION(
isolate, maybe_time,
temporal::ToTimeRecordOrMidnight(isolate, temporal_time_obj, time_output,
method_name));
// Rest of the steps handled in Rust.
return ConstructRustWrappingType<JSTemporalPlainDateTime>(
isolate,
temporal_date->wrapped_rust()->to_plain_date_time(maybe_time.raw()));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.with
MaybeDirectHandle<JSTemporalPlainDate> JSTemporalPlainDate::With(
Isolate* isolate, DirectHandle<JSTemporalPlainDate> temporal_date,
DirectHandle<Object> temporal_date_like_obj,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.PlainDate.prototype.with";
return temporal::GenericWith<JSTemporalPlainDate, temporal_rs::PartialDate>(
isolate, temporal_date, temporal_date_like_obj, options_obj,
temporal::kAllDateFlags, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.withcalendar
MaybeDirectHandle<JSTemporalPlainDate> JSTemporalPlainDate::WithCalendar(
Isolate* isolate, DirectHandle<JSTemporalPlainDate> temporal_date,
DirectHandle<Object> calendar_id) {
// 3. Let calendar be ? ToTemporalCalendarIdentifier(calendarLike).
temporal_rs::AnyCalendarKind calendar_kind;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, calendar_kind,
temporal::ToTemporalCalendarIdentifier(isolate, calendar_id));
// Rest of the steps handled in Rust
return ConstructRustWrappingType<JSTemporalPlainDate>(
isolate, temporal_date->wrapped_rust()->with_calendar(calendar_kind));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tozoneddatetime
MaybeDirectHandle<JSTemporalZonedDateTime> JSTemporalPlainDate::ToZonedDateTime(
Isolate* isolate, DirectHandle<JSTemporalPlainDate> temporal_date,
DirectHandle<Object> item_obj) {
static const char method_name[] = "Temporal.PlainDate.toZonedDateTime";
// 1. Let temporalDate be the this value.
// 2. Perform ? RequireInternalSlot(temporalDate,
// [[InitializedTemporalDate]]).
temporal_rs::TimeZone time_zone;
DirectHandle<Object> temporal_time_obj;
// 3. If item is an Object, then
if (IsJSReceiver(*item_obj)) {
DirectHandle<JSReceiver> item = Cast<JSReceiver>(item_obj);
// a. Let timeZoneLike be ? Get(item, "timeZone").
DirectHandle<Object> time_zone_like;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, time_zone_like,
JSReceiver::GetProperty(isolate, item,
isolate->factory()->timeZone_string()));
// b. If timeZoneLike is undefined, then
if (IsUndefined(*time_zone_like)) {
// i. Let timeZone be ? ToTemporalTimeZoneIdentifier(item).
MOVE_RETURN_ON_EXCEPTION(
isolate, time_zone,
temporal::ToTemporalTimeZoneIdentifier(isolate, item));
// ii. Let temporalTime be undefined.
// c. Else,
} else {
// i. Let timeZone be ? ToTemporalTimeZoneIdentifier(timeZoneLike).
MOVE_RETURN_ON_EXCEPTION(
isolate, time_zone,
temporal::ToTemporalTimeZoneIdentifier(isolate, time_zone_like));
// ii. Let temporalTime be ? Get(item, "plainTime").
ASSIGN_RETURN_ON_EXCEPTION(
isolate, temporal_time_obj,
JSReceiver::GetProperty(isolate, item,
isolate->factory()->plainTime_string()));
}
// 4. Else,
} else {
// a. Let timeZone be ? ToTemporalTimeZoneIdentifier(item).
MOVE_RETURN_ON_EXCEPTION(
isolate, time_zone,
temporal::ToTemporalTimeZoneIdentifier(isolate, item_obj));
// b. Let temporalTime be undefined.
}
DirectHandle<JSTemporalPlainTime> temporal_time;
CppGCManaged<temporal_rs::PlainTime>::Ptr temporal_time_rust;
// 5. If temporalTime is undefined, then
if (temporal_time_obj.is_null() || IsUndefined(*temporal_time_obj)) {
// a. Let epochNs be ? GetStartOfDay(timeZone, temporalDate.[[ISODate]]).
// (handled in Rust by temporal_time_rust being a null pointer)
// 6. Else,
} else {
// a. Set temporalTime to ? ToTemporalTime(temporalTime).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, temporal_time,
temporal::ToTemporalTime(isolate, temporal_time_obj, {}, method_name));
temporal_time_rust = temporal_time->wrapped_rust();
// (Rest of the steps handled in Rust)
}
return ConstructRustWrappingType<JSTemporalZonedDateTime>(
isolate, temporal_date->wrapped_rust()->to_zoned_date_time_with_provider(
time_zone, temporal_time_rust.raw(), TimeZoneProvider()));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.add
MaybeDirectHandle<JSTemporalPlainDate> JSTemporalPlainDate::Add(
Isolate* isolate, DirectHandle<JSTemporalPlainDate> temporal_date,
DirectHandle<Object> temporal_duration_like,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.PlainDate.prototype.add";
// 3. Return ? AddDurationToDate(add, temporalDate, temporalDurationLike,
// options).
return temporal::AddDurationToGeneric(isolate, &temporal_rs::PlainDate::add,
temporal_date, temporal_duration_like,
options_obj, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.subtract
MaybeDirectHandle<JSTemporalPlainDate> JSTemporalPlainDate::Subtract(
Isolate* isolate, DirectHandle<JSTemporalPlainDate> temporal_date,
DirectHandle<Object> temporal_duration_like,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.PlainDate.prototype.subtract";
// 3. Return ? AddDurationToDate(subtract, temporalDate, temporalDurationLike,
// options).
return temporal::AddDurationToGeneric<JSTemporalPlainDate>(
isolate, &temporal_rs::PlainDate::subtract, temporal_date,
temporal_duration_like, options_obj, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.until
MaybeDirectHandle<JSTemporalDuration> JSTemporalPlainDate::Until(
Isolate* isolate, DirectHandle<JSTemporalPlainDate> handle,
DirectHandle<Object> other, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.PlainDate.prototype.until";
return temporal::GenericDifferenceTemporal(
isolate, &temporal_rs::PlainDate::until, UnitGroup::kDate, Unit::Day,
handle, other, options, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.since
MaybeDirectHandle<JSTemporalDuration> JSTemporalPlainDate::Since(
Isolate* isolate, DirectHandle<JSTemporalPlainDate> handle,
DirectHandle<Object> other, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.PlainDate.prototype.since";
return temporal::GenericDifferenceTemporal(
isolate, &temporal_rs::PlainDate::since, UnitGroup::kDate, Unit::Day,
handle, other, options, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.now.plaindateiso
MaybeDirectHandle<JSTemporalPlainDate> JSTemporalPlainDate::NowISO(
Isolate* isolate, DirectHandle<Object> temporal_time_zone_like) {
std::unique_ptr<temporal_rs::ZonedDateTime> zdt;
MOVE_RETURN_ON_EXCEPTION(
isolate, zdt,
temporal::GenericTemporalNowISO(isolate, temporal_time_zone_like));
return ConstructRustWrappingType<JSTemporalPlainDate>(isolate,
zdt->to_plain_date());
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.from
MaybeDirectHandle<JSTemporalPlainDate> JSTemporalPlainDate::From(
Isolate* isolate, DirectHandle<Object> item_obj,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.PlainDate.from";
DirectHandle<JSTemporalPlainDate> item;
return temporal::ToTemporalDate(isolate, item_obj, options_obj, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tostring
MaybeDirectHandle<String> JSTemporalPlainDate::ToString(
Isolate* isolate, DirectHandle<JSTemporalPlainDate> temporal_date,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.PlainDate.prototype.toString";
// 3. Let resolvedOptions be ?GetOptionsObject(options).
DirectHandle<JSReceiver> options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options, GetOptionsObject(isolate, options_obj, method_name));
// 4. Let showCalendar be ?GetTemporalShowCalendarNameOption(resolvedOptions).
temporal_rs::DisplayCalendar show_calendar;
ASSIGN_RETURN_ON_EXCEPTION(isolate, show_calendar,
temporal::GetTemporalShowCalendarNameOption(
isolate, options, method_name));
// 5. Return TemporalDateToString(temporalDate, showCalendar).
return temporal::GenericTemporalToString(
isolate, temporal_date, &temporal_rs::PlainDate::to_ixdtf_string,
show_calendar);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tojson
MaybeDirectHandle<String> JSTemporalPlainDate::ToJSON(
Isolate* isolate, DirectHandle<JSTemporalPlainDate> temporal_date) {
return temporal::GenericTemporalToString(
isolate, temporal_date, &temporal_rs::PlainDate::to_ixdtf_string,
temporal_rs::DisplayCalendar::Auto);
}
MaybeDirectHandle<String> JSTemporalPlainDate::ToLocaleString(
Isolate* isolate, DirectHandle<JSTemporalPlainDate> temporal_date,
DirectHandle<Object> locales, DirectHandle<Object> options) {
#ifdef V8_INTL_SUPPORT
// https://tc39.es/proposal-temporal/#sup-temporal.plaindate.prototype.tolocalestring
return JSDateTimeFormat::TemporalToLocaleString(
isolate, temporal_date, locales, options,
JSDateTimeFormat::RequiredOption::kDate,
JSDateTimeFormat::DefaultsOption::kDate,
"Temporal.PlainDate.prototype.toLocaleString");
#else // V8_INTL_SUPPORT
// https://tc39.es/proposal-temporal/#sec-temporal.plaindate.prototype.tolocalestring
return temporal::GenericTemporalToString(
isolate, temporal_date, &temporal_rs::PlainDate::to_ixdtf_string,
temporal_rs::DisplayCalendar::Auto);
#endif // V8_INTL_SUPPORT
}
Maybe<int64_t> JSTemporalPlainDate::GetEpochMillisecondsForUtc(
Isolate* isolate) {
return ExtractRustResult(isolate, this->wrapped_rust()->epoch_ms_for_utc());
}
// https://tc39.es/proposal-temporal/#sec-temporal-createtemporaldatetime
MaybeDirectHandle<JSTemporalPlainDateTime> JSTemporalPlainDateTime::Constructor(
Isolate* isolate, DirectHandle<JSFunction> target,
DirectHandle<HeapObject> new_target, DirectHandle<Object> iso_year_obj,
DirectHandle<Object> iso_month_obj, DirectHandle<Object> iso_day_obj,
DirectHandle<Object> hour_obj, DirectHandle<Object> minute_obj,
DirectHandle<Object> second_obj, DirectHandle<Object> millisecond_obj,
DirectHandle<Object> microsecond_obj, DirectHandle<Object> nanosecond_obj,
DirectHandle<Object> calendar_like) {
// 1. If NewTarget is undefined, then
if (IsUndefined(*new_target)) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
isolate->factory()->NewStringFromAsciiChecked(
"Temporal.PlainDateTime")));
}
// 2. Set isoYear to ?ToIntegerWithTruncation(isoYear).
double y = 0;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, y, temporal::ToIntegerWithTruncation(isolate, iso_year_obj));
// 3. Set isoMonth to ?ToIntegerWithTruncation(isoMonth).
double m = 0;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, m, temporal::ToIntegerWithTruncation(isolate, iso_month_obj));
// 4. Set isoDay to ?ToIntegerWithTruncation(isoDay).
double d = 0;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, d, temporal::ToIntegerWithTruncation(isolate, iso_day_obj));
// 5. If hour is undefined, set hour to 0; else set hour to ?
// ToIntegerWithTruncation(hour).
double hour = 0;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, hour,
temporal::ToIntegerWithTruncationOrZero(isolate, hour_obj));
// 6. If minute is undefined, set minute to 0; else set minute to ?
// ToIntegerWithTruncation(minute).
double minute = 0;
if (!IsUndefined(*minute_obj)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, minute,
temporal::ToIntegerWithTruncationOrZero(isolate, minute_obj));
}
// 7. If second is undefined, set second to 0; else set second to ?
// ToIntegerWithTruncation(second).
double second = 0;
if (!IsUndefined(*second_obj)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, second,
temporal::ToIntegerWithTruncationOrZero(isolate, second_obj));
}
// 8. If millisecond is undefined, set millisecond to 0; else set millisecond
// to ? ToIntegerWithTruncation(millisecond).
double millisecond = 0;
if (!IsUndefined(*millisecond_obj)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, millisecond,
temporal::ToIntegerWithTruncationOrZero(isolate, millisecond_obj));
}
// 9. If microsecond is undefined, set microsecond to 0; else set microsecond
// to ? ToIntegerWithTruncation(microsecond).
double microsecond = 0;
if (!IsUndefined(*microsecond_obj)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, microsecond,
temporal::ToIntegerWithTruncationOrZero(isolate, microsecond_obj));
}
// 10. If nanosecond is undefined, set nanosecond to 0; else set nanosecond to
// ? ToIntegerWithTruncation(nanosecond).
double nanosecond = 0;
if (!IsUndefined(*nanosecond_obj)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, nanosecond,
temporal::ToIntegerWithTruncationOrZero(isolate, nanosecond_obj));
}
// 11. If calendar is undefined, set calendar to "iso8601".
temporal_rs::AnyCalendarKind calendar = temporal_rs::AnyCalendarKind::Iso;
if (!IsUndefined(*calendar_like)) {
// 12. If calendar is not a String, throw a TypeError exception.
if (!IsString(*calendar_like)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kCalendarMustBeString));
}
// 13. Set calendar to ?CanonicalizeCalendar(calendar).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, calendar,
temporal::CanonicalizeCalendar(isolate, Cast<String>(calendar_like)));
}
// 14. If IsValidISODate(isoYear, isoMonth, isoDay) is false, throw a
// RangeError exception.
if (!temporal::IsValidIsoDate(y, m, d)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kInvalidIsoDate));
}
// 16. If IsValidTime(hour, minute, second, millisecond, microsecond,
// nanosecond) is false, throw a RangeError exception.
if (!temporal::IsValidTime(hour, minute, second, millisecond, microsecond,
nanosecond)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kInvalidTime));
}
// Rest of the steps handled in Rust.
// These static_casts are fine to perform since IsValid* will have constrained
// these values to range already.
// https://github.com/boa-dev/temporal/issues/334 for moving this logic into
// the Rust code.
auto rust_object = temporal_rs::PlainDateTime::try_new(
static_cast<int32_t>(y), static_cast<uint8_t>(m), static_cast<uint8_t>(d),
static_cast<uint8_t>(hour), static_cast<uint8_t>(minute),
static_cast<uint8_t>(second), static_cast<uint16_t>(millisecond),
static_cast<uint16_t>(microsecond), static_cast<uint16_t>(nanosecond),
calendar);
return ConstructRustWrappingType<JSTemporalPlainDateTime>(
isolate, target, new_target, std::move(rust_object));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.from
MaybeDirectHandle<JSTemporalPlainDateTime> JSTemporalPlainDateTime::From(
Isolate* isolate, DirectHandle<Object> item_obj,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.PlainDateTime.from";
DirectHandle<JSTemporalPlainDateTime> item;
return temporal::ToTemporalDateTime(isolate, item_obj, options_obj,
method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.compare
MaybeDirectHandle<Smi> JSTemporalPlainDateTime::Compare(
Isolate* isolate, DirectHandle<Object> one_obj,
DirectHandle<Object> two_obj) {
static const char method_name[] = "Temporal.PlainDateTime.compare";
DirectHandle<JSTemporalPlainDateTime> one;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, one,
temporal::ToTemporalDateTime(isolate, one_obj, {}, method_name));
DirectHandle<JSTemporalPlainDateTime> two;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, two,
temporal::ToTemporalDateTime(isolate, two_obj, {}, method_name));
return direct_handle(Smi::FromInt(temporal_rs::PlainDateTime::compare(
*one->wrapped_rust(), *two->wrapped_rust())),
isolate);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.equals
MaybeDirectHandle<Oddball> JSTemporalPlainDateTime::Equals(
Isolate* isolate, DirectHandle<JSTemporalPlainDateTime> date_time,
DirectHandle<Object> other_obj) {
static const char method_name[] = "Temporal.PlainDateTime.prototype.equals";
DirectHandle<JSTemporalPlainDateTime> other;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, other,
temporal::ToTemporalDateTime(isolate, other_obj, {}, method_name));
auto equals = date_time->wrapped_rust()->equals(*other->wrapped_rust());
return isolate->factory()->ToBoolean(equals);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.with
MaybeDirectHandle<JSTemporalPlainDateTime> JSTemporalPlainDateTime::With(
Isolate* isolate, DirectHandle<JSTemporalPlainDateTime> date_time,
DirectHandle<Object> temporal_date_time_like_obj,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.PlainDateTime.prototype.with";
using enum temporal::CalendarFieldsFlag;
return temporal::GenericWith<JSTemporalPlainDateTime,
temporal_rs::PartialDateTime>(
isolate, date_time, temporal_date_time_like_obj, options_obj,
temporal::kAllDateFlags | kTimeFields, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.withcalendar
MaybeDirectHandle<JSTemporalPlainDateTime>
JSTemporalPlainDateTime::WithCalendar(
Isolate* isolate, DirectHandle<JSTemporalPlainDateTime> temporal_date,
DirectHandle<Object> calendar_id) {
// 3. Let calendar be ? ToTemporalCalendarIdentifier(calendarLike).
temporal_rs::AnyCalendarKind calendar_kind;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, calendar_kind,
temporal::ToTemporalCalendarIdentifier(isolate, calendar_id));
// Rest of the steps handled in Rust
return ConstructRustWrappingType<JSTemporalPlainDateTime>(
isolate, temporal_date->wrapped_rust()->with_calendar(calendar_kind));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.withplaintime
MaybeDirectHandle<JSTemporalPlainDateTime>
JSTemporalPlainDateTime::WithPlainTime(
Isolate* isolate, DirectHandle<JSTemporalPlainDateTime> date_time,
DirectHandle<Object> plain_time_like) {
static const char method_name[] =
"Temporal.PlainDateTime.prototype.withPlainTime";
// 3. Let time be ? ToTimeRecordOrMidnight(plainTimeLike).
CppGCManaged<temporal_rs::PlainTime>::Ptr maybe_time;
DirectHandle<JSTemporalPlainTime> time_output;
MOVE_RETURN_ON_EXCEPTION(
isolate, maybe_time,
temporal::ToTimeRecordOrMidnight(isolate, plain_time_like, time_output,
method_name));
// Rest of the steps handled in Rust.
return ConstructRustWrappingType<JSTemporalPlainDateTime>(
isolate, date_time->wrapped_rust()->with_time(maybe_time.raw()));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.tozoneddatetime
MaybeDirectHandle<JSTemporalZonedDateTime>
JSTemporalPlainDateTime::ToZonedDateTime(
Isolate* isolate, DirectHandle<JSTemporalPlainDateTime> date_time,
DirectHandle<Object> temporal_time_zone_like,
DirectHandle<Object> options_obj) {
static const char method_name[] =
"Temporal.PlainDateTime.prototype.toZonedDateTime";
// 3. Let timeZone be ? ToTemporalTimeZoneIdentifier(temporalTimeZoneLike).
temporal_rs::TimeZone time_zone;
MOVE_RETURN_ON_EXCEPTION(
isolate, time_zone,
temporal::ToTemporalTimeZoneIdentifier(isolate, temporal_time_zone_like));
// 4. Let resolvedOptions be ? GetOptionsObject(options).
// 5. Let disambiguation be ?
// GetTemporalDisambiguationOption(resolvedOptions).
temporal_rs::Disambiguation disambiguation;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, disambiguation,
temporal::GetTemporalDisambiguationOptionHandleUndefined(
isolate, options_obj, method_name));
// Rest of the steps handled in Rust.
return ConstructRustWrappingType<JSTemporalZonedDateTime>(
isolate, date_time->wrapped_rust()->to_zoned_date_time_with_provider(
time_zone, disambiguation, TimeZoneProvider()));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.tostring
MaybeDirectHandle<String> JSTemporalPlainDateTime::ToString(
Isolate* isolate, DirectHandle<JSTemporalPlainDateTime> date_time,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.DateTime.prototype.toString";
// 3. Let resolvedOptions be ?GetOptionsObject(options).
DirectHandle<JSReceiver> options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options, GetOptionsObject(isolate, options_obj, method_name));
// 5. Let showCalendar be ?GetTemporalShowCalendarNameOption(resolvedOptions).
temporal_rs::DisplayCalendar show_calendar;
ASSIGN_RETURN_ON_EXCEPTION(isolate, show_calendar,
temporal::GetTemporalShowCalendarNameOption(
isolate, options, method_name));
// 6. Let digits be ?GetTemporalFractionalSecondDigitsOption(resolvedOptions).
temporal_rs::Precision digits;
ASSIGN_RETURN_ON_EXCEPTION(isolate, digits,
temporal::GetTemporalFractionalSecondDigitsOption(
isolate, options, method_name));
// 7. Let roundingMode be ? GetRoundingModeOption(resolvedOptions, trunc).
RoundingMode rounding_mode;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, rounding_mode,
temporal::GetRoundingModeOption(isolate, options, RoundingMode::Trunc,
method_name));
// 8. Let smallestUnit be ? GetTemporalUnitValuedOption(resolvedOptions,
// "smallestUnit", unset).
std::optional<Unit> smallest_unit;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, smallest_unit,
temporal::GetTemporalUnitValuedOption(
isolate, options, isolate->factory()->smallestUnit_string(),
DefaultValue::kUnset, method_name));
// 9. Perform ? ValidateTemporalUnitValue(smallestUnit, time).
RETURN_ON_EXCEPTION(isolate, temporal::ValidateTemporalUnitValue(
isolate, smallest_unit, UnitGroup::kTime));
// Rest of the steps handled in Rust
auto rust_options = temporal_rs::ToStringRoundingOptions{
.precision = digits,
.smallest_unit = smallest_unit,
.rounding_mode = rounding_mode,
};
return temporal::GenericTemporalToString(
isolate, date_time, &temporal_rs::PlainDateTime::to_ixdtf_string,
std::move(rust_options), show_calendar);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.tojson
MaybeDirectHandle<String> JSTemporalPlainDateTime::ToJSON(
Isolate* isolate, DirectHandle<JSTemporalPlainDateTime> date_time) {
return temporal::GenericTemporalToString(
isolate, date_time, &temporal_rs::PlainDateTime::to_ixdtf_string,
std::move(temporal::kToStringAuto), temporal_rs::DisplayCalendar::Auto);
}
MaybeDirectHandle<String> JSTemporalPlainDateTime::ToLocaleString(
Isolate* isolate, DirectHandle<JSTemporalPlainDateTime> date_time,
DirectHandle<Object> locales, DirectHandle<Object> options) {
#ifdef V8_INTL_SUPPORT
// https://tc39.es/proposal-temporal/#sup-temporal.plaindatetime.prototype.tolocalestring
return JSDateTimeFormat::TemporalToLocaleString(
isolate, date_time, locales, options,
JSDateTimeFormat::RequiredOption::kAny,
JSDateTimeFormat::DefaultsOption::kAll,
"Temporal.PlainDateTime.prototype.toLocaleString");
#else // V8_INTL_SUPPORT
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.tolocalestring
return temporal::GenericTemporalToString(
isolate, date_time, &temporal_rs::PlainDateTime::to_ixdtf_string,
std::move(temporal::kToStringAuto), temporal_rs::DisplayCalendar::Auto);
#endif // V8_INTL_SUPPORT
}
// https://tc39.es/proposal-temporal/#sec-temporal.now.plaindatetimeiso
MaybeDirectHandle<JSTemporalPlainDateTime> JSTemporalPlainDateTime::NowISO(
Isolate* isolate, DirectHandle<Object> temporal_time_zone_like) {
std::unique_ptr<temporal_rs::ZonedDateTime> zdt;
MOVE_RETURN_ON_EXCEPTION(
isolate, zdt,
temporal::GenericTemporalNowISO(isolate, temporal_time_zone_like));
return ConstructRustWrappingType<JSTemporalPlainDateTime>(
isolate, zdt->to_plain_datetime());
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.round
MaybeDirectHandle<JSTemporalPlainDateTime> JSTemporalPlainDateTime::Round(
Isolate* isolate, DirectHandle<JSTemporalPlainDateTime> date_time,
DirectHandle<Object> round_to_obj) {
static const char method_name[] = "Temporal.PlainDateTime.prototype.round";
// 1. Let dateTime be the this value.
// 2. Perform ? RequireInternalSlot(dateTime,
// [[InitializedTemporalDateTime]]).
// (handled by type system)
// 3. If roundTo is undefined, then
if (IsUndefined(*round_to_obj)) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kRoundToMissing));
}
DirectHandle<JSReceiver> round_to;
auto factory = isolate->factory();
// 4. If roundTo is a String, then
if (IsString(*round_to_obj)) {
// a. Let paramString be roundTo.
DirectHandle<String> param_string = Cast<String>(round_to_obj);
// b. Set roundTo to ! OrdinaryObjectCreate(null).
round_to = factory->NewJSObjectWithNullProto();
// c. c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit",
// paramString).
CHECK(JSReceiver::CreateDataProperty(isolate, round_to,
factory->smallestUnit_string(),
param_string, Just(kThrowOnError))
.FromJust());
// 5. Else,
} else {
// a. Set roundTo to ? GetOptionsObject(roundTo).
// We have already checked for undefined, we can hoist the JSReceiver
// check out and just cast
if (!IsJSReceiver(*round_to_obj)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kRoundToMustBeObject));
}
round_to = Cast<JSReceiver>(round_to_obj);
}
// 6. NOTE: (...)
// 7. Let roundingIncrement be ? GetRoundingIncrementOption(roundTo).
uint32_t rounding_increment;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, rounding_increment,
temporal::GetRoundingIncrementOption(isolate, round_to));
// 8. Let roundingMode be ? GetRoundingModeOption(roundTo, half-expand).
RoundingMode rounding_mode;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, rounding_mode,
temporal::GetRoundingModeOption(isolate, round_to,
RoundingMode::HalfExpand, method_name));
// 9. Let smallestUnit be ? GetTemporalUnitValuedOption(roundTo,
// "smallestUnit", required).
std::optional<Unit> smallest_unit;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, smallest_unit,
temporal::GetTemporalUnitValuedOption(
isolate, round_to, isolate->factory()->smallestUnit_string(),
DefaultValue::kRequired, method_name));
// 10. Perform ? ValidateTemporalUnitValue(smallestUnit, time, « day »).
RETURN_ON_EXCEPTION(isolate,
temporal::ValidateTemporalUnitValue(
isolate, smallest_unit, UnitGroup::kTime, Unit::Day));
// Rest of the steps handled in Rust
auto options = temporal_rs::RoundingOptions{.largest_unit = std::nullopt,
.smallest_unit = smallest_unit,
.rounding_mode = rounding_mode,
.increment = rounding_increment};
auto rounded = date_time->wrapped_rust()->round(options);
return ConstructRustWrappingType<JSTemporalPlainDateTime>(isolate,
std::move(rounded));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.add
MaybeDirectHandle<JSTemporalPlainDateTime> JSTemporalPlainDateTime::Add(
Isolate* isolate, DirectHandle<JSTemporalPlainDateTime> date_time,
DirectHandle<Object> temporal_duration_like, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.PlainDateTime.prototype.add";
// 3. Return ? AddDurationToDateTime(add, temporalDate, temporalDurationLike,
// options).
return temporal::AddDurationToGeneric(
isolate, &temporal_rs::PlainDateTime::add, date_time,
temporal_duration_like, options, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.subtract
MaybeDirectHandle<JSTemporalPlainDateTime> JSTemporalPlainDateTime::Subtract(
Isolate* isolate, DirectHandle<JSTemporalPlainDateTime> date_time,
DirectHandle<Object> temporal_duration_like, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.PlainDateTime.prototype.subtract";
// 3. Return ? AddDurationToDateTime(subtract, temporalDate,
// temporalDurationLike, options).
return temporal::AddDurationToGeneric(
isolate, &temporal_rs::PlainDateTime::subtract, date_time,
temporal_duration_like, options, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.until
MaybeDirectHandle<JSTemporalDuration> JSTemporalPlainDateTime::Until(
Isolate* isolate, DirectHandle<JSTemporalPlainDateTime> handle,
DirectHandle<Object> other, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.PlainDateTime.prototype.until";
return temporal::GenericDifferenceTemporal(
isolate, &temporal_rs::PlainDateTime::until, UnitGroup::kDateTime,
Unit::Nanosecond, handle, other, options, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.since
MaybeDirectHandle<JSTemporalDuration> JSTemporalPlainDateTime::Since(
Isolate* isolate, DirectHandle<JSTemporalPlainDateTime> handle,
DirectHandle<Object> other, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.PlainDateTime.prototype.since";
return temporal::GenericDifferenceTemporal(
isolate, &temporal_rs::PlainDateTime::since, UnitGroup::kDateTime,
Unit::Nanosecond, handle, other, options, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.toplaindate
MaybeDirectHandle<JSTemporalPlainDate> JSTemporalPlainDateTime::ToPlainDate(
Isolate* isolate, DirectHandle<JSTemporalPlainDateTime> date_time) {
return temporal::GenericToTemporalMethod<JSTemporalPlainDate>(
isolate, date_time, &temporal_rs::PlainDateTime::to_plain_date);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaindatetime.prototype.toplaintime
MaybeDirectHandle<JSTemporalPlainTime> JSTemporalPlainDateTime::ToPlainTime(
Isolate* isolate, DirectHandle<JSTemporalPlainDateTime> date_time) {
return temporal::GenericToTemporalMethod<JSTemporalPlainTime>(
isolate, date_time, &temporal_rs::PlainDateTime::to_plain_time);
}
// https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporaldate
V8_WARN_UNUSED_RESULT Maybe<int64_t>
JSTemporalPlainDateTime::GetEpochMillisecondsForUtc(Isolate* isolate) {
return ExtractRustResult(isolate, this->wrapped_rust()->epoch_ms_for_utc());
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday
MaybeDirectHandle<JSTemporalPlainMonthDay> JSTemporalPlainMonthDay::Constructor(
Isolate* isolate, DirectHandle<JSFunction> target,
DirectHandle<HeapObject> new_target, DirectHandle<Object> iso_month_obj,
DirectHandle<Object> iso_day_obj, DirectHandle<Object> calendar_like,
DirectHandle<Object> reference_iso_year_obj) {
// 1. If NewTarget is undefined, then
if (IsUndefined(*new_target)) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
isolate->factory()->NewStringFromAsciiChecked(
"Temporal.PlainYearMonth")));
}
// 2. If referenceISOYear is undefined, then
// a. Set referenceISOYear to 1𝔽.
double reference_iso_year = 1972.0;
// 3. Let m be ? ToIntegerWithTruncation(isoMonth).
double m = 0;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, m, temporal::ToIntegerWithTruncation(isolate, iso_month_obj));
// 4. Let d be ? ToIntegerWithTruncation(isoYear).
double d = 0;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, d, temporal::ToIntegerWithTruncation(isolate, iso_day_obj));
// 5. If calendar is undefined, set calendar to "iso8601".
temporal_rs::AnyCalendarKind calendar = temporal_rs::AnyCalendarKind::Iso;
if (!IsUndefined(*calendar_like)) {
// 6. If calendar is not a String, throw a TypeError exception.
if (!IsString(*calendar_like)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kCalendarMustBeString));
}
// 7. Set calendar to ?CanonicalizeCalendar(calendar).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, calendar,
temporal::CanonicalizeCalendar(isolate, Cast<String>(calendar_like)));
}
if (!IsUndefined(*reference_iso_year_obj)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, reference_iso_year,
temporal::ToIntegerWithTruncation(isolate, reference_iso_year_obj));
}
// 9. If IsValidISODate(ref, m, d) is false, throw a RangeError exception.
if (!temporal::IsValidIsoDate(reference_iso_year, m, d)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kInvalidIsoDate));
}
// Rest of the steps handled in Rust.
// These static_casts are fine to perform since IsValid* will have constrained
// these values to range already.
// https://github.com/boa-dev/temporal/issues/334 for moving this logic into
// the Rust code.
auto rust_object = temporal_rs::PlainMonthDay::try_new_with_overflow(
static_cast<uint8_t>(m), static_cast<uint8_t>(d), calendar,
temporal_rs::ArithmeticOverflow::Reject,
static_cast<int32_t>(reference_iso_year));
return ConstructRustWrappingType<JSTemporalPlainMonthDay>(
isolate, target, new_target, std::move(rust_object));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.from
MaybeDirectHandle<JSTemporalPlainMonthDay> JSTemporalPlainMonthDay::From(
Isolate* isolate, DirectHandle<Object> item_obj,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.PlainMonthDay.from";
DirectHandle<JSTemporalPlainMonthDay> item;
return temporal::ToTemporalMonthDay(isolate, item_obj, options_obj,
method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.equals
MaybeDirectHandle<Oddball> JSTemporalPlainMonthDay::Equals(
Isolate* isolate, DirectHandle<JSTemporalPlainMonthDay> month_day,
DirectHandle<Object> other_obj) {
static const char method_name[] = "Temporal.PlainMonthDay.prototype.equals";
DirectHandle<JSTemporalPlainMonthDay> other;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, other,
temporal::ToTemporalMonthDay(isolate, other_obj, {}, method_name));
auto equals = month_day->wrapped_rust()->equals(*other->wrapped_rust());
return isolate->factory()->ToBoolean(equals);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.with
MaybeDirectHandle<JSTemporalPlainMonthDay> JSTemporalPlainMonthDay::With(
Isolate* isolate, DirectHandle<JSTemporalPlainMonthDay> temporal_month_day,
DirectHandle<Object> temporal_month_day_like_obj,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.PlainYearMonth.prototype.with";
using enum temporal::CalendarFieldsFlag;
// 6. Let partialMonthDay be ? PrepareCalendarFields(calendar,
// temporalMonthDayLike, « year, month, month-code, day », « », partial).
return temporal::GenericWith<JSTemporalPlainMonthDay,
temporal_rs::PartialDate>(
isolate, temporal_month_day, temporal_month_day_like_obj, options_obj,
kYearFields | kMonthFields | kDay, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.toplaindate
MaybeDirectHandle<JSTemporalPlainDate> JSTemporalPlainMonthDay::ToPlainDate(
Isolate* isolate, DirectHandle<JSTemporalPlainMonthDay> month_day,
DirectHandle<Object> item_obj) {
// 1. Let monthDay be the this value.
// 2. Perform ? RequireInternalSlot(monthDay,
// [[InitializedTemporalMonthDay]]).
// (handled by type system)
// 3. If item is not an Object, then
if (!IsJSReceiver(*item_obj)) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kYearMustBeObject));
}
DirectHandle<JSReceiver> item = Cast<JSReceiver>(item_obj);
// 4. Let calendar be monthDay.[[Calendar]].
auto calendar = month_day->wrapped_rust()->calendar().kind();
// 5. Let fields be ISODateToFields(calendar, monthDay.[[ISODate]],
// month-day).
// (handled in Rust)
// 6. Let inputFields be ? PrepareCalendarFields(calendar, item, « year », «
// », « »).
using enum temporal::CalendarFieldsFlag;
temporal::CombinedRecord fields;
MOVE_RETURN_ON_EXCEPTION(
isolate, fields,
temporal::PrepareCalendarFields(isolate, calendar, item, kYearFields,
temporal::RequiredFields::kNone));
temporal_rs::PartialDate partial_date = temporal::kNullPartialDate;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, partial_date,
fields.Regulate<temporal_rs::PartialDate>(
isolate, temporal_rs::ArithmeticOverflow::Constrain));
return ConstructRustWrappingType<JSTemporalPlainDate>(
isolate, month_day->wrapped_rust()->to_plain_date(partial_date));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tostring
MaybeDirectHandle<String> JSTemporalPlainMonthDay::ToString(
Isolate* isolate, DirectHandle<JSTemporalPlainMonthDay> month_day,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.PlainMonthDay.prototype.toString";
// 3. Let resolvedOptions be ?GetOptionsObject(options).
DirectHandle<JSReceiver> options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options, GetOptionsObject(isolate, options_obj, method_name));
// 4. Let showCalendar be ?GetTemporalShowCalendarNameOption(resolvedOptions).
temporal_rs::DisplayCalendar show_calendar;
ASSIGN_RETURN_ON_EXCEPTION(isolate, show_calendar,
temporal::GetTemporalShowCalendarNameOption(
isolate, options, method_name));
// 5. Return TemporalYearMonthToString(monthDay, showCalendar).
return temporal::GenericTemporalToString(
isolate, month_day, &temporal_rs::PlainMonthDay::to_ixdtf_string,
show_calendar);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tojson
MaybeDirectHandle<String> JSTemporalPlainMonthDay::ToJSON(
Isolate* isolate, DirectHandle<JSTemporalPlainMonthDay> month_day) {
return temporal::GenericTemporalToString(
isolate, month_day, &temporal_rs::PlainMonthDay::to_ixdtf_string,
temporal_rs::DisplayCalendar::Auto);
}
MaybeDirectHandle<String> JSTemporalPlainMonthDay::ToLocaleString(
Isolate* isolate, DirectHandle<JSTemporalPlainMonthDay> month_day,
DirectHandle<Object> locales, DirectHandle<Object> options) {
#ifdef V8_INTL_SUPPORT
// https://tc39.es/proposal-temporal/#sup-temporal.plainmonthday.prototype.tolocalestring
return JSDateTimeFormat::TemporalToLocaleString(
isolate, month_day, locales, options,
JSDateTimeFormat::RequiredOption::kDate,
JSDateTimeFormat::DefaultsOption::kDate,
"Temporal.PlainMonthDay.prototype.toLocaleString");
#else // V8_INTL_SUPPORT
// https://tc39.es/proposal-temporal/#sec-temporal.plainmonthday.prototype.tolocalestring
return temporal::GenericTemporalToString(
isolate, month_day, &temporal_rs::PlainMonthDay::to_ixdtf_string,
temporal_rs::DisplayCalendar::Auto);
#endif // V8_INTL_SUPPORT
}
// https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporalmonthday
Maybe<int64_t> JSTemporalPlainMonthDay::GetEpochMillisecondsForUtc(
Isolate* isolate) {
return ExtractRustResult(isolate, this->wrapped_rust()->epoch_ms_for_utc());
}
MaybeDirectHandle<JSTemporalPlainYearMonth>
JSTemporalPlainYearMonth::Constructor(
Isolate* isolate, DirectHandle<JSFunction> target,
DirectHandle<HeapObject> new_target, DirectHandle<Object> iso_year_obj,
DirectHandle<Object> iso_month_obj, DirectHandle<Object> calendar_like,
DirectHandle<Object> reference_iso_day_obj) {
// 1. If NewTarget is undefined, then
if (IsUndefined(*new_target)) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
isolate->factory()->NewStringFromAsciiChecked(
"Temporal.PlainYearMonth")));
}
// 2. If referenceISODay is undefined, then
// a. Set referenceISODay to 1𝔽.
double reference_iso_day = 1.0;
// 3. Let y be ? ToIntegerWithTruncation(isoYear).
double y = 0;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, y, temporal::ToIntegerWithTruncation(isolate, iso_year_obj));
// 4. Let m be ? ToIntegerWithTruncation(isoMonth).
double m = 0;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, m, temporal::ToIntegerWithTruncation(isolate, iso_month_obj));
// 5. If calendar is undefined, set calendar to "iso8601".
temporal_rs::AnyCalendarKind calendar = temporal_rs::AnyCalendarKind::Iso;
if (!IsUndefined(*calendar_like)) {
// 6. If calendar is not a String, throw a TypeError exception.
if (!IsString(*calendar_like)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kCalendarMustBeString));
}
// 7. Set calendar to ?CanonicalizeCalendar(calendar).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, calendar,
temporal::CanonicalizeCalendar(isolate, Cast<String>(calendar_like)));
}
if (!IsUndefined(*reference_iso_day_obj)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, reference_iso_day,
temporal::ToIntegerWithTruncation(isolate, reference_iso_day_obj));
}
// 9. If IsValidISODate(y, m, ref) is false, throw a RangeError exception.
if (!temporal::IsValidIsoDate(y, m, reference_iso_day)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kInvalidIsoDate));
}
// Rest of the steps handled in Rust.
// These static_casts are fine to perform since IsValid* will have constrained
// these values to range already.
// https://github.com/boa-dev/temporal/issues/334 for moving this logic into
// the Rust code.
auto rust_object = temporal_rs::PlainYearMonth::try_new_with_overflow(
static_cast<int32_t>(y), static_cast<uint8_t>(m),
static_cast<uint8_t>(reference_iso_day), calendar,
temporal_rs::ArithmeticOverflow::Reject);
return ConstructRustWrappingType<JSTemporalPlainYearMonth>(
isolate, target, new_target, std::move(rust_object));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.from
MaybeDirectHandle<JSTemporalPlainYearMonth> JSTemporalPlainYearMonth::From(
Isolate* isolate, DirectHandle<Object> item_obj,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.PlainYearMonth.from";
DirectHandle<JSTemporalPlainYearMonth> item;
return temporal::ToTemporalYearMonth(isolate, item_obj, options_obj,
method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.compare
MaybeDirectHandle<Smi> JSTemporalPlainYearMonth::Compare(
Isolate* isolate, DirectHandle<Object> one_obj,
DirectHandle<Object> two_obj) {
static const char method_name[] = "Temporal.PlainYearMonth.compare";
DirectHandle<JSTemporalPlainYearMonth> one;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, one,
temporal::ToTemporalYearMonth(isolate, one_obj, {}, method_name));
DirectHandle<JSTemporalPlainYearMonth> two;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, two,
temporal::ToTemporalYearMonth(isolate, two_obj, {}, method_name));
return direct_handle(Smi::FromInt(temporal_rs::PlainYearMonth::compare(
*one->wrapped_rust(), *two->wrapped_rust())),
isolate);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.equals
MaybeDirectHandle<Oddball> JSTemporalPlainYearMonth::Equals(
Isolate* isolate, DirectHandle<JSTemporalPlainYearMonth> year_month,
DirectHandle<Object> other_obj) {
static const char method_name[] = "Temporal.PlainYearMonth.prototype.equals";
DirectHandle<JSTemporalPlainYearMonth> other;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, other,
temporal::ToTemporalYearMonth(isolate, other_obj, {}, method_name));
auto equals = year_month->wrapped_rust()->equals(*other->wrapped_rust());
return isolate->factory()->ToBoolean(equals);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.add
MaybeDirectHandle<JSTemporalPlainYearMonth> JSTemporalPlainYearMonth::Add(
Isolate* isolate, DirectHandle<JSTemporalPlainYearMonth> year_month,
DirectHandle<Object> temporal_duration_like, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.PlainYearMonth.prototype.add";
// 3. Return ? AddDurationToYearMonth(add, temporalDate, temporalDurationLike,
// options).
return temporal::AddDurationToGeneric(
isolate, &temporal_rs::PlainYearMonth::add, year_month,
temporal_duration_like, options, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.subtract
MaybeDirectHandle<JSTemporalPlainYearMonth> JSTemporalPlainYearMonth::Subtract(
Isolate* isolate, DirectHandle<JSTemporalPlainYearMonth> year_month,
DirectHandle<Object> temporal_duration_like, DirectHandle<Object> options) {
static const char method_name[] =
"Temporal.PlainYearMonth.prototype.subtract";
// 3. Return ? AddDurationToYearMonth(subtract, temporalDate,
// temporalDurationLike, options).
return temporal::AddDurationToGeneric(
isolate, &temporal_rs::PlainYearMonth::subtract, year_month,
temporal_duration_like, options, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.until
MaybeDirectHandle<JSTemporalDuration> JSTemporalPlainYearMonth::Until(
Isolate* isolate, DirectHandle<JSTemporalPlainYearMonth> handle,
DirectHandle<Object> other, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.PlainYearMonth.prototype.until";
// Uplifted from step 5 of DifferenceTemporalPlainYearMonth
//
// (Differ..YearMonth) 5. Let settings be
// ? GetDifferenceSettings(operation, resolvedOptions, date, « week, day »,
// month, year).
return temporal::GenericDifferenceTemporal(
isolate, &temporal_rs::PlainYearMonth::until, UnitGroup::kDate,
Unit::Month, handle, other, options, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.since
MaybeDirectHandle<JSTemporalDuration> JSTemporalPlainYearMonth::Since(
Isolate* isolate, DirectHandle<JSTemporalPlainYearMonth> handle,
DirectHandle<Object> other, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.PlainYearMonth.prototype.since";
// Uplifted from step 5 of DifferenceTemporalPlainYearMonth
//
// (Differ..YearMonth) 5. Let settings be
// ? GetDifferenceSettings(operation, resolvedOptions, date, « week, day »,
// month, year).
return temporal::GenericDifferenceTemporal(
isolate, &temporal_rs::PlainYearMonth::since, UnitGroup::kDate,
Unit::Month, handle, other, options, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.with
MaybeDirectHandle<JSTemporalPlainYearMonth> JSTemporalPlainYearMonth::With(
Isolate* isolate,
DirectHandle<JSTemporalPlainYearMonth> temporal_year_month,
DirectHandle<Object> temporal_year_month_like_obj,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.PlainYearMonth.prototype.with";
using enum temporal::CalendarFieldsFlag;
return temporal::GenericWith<JSTemporalPlainYearMonth,
temporal_rs::PartialDate>(
isolate, temporal_year_month, temporal_year_month_like_obj, options_obj,
kYearFields | kMonthFields, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.toplaindate
MaybeDirectHandle<JSTemporalPlainDate> JSTemporalPlainYearMonth::ToPlainDate(
Isolate* isolate, DirectHandle<JSTemporalPlainYearMonth> year_month,
DirectHandle<Object> item_obj) {
// 1. Let yearMonth be the this value.
// 2. Perform ? RequireInternalSlot(yearMonth,
// [[InitializedTemporalYearMonth]]).
// (handled by type system)
// 3. If item is not an Object, then
if (!IsJSReceiver(*item_obj)) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kYearMustBeObject));
}
DirectHandle<JSReceiver> item = Cast<JSReceiver>(item_obj);
// 4. Let calendar be yearMonth.[[Calendar]].
auto calendar = year_month->wrapped_rust()->calendar().kind();
// 5. Let fields be ISODateToFields(calendar, yearMonth.[[ISODate]],
// year-month).
// (handled in Rust)
// 6. Let inputFields be ? PrepareCalendarFields(calendar, item, « day », « »,
// « »).
using enum temporal::CalendarFieldsFlag;
temporal::CombinedRecord fields;
MOVE_RETURN_ON_EXCEPTION(
isolate, fields,
temporal::PrepareCalendarFields(isolate, calendar, item, kDay,
temporal::RequiredFields::kNone));
temporal_rs::PartialDate partial_date = temporal::kNullPartialDate;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, partial_date,
fields.Regulate<temporal_rs::PartialDate>(
isolate, temporal_rs::ArithmeticOverflow::Constrain));
return ConstructRustWrappingType<JSTemporalPlainDate>(
isolate, year_month->wrapped_rust()->to_plain_date(partial_date));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.tostring
MaybeDirectHandle<String> JSTemporalPlainYearMonth::ToString(
Isolate* isolate, DirectHandle<JSTemporalPlainYearMonth> year_month,
DirectHandle<Object> options_obj) {
static const char method_name[] =
"Temporal.PlainYearMonth.prototype.toString";
// 3. Let resolvedOptions be ?GetOptionsObject(options).
DirectHandle<JSReceiver> options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options, GetOptionsObject(isolate, options_obj, method_name));
// 4. Let showCalendar be ?GetTemporalShowCalendarNameOption(resolvedOptions).
temporal_rs::DisplayCalendar show_calendar;
ASSIGN_RETURN_ON_EXCEPTION(isolate, show_calendar,
temporal::GetTemporalShowCalendarNameOption(
isolate, options, method_name));
// 5. Return TemporalYearMonthToString(yearMonth, showCalendar).
return temporal::GenericTemporalToString(
isolate, year_month, &temporal_rs::PlainYearMonth::to_ixdtf_string,
show_calendar);
}
MaybeDirectHandle<String> JSTemporalPlainYearMonth::ToLocaleString(
Isolate* isolate, DirectHandle<JSTemporalPlainYearMonth> year_month,
DirectHandle<Object> locales, DirectHandle<Object> options) {
#ifdef V8_INTL_SUPPORT
// https://tc39.es/proposal-temporal/#sup-temporal.plainyearmonth.prototype.tolocalestring
return JSDateTimeFormat::TemporalToLocaleString(
isolate, year_month, locales, options,
JSDateTimeFormat::RequiredOption::kDate,
JSDateTimeFormat::DefaultsOption::kDate,
"Temporal.PlainYearMonth.prototype.toLocaleString");
#else // V8_INTL_SUPPORT
// https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.tolocalestring
return temporal::GenericTemporalToString(
isolate, year_month, &temporal_rs::PlainYearMonth::to_ixdtf_string,
temporal_rs::DisplayCalendar::Auto);
#endif // V8_INTL_SUPPORT
}
// https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporalyearmonth
Maybe<int64_t> JSTemporalPlainYearMonth::GetEpochMillisecondsForUtc(
Isolate* isolate) {
return ExtractRustResult(isolate, this->wrapped_rust()->epoch_ms_for_utc());
}
// https://tc39.es/proposal-temporal/#sec-temporal.plainyearmonth.prototype.tojson
MaybeDirectHandle<String> JSTemporalPlainYearMonth::ToJSON(
Isolate* isolate, DirectHandle<JSTemporalPlainYearMonth> year_month) {
return temporal::GenericTemporalToString(
isolate, year_month, &temporal_rs::PlainYearMonth::to_ixdtf_string,
temporal_rs::DisplayCalendar::Auto);
}
// https://tc39.es/proposal-temporal/#sec-temporal-plaintime-constructor
MaybeDirectHandle<JSTemporalPlainTime> JSTemporalPlainTime::Constructor(
Isolate* isolate, DirectHandle<JSFunction> target,
DirectHandle<HeapObject> new_target, DirectHandle<Object> hour_obj,
DirectHandle<Object> minute_obj, DirectHandle<Object> second_obj,
DirectHandle<Object> millisecond_obj, DirectHandle<Object> microsecond_obj,
DirectHandle<Object> nanosecond_obj) {
// 1. If NewTarget is undefined, then
if (IsUndefined(*new_target)) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
isolate->factory()->NewStringFromAsciiChecked(
"Temporal.PlainTime")));
}
// 2. If hour is undefined, set hour to 0; else set hour to ?
// ToIntegerWithTruncation(hour).
double hour = 0;
if (!IsUndefined(*hour_obj)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, hour,
temporal::ToIntegerWithTruncationOrZero(isolate, hour_obj));
}
// 3. If minute is undefined, set minute to 0; else set minute to ?
// ToIntegerWithTruncation(minute).
double minute = 0;
if (!IsUndefined(*minute_obj)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, minute,
temporal::ToIntegerWithTruncationOrZero(isolate, minute_obj));
}
// 4. If second is undefined, set second to 0; else set second to ?
// ToIntegerWithTruncation(second).
double second = 0;
if (!IsUndefined(*second_obj)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, second,
temporal::ToIntegerWithTruncationOrZero(isolate, second_obj));
}
// 5. If millisecond is undefined, set millisecond to 0; else set millisecond
// to ? ToIntegerWithTruncation(millisecond).
double millisecond = 0;
if (!IsUndefined(*millisecond_obj)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, millisecond,
temporal::ToIntegerWithTruncationOrZero(isolate, millisecond_obj));
}
// 6. If microsecond is undefined, set microsecond to 0; else set microsecond
// to ? ToIntegerWithTruncation(microsecond).
double microsecond = 0;
if (!IsUndefined(*microsecond_obj)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, microsecond,
temporal::ToIntegerWithTruncationOrZero(isolate, microsecond_obj));
}
// 7. If nanosecond is undefined, set nanosecond to 0; else set nanosecond to
// ? ToIntegerWithTruncation(nanosecond).
double nanosecond = 0;
if (!IsUndefined(*nanosecond_obj)) {
ASSIGN_RETURN_ON_EXCEPTION(
isolate, nanosecond,
temporal::ToIntegerWithTruncationOrZero(isolate, nanosecond_obj));
}
// 8. If IsValidTime(hour, minute, second, millisecond, microsecond,
// nanosecond) is false, throw a RangeError exception.
if (!temporal::IsValidTime(hour, minute, second, millisecond, microsecond,
nanosecond)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_RANGE_ERROR(kInvalidTime));
}
// Rest of the steps handled in Rust
// These static_casts are fine to perform since IsValid* will have constrained
// these values to range already.
// https://github.com/boa-dev/temporal/issues/334 for moving this logic into
// the Rust code
auto rust_object = temporal_rs::PlainTime::try_new(
static_cast<uint8_t>(hour), static_cast<uint8_t>(minute),
static_cast<uint8_t>(second), static_cast<uint16_t>(millisecond),
static_cast<uint16_t>(microsecond), static_cast<uint16_t>(nanosecond));
return ConstructRustWrappingType<JSTemporalPlainTime>(
isolate, target, new_target, std::move(rust_object));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaintime.compare
MaybeDirectHandle<Smi> JSTemporalPlainTime::Compare(
Isolate* isolate, DirectHandle<Object> one_obj,
DirectHandle<Object> two_obj) {
static const char method_name[] = "Temporal.PlainTime.compare";
DirectHandle<JSTemporalPlainTime> one;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, one,
temporal::ToTemporalTime(isolate, one_obj, {}, method_name));
DirectHandle<JSTemporalPlainTime> two;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, two,
temporal::ToTemporalTime(isolate, two_obj, {}, method_name));
return direct_handle(Smi::FromInt(temporal_rs::PlainTime::compare(
*one->wrapped_rust(), *two->wrapped_rust())),
isolate);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.equals
MaybeDirectHandle<Oddball> JSTemporalPlainTime::Equals(
Isolate* isolate, DirectHandle<JSTemporalPlainTime> temporal_time,
DirectHandle<Object> other_obj) {
static const char method_name[] = "Temporal.PlainTime.prototype.equals";
// 3. Set other to ? ToTemporalTime(other).
DirectHandle<JSTemporalPlainTime> other;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, other,
temporal::ToTemporalTime(isolate, other_obj, {}, method_name));
// Rest of the steps handled in Rust
auto equals = temporal_time->wrapped_rust()->equals(*other->wrapped_rust());
return isolate->factory()->ToBoolean(equals);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.round
MaybeDirectHandle<JSTemporalPlainTime> JSTemporalPlainTime::Round(
Isolate* isolate, DirectHandle<JSTemporalPlainTime> temporal_time,
DirectHandle<Object> round_to_obj) {
static const char method_name[] = "Temporal.PlainDateTime.prototype.round";
// 1. Let temporalTime be the this value.
// 2. Perform ? RequireInternalSlot(dateTime, [[InitializedTemporalTime]]).
// (handled by type system)
// 3. If roundTo is undefined, then
if (IsUndefined(*round_to_obj)) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kRoundToMissing));
}
DirectHandle<JSReceiver> round_to;
auto factory = isolate->factory();
// 4. If roundTo is a String, then
if (IsString(*round_to_obj)) {
// a. Let paramString be roundTo.
DirectHandle<String> param_string = Cast<String>(round_to_obj);
// b. Set roundTo to ! OrdinaryObjectCreate(null).
round_to = factory->NewJSObjectWithNullProto();
// TODO(manishearth) Ideally we don't have to perform this allocation
// c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit",
// paramString).
CHECK(JSReceiver::CreateDataProperty(isolate, round_to,
factory->smallestUnit_string(),
param_string, Just(kThrowOnError))
.FromJust());
// 5. Else,
} else {
// a. Set roundTo to ? GetOptionsObject(roundTo).
// We have already checked for undefined, we can hoist the JSReceiver
// check out and just cast
if (!IsJSReceiver(*round_to_obj)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kRoundToMustBeObject));
}
round_to = Cast<JSReceiver>(round_to_obj);
}
// 6. NOTE: (...)
// 7. Let roundingIncrement be ? GetRoundingIncrementOption(roundTo).
uint32_t rounding_increment;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, rounding_increment,
temporal::GetRoundingIncrementOption(isolate, round_to));
// 8. Let roundingMode be ? GetRoundingModeOption(roundTo, half-expand).
RoundingMode rounding_mode;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, rounding_mode,
temporal::GetRoundingModeOption(isolate, round_to,
RoundingMode::HalfExpand, method_name));
// 9. Let smallestUnit be ? GetTemporalUnitValuedOption(roundTo,
// "smallestUnit", required).
std::optional<Unit> smallest_unit;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, smallest_unit,
temporal::GetTemporalUnitValuedOption(
isolate, round_to, factory->smallestUnit_string(),
DefaultValue::kRequired, method_name));
// 10. Perform ? ValidateTemporalUnitValue(smallestUnit, time).
RETURN_ON_EXCEPTION(isolate, temporal::ValidateTemporalUnitValue(
isolate, smallest_unit, UnitGroup::kTime));
// Rest of the steps handled in Rust
auto options = temporal_rs::RoundingOptions{.largest_unit = std::nullopt,
.smallest_unit = smallest_unit,
.rounding_mode = rounding_mode,
.increment = rounding_increment};
auto rounded = temporal_time->wrapped_rust()->round(options);
return ConstructRustWrappingType<JSTemporalPlainTime>(isolate,
std::move(rounded));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.with
MaybeDirectHandle<JSTemporalPlainTime> JSTemporalPlainTime::With(
Isolate* isolate, DirectHandle<JSTemporalPlainTime> temporal_time,
DirectHandle<Object> temporal_time_like_obj,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.PlainTime.prototype.with";
// 3. If ? IsPartialTemporalObject(temporalTimeLike) is false, throw a
// TypeError exception.
bool is_partial = false;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, is_partial,
temporal::IsPartialTemporalObject(isolate, temporal_time_like_obj));
if (!is_partial) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kWithNoPartial));
}
// 4. Let partialTime be ? ToTemporalTimeRecord(temporalTimeLike, partial).
temporal::TimeRecord partial_time;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, partial_time,
temporal::ToTemporalTimeRecord(isolate,
Cast<JSReceiver>(temporal_time_like_obj),
method_name, kPartial));
// Intervening steps handled by Rust, but are not externally observable
// 17. Let resolvedOptions be ? GetOptionsObject(options).
// 18. Let overflow be ? GetTemporalOverflowOption(resolvedOptions).
temporal_rs::ArithmeticOverflow overflow;
ASSIGN_RETURN_ON_EXCEPTION(isolate, overflow,
temporal::ToTemporalOverflowHandleUndefined(
isolate, options_obj, method_name));
// 19. Let result be ? RegulateTime(hour, minute, second, millisecond,
// microsecond, nanosecond, overflow).
// *technically* this wants to use a full TimeRecord object with
// all None fields filled from the PlainTime. However, we don't
// actually need to do this: RegulateTime will ignore the None
// fields and the Rust code below will handle the rest.
temporal_rs::PartialTime result;
ASSIGN_RETURN_ON_EXCEPTION(isolate, result,
partial_time.Regulate(isolate, overflow));
// 20. Return ! CreateTemporalTime(result).
return ConstructRustWrappingType<JSTemporalPlainTime>(
isolate, temporal_time->wrapped_rust()->with(result, overflow));
}
// https://tc39.es/proposal-temporal/#sec-temporal.now.plaintimeiso
MaybeDirectHandle<JSTemporalPlainTime> JSTemporalPlainTime::NowISO(
Isolate* isolate, DirectHandle<Object> temporal_time_zone_like) {
std::unique_ptr<temporal_rs::ZonedDateTime> zdt;
MOVE_RETURN_ON_EXCEPTION(
isolate, zdt,
temporal::GenericTemporalNowISO(isolate, temporal_time_zone_like));
return ConstructRustWrappingType<JSTemporalPlainTime>(isolate,
zdt->to_plain_time());
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaintime.from
MaybeDirectHandle<JSTemporalPlainTime> JSTemporalPlainTime::From(
Isolate* isolate, DirectHandle<Object> item_obj,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.PlainTime.from";
DirectHandle<JSTemporalPlainTime> item;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, item,
temporal::ToTemporalTime(isolate, item_obj, options_obj, method_name));
return item;
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.add
MaybeDirectHandle<JSTemporalPlainTime> JSTemporalPlainTime::Add(
Isolate* isolate, DirectHandle<JSTemporalPlainTime> temporal_time,
DirectHandle<Object> temporal_duration_like) {
static const char method_name[] = "Temporal.PlainTime.prototype.add";
DirectHandle<JSTemporalDuration> other_duration;
ASSIGN_RETURN_ON_EXCEPTION(isolate, other_duration,
temporal::ToTemporalDuration(
isolate, temporal_duration_like, method_name));
auto added =
temporal_time->wrapped_rust()->add(*other_duration->wrapped_rust());
return ConstructRustWrappingType<JSTemporalPlainTime>(isolate,
std::move(added));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.subtract
MaybeDirectHandle<JSTemporalPlainTime> JSTemporalPlainTime::Subtract(
Isolate* isolate, DirectHandle<JSTemporalPlainTime> temporal_time,
DirectHandle<Object> temporal_duration_like) {
static const char method_name[] = "Temporal.PlainTime.prototype.subtract";
DirectHandle<JSTemporalDuration> other_duration;
ASSIGN_RETURN_ON_EXCEPTION(isolate, other_duration,
temporal::ToTemporalDuration(
isolate, temporal_duration_like, method_name));
auto subtracted =
temporal_time->wrapped_rust()->subtract(*other_duration->wrapped_rust());
return ConstructRustWrappingType<JSTemporalPlainTime>(isolate,
std::move(subtracted));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.until
MaybeDirectHandle<JSTemporalDuration> JSTemporalPlainTime::Until(
Isolate* isolate, DirectHandle<JSTemporalPlainTime> handle,
DirectHandle<Object> other, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.PlainTime.prototype.until";
return temporal::GenericDifferenceTemporal(
isolate, &temporal_rs::PlainTime::until, UnitGroup::kTime,
Unit::Nanosecond, handle, other, options, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.since
MaybeDirectHandle<JSTemporalDuration> JSTemporalPlainTime::Since(
Isolate* isolate, DirectHandle<JSTemporalPlainTime> handle,
DirectHandle<Object> other, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.PlainTime.prototype.since";
return temporal::GenericDifferenceTemporal(
isolate, &temporal_rs::PlainTime::since, UnitGroup::kTime,
Unit::Nanosecond, handle, other, options, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.tostring
MaybeDirectHandle<String> JSTemporalPlainTime::ToString(
Isolate* isolate, DirectHandle<JSTemporalPlainTime> temporal_time,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.PlainTime.prototype.toString";
// 3. Let resolvedOptions be ?GetOptionsObject(options).
DirectHandle<JSReceiver> options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options, GetOptionsObject(isolate, options_obj, method_name));
// 5. Let digits be ?GetTemporalFractionalSecondDigitsOption(resolvedOptions).
temporal_rs::Precision digits;
ASSIGN_RETURN_ON_EXCEPTION(isolate, digits,
temporal::GetTemporalFractionalSecondDigitsOption(
isolate, options, method_name));
// 6. Let roundingMode be ? GetRoundingModeOption(resolvedOptions, trunc).
RoundingMode rounding_mode;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, rounding_mode,
temporal::GetRoundingModeOption(isolate, options, RoundingMode::Trunc,
method_name));
// 8. Let smallestUnit be ? GetTemporalUnitValuedOption(resolvedOptions,
// "smallestUnit", unset).
std::optional<Unit> smallest_unit;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, smallest_unit,
temporal::GetTemporalUnitValuedOption(
isolate, options, isolate->factory()->smallestUnit_string(),
DefaultValue::kUnset, method_name));
// 9. Perform ? ValidateTemporalUnitValue(smallestUnit, time).
RETURN_ON_EXCEPTION(isolate, temporal::ValidateTemporalUnitValue(
isolate, smallest_unit, UnitGroup::kTime));
// 8-10 performed by Rust
auto rust_options = temporal_rs::ToStringRoundingOptions{
.precision = digits,
.smallest_unit = smallest_unit,
.rounding_mode = rounding_mode,
};
return temporal::GenericTemporalToString(
isolate, temporal_time, &temporal_rs::PlainTime::to_ixdtf_string,
std::move(rust_options));
}
// https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.tojson
MaybeDirectHandle<String> JSTemporalPlainTime::ToJSON(
Isolate* isolate, DirectHandle<JSTemporalPlainTime> temporal_time) {
return temporal::GenericTemporalToString(
isolate, temporal_time, &temporal_rs::PlainTime::to_ixdtf_string,
std::move(temporal::kToStringAuto));
}
MaybeDirectHandle<String> JSTemporalPlainTime::ToLocaleString(
Isolate* isolate, DirectHandle<JSTemporalPlainTime> temporal_time,
DirectHandle<Object> locales, DirectHandle<Object> options) {
#ifdef V8_INTL_SUPPORT
// https://tc39.es/proposal-temporal/#sup-temporal.plaintime.prototype.tolocalestring
return JSDateTimeFormat::TemporalToLocaleString(
isolate, temporal_time, locales, options,
JSDateTimeFormat::RequiredOption::kTime,
JSDateTimeFormat::DefaultsOption::kTime,
"Temporal.PlainTime.prototype.toLocaleString");
#else // V8_INTL_SUPPORT
// https://tc39.es/proposal-temporal/#sec-temporal.plaintime.prototype.tolocalestring
return temporal::GenericTemporalToString(
isolate, temporal_time, &temporal_rs::PlainTime::to_ixdtf_string,
std::move(temporal::kToStringAuto));
#endif // V8_INTL_SUPPORT
}
// https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporaltime
Maybe<int64_t> JSTemporalPlainTime::GetEpochMillisecondsForUtc(
Isolate* isolate) {
return ExtractRustResult(isolate, this->wrapped_rust()->epoch_ms_for_utc());
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime
MaybeDirectHandle<JSTemporalZonedDateTime> JSTemporalZonedDateTime::Constructor(
Isolate* isolate, DirectHandle<JSFunction> target,
DirectHandle<HeapObject> new_target,
DirectHandle<Object> epoch_nanoseconds_obj,
DirectHandle<Object> time_zone_like, DirectHandle<Object> calendar_like) {
// 1. If NewTarget is undefined, throw a TypeError exception.
if (IsUndefined(*new_target)) {
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
isolate->factory()->NewStringFromAsciiChecked(
"Temporal.ZonedDateTime")));
}
// 2. Set epochNanoseconds to ? ToBigInt(epochNanoseconds).
DirectHandle<BigInt> epoch_nanoseconds;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, epoch_nanoseconds,
BigInt::FromObject(isolate, epoch_nanoseconds_obj));
// 3. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a
// RangeError exception.
temporal_rs::I128Nanoseconds ns;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, ns, temporal::GetI128FromBigInt(isolate, epoch_nanoseconds));
// 4. If timeZone is not a String, throw a TypeError exception.
if (!IsString(*time_zone_like)) {
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_TYPE_ERROR("Time zone must be string"));
}
auto tz_str = Cast<String>(time_zone_like);
auto tz_stdstr = tz_str->ToStdString();
// 5. Let timeZoneParse be ? ParseTimeZoneIdentifier(timeZone).
// 6. If timeZoneParse.[[OffsetMinutes]] is empty, then
// a. Let identifierRecord be
// GetAvailableNamedTimeZoneIdentifier(timeZoneParse.[[Name]]). b. If
// identifierRecord is empty, throw a RangeError exception. c. Set timeZone
// to identifierRecord.[[Identifier]].
// 7. Else,
// a. Set timeZone to
// FormatOffsetTimeZoneIdentifier(timeZoneParse.[[OffsetMinutes]]).
temporal_rs::TimeZone time_zone;
MOVE_RETURN_ON_EXCEPTION(
isolate, time_zone,
ExtractRustResult(
isolate, temporal_rs::TimeZone::try_from_identifier_str_with_provider(
tz_stdstr, TimeZoneProvider())));
// 8. If calendar is undefined, set calendar to "iso8601".
temporal_rs::AnyCalendarKind calendar = temporal_rs::AnyCalendarKind::Iso;
// We pre-parsed "iso8601" so we can do steps 9 and 10 in a branch
if (!IsUndefined(*calendar_like)) {
// 9. If calendar is not a String, throw a TypeError exception.
if (!IsString(*calendar_like)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kCalendarMustBeString));
}
// 10. Set calendar to ? CanonicalizeCalendar(calendar).
ASSIGN_RETURN_ON_EXCEPTION_VALUE(
isolate, calendar,
temporal::CanonicalizeCalendar(isolate, Cast<String>(calendar_like)),
kNullMaybeHandle);
}
// 11. Return ? CreateTemporalZonedDateTime(epochNanoseconds, timeZone,
// calendar, NewTarget).
return ConstructRustWrappingType<JSTemporalZonedDateTime>(
isolate, target, new_target,
temporal_rs::ZonedDateTime::try_new_with_provider(ns, calendar, time_zone,
TimeZoneProvider()));
}
// https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.hoursinday
MaybeDirectHandle<Number> JSTemporalZonedDateTime::HoursInDay(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time) {
double hours;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, hours,
ExtractRustResult(
isolate, zoned_date_time->wrapped_rust()->hours_in_day_with_provider(
TimeZoneProvider())));
return isolate->factory()->NewNumber(hours);
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.from
MaybeDirectHandle<JSTemporalZonedDateTime> JSTemporalZonedDateTime::From(
Isolate* isolate, DirectHandle<Object> item_obj,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.ZonedDateTime.from";
DirectHandle<JSTemporalPlainDateTime> item;
return temporal::ToTemporalZonedDateTime(isolate, item_obj, options_obj,
method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.compare
MaybeDirectHandle<Smi> JSTemporalZonedDateTime::Compare(
Isolate* isolate, DirectHandle<Object> one_obj,
DirectHandle<Object> two_obj) {
static const char method_name[] = "Temporal.ZonedDateTime.compare";
DirectHandle<JSTemporalZonedDateTime> one;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, one,
temporal::ToTemporalZonedDateTime(isolate, one_obj, {}, method_name));
DirectHandle<JSTemporalZonedDateTime> two;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, two,
temporal::ToTemporalZonedDateTime(isolate, two_obj, {}, method_name));
return direct_handle(
Smi::FromInt(one->wrapped_rust()->compare_instant(*two->wrapped_rust())),
isolate);
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.equals
MaybeDirectHandle<Oddball> JSTemporalZonedDateTime::Equals(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time,
DirectHandle<Object> other_obj) {
static const char method_name[] = "Temporal.ZonedDateTime.prototype.equals";
// 3. Set other to ? ToTemporalZonedDateTime(other).
DirectHandle<JSTemporalZonedDateTime> other;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, other,
temporal::ToTemporalZonedDateTime(isolate, other_obj, {}, method_name));
// Rest of the steps handled in Rust.
auto result = zoned_date_time->wrapped_rust()->equals_with_provider(
*other->wrapped_rust(), TimeZoneProvider());
bool equals;
ASSIGN_RETURN_ON_EXCEPTION(isolate, equals,
ExtractRustResult(isolate, std::move(result)));
return isolate->factory()->ToBoolean(equals);
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.with
MaybeDirectHandle<JSTemporalZonedDateTime> JSTemporalZonedDateTime::With(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time,
DirectHandle<Object> temporal_zoned_date_time_like_obj,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.ZonedDateTime.prototype.with";
using enum temporal::CalendarFieldsFlag;
return temporal::GenericWith<JSTemporalZonedDateTime,
temporal_rs::PartialZonedDateTime>(
isolate, zoned_date_time, temporal_zoned_date_time_like_obj, options_obj,
temporal::kAllDateFlags | kTimeFields | kOffset, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.withcalendar
MaybeDirectHandle<JSTemporalZonedDateTime>
JSTemporalZonedDateTime::WithCalendar(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> temporal_date,
DirectHandle<Object> calendar_id) {
// 3. Let calendar be ? ToTemporalCalendarIdentifier(calendarLike).
temporal_rs::AnyCalendarKind calendar_kind;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, calendar_kind,
temporal::ToTemporalCalendarIdentifier(isolate, calendar_id));
// Rest of the steps handled in Rust
return ConstructRustWrappingType<JSTemporalZonedDateTime>(
isolate, temporal_date->wrapped_rust()->with_calendar(calendar_kind));
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.withplaintime
MaybeDirectHandle<JSTemporalZonedDateTime>
JSTemporalZonedDateTime::WithPlainTime(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time,
DirectHandle<Object> plain_time_like) {
static const char method_name[] =
"Temporal.ZonedDateTime.prototype.withPlainTime";
// 1. Let zonedDateTime be the this value.
//
// 2. Perform ? RequireInternalSlot(zonedDateTime,
// [[InitializedTemporalZonedDateTime]]).
//
// 3. Let timeZone be zonedDateTime.[[TimeZone]].
//
// 4. Let calendar be zonedDateTime.[[Calendar]].
// (handled by type system)
// 5. Let isoDateTime be GetISODateTimeFor(timeZone,
// zonedDateTime.[[EpochNanoseconds]]).
// (handled later in Rust, idempotent)
DirectHandle<JSTemporalPlainTime> plain_time_obj;
CppGCManaged<temporal_rs::PlainTime>::Ptr plain_time;
// 6. If plainTimeLike is undefined, then
if (IsUndefined(*plain_time_like)) {
// a. Let epochNs be ? GetStartOfDay(timeZone, isoDateTime.[[ISODate]]).
// (handled later in Rust, there are no steps between this and the Rust
// call. Rust handles this by detecting a null time argument)
// 7. Else,
} else {
// a. Let plainTime be ? ToTemporalTime(plainTimeLike).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, plain_time_obj,
temporal::ToTemporalTime(isolate, plain_time_like, {}, method_name));
plain_time = plain_time_obj->wrapped_rust();
// b. Let resultISODateTime be
// CombineISODateAndTimeRecord(isoDateTime.[[ISODate]], plainTime.[[Time]]).
// c. Let epochNs be ? GetEpochNanosecondsFor(timeZone, resultISODateTime,
// compatible).
// (handled later in Rust, there are no steps between this and the Rust
// call)
}
// Rest of the steps handled in Rust.
return ConstructRustWrappingType<JSTemporalZonedDateTime>(
isolate, zoned_date_time->wrapped_rust()->with_plain_time_and_provider(
plain_time.raw(), TimeZoneProvider()));
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.withtimezone
MaybeDirectHandle<JSTemporalZonedDateTime>
JSTemporalZonedDateTime::WithTimeZone(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time,
DirectHandle<Object> time_zone_like) {
// 3. Let timeZone be ? ToTemporalTimeZoneIdentifier(timeZoneLike).
temporal_rs::TimeZone time_zone;
MOVE_RETURN_ON_EXCEPTION(
isolate, time_zone,
temporal::ToTemporalTimeZoneIdentifier(isolate, time_zone_like));
// 4. Return ! CreateTemporalZonedDateTime(zonedDateTime.[[EpochNanoseconds]],
// timeZone, zonedDateTime.[[Calendar]]).
return ConstructRustWrappingType<JSTemporalZonedDateTime>(
isolate, zoned_date_time->wrapped_rust()->with_timezone_with_provider(
time_zone, TimeZoneProvider()));
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.tostring
MaybeDirectHandle<String> JSTemporalZonedDateTime::ToString(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.ZonedDateTime.prototype.toString";
// 3. Let resolvedOptions be ?GetOptionsObject(options).
DirectHandle<JSReceiver> options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options, GetOptionsObject(isolate, options_obj, method_name));
// 5. Let showCalendar be ?GetTemporalShowCalendarNameOption(resolvedOptions).
temporal_rs::DisplayCalendar show_calendar;
ASSIGN_RETURN_ON_EXCEPTION(isolate, show_calendar,
temporal::GetTemporalShowCalendarNameOption(
isolate, options, method_name));
// 6. Let digits be ?GetTemporalFractionalSecondDigitsOption(resolvedOptions).
temporal_rs::Precision digits;
ASSIGN_RETURN_ON_EXCEPTION(isolate, digits,
temporal::GetTemporalFractionalSecondDigitsOption(
isolate, options, method_name));
// 7. Let showOffset be ? GetTemporalShowOffsetOption(resolvedOptions)..
temporal_rs::DisplayOffset show_offset;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, show_offset,
temporal::GetTemporalShowOffsetOption(isolate, options, method_name));
// 8. Let roundingMode be ? GetRoundingModeOption(resolvedOptions, trunc).
RoundingMode rounding_mode;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, rounding_mode,
temporal::GetRoundingModeOption(isolate, options, RoundingMode::Trunc,
method_name));
// 9. Let smallestUnit be ? GetTemporalUnitValuedOption(resolvedOptions,
// "smallestUnit", unset).
std::optional<Unit> smallest_unit;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, smallest_unit,
temporal::GetTemporalUnitValuedOption(
isolate, options, isolate->factory()->smallestUnit_string(),
DefaultValue::kUnset, method_name));
// 10. Let showTimeZone be
// ? GetTemporalShowTimeZoneNameOption(resolvedOptions).
temporal_rs::DisplayTimeZone show_tz;
ASSIGN_RETURN_ON_EXCEPTION(isolate, show_tz,
temporal::GetTemporalShowTimeZoneNameOption(
isolate, options, method_name));
// 11. Perform ? ValidateTemporalUnitValue(smallestUnit, time).
RETURN_ON_EXCEPTION(isolate, temporal::ValidateTemporalUnitValue(
isolate, smallest_unit, UnitGroup::kTime));
// 12. If smallestUnit is hour, throw a RangeError exception.
if (smallest_unit == Unit::Hour) {
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_RANGE_ERROR("smallestUnit cannot be Hour."));
}
// Rest of the steps handled in Rust
auto rust_options = temporal_rs::ToStringRoundingOptions{
.precision = digits,
.smallest_unit = smallest_unit,
.rounding_mode = rounding_mode,
};
return temporal::GenericTemporalToString(
isolate, zoned_date_time,
&temporal_rs::ZonedDateTime::to_ixdtf_string_with_provider, show_offset,
show_tz, show_calendar, std::move(rust_options),
std::ref(TimeZoneProvider()));
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.tojson
MaybeDirectHandle<String> JSTemporalZonedDateTime::ToJSON(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time) {
return temporal::GenericTemporalToString(
isolate, zoned_date_time,
&temporal_rs::ZonedDateTime::to_ixdtf_string_with_provider,
temporal_rs::DisplayOffset::Auto, temporal_rs::DisplayTimeZone::Auto,
temporal_rs::DisplayCalendar::Auto, std::move(temporal::kToStringAuto),
std::ref(TimeZoneProvider()));
}
MaybeDirectHandle<String> JSTemporalZonedDateTime::ToLocaleString(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time,
DirectHandle<Object> locales, DirectHandle<Object> options) {
#ifdef V8_INTL_SUPPORT
// https://tc39.es/proposal-temporal/#sub-temporal.zoneddatetime.prototype.tolocalestring
return JSDateTimeFormat::TemporalZonedDateTimeToLocaleString(
isolate, zoned_date_time, locales, options,
"Temporal.ZonedDateTime.prototype.toLocaleString");
#else // V8_INTL_SUPPORT
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.tolocalestring
return temporal::GenericTemporalToString(
isolate, zoned_date_time,
&temporal_rs::ZonedDateTime::to_ixdtf_string_with_provider,
temporal_rs::DisplayOffset::Auto, temporal_rs::DisplayTimeZone::Auto,
temporal_rs::DisplayCalendar::Auto, std::move(temporal::kToStringAuto),
std::ref(TimeZoneProvider()));
#endif // V8_INTL_SUPPORT
}
// https://tc39.es/proposal-temporal/#sec-temporal.now.zoneddatetimeiso
MaybeDirectHandle<JSTemporalZonedDateTime> JSTemporalZonedDateTime::NowISO(
Isolate* isolate, DirectHandle<Object> temporal_time_zone_like) {
std::unique_ptr<temporal_rs::ZonedDateTime> zdt;
MOVE_RETURN_ON_EXCEPTION(
isolate, zdt,
temporal::GenericTemporalNowISO(isolate, temporal_time_zone_like));
return ConstructRustWrappingType<JSTemporalZonedDateTime>(isolate,
std::move(zdt));
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.round
MaybeDirectHandle<JSTemporalZonedDateTime> JSTemporalZonedDateTime::Round(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time,
DirectHandle<Object> round_to_obj) {
static const char method_name[] = "Temporal.PlainDateTime.prototype.round";
// 1. Let zonedDateTime be the this value.
// 2. Perform ? RequireInternalSlot(zonedDateTime,
// [[InitializedTemporalZonedDateTime]]).
// (handled by type system)
// 3. If roundTo is undefined, then
if (IsUndefined(*round_to_obj)) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kRoundToMissing));
}
DirectHandle<JSReceiver> round_to;
auto factory = isolate->factory();
// 4. If roundTo is a String, then
if (IsString(*round_to_obj)) {
// a. Let paramString be roundTo.
DirectHandle<String> param_string = Cast<String>(round_to_obj);
// b. Set roundTo to ! OrdinaryObjectCreate(null).
round_to = factory->NewJSObjectWithNullProto();
// c. c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit",
// paramString).
CHECK(JSReceiver::CreateDataProperty(isolate, round_to,
factory->smallestUnit_string(),
param_string, Just(kThrowOnError))
.FromJust());
// 5. Else,
} else {
// a. Set roundTo to ? GetOptionsObject(roundTo).
// We have already checked for undefined, we can hoist the JSReceiver
// check out and just cast
if (!IsJSReceiver(*round_to_obj)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kRoundToMustBeObject));
}
round_to = Cast<JSReceiver>(round_to_obj);
}
// 6. NOTE: (...)
// 7. Let roundingIncrement be ? GetRoundingIncrementOption(roundTo).
uint32_t rounding_increment;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, rounding_increment,
temporal::GetRoundingIncrementOption(isolate, round_to));
// 8. Let roundingMode be ? GetRoundingModeOption(roundTo, half-expand).
RoundingMode rounding_mode;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, rounding_mode,
temporal::GetRoundingModeOption(isolate, round_to,
RoundingMode::HalfExpand, method_name));
// 9. Let smallestUnit be ? GetTemporalUnitValuedOption(roundTo,
// "smallestUnit", required).
std::optional<Unit> smallest_unit;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, smallest_unit,
temporal::GetTemporalUnitValuedOption(
isolate, round_to, isolate->factory()->smallestUnit_string(),
DefaultValue::kRequired, method_name));
// 10. Perform ? ValidateTemporalUnitValue(smallestUnit, time, « day »).
RETURN_ON_EXCEPTION(isolate,
temporal::ValidateTemporalUnitValue(
isolate, smallest_unit, UnitGroup::kTime, Unit::Day));
// Rest of the steps handled in Rust
auto options = temporal_rs::RoundingOptions{.largest_unit = std::nullopt,
.smallest_unit = smallest_unit,
.rounding_mode = rounding_mode,
.increment = rounding_increment};
auto rounded = zoned_date_time->wrapped_rust()->round_with_provider(
options, TimeZoneProvider());
return ConstructRustWrappingType<JSTemporalZonedDateTime>(isolate,
std::move(rounded));
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.add
MaybeDirectHandle<JSTemporalZonedDateTime> JSTemporalZonedDateTime::Add(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time,
DirectHandle<Object> temporal_duration_like, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.ZonedDateTime.prototype.add";
// 3. Return ? AddDurationToZonedDateTime(add, temporalDate,
// temporalDurationLike, options).
return temporal::AddDurationToGeneric(
isolate, &temporal_rs::ZonedDateTime::add_with_provider, zoned_date_time,
temporal_duration_like, options, method_name, TimeZoneProvider());
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.subtract
MaybeDirectHandle<JSTemporalZonedDateTime> JSTemporalZonedDateTime::Subtract(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time,
DirectHandle<Object> temporal_duration_like, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.ZonedDateTime.prototype.subtract";
// 3. Return ? AddDurationToZonedDateTime(subtract, temporalDate,
// temporalDurationLike, options).
return temporal::AddDurationToGeneric(
isolate, &temporal_rs::ZonedDateTime::subtract_with_provider,
zoned_date_time, temporal_duration_like, options, method_name,
TimeZoneProvider());
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.until
MaybeDirectHandle<JSTemporalDuration> JSTemporalZonedDateTime::Until(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> handle,
DirectHandle<Object> other, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.ZonedDateTime.prototype.since";
return temporal::GenericDifferenceTemporal(
isolate, &temporal_rs::ZonedDateTime::until_with_provider,
UnitGroup::kDateTime, Unit::Nanosecond, handle, other, options,
method_name, TimeZoneProvider());
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.since
MaybeDirectHandle<JSTemporalDuration> JSTemporalZonedDateTime::Since(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> handle,
DirectHandle<Object> other, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.ZonedDateTime.prototype.since";
return temporal::GenericDifferenceTemporal(
isolate, &temporal_rs::ZonedDateTime::since_with_provider,
UnitGroup::kDateTime, Unit::Nanosecond, handle, other, options,
method_name, TimeZoneProvider());
}
// https://tc39.es/proposal-temporal/#sec-temporal.now.instant
MaybeDirectHandle<JSTemporalInstant> JSTemporalInstant::Now(Isolate* isolate) {
auto ns = temporal::SystemUTCEpochNanoseconds(isolate);
return ConstructRustWrappingType<JSTemporalInstant>(
isolate, temporal_rs::Instant::try_new(ns));
}
// https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.offsetnanoseconds
MaybeDirectHandle<Object> JSTemporalZonedDateTime::OffsetNanoseconds(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time) {
int64_t offset_ns = zoned_date_time->wrapped_rust()->offset_nanoseconds();
return isolate->factory()->NewNumberFromInt64(offset_ns);
}
// https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.epochnanoseconds
MaybeDirectHandle<BigInt> JSTemporalZonedDateTime::EpochNanoseconds(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time) {
return temporal::I128ToBigInt(
isolate, zoned_date_time->wrapped_rust()->epoch_nanoseconds());
}
// https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.timezoneid
MaybeDirectHandle<String> JSTemporalZonedDateTime::TimeZoneId(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time) {
std::string id;
MOVE_RETURN_ON_EXCEPTION(
isolate, id,
ExtractRustResult(
isolate,
zoned_date_time->wrapped_rust()->timezone().identifier_with_provider(
TimeZoneProvider())));
IncrementalStringBuilder builder(isolate);
builder.AppendString(id);
return builder.Finish().ToHandleChecked();
}
// https://tc39.es/proposal-temporal/#sec-get-temporal.zoneddatetime.prototype.offset
MaybeDirectHandle<String> JSTemporalZonedDateTime::Offset(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time) {
std::string offset;
MOVE_RETURN_ON_EXCEPTION(
isolate, offset,
ExtractRustResult(isolate, zoned_date_time->wrapped_rust()->offset()));
IncrementalStringBuilder builder(isolate);
builder.AppendString(offset);
return builder.Finish().ToHandleChecked();
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.startofday
MaybeDirectHandle<JSTemporalZonedDateTime> JSTemporalZonedDateTime::StartOfDay(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time) {
return ConstructRustWrappingType<JSTemporalZonedDateTime>(
isolate, zoned_date_time->wrapped_rust()->start_of_day_with_provider(
TimeZoneProvider()));
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.gettimezonetransition
MaybeDirectHandle<UnionOf<JSTemporalZonedDateTime, Null>>
JSTemporalZonedDateTime::GetTimeZoneTransition(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time,
DirectHandle<Object> direction_param_obj) {
static const char method_name[] =
"Temporal.ZonedDateTime.prototype.getTimeZoneTransition";
// 4. If directionParam is undefined, throw a TypeError exception.
if (IsUndefined(*direction_param_obj)) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(
"Must specify a direction parameter."));
}
// 3. If roundTo is undefined, then
DirectHandle<JSReceiver> direction_param;
auto factory = isolate->factory();
// 5. If directionParam is a String, then
if (IsString(*direction_param_obj)) {
// a. a. Let paramString be directionParam.
DirectHandle<String> param_string = Cast<String>(direction_param_obj);
// b. Set directionParam to OrdinaryObjectCreate(null).
direction_param = factory->NewJSObjectWithNullProto();
// TODO(manishearth) Ideally we don't have to perform this allocation
// c. Perform ! CreateDataPropertyOrThrow(directionParam, "direction",
// paramString).
CHECK(JSReceiver::CreateDataProperty(isolate, direction_param,
factory->direction_string(),
param_string, Just(kThrowOnError))
.FromJust());
// 6. Else,
} else {
// a. Set directionParam to ? GetOptionsObject(directionParam).
// We have already checked for undefined, we can hoist the JSReceiver
// check out and just cast
if (!IsJSReceiver(*direction_param_obj)) {
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(
"directionParam must be object or string."));
}
direction_param = Cast<JSReceiver>(direction_param_obj);
}
// 7. Let direction be ? GetDirectionOption(directionParam).
temporal_rs::TransitionDirection dir;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, dir,
temporal::GetDirectionOption(isolate, direction_param, method_name));
// Steps 8-10 handled in Rust.
std::unique_ptr<temporal_rs::ZonedDateTime> zdt;
MOVE_RETURN_ON_EXCEPTION(
isolate, zdt,
ExtractRustResult(isolate, zoned_date_time->wrapped_rust()
->get_time_zone_transition_with_provider(
dir, TimeZoneProvider())));
// 11. If transition is null, return null.
if (!zdt) {
return isolate->factory()->null_value();
}
return ConstructRustWrappingType<JSTemporalZonedDateTime>(isolate,
std::move(zdt));
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.toinstant
MaybeDirectHandle<JSTemporalInstant> JSTemporalZonedDateTime::ToInstant(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time) {
return temporal::GenericToTemporalMethod<JSTemporalInstant>(
isolate, zoned_date_time, &temporal_rs::ZonedDateTime::to_instant);
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.toplaindate
MaybeDirectHandle<JSTemporalPlainDate> JSTemporalZonedDateTime::ToPlainDate(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time) {
return temporal::GenericToTemporalMethod<JSTemporalPlainDate>(
isolate, zoned_date_time, &temporal_rs::ZonedDateTime::to_plain_date);
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.toplaintime
MaybeDirectHandle<JSTemporalPlainTime> JSTemporalZonedDateTime::ToPlainTime(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time) {
return temporal::GenericToTemporalMethod<JSTemporalPlainTime>(
isolate, zoned_date_time, &temporal_rs::ZonedDateTime::to_plain_time);
}
// https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime.prototype.toplaindatetime
MaybeDirectHandle<JSTemporalPlainDateTime>
JSTemporalZonedDateTime::ToPlainDateTime(
Isolate* isolate, DirectHandle<JSTemporalZonedDateTime> zoned_date_time) {
return temporal::GenericToTemporalMethod<JSTemporalPlainDateTime>(
isolate, zoned_date_time, &temporal_rs::ZonedDateTime::to_plain_datetime);
}
namespace temporal {
// https://tc39.es/proposal-temporal/#sec-temporal-createtemporalinstant, but
// this also performs the validity check
MaybeDirectHandle<JSTemporalInstant> CreateTemporalInstantWithValidityCheck(
Isolate* isolate, DirectHandle<JSFunction> target,
DirectHandle<HeapObject> new_target,
DirectHandle<BigInt> epoch_nanoseconds) {
temporal_rs::I128Nanoseconds ns;
ASSIGN_RETURN_ON_EXCEPTION(isolate, ns,
GetI128FromBigInt(isolate, epoch_nanoseconds));
return ConstructRustWrappingType<JSTemporalInstant>(
isolate, target, new_target, temporal_rs::Instant::try_new(ns));
}
MaybeDirectHandle<JSTemporalInstant> CreateTemporalInstantWithValidityCheck(
Isolate* isolate, DirectHandle<BigInt> epoch_nanoseconds) {
auto ctor = JSTemporalInstant::GetConstructorTarget(isolate);
return CreateTemporalInstantWithValidityCheck(isolate, ctor, ctor,
epoch_nanoseconds);
}
} // namespace temporal
// https://tc39.es/proposal-temporal/#sec-temporal.instant
MaybeDirectHandle<JSTemporalInstant> JSTemporalInstant::Constructor(
Isolate* isolate, DirectHandle<JSFunction> target,
DirectHandle<HeapObject> new_target,
DirectHandle<Object> epoch_nanoseconds_obj) {
// 1. If NewTarget is undefined, then
if (IsUndefined(*new_target)) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate,
NewTypeError(MessageTemplate::kMethodInvokedOnWrongType,
isolate->factory()->NewStringFromAsciiChecked(
"Temporal.Instant")));
}
// 2. Let epochNanoseconds be ? ToBigInt(epochNanoseconds).
DirectHandle<BigInt> epoch_nanoseconds;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, epoch_nanoseconds,
BigInt::FromObject(isolate, epoch_nanoseconds_obj));
return temporal::CreateTemporalInstantWithValidityCheck(
isolate, target, new_target, epoch_nanoseconds);
}
// https://tc39.es/proposal-temporal/#sec-temporal.instant.from
MaybeDirectHandle<JSTemporalInstant> JSTemporalInstant::From(
Isolate* isolate, DirectHandle<Object> item) {
static const char method_name[] = "Temporal.Instant.from";
DirectHandle<JSTemporalInstant> item_instant;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, item_instant,
temporal::ToTemporalInstant(isolate, item, method_name));
return item_instant;
}
// https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochmilliseconds
MaybeDirectHandle<JSTemporalInstant> JSTemporalInstant::FromEpochMilliseconds(
Isolate* isolate, DirectHandle<Object> epoch_milliseconds) {
// 1. Let number be ? ToNumber(epochMilliseconds).
DirectHandle<Number> number;
ASSIGN_RETURN_ON_EXCEPTION(isolate, number,
Object::ToNumber(isolate, epoch_milliseconds));
double ms = Object::NumberValue(*number);
// 2. Set epochMilliseconds to ?NumberToBigInt(epochMilliseconds).
//
// Instead of allocating a BigInt, we perform the necessary check from
// NumberToBigInt and cast:
//
// (NumberToBigInt) 1. If number is not an integral Number, throw a RangeError
// exception.
if (!std::isfinite(ms) ||
!base::IsValueInRangeForNumericType<int64_t, double>(ms) ||
nearbyint(ms) != ms) {
THROW_NEW_ERROR(isolate,
NEW_TEMPORAL_RANGE_ERROR("Expected finite integer."));
}
auto ms_int = static_cast<int64_t>(ms);
// 3. Let epochNanoseconds be epochMilliseconds × ℤ(10**6).
// 4. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a
// RangeError exception.
//
// (Rest of the steps handled by Rust)
return ConstructRustWrappingType<JSTemporalInstant>(
isolate, temporal_rs::Instant::from_epoch_milliseconds(ms_int));
}
// https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochnanoseconds
MaybeDirectHandle<JSTemporalInstant> JSTemporalInstant::FromEpochNanoseconds(
Isolate* isolate, DirectHandle<Object> epoch_nanoseconds_obj) {
// 1. Set epochNanoseconds to ? ToBigInt(epochNanoseconds).
DirectHandle<BigInt> epoch_nanoseconds;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, epoch_nanoseconds,
BigInt::FromObject(isolate, epoch_nanoseconds_obj));
// Rest of the steps handled by CreateTemporalInstantWithValidityCheck
return temporal::CreateTemporalInstantWithValidityCheck(isolate,
epoch_nanoseconds);
}
// https://tc39.es/proposal-temporal/#sec-temporal.instant.compare
MaybeDirectHandle<Smi> JSTemporalInstant::Compare(
Isolate* isolate, DirectHandle<Object> one_obj,
DirectHandle<Object> two_obj) {
static const char method_name[] = "Temporal.Instant.compare";
DirectHandle<JSTemporalInstant> one;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, one, temporal::ToTemporalInstant(isolate, one_obj, method_name));
DirectHandle<JSTemporalInstant> two;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, two, temporal::ToTemporalInstant(isolate, two_obj, method_name));
return direct_handle(
Smi::FromInt(one->wrapped_rust()->compare(*two->wrapped_rust())),
isolate);
}
// https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.equals
MaybeDirectHandle<Oddball> JSTemporalInstant::Equals(
Isolate* isolate, DirectHandle<JSTemporalInstant> handle,
DirectHandle<Object> other_obj) {
static const char method_name[] = "Temporal.Instant.prototype.equals";
DirectHandle<JSTemporalInstant> other;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, other,
temporal::ToTemporalInstant(isolate, other_obj, method_name));
auto this_ns = handle->wrapped_rust()->epoch_nanoseconds();
auto other_ns = other->wrapped_rust()->epoch_nanoseconds();
// equals() isn't exposed over FFI, but it's easy enough to do here
// in the future we can use https://github.com/boa-dev/temporal/pull/311
return isolate->factory()->ToBoolean(this_ns.high == other_ns.high &&
this_ns.low == other_ns.low);
}
// https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.round
MaybeDirectHandle<JSTemporalInstant> JSTemporalInstant::Round(
Isolate* isolate, DirectHandle<JSTemporalInstant> handle,
DirectHandle<Object> round_to_obj) {
static const char method_name[] = "Temporal.Instant.prototype.round";
Factory* factory = isolate->factory();
// 1. Let instant be the this value.
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
// 3. If roundTo is undefined, then
if (IsUndefined(*round_to_obj)) {
// a. Throw a TypeError exception.
THROW_NEW_ERROR(isolate, NEW_TEMPORAL_TYPE_ERROR(kRoundToMissing));
}
DirectHandle<JSReceiver> round_to;
// 4. If Type(roundTo) is String, then
if (IsString(*round_to_obj)) {
// TODO(415359720) This could be done more efficiently, if we had better
// GetStringOption APIs
// a. Let paramString be roundTo.
DirectHandle<String> param_string = Cast<String>(round_to_obj);
// b. Set roundTo to ! OrdinaryObjectCreate(null).
round_to = factory->NewJSObjectWithNullProto();
// c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit",
// paramString).
CHECK(JSReceiver::CreateDataProperty(isolate, round_to,
factory->smallestUnit_string(),
param_string, Just(kThrowOnError))
.FromJust());
} else {
// a. Set roundTo to ? GetOptionsObject(roundTo).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, round_to,
GetOptionsObject(isolate, round_to_obj, method_name));
}
// 7. Let roundingIncrement be ? GetRoundingIncrementOption(roundTo).
uint32_t rounding_increment;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, rounding_increment,
temporal::GetRoundingIncrementOption(isolate, round_to));
// 8. Let roundingMode be ? GetRoundingModeOption(roundTo, half-expand).
RoundingMode rounding_mode;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, rounding_mode,
temporal::GetRoundingModeOption(isolate, round_to,
RoundingMode::HalfExpand, method_name));
// 9. Let smallestUnit be ? GetTemporalUnitValuedOption(roundTo,
// "smallestUnit", required).
std::optional<Unit> smallest_unit;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, smallest_unit,
temporal::GetTemporalUnitValuedOption(
isolate, round_to, isolate->factory()->smallestUnit_string(),
DefaultValue::kRequired, method_name));
// 10. Perform ? ValidateTemporalUnitValue(smallestUnit, time).
RETURN_ON_EXCEPTION(isolate, temporal::ValidateTemporalUnitValue(
isolate, smallest_unit, UnitGroup::kTime));
auto options = temporal_rs::RoundingOptions{.largest_unit = std::nullopt,
.smallest_unit = smallest_unit,
.rounding_mode = rounding_mode,
.increment = rounding_increment};
auto rounded = handle->wrapped_rust()->round(options);
return ConstructRustWrappingType<JSTemporalInstant>(isolate,
std::move(rounded));
}
// https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.epochmilliseconds
MaybeDirectHandle<Number> JSTemporalInstant::EpochMilliseconds(
Isolate* isolate, DirectHandle<JSTemporalInstant> handle) {
int64_t ms = handle->wrapped_rust()->epoch_milliseconds();
return isolate->factory()->NewNumberFromInt64(ms);
}
// https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.epochnanoseconds
MaybeDirectHandle<BigInt> JSTemporalInstant::EpochNanoseconds(
Isolate* isolate, DirectHandle<JSTemporalInstant> handle) {
return temporal::I128ToBigInt(isolate,
handle->wrapped_rust()->epoch_nanoseconds());
}
// https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tozoneddatetime
MaybeDirectHandle<JSTemporalZonedDateTime>
JSTemporalInstant::ToZonedDateTimeISO(Isolate* isolate,
DirectHandle<JSTemporalInstant> instant,
DirectHandle<Object> time_zone_obj) {
// 3. Let timeZone be ? ToTemporalTimeZoneIdentifier(temporalTimeZoneLike).
temporal_rs::TimeZone time_zone;
MOVE_RETURN_ON_EXCEPTION(
isolate, time_zone,
temporal::ToTemporalTimeZoneIdentifier(isolate, time_zone_obj));
return ConstructRustWrappingType<JSTemporalZonedDateTime>(
isolate, instant->wrapped_rust()->to_zoned_date_time_iso_with_provider(
time_zone, TimeZoneProvider()));
}
// https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tostring
MaybeDirectHandle<String> JSTemporalInstant::ToString(
Isolate* isolate, DirectHandle<JSTemporalInstant> instant,
DirectHandle<Object> options_obj) {
static const char method_name[] = "Temporal.Instant.prototype.toString";
// 3. Set options to ? GetOptionsObject(options).
DirectHandle<JSReceiver> options;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, options, GetOptionsObject(isolate, options_obj, method_name));
// 5. Let digits be ?
// GetTemporalFractionalSecondDigitsOption(resolvedOptions).
temporal_rs::Precision digits;
ASSIGN_RETURN_ON_EXCEPTION(isolate, digits,
temporal::GetTemporalFractionalSecondDigitsOption(
isolate, options, method_name));
// 6. Let roundingMode be ? GetRoundingModeOption(resolvedOptions, trunc).
RoundingMode rounding_mode;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, rounding_mode,
temporal::GetRoundingModeOption(isolate, options, RoundingMode::Trunc,
method_name));
// 7. Let smallestUnit be ? GetTemporalUnitValuedOption(resolvedOptions,
// "smallestUnit", unset).
std::optional<Unit> smallest_unit;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, smallest_unit,
temporal::GetTemporalUnitValuedOption(
isolate, options, isolate->factory()->smallestUnit_string(),
DefaultValue::kUnset, method_name));
// 8. Let timeZone be ? Get(resolvedOptions, "timeZone").
DirectHandle<Object> time_zone;
// Let val be ? Get(temporalDurationLike, fieldName).
ASSIGN_RETURN_ON_EXCEPTION(
isolate, time_zone,
JSReceiver::GetProperty(isolate, options,
isolate->factory()->timeZone_string()));
// 9. Perform ? ValidateTemporalUnitValue(smallestUnit, time).
RETURN_ON_EXCEPTION(isolate, temporal::ValidateTemporalUnitValue(
isolate, smallest_unit, UnitGroup::kTime));
// 10. If smallestUnit is hour, throw a RangeError exception.
if (smallest_unit == Unit::Hour) {
THROW_NEW_ERROR(isolate,
NewRangeError(MessageTemplate::kPropertyValueOutOfRange,
isolate->factory()->smallestUnit_string()));
}
std::optional<temporal_rs::TimeZone> tz_for_passing;
// 11. If timeZone is not undefined, then
if (!IsUndefined(*time_zone)) {
temporal_rs::TimeZone rust_time_zone;
// a. Set timeZone to ? ToTemporalTimeZoneIdentifier(timeZone).
MOVE_RETURN_ON_EXCEPTION(
isolate, rust_time_zone,
temporal::ToTemporalTimeZoneIdentifier(isolate, time_zone));
tz_for_passing = rust_time_zone;
}
auto rust_options = temporal_rs::ToStringRoundingOptions{
.precision = digits,
.smallest_unit = smallest_unit,
.rounding_mode = rounding_mode,
};
return temporal::GenericTemporalToString(
isolate, instant, &temporal_rs::Instant::to_ixdtf_string_with_provider,
tz_for_passing, std::move(rust_options), std::ref(TimeZoneProvider()));
}
// https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tojson
MaybeDirectHandle<String> JSTemporalInstant::ToJSON(
Isolate* isolate, DirectHandle<JSTemporalInstant> instant) {
return temporal::GenericTemporalToString(
isolate, instant, &temporal_rs::Instant::to_ixdtf_string_with_provider,
std::nullopt, std::move(temporal::kToStringAuto),
std::ref(TimeZoneProvider()));
}
MaybeDirectHandle<String> JSTemporalInstant::ToLocaleString(
Isolate* isolate, DirectHandle<JSTemporalInstant> instant,
DirectHandle<Object> locales, DirectHandle<Object> options) {
#ifdef V8_INTL_SUPPORT
// https://tc39.es/proposal-temporal/#sub-temporal.instant.prototype.tolocalestring
return JSDateTimeFormat::TemporalToLocaleString(
isolate, instant, locales, options,
JSDateTimeFormat::RequiredOption::kAny,
JSDateTimeFormat::DefaultsOption::kAll,
"Temporal.Instant.prototype.toLocaleString");
#else // V8_INTL_SUPPORT
// https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.tolocalestring
return temporal::GenericTemporalToString(
isolate, instant, &temporal_rs::Instant::to_ixdtf_string_with_provider,
std::nullopt, std::move(temporal::kToStringAuto),
std::ref(TimeZoneProvider()));
#endif // V8_INTL_SUPPORT
}
// https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.add
MaybeDirectHandle<JSTemporalInstant> JSTemporalInstant::Add(
Isolate* isolate, DirectHandle<JSTemporalInstant> handle,
DirectHandle<Object> temporal_duration_like) {
static const char method_name[] = "Temporal.Duration.prototype.add";
DirectHandle<JSTemporalDuration> other_duration;
ASSIGN_RETURN_ON_EXCEPTION(isolate, other_duration,
temporal::ToTemporalDuration(
isolate, temporal_duration_like, method_name));
auto added = handle->wrapped_rust()->add(*other_duration->wrapped_rust());
return ConstructRustWrappingType<JSTemporalInstant>(isolate,
std::move(added));
}
// https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.subtract
MaybeDirectHandle<JSTemporalInstant> JSTemporalInstant::Subtract(
Isolate* isolate, DirectHandle<JSTemporalInstant> handle,
DirectHandle<Object> temporal_duration_like) {
static const char method_name[] = "Temporal.Duration.prototype.subtract";
DirectHandle<JSTemporalDuration> other_duration;
ASSIGN_RETURN_ON_EXCEPTION(isolate, other_duration,
temporal::ToTemporalDuration(
isolate, temporal_duration_like, method_name));
auto subtracted =
handle->wrapped_rust()->subtract(*other_duration->wrapped_rust());
return ConstructRustWrappingType<JSTemporalInstant>(isolate,
std::move(subtracted));
}
// https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.until
MaybeDirectHandle<JSTemporalDuration> JSTemporalInstant::Until(
Isolate* isolate, DirectHandle<JSTemporalInstant> handle,
DirectHandle<Object> other, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.Instant.prototype.until";
return temporal::GenericDifferenceTemporal(
isolate, &temporal_rs::Instant::until, UnitGroup::kTime, Unit::Nanosecond,
handle, other, options, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.since
MaybeDirectHandle<JSTemporalDuration> JSTemporalInstant::Since(
Isolate* isolate, DirectHandle<JSTemporalInstant> handle,
DirectHandle<Object> other, DirectHandle<Object> options) {
static const char method_name[] = "Temporal.Instant.prototype.since";
return temporal::GenericDifferenceTemporal(
isolate, &temporal_rs::Instant::since, UnitGroup::kTime, Unit::Nanosecond,
handle, other, options, method_name);
}
// https://tc39.es/proposal-temporal/#sec-temporal.now.timezoneid
V8_WARN_UNUSED_RESULT MaybeDirectHandle<String> JSTemporalNowTimeZoneId(
Isolate* isolate) {
// 1. Return SystemTimeZoneIdentifier().
#ifdef V8_INTL_SUPPORT
auto tz_str = Intl::DefaultTimeZone();
IncrementalStringBuilder builder(isolate);
builder.AppendString(tz_str);
return builder.Finish().ToHandleChecked();
#else
return isolate->factory()->UTC_string();
#endif
}
} // namespace v8::internal