mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-31 05:09:12 +00:00
LibJS: Implement the Temporal.Instant constructor
And the simple Temporal.Instant.prototype getters, so that the constructed Temporal.Instant may actually be validated.
This commit is contained in:
parent
1675f40e29
commit
90820873a2
Notes:
github-actions[bot]
2024-11-25 12:34:28 +00:00
Author: https://github.com/trflynn89
Commit: 90820873a2
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2557
Reviewed-by: https://github.com/shannonbooth ✅
22 changed files with 710 additions and 14 deletions
|
@ -211,6 +211,8 @@ set(SOURCES
|
|||
Runtime/Temporal/DurationConstructor.cpp
|
||||
Runtime/Temporal/DurationPrototype.cpp
|
||||
Runtime/Temporal/Instant.cpp
|
||||
Runtime/Temporal/InstantConstructor.cpp
|
||||
Runtime/Temporal/InstantPrototype.cpp
|
||||
Runtime/Temporal/ISO8601.cpp
|
||||
Runtime/Temporal/PlainDate.cpp
|
||||
Runtime/Temporal/PlainDateConstructor.cpp
|
||||
|
|
|
@ -89,6 +89,7 @@
|
|||
|
||||
#define JS_ENUMERATE_TEMPORAL_OBJECTS \
|
||||
__JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor) \
|
||||
__JS_ENUMERATE(Instant, instant, InstantPrototype, InstantConstructor) \
|
||||
__JS_ENUMERATE(PlainDate, plain_date, PlainDatePrototype, PlainDateConstructor) \
|
||||
__JS_ENUMERATE(PlainDateTime, plain_date_time, PlainDateTimePrototype, PlainDateTimeConstructor) \
|
||||
__JS_ENUMERATE(PlainMonthDay, plain_month_day, PlainMonthDayPrototype, PlainMonthDayConstructor) \
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
#include <LibJS/Runtime/StringObject.h>
|
||||
#include <LibJS/Runtime/StringPrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/Duration.h>
|
||||
#include <LibJS/Runtime/Temporal/Instant.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDate.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDay.h>
|
||||
|
@ -840,6 +841,14 @@ ErrorOr<void> print_temporal_duration(JS::PrintContext& print_context, JS::Tempo
|
|||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> print_temporal_instant(JS::PrintContext& print_context, JS::Temporal::Instant const& instant, HashTable<JS::Object*>& seen_objects)
|
||||
{
|
||||
TRY(print_type(print_context, "Temporal.Instant"sv));
|
||||
TRY(js_out(print_context, " "));
|
||||
TRY(print_value(print_context, instant.epoch_nanoseconds(), seen_objects));
|
||||
return {};
|
||||
}
|
||||
|
||||
ErrorOr<void> print_temporal_plain_date(JS::PrintContext& print_context, JS::Temporal::PlainDate const& plain_date, HashTable<JS::Object*>& seen_objects)
|
||||
{
|
||||
TRY(print_type(print_context, "Temporal.PlainDate"sv));
|
||||
|
@ -1000,6 +1009,8 @@ ErrorOr<void> print_value(JS::PrintContext& print_context, JS::Value value, Hash
|
|||
return print_intl_duration_format(print_context, static_cast<JS::Intl::DurationFormat&>(object), seen_objects);
|
||||
if (is<JS::Temporal::Duration>(object))
|
||||
return print_temporal_duration(print_context, static_cast<JS::Temporal::Duration&>(object), seen_objects);
|
||||
if (is<JS::Temporal::Instant>(object))
|
||||
return print_temporal_instant(print_context, static_cast<JS::Temporal::Instant&>(object), seen_objects);
|
||||
if (is<JS::Temporal::PlainDate>(object))
|
||||
return print_temporal_plain_date(print_context, static_cast<JS::Temporal::PlainDate&>(object), seen_objects);
|
||||
if (is<JS::Temporal::PlainDateTime>(object))
|
||||
|
|
|
@ -101,6 +101,8 @@
|
|||
#include <LibJS/Runtime/SymbolPrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/DurationConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/DurationPrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/InstantConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/InstantPrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDatePrototype.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateTimeConstructor.h>
|
||||
|
|
|
@ -1754,4 +1754,19 @@ Crypto::SignedBigInteger get_utc_epoch_nanoseconds(ISODateTime const& iso_date_t
|
|||
iso_date_time.time.nanosecond);
|
||||
}
|
||||
|
||||
// AD-HOC
|
||||
// FIXME: We should add a generic floor() method to our BigInt classes. But for now, since we know we are only dividing
|
||||
// by powers of 10, we can implement a very situationally specific method to compute the floor of a division.
|
||||
Crypto::SignedBigInteger big_floor(Crypto::SignedBigInteger const& numerator, Crypto::UnsignedBigInteger const& denominator)
|
||||
{
|
||||
auto result = numerator.divided_by(denominator);
|
||||
|
||||
if (result.remainder.is_zero())
|
||||
return result.quotient;
|
||||
if (!result.quotient.is_negative() && result.remainder.is_positive())
|
||||
return result.quotient;
|
||||
|
||||
return result.quotient.minus(Crypto::SignedBigInteger { 1 });
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -261,4 +261,6 @@ ThrowCompletionOr<RoundingMode> get_rounding_mode_option(VM&, Object const& opti
|
|||
ThrowCompletionOr<u64> get_rounding_increment_option(VM&, Object const& options);
|
||||
Crypto::SignedBigInteger get_utc_epoch_nanoseconds(ISODateTime const&);
|
||||
|
||||
Crypto::SignedBigInteger big_floor(Crypto::SignedBigInteger const& numerator, Crypto::UnsignedBigInteger const& denominator);
|
||||
|
||||
}
|
||||
|
|
|
@ -7,10 +7,35 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/BigInt.h>
|
||||
#include <LibJS/Runtime/Date.h>
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/Instant.h>
|
||||
#include <LibJS/Runtime/Temporal/InstantConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainTime.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibJS/Runtime/ValueInlines.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(Instant);
|
||||
|
||||
// 8 Temporal.Instant Objects, https://tc39.es/proposal-temporal/#sec-temporal-instant-objects
|
||||
Instant::Instant(BigInt const& epoch_nanoseconds, Object& prototype)
|
||||
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
||||
, m_epoch_nanoseconds(epoch_nanoseconds)
|
||||
{
|
||||
}
|
||||
|
||||
void Instant::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_epoch_nanoseconds);
|
||||
}
|
||||
|
||||
// nsMaxInstant = 10**8 × nsPerDay = 8.64 × 10**21
|
||||
Crypto::SignedBigInteger const NANOSECONDS_MAX_INSTANT = "8640000000000000000000"_sbigint;
|
||||
|
||||
|
@ -47,4 +72,96 @@ bool is_valid_epoch_nanoseconds(Crypto::SignedBigInteger const& epoch_nanosecond
|
|||
return true;
|
||||
}
|
||||
|
||||
// 8.5.2 CreateTemporalInstant ( epochNanoseconds [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidepochnanoseconds
|
||||
ThrowCompletionOr<GC::Ref<Instant>> create_temporal_instant(VM& vm, BigInt const& epoch_nanoseconds, GC::Ptr<FunctionObject> new_target)
|
||||
{
|
||||
auto& realm = *vm.current_realm();
|
||||
|
||||
// 1. Assert: IsValidEpochNanoseconds(epochNanoseconds) is true.
|
||||
VERIFY(is_valid_epoch_nanoseconds(epoch_nanoseconds.big_integer()));
|
||||
|
||||
// 2. If newTarget is not present, set newTarget to %Temporal.Instant%.
|
||||
if (!new_target)
|
||||
new_target = realm.intrinsics().temporal_instant_constructor();
|
||||
|
||||
// 3. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.Instant.prototype%", « [[InitializedTemporalInstant]], [[EpochNanoseconds]] »).
|
||||
// 4. Set object.[[EpochNanoseconds]] to epochNanoseconds.
|
||||
auto object = TRY(ordinary_create_from_constructor<Instant>(vm, *new_target, &Intrinsics::temporal_instant_prototype, epoch_nanoseconds));
|
||||
|
||||
// 5. Return object.
|
||||
return object;
|
||||
}
|
||||
|
||||
// 8.5.3 ToTemporalInstant ( item ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalinstant
|
||||
ThrowCompletionOr<GC::Ref<Instant>> to_temporal_instant(VM& vm, Value item)
|
||||
{
|
||||
// 1. If item is an Object, then
|
||||
if (item.is_object()) {
|
||||
auto const& object = item.as_object();
|
||||
|
||||
// a. If item has an [[InitializedTemporalInstant]] or [[InitializedTemporalZonedDateTime]] internal slot, then
|
||||
// FIXME: Handle ZonedDateTime.
|
||||
if (is<Instant>(object)) {
|
||||
// i. Return ! CreateTemporalInstant(item.[[EpochNanoseconds]]).
|
||||
return MUST(create_temporal_instant(vm, static_cast<Instant const&>(object).epoch_nanoseconds()));
|
||||
}
|
||||
|
||||
// b. NOTE: This use of ToPrimitive allows Instant-like objects to be converted.
|
||||
// c. Set item to ? ToPrimitive(item, STRING).
|
||||
item = TRY(item.to_primitive(vm, Value::PreferredType::String));
|
||||
}
|
||||
|
||||
// 2. If item is not a String, throw a TypeError exception.
|
||||
if (!item.is_string())
|
||||
return vm.throw_completion<TypeError>(ErrorType::TemporalInvalidInstantString, item);
|
||||
|
||||
// 3. Let parsed be ? ParseISODateTime(item, « TemporalInstantString »).
|
||||
auto parsed = TRY(parse_iso_date_time(vm, item.as_string().utf8_string_view(), { { Production::TemporalInstantString } }));
|
||||
|
||||
// 4. Assert: Either parsed.[[TimeZone]].[[OffsetString]] is not empty or parsed.[[TimeZone]].[[Z]] is true, but not both.
|
||||
auto const& offset_string = parsed.time_zone.offset_string;
|
||||
auto z_designator = parsed.time_zone.z_designator;
|
||||
|
||||
VERIFY(offset_string.has_value() || z_designator);
|
||||
VERIFY(!offset_string.has_value() || !z_designator);
|
||||
|
||||
// 5. If parsed.[[TimeZone]].[[Z]] is true, let offsetNanoseconds be 0; otherwise, let offsetNanoseconds be
|
||||
// ! ParseDateTimeUTCOffset(parsed.[[TimeZone]].[[OffsetString]]).
|
||||
auto offset_nanoseconds = z_designator ? 0.0 : parse_date_time_utc_offset(*offset_string);
|
||||
|
||||
// 6. If parsed.[[Time]] is START-OF-DAY, let time be MidnightTimeRecord(); else let time be parsed.[[Time]].
|
||||
auto time = parsed.time.has<ParsedISODateTime::StartOfDay>() ? midnight_time_record() : parsed.time.get<Time>();
|
||||
|
||||
// 7. Let balanced be BalanceISODateTime(parsed.[[Year]], parsed.[[Month]], parsed.[[Day]], time.[[Hour]], time.[[Minute]], time.[[Second]], time.[[Millisecond]], time.[[Microsecond]], time.[[Nanosecond]] - offsetNanoseconds).
|
||||
auto balanced = balance_iso_date_time(*parsed.year, parsed.month, parsed.day, time.hour, time.minute, time.second, time.millisecond, time.microsecond, time.nanosecond - offset_nanoseconds);
|
||||
|
||||
// 8. Perform ? CheckISODaysRange(balanced.[[ISODate]]).
|
||||
TRY(check_iso_days_range(vm, balanced.iso_date));
|
||||
|
||||
// 9. Let epochNanoseconds be GetUTCEpochNanoseconds(balanced).
|
||||
auto epoch_nanoseconds = get_utc_epoch_nanoseconds(balanced);
|
||||
|
||||
// 10. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
|
||||
if (!is_valid_epoch_nanoseconds(epoch_nanoseconds))
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidEpochNanoseconds);
|
||||
|
||||
// 11. Return ! CreateTemporalInstant(epochNanoseconds).
|
||||
return MUST(create_temporal_instant(vm, BigInt::create(vm, move(epoch_nanoseconds))));
|
||||
}
|
||||
|
||||
// 8.5.4 CompareEpochNanoseconds ( epochNanosecondsOne, epochNanosecondsTwo ), https://tc39.es/proposal-temporal/#sec-temporal-compareepochnanoseconds
|
||||
i8 compare_epoch_nanoseconds(Crypto::SignedBigInteger const& epoch_nanoseconds_one, Crypto::SignedBigInteger const& epoch_nanoseconds_two)
|
||||
{
|
||||
// 1. If epochNanosecondsOne > epochNanosecondsTwo, return 1.
|
||||
if (epoch_nanoseconds_one > epoch_nanoseconds_two)
|
||||
return 1;
|
||||
|
||||
// 2. If epochNanosecondsOne < epochNanosecondsTwo, return -1.
|
||||
if (epoch_nanoseconds_one < epoch_nanoseconds_two)
|
||||
return -1;
|
||||
|
||||
// 3. Return 0.
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,9 +10,28 @@
|
|||
|
||||
#include <LibCrypto/BigInt/SignedBigInteger.h>
|
||||
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
|
||||
#include <LibJS/Runtime/BigInt.h>
|
||||
#include <LibJS/Runtime/Object.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
class Instant final : public Object {
|
||||
JS_OBJECT(Instant, Object);
|
||||
GC_DECLARE_ALLOCATOR(Instant);
|
||||
|
||||
public:
|
||||
virtual ~Instant() override = default;
|
||||
|
||||
[[nodiscard]] GC::Ref<BigInt const> epoch_nanoseconds() const { return m_epoch_nanoseconds; }
|
||||
|
||||
private:
|
||||
Instant(BigInt const& epoch_nanoseconds, Object& prototype);
|
||||
|
||||
virtual void visit_edges(Visitor&) override;
|
||||
|
||||
GC::Ref<BigInt const> m_epoch_nanoseconds; // [[EpochNanoseconds]]
|
||||
};
|
||||
|
||||
// https://tc39.es/proposal-temporal/#eqn-nsMaxInstant
|
||||
extern Crypto::SignedBigInteger const NANOSECONDS_MAX_INSTANT;
|
||||
|
||||
|
@ -37,5 +56,8 @@ extern Crypto::UnsignedBigInteger const MINUTES_PER_HOUR;
|
|||
extern Crypto::UnsignedBigInteger const HOURS_PER_DAY;
|
||||
|
||||
bool is_valid_epoch_nanoseconds(Crypto::SignedBigInteger const& epoch_nanoseconds);
|
||||
ThrowCompletionOr<GC::Ref<Instant>> create_temporal_instant(VM&, BigInt const& epoch_nanoseconds, GC::Ptr<FunctionObject> new_target = {});
|
||||
ThrowCompletionOr<GC::Ref<Instant>> to_temporal_instant(VM&, Value item);
|
||||
i8 compare_epoch_nanoseconds(Crypto::SignedBigInteger const& epoch_nanoseconds_one, Crypto::SignedBigInteger const& epoch_nanoseconds_two);
|
||||
|
||||
}
|
||||
|
|
122
Libraries/LibJS/Runtime/Temporal/InstantConstructor.cpp
Normal file
122
Libraries/LibJS/Runtime/Temporal/InstantConstructor.cpp
Normal file
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/Realm.h>
|
||||
#include <LibJS/Runtime/Temporal/Instant.h>
|
||||
#include <LibJS/Runtime/Temporal/InstantConstructor.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibJS/Runtime/ValueInlines.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(InstantConstructor);
|
||||
|
||||
// 8.1 The Temporal.Instant Constructor, https://tc39.es/proposal-temporal/#sec-temporal-instant-constructor
|
||||
InstantConstructor::InstantConstructor(Realm& realm)
|
||||
: NativeFunction(realm.vm().names.Instant.as_string(), realm.intrinsics().function_prototype())
|
||||
{
|
||||
}
|
||||
|
||||
void InstantConstructor::initialize(Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 8.2.1 Temporal.Instant.prototype, https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype
|
||||
define_direct_property(vm.names.prototype, realm.intrinsics().temporal_instant_prototype(), 0);
|
||||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(realm, vm.names.from, from, 1, attr);
|
||||
define_native_function(realm, vm.names.fromEpochMilliseconds, from_epoch_milliseconds, 1, attr);
|
||||
define_native_function(realm, vm.names.fromEpochNanoseconds, from_epoch_nanoseconds, 1, attr);
|
||||
define_native_function(realm, vm.names.compare, compare, 2, attr);
|
||||
|
||||
define_direct_property(vm.names.length, Value(1), Attribute::Configurable);
|
||||
}
|
||||
|
||||
// 8.1.1 Temporal.Instant ( epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal.instant
|
||||
ThrowCompletionOr<Value> InstantConstructor::call()
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 1. If NewTarget is undefined, then
|
||||
// a. Throw a TypeError exception.
|
||||
return vm.throw_completion<TypeError>(ErrorType::ConstructorWithoutNew, "Temporal.Instant");
|
||||
}
|
||||
|
||||
// 8.1.1 Temporal.Instant ( epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal.instant
|
||||
ThrowCompletionOr<GC::Ref<Object>> InstantConstructor::construct(FunctionObject& new_target)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 2. Let epochNanoseconds be ? ToBigInt(epochNanoseconds).
|
||||
auto epoch_nanoseconds = TRY(vm.argument(0).to_bigint(vm));
|
||||
|
||||
// 3. If ! IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
|
||||
if (!is_valid_epoch_nanoseconds(epoch_nanoseconds->big_integer()))
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidEpochNanoseconds);
|
||||
|
||||
// 4. Return ? CreateTemporalInstant(epochNanoseconds, NewTarget).
|
||||
return TRY(create_temporal_instant(vm, epoch_nanoseconds, &new_target));
|
||||
}
|
||||
|
||||
// 8.2.2 Temporal.Instant.from ( item ), https://tc39.es/proposal-temporal/#sec-temporal.instant.from
|
||||
JS_DEFINE_NATIVE_FUNCTION(InstantConstructor::from)
|
||||
{
|
||||
// 1. Return ? ToTemporalInstant(item).
|
||||
return TRY(to_temporal_instant(vm, vm.argument(0)));
|
||||
}
|
||||
|
||||
// 8.2.4 Temporal.Instant.fromEpochMilliseconds ( epochMilliseconds ), https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochmilliseconds
|
||||
JS_DEFINE_NATIVE_FUNCTION(InstantConstructor::from_epoch_milliseconds)
|
||||
{
|
||||
// 1. Set epochMilliseconds to ? ToNumber(epochMilliseconds).
|
||||
auto epoch_milliseconds_value = TRY(vm.argument(0).to_number(vm));
|
||||
|
||||
// 2. Set epochMilliseconds to ? NumberToBigInt(epochMilliseconds).
|
||||
auto epoch_milliseconds = TRY(number_to_bigint(vm, epoch_milliseconds_value));
|
||||
|
||||
// 3. Let epochNanoseconds be epochMilliseconds × ℤ(10**6).
|
||||
auto epoch_nanoseconds = epoch_milliseconds->big_integer().multiplied_by(NANOSECONDS_PER_MILLISECOND);
|
||||
|
||||
// 4. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
|
||||
if (!is_valid_epoch_nanoseconds(epoch_nanoseconds))
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidEpochNanoseconds);
|
||||
|
||||
// 5. Return ! CreateTemporalInstant(epochNanoseconds).
|
||||
return MUST(create_temporal_instant(vm, BigInt::create(vm, move(epoch_nanoseconds))));
|
||||
}
|
||||
|
||||
// 8.2.6 Temporal.Instant.fromEpochNanoseconds ( epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal.instant.fromepochnanoseconds
|
||||
JS_DEFINE_NATIVE_FUNCTION(InstantConstructor::from_epoch_nanoseconds)
|
||||
{
|
||||
// 1. Set epochNanoseconds to ? ToBigInt(epochNanoseconds).
|
||||
auto epoch_nanoseconds = TRY(vm.argument(0).to_bigint(vm));
|
||||
|
||||
// 2. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
|
||||
if (!is_valid_epoch_nanoseconds(epoch_nanoseconds->big_integer()))
|
||||
return vm.throw_completion<RangeError>(ErrorType::TemporalInvalidEpochNanoseconds);
|
||||
|
||||
// 3. Return ! CreateTemporalInstant(epochNanoseconds).
|
||||
return MUST(create_temporal_instant(vm, epoch_nanoseconds));
|
||||
}
|
||||
|
||||
// 8.2.7 Temporal.Instant.compare ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal.instant.compare
|
||||
JS_DEFINE_NATIVE_FUNCTION(InstantConstructor::compare)
|
||||
{
|
||||
// 1. Set one to ? ToTemporalInstant(one).
|
||||
auto one = TRY(to_temporal_instant(vm, vm.argument(0)));
|
||||
|
||||
// 2. Set two to ? ToTemporalInstant(two).
|
||||
auto two = TRY(to_temporal_instant(vm, vm.argument(1)));
|
||||
|
||||
// 3. Return 𝔽(CompareEpochNanoseconds(one.[[EpochNanoseconds]], two.[[EpochNanoseconds]])).
|
||||
return compare_epoch_nanoseconds(one->epoch_nanoseconds()->big_integer(), two->epoch_nanoseconds()->big_integer());
|
||||
}
|
||||
|
||||
}
|
36
Libraries/LibJS/Runtime/Temporal/InstantConstructor.h
Normal file
36
Libraries/LibJS/Runtime/Temporal/InstantConstructor.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2022, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/NativeFunction.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
class InstantConstructor final : public NativeFunction {
|
||||
JS_OBJECT(InstantConstructor, NativeFunction);
|
||||
GC_DECLARE_ALLOCATOR(InstantConstructor);
|
||||
|
||||
public:
|
||||
virtual void initialize(Realm&) override;
|
||||
virtual ~InstantConstructor() override = default;
|
||||
|
||||
virtual ThrowCompletionOr<Value> call() override;
|
||||
virtual ThrowCompletionOr<GC::Ref<Object>> construct(FunctionObject& new_target) override;
|
||||
|
||||
private:
|
||||
explicit InstantConstructor(Realm&);
|
||||
|
||||
virtual bool has_constructor() const override { return true; }
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(from);
|
||||
JS_DECLARE_NATIVE_FUNCTION(from_epoch_milliseconds);
|
||||
JS_DECLARE_NATIVE_FUNCTION(from_epoch_nanoseconds);
|
||||
JS_DECLARE_NATIVE_FUNCTION(compare);
|
||||
};
|
||||
|
||||
}
|
72
Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp
Normal file
72
Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (c) 2021-2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibJS/Runtime/Temporal/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Temporal/InstantPrototype.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
GC_DEFINE_ALLOCATOR(InstantPrototype);
|
||||
|
||||
// 8.3 Properties of the Temporal.Instant Prototype Object, https://tc39.es/proposal-temporal/#sec-properties-of-the-temporal-instant-prototype-object
|
||||
InstantPrototype::InstantPrototype(Realm& realm)
|
||||
: PrototypeObject(realm.intrinsics().object_prototype())
|
||||
{
|
||||
}
|
||||
|
||||
void InstantPrototype::initialize(Realm& realm)
|
||||
{
|
||||
Base::initialize(realm);
|
||||
|
||||
auto& vm = this->vm();
|
||||
|
||||
// 8.3.2 Temporal.Instant.prototype[ %Symbol.toStringTag% ], https://tc39.es/proposal-temporal/#sec-properties-of-the-temporal-instant-prototype-object
|
||||
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Temporal.Instant"_string), Attribute::Configurable);
|
||||
|
||||
define_native_accessor(realm, vm.names.epochMilliseconds, epoch_milliseconds_getter, {}, Attribute::Configurable);
|
||||
define_native_accessor(realm, vm.names.epochNanoseconds, epoch_nanoseconds_getter, {}, Attribute::Configurable);
|
||||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_native_function(realm, vm.names.valueOf, value_of, 0, attr);
|
||||
}
|
||||
|
||||
// 8.3.3 get Temporal.Instant.prototype.epochMilliseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochmilliseconds
|
||||
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::epoch_milliseconds_getter)
|
||||
{
|
||||
// 1. Let instant be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
|
||||
auto instant = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Let ns be instant.[[EpochNanoseconds]].
|
||||
auto nanoseconds = instant->epoch_nanoseconds();
|
||||
|
||||
// 4. Let ms be floor(ℝ(ns) / 10**6).
|
||||
auto milliseconds = big_floor(nanoseconds->big_integer(), NANOSECONDS_PER_MILLISECOND);
|
||||
|
||||
// 5. Return 𝔽(ms).
|
||||
return milliseconds.to_double();
|
||||
}
|
||||
|
||||
// 8.3.4 get Temporal.Instant.prototype.epochNanoseconds, https://tc39.es/proposal-temporal/#sec-get-temporal.instant.prototype.epochnanoseconds
|
||||
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::epoch_nanoseconds_getter)
|
||||
{
|
||||
// 1. Let instant be the this value.
|
||||
// 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]).
|
||||
auto instant = TRY(typed_this_object(vm));
|
||||
|
||||
// 3. Return instant.[[EpochNanoseconds]].
|
||||
return instant->epoch_nanoseconds();
|
||||
}
|
||||
|
||||
// 8.3.14 Temporal.Instant.prototype.valueOf ( ), https://tc39.es/proposal-temporal/#sec-temporal.instant.prototype.valueof
|
||||
JS_DEFINE_NATIVE_FUNCTION(InstantPrototype::value_of)
|
||||
{
|
||||
// 1. Throw a TypeError exception.
|
||||
return vm.throw_completion<TypeError>(ErrorType::Convert, "Temporal.Instant", "a primitive value");
|
||||
}
|
||||
|
||||
}
|
31
Libraries/LibJS/Runtime/Temporal/InstantPrototype.h
Normal file
31
Libraries/LibJS/Runtime/Temporal/InstantPrototype.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024, Tim Flynn <trflynn89@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibJS/Runtime/PrototypeObject.h>
|
||||
#include <LibJS/Runtime/Temporal/Instant.h>
|
||||
|
||||
namespace JS::Temporal {
|
||||
|
||||
class InstantPrototype final : public PrototypeObject<InstantPrototype, Instant> {
|
||||
JS_PROTOTYPE_OBJECT(InstantPrototype, Instant, Temporal.Instant);
|
||||
GC_DECLARE_ALLOCATOR(InstantPrototype);
|
||||
|
||||
public:
|
||||
virtual void initialize(Realm&) override;
|
||||
virtual ~InstantPrototype() override = default;
|
||||
|
||||
private:
|
||||
explicit InstantPrototype(Realm&);
|
||||
|
||||
JS_DECLARE_NATIVE_FUNCTION(epoch_milliseconds_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(epoch_nanoseconds_getter);
|
||||
JS_DECLARE_NATIVE_FUNCTION(value_of);
|
||||
};
|
||||
|
||||
}
|
|
@ -26,20 +26,6 @@ PlainTime::PlainTime(Time const& time, Object& prototype)
|
|||
{
|
||||
}
|
||||
|
||||
// FIXME: We should add a generic floor() method to our BigInt classes. But for now, since we know we are only dividing
|
||||
// by powers of 10, we can implement a very situationally specific method to compute the floor of a division.
|
||||
static TimeDuration big_floor(TimeDuration const& numerator, Crypto::UnsignedBigInteger const& denominator)
|
||||
{
|
||||
auto result = numerator.divided_by(denominator);
|
||||
|
||||
if (result.remainder.is_zero())
|
||||
return result.quotient;
|
||||
if (!result.quotient.is_negative() && result.remainder.is_positive())
|
||||
return result.quotient;
|
||||
|
||||
return result.quotient.minus(TimeDuration { 1 });
|
||||
}
|
||||
|
||||
// 4.5.2 CreateTimeRecord ( hour, minute, second, millisecond, microsecond, nanosecond [ , deltaDays ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtimerecord
|
||||
Time create_time_record(double hour, double minute, double second, double millisecond, double microsecond, double nanosecond, double delta_days)
|
||||
{
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/Temporal/DurationConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/InstantConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainDateTimeConstructor.h>
|
||||
#include <LibJS/Runtime/Temporal/PlainMonthDayConstructor.h>
|
||||
|
@ -35,6 +36,7 @@ void Temporal::initialize(Realm& realm)
|
|||
|
||||
u8 attr = Attribute::Writable | Attribute::Configurable;
|
||||
define_intrinsic_accessor(vm.names.Duration, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_duration_constructor(); });
|
||||
define_intrinsic_accessor(vm.names.Instant, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_instant_constructor(); });
|
||||
define_intrinsic_accessor(vm.names.PlainDate, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_date_constructor(); });
|
||||
define_intrinsic_accessor(vm.names.PlainDateTime, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_date_time_constructor(); });
|
||||
define_intrinsic_accessor(vm.names.PlainMonthDay, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_month_day_constructor(); });
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 2", () => {
|
||||
expect(Temporal.Instant.compare).toHaveLength(2);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const instant1 = new Temporal.Instant(111n);
|
||||
expect(Temporal.Instant.compare(instant1, instant1)).toBe(0);
|
||||
const instant2 = new Temporal.Instant(999n);
|
||||
expect(Temporal.Instant.compare(instant1, instant2)).toBe(-1);
|
||||
expect(Temporal.Instant.compare(instant2, instant1)).toBe(1);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,64 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.Instant.from).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("Instant instance argument", () => {
|
||||
const instant = new Temporal.Instant(123n);
|
||||
expect(Temporal.Instant.from(instant).epochNanoseconds).toBe(123n);
|
||||
});
|
||||
|
||||
test("Instant string argument", () => {
|
||||
expect(Temporal.Instant.from("1975-02-02T14:25:36.123456789Z").epochNanoseconds).toBe(
|
||||
160583136123456789n
|
||||
);
|
||||
// Time zone is not validated
|
||||
expect(
|
||||
Temporal.Instant.from("1975-02-02T14:25:36.123456789Z[Custom/TimeZone]")
|
||||
.epochNanoseconds
|
||||
).toBe(160583136123456789n);
|
||||
|
||||
// Accepts but ignores the calendar.
|
||||
let result = null;
|
||||
expect(() => {
|
||||
result = Temporal.Instant.from("1970-01-01T00:00Z[u-ca=UTC]");
|
||||
}).not.toThrow();
|
||||
expect(result).toBeInstanceOf(Temporal.Instant);
|
||||
expect(result.epochNanoseconds).toBe(0n);
|
||||
|
||||
// Does not validate calendar name, it only checks that the calendar name matches the grammar.
|
||||
result = null;
|
||||
expect(() => {
|
||||
result = Temporal.Instant.from("1970-01-01T00:00Z[u-ca=aAaAaAaA-bBbBbBb]");
|
||||
}).not.toThrow();
|
||||
expect(result).toBeInstanceOf(Temporal.Instant);
|
||||
expect(result.epochNanoseconds).toBe(0n);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("invalid instant string", () => {
|
||||
expect(() => {
|
||||
Temporal.Instant.from("foo");
|
||||
}).toThrowWithMessage(RangeError, "Invalid ISO date time");
|
||||
});
|
||||
|
||||
test("invalid epoch nanoseconds", () => {
|
||||
// Test cases from https://github.com/tc39/proposal-temporal/commit/baead4d85bc3e9ecab1e9824c3d3fe4fdd77fc3a
|
||||
expect(() => {
|
||||
Temporal.Instant.from("-271821-04-20T00:00:00+00:01");
|
||||
}).toThrowWithMessage(RangeError, "Invalid ISO date");
|
||||
expect(() => {
|
||||
Temporal.Instant.from("+275760-09-13T00:00:00-00:01");
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"Invalid epoch nanoseconds value, must be in range -86400 * 10^17 to 86400 * 10^17"
|
||||
);
|
||||
});
|
||||
|
||||
test("annotations must match annotation grammar even though they're ignored", () => {
|
||||
expect(() => {
|
||||
Temporal.Instant.from("1970-01-01T00:00Z[SerenityOS=cool]");
|
||||
}).toThrowWithMessage(RangeError, "Invalid ISO date time");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,52 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.Instant.fromEpochMilliseconds).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
expect(Temporal.Instant.fromEpochMilliseconds(0).epochMilliseconds).toBe(0);
|
||||
expect(Temporal.Instant.fromEpochMilliseconds(1).epochMilliseconds).toBe(1);
|
||||
expect(Temporal.Instant.fromEpochMilliseconds(999_999_999).epochMilliseconds).toBe(
|
||||
999_999_999
|
||||
);
|
||||
expect(
|
||||
Temporal.Instant.fromEpochMilliseconds(8_640_000_000_000_000).epochMilliseconds
|
||||
).toBe(8_640_000_000_000_000);
|
||||
|
||||
expect(Temporal.Instant.fromEpochMilliseconds(-0).epochMilliseconds).toBe(0);
|
||||
expect(Temporal.Instant.fromEpochMilliseconds(-1).epochMilliseconds).toBe(-1);
|
||||
expect(Temporal.Instant.fromEpochMilliseconds(-999_999_999).epochMilliseconds).toBe(
|
||||
-999_999_999
|
||||
);
|
||||
expect(
|
||||
Temporal.Instant.fromEpochMilliseconds(-8_640_000_000_000_000).epochMilliseconds
|
||||
).toBe(-8_640_000_000_000_000);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("argument must be coercible to BigInt", () => {
|
||||
expect(() => {
|
||||
Temporal.Instant.fromEpochMilliseconds(1.23);
|
||||
}).toThrowWithMessage(RangeError, "Cannot convert non-integral number to BigInt");
|
||||
// NOTE: ToNumber is called on the argument first, so this is effectively NaN.
|
||||
expect(() => {
|
||||
Temporal.Instant.fromEpochMilliseconds("foo");
|
||||
}).toThrowWithMessage(RangeError, "Cannot convert non-integral number to BigInt");
|
||||
});
|
||||
|
||||
test("out-of-range epoch milliseconds value", () => {
|
||||
expect(() => {
|
||||
Temporal.Instant.fromEpochMilliseconds(8_640_000_000_000_001);
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"Invalid epoch nanoseconds value, must be in range -86400 * 10^17 to 86400 * 10^17"
|
||||
);
|
||||
expect(() => {
|
||||
Temporal.Instant.fromEpochMilliseconds(-8_640_000_000_000_001);
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"Invalid epoch nanoseconds value, must be in range -86400 * 10^17 to 86400 * 10^17"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,51 @@
|
|||
describe("correct behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.Instant.fromEpochNanoseconds).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
expect(Temporal.Instant.fromEpochNanoseconds(0n).epochNanoseconds).toBe(0n);
|
||||
expect(Temporal.Instant.fromEpochNanoseconds(1n).epochNanoseconds).toBe(1n);
|
||||
expect(Temporal.Instant.fromEpochNanoseconds(999_999_999n).epochNanoseconds).toBe(
|
||||
999_999_999n
|
||||
);
|
||||
expect(
|
||||
Temporal.Instant.fromEpochNanoseconds(8_640_000_000_000_000_000_000n).epochNanoseconds
|
||||
).toBe(8_640_000_000_000_000_000_000n);
|
||||
|
||||
expect(Temporal.Instant.fromEpochNanoseconds(-0n).epochNanoseconds).toBe(0n);
|
||||
expect(Temporal.Instant.fromEpochNanoseconds(-1n).epochNanoseconds).toBe(-1n);
|
||||
expect(Temporal.Instant.fromEpochNanoseconds(-999_999_999n).epochNanoseconds).toBe(
|
||||
-999_999_999n
|
||||
);
|
||||
expect(
|
||||
Temporal.Instant.fromEpochNanoseconds(-8_640_000_000_000_000_000_000n).epochNanoseconds
|
||||
).toBe(-8_640_000_000_000_000_000_000n);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("argument must be coercible to BigInt", () => {
|
||||
expect(() => {
|
||||
Temporal.Instant.fromEpochNanoseconds(123);
|
||||
}).toThrowWithMessage(TypeError, "Cannot convert number to BigInt");
|
||||
expect(() => {
|
||||
Temporal.Instant.fromEpochNanoseconds("foo");
|
||||
}).toThrowWithMessage(SyntaxError, "Invalid value for BigInt: foo");
|
||||
});
|
||||
|
||||
test("out-of-range epoch nanoseconds value", () => {
|
||||
expect(() => {
|
||||
Temporal.Instant.fromEpochNanoseconds(8_640_000_000_000_000_000_001n);
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"Invalid epoch nanoseconds value, must be in range -86400 * 10^17 to 86400 * 10^17"
|
||||
);
|
||||
expect(() => {
|
||||
Temporal.Instant.fromEpochNanoseconds(-8_640_000_000_000_000_000_001n);
|
||||
}).toThrowWithMessage(
|
||||
RangeError,
|
||||
"Invalid epoch nanoseconds value, must be in range -86400 * 10^17 to 86400 * 10^17"
|
||||
);
|
||||
});
|
||||
});
|
30
Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.js
Normal file
30
Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
describe("errors", () => {
|
||||
test("called without new", () => {
|
||||
expect(() => {
|
||||
Temporal.Instant();
|
||||
}).toThrowWithMessage(TypeError, "Temporal.Instant constructor must be called with 'new'");
|
||||
});
|
||||
|
||||
test("argument must be coercible to bigint", () => {
|
||||
expect(() => {
|
||||
new Temporal.Instant(123);
|
||||
}).toThrowWithMessage(TypeError, "Cannot convert number to BigInt");
|
||||
expect(() => {
|
||||
new Temporal.Instant("foo");
|
||||
}).toThrowWithMessage(SyntaxError, "Invalid value for BigInt: foo");
|
||||
});
|
||||
});
|
||||
|
||||
describe("normal behavior", () => {
|
||||
test("length is 1", () => {
|
||||
expect(Temporal.Instant).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("basic functionality", () => {
|
||||
const instant = new Temporal.Instant(123n);
|
||||
expect(instant.epochNanoseconds).toBe(123n);
|
||||
expect(typeof instant).toBe("object");
|
||||
expect(instant).toBeInstanceOf(Temporal.Instant);
|
||||
expect(Object.getPrototypeOf(instant)).toBe(Temporal.Instant.prototype);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
expect(new Temporal.Instant(0n).epochMilliseconds).toBe(0);
|
||||
expect(new Temporal.Instant(1n).epochMilliseconds).toBe(0);
|
||||
expect(new Temporal.Instant(999_999n).epochMilliseconds).toBe(0);
|
||||
expect(new Temporal.Instant(1_000_000n).epochMilliseconds).toBe(1);
|
||||
expect(new Temporal.Instant(1_500_000n).epochMilliseconds).toBe(1);
|
||||
expect(new Temporal.Instant(1_999_999n).epochMilliseconds).toBe(1);
|
||||
expect(new Temporal.Instant(2_000_000n).epochMilliseconds).toBe(2);
|
||||
expect(new Temporal.Instant(8_640_000_000_000_000_000_000n).epochMilliseconds).toBe(
|
||||
8_640_000_000_000_000
|
||||
);
|
||||
|
||||
expect(new Temporal.Instant(-0n).epochMilliseconds).toBe(0);
|
||||
expect(new Temporal.Instant(-1n).epochMilliseconds).toBe(-1);
|
||||
expect(new Temporal.Instant(-999_999n).epochMilliseconds).toBe(-1);
|
||||
expect(new Temporal.Instant(-1_000_000n).epochMilliseconds).toBe(-1);
|
||||
expect(new Temporal.Instant(-1_500_000n).epochMilliseconds).toBe(-2);
|
||||
expect(new Temporal.Instant(-1_999_999n).epochMilliseconds).toBe(-2);
|
||||
expect(new Temporal.Instant(-2_000_000n).epochMilliseconds).toBe(-2);
|
||||
expect(new Temporal.Instant(-8_640_000_000_000_000_000_000n).epochMilliseconds).toBe(
|
||||
-8_640_000_000_000_000
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.Instant object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.Instant.prototype, "epochMilliseconds", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Instant");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,25 @@
|
|||
describe("correct behavior", () => {
|
||||
test("basic functionality", () => {
|
||||
expect(new Temporal.Instant(0n).epochNanoseconds).toBe(0n);
|
||||
expect(new Temporal.Instant(1n).epochNanoseconds).toBe(1n);
|
||||
expect(new Temporal.Instant(999n).epochNanoseconds).toBe(999n);
|
||||
expect(new Temporal.Instant(8_640_000_000_000_000_000_000n).epochNanoseconds).toBe(
|
||||
8_640_000_000_000_000_000_000n
|
||||
);
|
||||
|
||||
expect(new Temporal.Instant(-0n).epochNanoseconds).toBe(-0n);
|
||||
expect(new Temporal.Instant(-1n).epochNanoseconds).toBe(-1n);
|
||||
expect(new Temporal.Instant(-999n).epochNanoseconds).toBe(-999n);
|
||||
expect(new Temporal.Instant(-8_640_000_000_000_000_000_000n).epochNanoseconds).toBe(
|
||||
-8_640_000_000_000_000_000_000n
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("errors", () => {
|
||||
test("this value must be a Temporal.Instant object", () => {
|
||||
expect(() => {
|
||||
Reflect.get(Temporal.Instant.prototype, "epochNanoseconds", "foo");
|
||||
}).toThrowWithMessage(TypeError, "Not an object of type Temporal.Instant");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
describe("errors", () => {
|
||||
test("throws TypeError", () => {
|
||||
expect(() => {
|
||||
new Temporal.Instant(0n).valueOf();
|
||||
}).toThrowWithMessage(TypeError, "Cannot convert Temporal.Instant to a primitive value");
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue