From f2c19f96f8ed8da45bc7eab1979a91e934b67e7a Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sun, 24 Nov 2024 17:23:31 -0500 Subject: [PATCH] LibJS: Implement Temporal.Now --- Libraries/LibJS/CMakeLists.txt | 1 + Libraries/LibJS/Forward.h | 1 + Libraries/LibJS/Runtime/CommonPropertyNames.h | 1 + Libraries/LibJS/Runtime/Temporal/Now.cpp | 132 ++++++++++++++++++ Libraries/LibJS/Runtime/Temporal/Now.h | 37 +++++ Libraries/LibJS/Runtime/Temporal/Temporal.cpp | 2 + Libraries/LibJS/Runtime/VM.cpp | 16 +++ Libraries/LibJS/Runtime/VM.h | 2 + .../Temporal/Now/Now.@@toStringTag.js | 3 + .../builtins/Temporal/Now/Now.plainDateISO.js | 19 +++ .../Temporal/Now/Now.plainDateTimeISO.js | 19 +++ .../builtins/Temporal/Now/Now.plainTimeISO.js | 19 +++ 12 files changed, 252 insertions(+) create mode 100644 Libraries/LibJS/Runtime/Temporal/Now.cpp create mode 100644 Libraries/LibJS/Runtime/Temporal/Now.h create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/Now/Now.@@toStringTag.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/Now/Now.plainDateISO.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/Now/Now.plainDateTimeISO.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/Now/Now.plainTimeISO.js diff --git a/Libraries/LibJS/CMakeLists.txt b/Libraries/LibJS/CMakeLists.txt index 0e2667b3dd7..8638f11d4c1 100644 --- a/Libraries/LibJS/CMakeLists.txt +++ b/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 diff --git a/Libraries/LibJS/Forward.h b/Libraries/LibJS/Forward.h index bf8156703cf..1f573fdaf16 100644 --- a/Libraries/LibJS/Forward.h +++ b/Libraries/LibJS/Forward.h @@ -282,6 +282,7 @@ namespace Temporal { JS_ENUMERATE_TEMPORAL_OBJECTS #undef __JS_ENUMERATE +class Now; class Temporal; struct CalendarDate; diff --git a/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Libraries/LibJS/Runtime/CommonPropertyNames.h index e7c7872d53a..1255fc88b6e 100644 --- a/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/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) \ diff --git a/Libraries/LibJS/Runtime/Temporal/Now.cpp b/Libraries/LibJS/Runtime/Temporal/Now.cpp new file mode 100644 index 00000000000..a77846146b9 --- /dev/null +++ b/Libraries/LibJS/Runtime/Temporal/Now.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2021-2023, Linus Groh + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +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 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); +} + +} diff --git a/Libraries/LibJS/Runtime/Temporal/Now.h b/Libraries/LibJS/Runtime/Temporal/Now.h new file mode 100644 index 00000000000..073a966a5a5 --- /dev/null +++ b/Libraries/LibJS/Runtime/Temporal/Now.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021-2022, Linus Groh + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +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 system_date_time(VM&, Value temporal_time_zone_like); + +} diff --git a/Libraries/LibJS/Runtime/Temporal/Temporal.cpp b/Libraries/LibJS/Runtime/Temporal/Temporal.cpp index 61e8526f09d..d33dd274b4a 100644 --- a/Libraries/LibJS/Runtime/Temporal/Temporal.cpp +++ b/Libraries/LibJS/Runtime/Temporal/Temporal.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -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(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(); }); diff --git a/Libraries/LibJS/Runtime/VM.cpp b/Libraries/LibJS/Runtime/VM.cpp index 45d37722288..5ce09f0d403 100644 --- a/Libraries/LibJS/Runtime/VM.cpp +++ b/Libraries/LibJS/Runtime/VM.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -181,6 +183,20 @@ VM::VM(OwnPtr 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) { }; diff --git a/Libraries/LibJS/Runtime/VM.h b/Libraries/LibJS/Runtime/VM.h index 8d7fe05b86a..fe588aadb5c 100644 --- a/Libraries/LibJS/Runtime/VM.h +++ b/Libraries/LibJS/Runtime/VM.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -287,6 +288,7 @@ public: Function(ArrayBuffer&, size_t)> host_resize_array_buffer; Function host_unrecognized_date_string; Function(Realm&, NonnullOwnPtr, ShadowRealm&)> host_initialize_shadow_realm; + Function host_system_utc_epoch_nanoseconds; Vector stack_trace() const; diff --git a/Libraries/LibJS/Tests/builtins/Temporal/Now/Now.@@toStringTag.js b/Libraries/LibJS/Tests/builtins/Temporal/Now/Now.@@toStringTag.js new file mode 100644 index 00000000000..c9f5e49dc09 --- /dev/null +++ b/Libraries/LibJS/Tests/builtins/Temporal/Now/Now.@@toStringTag.js @@ -0,0 +1,3 @@ +test("basic functionality", () => { + expect(Temporal.Now[Symbol.toStringTag]).toBe("Temporal.Now"); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/Now/Now.plainDateISO.js b/Libraries/LibJS/Tests/builtins/Temporal/Now/Now.plainDateISO.js new file mode 100644 index 00000000000..735b89ca3c9 --- /dev/null +++ b/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"); + }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/Now/Now.plainDateTimeISO.js b/Libraries/LibJS/Tests/builtins/Temporal/Now/Now.plainDateTimeISO.js new file mode 100644 index 00000000000..7489a130a42 --- /dev/null +++ b/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"); + }); +}); diff --git a/Libraries/LibJS/Tests/builtins/Temporal/Now/Now.plainTimeISO.js b/Libraries/LibJS/Tests/builtins/Temporal/Now/Now.plainTimeISO.js new file mode 100644 index 00000000000..bb1ba36e5dd --- /dev/null +++ b/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"); + }); +});