瀏覽代碼

LibJS: Implement Temporal.Now

Timothy Flynn 7 月之前
父節點
當前提交
f2c19f96f8

+ 1 - 0
Libraries/LibJS/CMakeLists.txt

@@ -214,6 +214,7 @@ set(SOURCES
     Runtime/Temporal/InstantConstructor.cpp
     Runtime/Temporal/InstantPrototype.cpp
     Runtime/Temporal/ISO8601.cpp
+    Runtime/Temporal/Now.cpp
     Runtime/Temporal/PlainDate.cpp
     Runtime/Temporal/PlainDateConstructor.cpp
     Runtime/Temporal/PlainDatePrototype.cpp

+ 1 - 0
Libraries/LibJS/Forward.h

@@ -282,6 +282,7 @@ namespace Temporal {
 JS_ENUMERATE_TEMPORAL_OBJECTS
 #undef __JS_ENUMERATE
 
+class Now;
 class Temporal;
 
 struct CalendarDate;

+ 1 - 0
Libraries/LibJS/Runtime/CommonPropertyNames.h

@@ -538,6 +538,7 @@ namespace JS {
     P(timeLog)                               \
     P(timeStyle)                             \
     P(timeZone)                              \
+    P(timeZoneId)                            \
     P(timeZoneName)                          \
     P(toArray)                               \
     P(toBase64)                              \

+ 132 - 0
Libraries/LibJS/Runtime/Temporal/Now.cpp

@@ -0,0 +1,132 @@
+/*
+ * 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/Date.h>
+#include <LibJS/Runtime/Temporal/Instant.h>
+#include <LibJS/Runtime/Temporal/Now.h>
+#include <LibJS/Runtime/Temporal/PlainDate.h>
+#include <LibJS/Runtime/Temporal/PlainDateTime.h>
+#include <LibJS/Runtime/Temporal/PlainTime.h>
+#include <LibJS/Runtime/Temporal/TimeZone.h>
+
+namespace JS::Temporal {
+
+GC_DEFINE_ALLOCATOR(Now);
+
+// 2 The Temporal.Now Object, https://tc39.es/proposal-temporal/#sec-temporal-now-object
+Now::Now(Realm& realm)
+    : Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().object_prototype())
+{
+}
+
+void Now::initialize(Realm& realm)
+{
+    Base::initialize(realm);
+
+    auto& vm = this->vm();
+
+    // 2.1.1 Temporal.Now [ %Symbol.toStringTag% ], https://tc39.es/proposal-temporal/#sec-temporal-now-%symbol.tostringtag%
+    define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Temporal.Now"_string), Attribute::Configurable);
+
+    u8 attr = Attribute::Writable | Attribute::Configurable;
+    define_native_function(realm, vm.names.timeZoneId, time_zone_id, 0, attr);
+    define_native_function(realm, vm.names.instant, instant, 0, attr);
+    define_native_function(realm, vm.names.plainDateTimeISO, plain_date_time_iso, 0, attr);
+    define_native_function(realm, vm.names.plainDateISO, plain_date_iso, 0, attr);
+    define_native_function(realm, vm.names.plainTimeISO, plain_time_iso, 0, attr);
+}
+
+// 2.2.1 Temporal.Now.timeZoneId ( ), https://tc39.es/proposal-temporal/#sec-temporal.now.timezoneid
+JS_DEFINE_NATIVE_FUNCTION(Now::time_zone_id)
+{
+    // 1. Return SystemTimeZoneIdentifier().
+    return PrimitiveString::create(vm, system_time_zone_identifier());
+}
+
+// 2.2.2 Temporal.Now.instant ( ), https://tc39.es/proposal-temporal/#sec-temporal.now.instant
+JS_DEFINE_NATIVE_FUNCTION(Now::instant)
+{
+    // 1. Let ns be SystemUTCEpochNanoseconds().
+    auto nanoseconds = system_utc_epoch_nanoseconds(vm);
+
+    // 2. Return ! CreateTemporalInstant(ns).
+    return MUST(create_temporal_instant(vm, BigInt::create(vm, move(nanoseconds))));
+}
+
+// 2.2.3 Temporal.Now.plainDateTimeISO ( [ temporalTimeZoneLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.now.plaindatetimeiso
+JS_DEFINE_NATIVE_FUNCTION(Now::plain_date_time_iso)
+{
+    auto temporal_time_zone_like = vm.argument(0);
+
+    // 1. Let isoDateTime be ? SystemDateTime(temporalTimeZoneLike).
+    auto iso_date_time = TRY(system_date_time(vm, temporal_time_zone_like));
+
+    // 2. Return ! CreateTemporalDateTime(isoDateTime, "iso8601").
+    return MUST(create_temporal_date_time(vm, iso_date_time, "iso8601"_string));
+}
+
+// 2.2.5 Temporal.Now.plainDateISO ( [ temporalTimeZoneLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.now.plaindateiso
+JS_DEFINE_NATIVE_FUNCTION(Now::plain_date_iso)
+{
+    auto temporal_time_zone_like = vm.argument(0);
+
+    // 1. Let isoDateTime be ? SystemDateTime(temporalTimeZoneLike).
+    auto iso_date_time = TRY(system_date_time(vm, temporal_time_zone_like));
+
+    // 2. Return ! CreateTemporalDate(isoDateTime.[[ISODate]], "iso8601").
+    return MUST(create_temporal_date(vm, iso_date_time.iso_date, "iso8601"_string));
+}
+
+// 2.2.6 Temporal.Now.plainTimeISO ( [ temporalTimeZoneLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.now.plaintimeiso
+JS_DEFINE_NATIVE_FUNCTION(Now::plain_time_iso)
+{
+    auto temporal_time_zone_like = vm.argument(0);
+
+    // 1. Let isoDateTime be ? SystemDateTime(temporalTimeZoneLike).
+    auto iso_date_time = TRY(system_date_time(vm, temporal_time_zone_like));
+
+    // 2. Return ! CreateTemporalTime(isoDateTime.[[Time]]).
+    return MUST(create_temporal_time(vm, iso_date_time.time));
+}
+
+// 2.3.3 SystemUTCEpochNanoseconds ( ), https://tc39.es/proposal-temporal/#sec-temporal-systemutcepochnanoseconds
+Crypto::SignedBigInteger system_utc_epoch_nanoseconds(VM& vm)
+{
+    // 1. Let global be GetGlobalObject().
+    auto const& global = vm.get_global_object();
+
+    // 2. Let nowNs be HostSystemUTCEpochNanoseconds(global).
+    auto now_ns = vm.host_system_utc_epoch_nanoseconds(global);
+
+    // 3. Return ℤ(nowNs).
+    return now_ns;
+}
+
+// 2.3.4 SystemDateTime ( temporalTimeZoneLike ), https://tc39.es/proposal-temporal/#sec-temporal-systemdatetime
+ThrowCompletionOr<ISODateTime> system_date_time(VM& vm, Value temporal_time_zone_like)
+{
+    String time_zone;
+
+    // 1. If temporalTimeZoneLike is undefined, then
+    if (temporal_time_zone_like.is_undefined()) {
+        // a. Let timeZone be SystemTimeZoneIdentifier().
+        time_zone = system_time_zone_identifier();
+    }
+    // 2. Else,
+    else {
+        // a. Let timeZone be ? ToTemporalTimeZoneIdentifier(temporalTimeZoneLike).
+        time_zone = TRY(to_temporal_time_zone_identifier(vm, temporal_time_zone_like));
+    }
+
+    // 3. Let epochNs be SystemUTCEpochNanoseconds().
+    auto epoch_nanoseconds = system_utc_epoch_nanoseconds(vm);
+
+    // 4. Return GetISODateTimeFor(timeZone, epochNs).
+    return get_iso_date_time_for(time_zone, epoch_nanoseconds);
+}
+
+}

+ 37 - 0
Libraries/LibJS/Runtime/Temporal/Now.h

@@ -0,0 +1,37 @@
+/*
+ * 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 <LibCrypto/BigInt/SignedBigInteger.h>
+#include <LibJS/Runtime/Completion.h>
+#include <LibJS/Runtime/Object.h>
+
+namespace JS::Temporal {
+
+class Now final : public Object {
+    JS_OBJECT(Now, Object);
+    GC_DECLARE_ALLOCATOR(Now);
+
+public:
+    virtual void initialize(Realm&) override;
+    virtual ~Now() override = default;
+
+private:
+    explicit Now(Realm&);
+
+    JS_DECLARE_NATIVE_FUNCTION(time_zone_id);
+    JS_DECLARE_NATIVE_FUNCTION(instant);
+    JS_DECLARE_NATIVE_FUNCTION(plain_date_time_iso);
+    JS_DECLARE_NATIVE_FUNCTION(plain_date_iso);
+    JS_DECLARE_NATIVE_FUNCTION(plain_time_iso);
+};
+
+Crypto::SignedBigInteger system_utc_epoch_nanoseconds(VM&);
+ThrowCompletionOr<ISODateTime> system_date_time(VM&, Value temporal_time_zone_like);
+
+}

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

@@ -8,6 +8,7 @@
 #include <LibJS/Runtime/GlobalObject.h>
 #include <LibJS/Runtime/Temporal/DurationConstructor.h>
 #include <LibJS/Runtime/Temporal/InstantConstructor.h>
+#include <LibJS/Runtime/Temporal/Now.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)
     define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, "Temporal"_string), Attribute::Configurable);
 
     u8 attr = Attribute::Writable | Attribute::Configurable;
+    define_direct_property(vm.names.Now, realm.create<Now>(realm), attr);
     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(); });

+ 16 - 0
Libraries/LibJS/Runtime/VM.cpp

@@ -12,6 +12,7 @@
 #include <AK/ScopeGuard.h>
 #include <AK/String.h>
 #include <AK/StringBuilder.h>
+#include <AK/Time.h>
 #include <LibFileSystem/FileSystem.h>
 #include <LibJS/AST.h>
 #include <LibJS/Bytecode/Interpreter.h>
@@ -29,6 +30,7 @@
 #include <LibJS/Runtime/PromiseCapability.h>
 #include <LibJS/Runtime/Reference.h>
 #include <LibJS/Runtime/Symbol.h>
+#include <LibJS/Runtime/Temporal/Instant.h>
 #include <LibJS/Runtime/VM.h>
 #include <LibJS/SourceTextModule.h>
 #include <LibJS/SyntheticModule.h>
@@ -181,6 +183,20 @@ VM::VM(OwnPtr<CustomData> custom_data, ErrorMessages error_messages)
         return {};
     };
 
+    // 2.3.1 HostSystemUTCEpochNanoseconds ( global ), https://tc39.es/proposal-temporal/#sec-hostsystemutcepochnanoseconds
+    host_system_utc_epoch_nanoseconds = [](Object const&) {
+        // 1. Let ns be the approximate current UTC date and time, in nanoseconds since the epoch.
+        Crypto::SignedBigInteger nanoseconds { AK::UnixDateTime::now().nanoseconds_since_epoch() };
+
+        // 2. Return the result of clamping ns between nsMinInstant and nsMaxInstant.
+        if (nanoseconds < Temporal::NANOSECONDS_MIN_INSTANT)
+            nanoseconds = Temporal::NANOSECONDS_MIN_INSTANT;
+        if (nanoseconds > Temporal::NANOSECONDS_MAX_INSTANT)
+            nanoseconds = Temporal::NANOSECONDS_MAX_INSTANT;
+
+        return nanoseconds;
+    };
+
     // AD-HOC: Inform the host that we received a date string we were unable to parse.
     host_unrecognized_date_string = [](StringView) {
     };

+ 2 - 0
Libraries/LibJS/Runtime/VM.h

@@ -15,6 +15,7 @@
 #include <AK/RefCounted.h>
 #include <AK/StackInfo.h>
 #include <AK/Variant.h>
+#include <LibCrypto/Forward.h>
 #include <LibGC/Function.h>
 #include <LibGC/Heap.h>
 #include <LibGC/MarkedVector.h>
@@ -287,6 +288,7 @@ public:
     Function<ThrowCompletionOr<HandledByHost>(ArrayBuffer&, size_t)> host_resize_array_buffer;
     Function<void(StringView)> host_unrecognized_date_string;
     Function<ThrowCompletionOr<void>(Realm&, NonnullOwnPtr<ExecutionContext>, ShadowRealm&)> host_initialize_shadow_realm;
+    Function<Crypto::SignedBigInteger(Object const& global)> host_system_utc_epoch_nanoseconds;
 
     Vector<StackTraceElement> stack_trace() const;
 

+ 3 - 0
Libraries/LibJS/Tests/builtins/Temporal/Now/Now.@@toStringTag.js

@@ -0,0 +1,3 @@
+test("basic functionality", () => {
+    expect(Temporal.Now[Symbol.toStringTag]).toBe("Temporal.Now");
+});

+ 19 - 0
Libraries/LibJS/Tests/builtins/Temporal/Now/Now.plainDateISO.js

@@ -0,0 +1,19 @@
+describe("correct behavior", () => {
+    test("length is 0", () => {
+        expect(Temporal.Now.plainDateISO).toHaveLength(0);
+    });
+
+    test("basic functionality", () => {
+        const plainDate = Temporal.Now.plainDateISO();
+        expect(plainDate).toBeInstanceOf(Temporal.PlainDate);
+        expect(plainDate.calendarId).toBe("iso8601");
+    });
+});
+
+describe("errors", () => {
+    test("invalid time zone name", () => {
+        expect(() => {
+            Temporal.Now.plainDateISO("foo");
+        }).toThrowWithMessage(RangeError, "Invalid time zone name 'foo");
+    });
+});

+ 19 - 0
Libraries/LibJS/Tests/builtins/Temporal/Now/Now.plainDateTimeISO.js

@@ -0,0 +1,19 @@
+describe("correct behavior", () => {
+    test("length is 0", () => {
+        expect(Temporal.Now.plainDateTimeISO).toHaveLength(0);
+    });
+
+    test("basic functionality", () => {
+        const plainDateTime = Temporal.Now.plainDateTimeISO();
+        expect(plainDateTime).toBeInstanceOf(Temporal.PlainDateTime);
+        expect(plainDateTime.calendarId).toBe("iso8601");
+    });
+});
+
+describe("errors", () => {
+    test("invalid time zone name", () => {
+        expect(() => {
+            Temporal.Now.plainDateTimeISO("foo");
+        }).toThrowWithMessage(RangeError, "Invalid time zone name 'foo");
+    });
+});

+ 19 - 0
Libraries/LibJS/Tests/builtins/Temporal/Now/Now.plainTimeISO.js

@@ -0,0 +1,19 @@
+describe("correct behavior", () => {
+    test("length is 0", () => {
+        expect(Temporal.Now.plainTimeISO).toHaveLength(0);
+    });
+
+    test("basic functionality", () => {
+        const plainTime = Temporal.Now.plainTimeISO();
+        expect(plainTime).toBeInstanceOf(Temporal.PlainTime);
+        expect(plainTime.calendarId).toBeUndefined();
+    });
+});
+
+describe("errors", () => {
+    test("invalid time zone name", () => {
+        expect(() => {
+            Temporal.Now.plainTimeISO("foo");
+        }).toThrowWithMessage(RangeError, "Invalid time zone name 'foo");
+    });
+});