Переглянути джерело

LibJS: Implement the Temporal.Instant constructor

And the simple Temporal.Instant.prototype getters, so that the
constructed Temporal.Instant may actually be validated.
Timothy Flynn 8 місяців тому
батько
коміт
90820873a2
22 змінених файлів з 710 додано та 14 видалено
  1. 2 0
      Libraries/LibJS/CMakeLists.txt
  2. 1 0
      Libraries/LibJS/Forward.h
  3. 11 0
      Libraries/LibJS/Print.cpp
  4. 2 0
      Libraries/LibJS/Runtime/Intrinsics.cpp
  5. 15 0
      Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp
  6. 2 0
      Libraries/LibJS/Runtime/Temporal/AbstractOperations.h
  7. 117 0
      Libraries/LibJS/Runtime/Temporal/Instant.cpp
  8. 22 0
      Libraries/LibJS/Runtime/Temporal/Instant.h
  9. 122 0
      Libraries/LibJS/Runtime/Temporal/InstantConstructor.cpp
  10. 36 0
      Libraries/LibJS/Runtime/Temporal/InstantConstructor.h
  11. 72 0
      Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp
  12. 31 0
      Libraries/LibJS/Runtime/Temporal/InstantPrototype.h
  13. 0 14
      Libraries/LibJS/Runtime/Temporal/PlainTime.cpp
  14. 2 0
      Libraries/LibJS/Runtime/Temporal/Temporal.cpp
  15. 13 0
      Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.compare.js
  16. 64 0
      Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.from.js
  17. 52 0
      Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.fromEpochMilliseconds.js
  18. 51 0
      Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.fromEpochNanoseconds.js
  19. 30 0
      Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.js
  20. 33 0
      Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.epochMilliseconds.js
  21. 25 0
      Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.epochNanoseconds.js
  22. 7 0
      Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.valueOf.js

+ 2 - 0
Libraries/LibJS/CMakeLists.txt

@@ -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

+ 1 - 0
Libraries/LibJS/Forward.h

@@ -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) \

+ 11 - 0
Libraries/LibJS/Print.cpp

@@ -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))

+ 2 - 0
Libraries/LibJS/Runtime/Intrinsics.cpp

@@ -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>

+ 15 - 0
Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp

@@ -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 });
+}
+
 }

+ 2 - 0
Libraries/LibJS/Runtime/Temporal/AbstractOperations.h

@@ -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);
+
 }

+ 117 - 0
Libraries/LibJS/Runtime/Temporal/Instant.cpp

@@ -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;
+}
+
 }

+ 22 - 0
Libraries/LibJS/Runtime/Temporal/Instant.h

@@ -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 - 0
Libraries/LibJS/Runtime/Temporal/InstantConstructor.cpp

@@ -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 - 0
Libraries/LibJS/Runtime/Temporal/InstantConstructor.h

@@ -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 - 0
Libraries/LibJS/Runtime/Temporal/InstantPrototype.cpp

@@ -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 - 0
Libraries/LibJS/Runtime/Temporal/InstantPrototype.h

@@ -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);
+};
+
+}

+ 0 - 14
Libraries/LibJS/Runtime/Temporal/PlainTime.cpp

@@ -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)
 {

+ 2 - 0
Libraries/LibJS/Runtime/Temporal/Temporal.cpp

@@ -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(); });

+ 13 - 0
Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.compare.js

@@ -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);
+    });
+});

+ 64 - 0
Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.from.js

@@ -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");
+    });
+});

+ 52 - 0
Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.fromEpochMilliseconds.js

@@ -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"
+        );
+    });
+});

+ 51 - 0
Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.fromEpochNanoseconds.js

@@ -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 - 0
Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.js

@@ -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);
+    });
+});

+ 33 - 0
Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.epochMilliseconds.js

@@ -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");
+    });
+});

+ 25 - 0
Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.epochNanoseconds.js

@@ -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");
+    });
+});

+ 7 - 0
Libraries/LibJS/Tests/builtins/Temporal/Instant/Instant.prototype.valueOf.js

@@ -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");
+    });
+});