Browse Source

LibJS: Start implementing Temporal.ZonedDateTime

This commit adds the ZonedDateTime object itself, its constructor and
prototype (currently empty), and the CreateTemporalZonedDateTime
abstract operation.
Linus Groh 3 years ago
parent
commit
cfb77b66e5

+ 3 - 0
Userland/Libraries/LibJS/CMakeLists.txt

@@ -148,6 +148,9 @@ set(SOURCES
     Runtime/Temporal/TimeZone.cpp
     Runtime/Temporal/TimeZoneConstructor.cpp
     Runtime/Temporal/TimeZonePrototype.cpp
+    Runtime/Temporal/ZonedDateTime.cpp
+    Runtime/Temporal/ZonedDateTimeConstructor.cpp
+    Runtime/Temporal/ZonedDateTimePrototype.cpp
     Runtime/TypedArray.cpp
     Runtime/TypedArrayConstructor.cpp
     Runtime/TypedArrayPrototype.cpp

+ 2 - 1
Userland/Libraries/LibJS/Forward.h

@@ -83,7 +83,8 @@
     __JS_ENUMERATE(PlainDate, plain_date, PlainDatePrototype, PlainDateConstructor)                  \
     __JS_ENUMERATE(PlainDateTime, plain_date_time, PlainDateTimePrototype, PlainDateTimeConstructor) \
     __JS_ENUMERATE(PlainTime, plain_time, PlainTimePrototype, PlainTimeConstructor)                  \
-    __JS_ENUMERATE(TimeZone, time_zone, TimeZonePrototype, TimeZoneConstructor)
+    __JS_ENUMERATE(TimeZone, time_zone, TimeZonePrototype, TimeZoneConstructor)                      \
+    __JS_ENUMERATE(ZonedDateTime, zoned_date_time, ZonedDateTimePrototype, ZonedDateTimeConstructor)
 
 #define JS_ENUMERATE_ITERATOR_PROTOTYPES                         \
     __JS_ENUMERATE(Iterator, iterator)                           \

+ 2 - 0
Userland/Libraries/LibJS/Runtime/GlobalObject.cpp

@@ -84,6 +84,8 @@
 #include <LibJS/Runtime/Temporal/Temporal.h>
 #include <LibJS/Runtime/Temporal/TimeZoneConstructor.h>
 #include <LibJS/Runtime/Temporal/TimeZonePrototype.h>
+#include <LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h>
+#include <LibJS/Runtime/Temporal/ZonedDateTimePrototype.h>
 #include <LibJS/Runtime/TypedArray.h>
 #include <LibJS/Runtime/TypedArrayConstructor.h>
 #include <LibJS/Runtime/TypedArrayPrototype.h>

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

@@ -14,6 +14,7 @@
 #include <LibJS/Runtime/Temporal/PlainTimeConstructor.h>
 #include <LibJS/Runtime/Temporal/Temporal.h>
 #include <LibJS/Runtime/Temporal/TimeZoneConstructor.h>
+#include <LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h>
 
 namespace JS::Temporal {
 
@@ -41,6 +42,7 @@ void Temporal::initialize(GlobalObject& global_object)
     define_direct_property(vm.names.PlainDateTime, global_object.temporal_plain_date_time_constructor(), attr);
     define_direct_property(vm.names.PlainTime, global_object.temporal_plain_time_constructor(), attr);
     define_direct_property(vm.names.TimeZone, global_object.temporal_time_zone_constructor(), attr);
+    define_direct_property(vm.names.ZonedDateTime, global_object.temporal_zoned_date_time_constructor(), attr);
 }
 
 }

+ 61 - 0
Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp

@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/AbstractOperations.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Temporal/Instant.h>
+#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
+#include <LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h>
+
+namespace JS::Temporal {
+
+// 6 Temporal.ZonedDateTime Objects, https://tc39.es/proposal-temporal/#sec-temporal-zoneddatetime-objects
+ZonedDateTime::ZonedDateTime(BigInt& nanoseconds, Object& time_zone, Object& calendar, Object& prototype)
+    : Object(prototype)
+    , m_nanoseconds(nanoseconds)
+    , m_time_zone(time_zone)
+    , m_calendar(calendar)
+{
+}
+
+void ZonedDateTime::visit_edges(Cell::Visitor& visitor)
+{
+    Base::visit_edges(visitor);
+
+    visitor.visit(&m_nanoseconds);
+    visitor.visit(&m_time_zone);
+    visitor.visit(&m_calendar);
+}
+
+// 6.5.3 CreateTemporalZonedDateTime ( epochNanoseconds, timeZone, calendar [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporalzoneddatetime
+ZonedDateTime* create_temporal_zoned_date_time(GlobalObject& global_object, BigInt& epoch_nanoseconds, Object& time_zone, Object& calendar, FunctionObject* new_target)
+{
+    auto& vm = global_object.vm();
+
+    // 1. Assert: Type(epochNanoseconds) is BigInt.
+    // 3. Assert: Type(timeZone) is Object.
+    // 4. Assert: Type(calendar) is Object.
+
+    // 2. Assert: ! IsValidEpochNanoseconds(epochNanoseconds) is true.
+    VERIFY(is_valid_epoch_nanoseconds(epoch_nanoseconds));
+
+    // 5. If newTarget is not present, set it to %Temporal.ZonedDateTime%.
+    if (!new_target)
+        new_target = global_object.temporal_zoned_date_time_constructor();
+
+    // 6. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.ZonedDateTime.prototype%", « [[InitializedTemporalZonedDateTime]], [[Nanoseconds]], [[TimeZone]], [[Calendar]] »).
+    // 7. Set object.[[Nanoseconds]] to epochNanoseconds.
+    // 8. Set object.[[TimeZone]] to timeZone.
+    // 9. Set object.[[Calendar]] to calendar.
+    auto* object = ordinary_create_from_constructor<ZonedDateTime>(global_object, *new_target, &GlobalObject::temporal_time_zone_prototype, epoch_nanoseconds, time_zone, calendar);
+    if (vm.exception())
+        return {};
+
+    // 10. Return object.
+    return object;
+}
+
+}

+ 39 - 0
Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.h

@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/BigInt.h>
+#include <LibJS/Runtime/Object.h>
+
+namespace JS::Temporal {
+
+class ZonedDateTime final : public Object {
+    JS_OBJECT(ZonedDateTime, Object);
+
+public:
+    ZonedDateTime(BigInt& nanoseconds, Object& time_zone, Object& calendar, Object& prototype);
+    virtual ~ZonedDateTime() override = default;
+
+    BigInt const& nanoseconds() const { return m_nanoseconds; }
+    BigInt& nanoseconds() { return m_nanoseconds; }
+    Object const& time_zone() const { return m_time_zone; }
+    Object& time_zone() { return m_time_zone; }
+    Object const& calendar() const { return m_calendar; }
+    Object& calendar() { return m_calendar; }
+
+private:
+    virtual void visit_edges(Visitor&) override;
+
+    // 6.4 Properties of Temporal.ZonedDateTime Instances, https://tc39.es/proposal-temporal/#sec-properties-of-temporal-zoneddatetime-instances
+    BigInt& m_nanoseconds; // [[Nanoseconds]]
+    Object& m_time_zone;   // [[TimeZone]]
+    Object& m_calendar;    // [[Calendar]]
+};
+
+ZonedDateTime* create_temporal_zoned_date_time(GlobalObject&, BigInt& epoch_nanoseconds, Object& time_zone, Object& calendar, FunctionObject* new_target = nullptr);
+
+}

+ 76 - 0
Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.cpp

@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Temporal/Calendar.h>
+#include <LibJS/Runtime/Temporal/Instant.h>
+#include <LibJS/Runtime/Temporal/TimeZone.h>
+#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
+#include <LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h>
+
+namespace JS::Temporal {
+
+// 6.1 The Temporal.ZonedDateTime Constructor, https://tc39.es/proposal-temporal/#sec-temporal-zoneddatetime-constructor
+ZonedDateTimeConstructor::ZonedDateTimeConstructor(GlobalObject& global_object)
+    : NativeFunction(vm().names.ZonedDateTime.as_string(), *global_object.function_prototype())
+{
+}
+
+void ZonedDateTimeConstructor::initialize(GlobalObject& global_object)
+{
+    NativeFunction::initialize(global_object);
+
+    auto& vm = this->vm();
+
+    // 6.2.1 Temporal.ZonedDateTime.prototype, https://tc39.es/proposal-temporal/#sec-temporal-zoneddatetime-prototype
+    define_direct_property(vm.names.prototype, global_object.temporal_zoned_date_time_prototype(), 0);
+
+    define_direct_property(vm.names.length, Value(2), Attribute::Configurable);
+}
+
+// 6.1.1 Temporal.ZonedDateTime ( epochNanoseconds, timeZoneLike [ , calendarLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime
+Value ZonedDateTimeConstructor::call()
+{
+    auto& vm = this->vm();
+
+    // 1. If NewTarget is undefined, then
+    // a. Throw a TypeError exception.
+    vm.throw_exception<TypeError>(global_object(), ErrorType::ConstructorWithoutNew, "Temporal.ZonedDateTime");
+    return {};
+}
+
+// 6.1.1 Temporal.ZonedDateTime ( epochNanoseconds, timeZoneLike [ , calendarLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.zoneddatetime
+Value ZonedDateTimeConstructor::construct(FunctionObject& new_target)
+{
+    auto& vm = this->vm();
+    auto& global_object = this->global_object();
+
+    // 2. Set epochNanoseconds to ? ToBigInt(epochNanoseconds).
+    auto* epoch_nanoseconds = vm.argument(0).to_bigint(global_object);
+    if (vm.exception())
+        return {};
+
+    // 3. If ! IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
+    if (!is_valid_epoch_nanoseconds(*epoch_nanoseconds)) {
+        vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidEpochNanoseconds);
+        return {};
+    }
+
+    // 4. Let timeZone be ? ToTemporalTimeZone(timeZoneLike).
+    auto* time_zone = to_temporal_time_zone(global_object, vm.argument(1));
+    if (vm.exception())
+        return {};
+
+    // 5. Let calendar be ? ToTemporalCalendarWithISODefault(calendarLike).
+    auto* calendar = to_temporal_calendar_with_iso_default(global_object, vm.argument(2));
+    if (vm.exception())
+        return {};
+
+    // 6. Return ? CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar, NewTarget).
+    return create_temporal_zoned_date_time(global_object, *epoch_nanoseconds, *time_zone, *calendar, &new_target);
+}
+
+}

+ 28 - 0
Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h

@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS::Temporal {
+
+class ZonedDateTimeConstructor final : public NativeFunction {
+    JS_OBJECT(ZonedDateTimeConstructor, NativeFunction);
+
+public:
+    explicit ZonedDateTimeConstructor(GlobalObject&);
+    virtual void initialize(GlobalObject&) override;
+    virtual ~ZonedDateTimeConstructor() override = default;
+
+    virtual Value call() override;
+    virtual Value construct(FunctionObject& new_target) override;
+
+private:
+    virtual bool has_constructor() const override { return true; }
+};
+
+}

+ 23 - 0
Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp

@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Temporal/ZonedDateTimePrototype.h>
+
+namespace JS::Temporal {
+
+// 6.3 Properties of the Temporal.ZonedDateTime Prototype Object, https://tc39.es/proposal-temporal/#sec-properties-of-the-temporal-zoneddatetime-prototype-object
+ZonedDateTimePrototype::ZonedDateTimePrototype(GlobalObject& global_object)
+    : Object(*global_object.object_prototype())
+{
+}
+
+void ZonedDateTimePrototype::initialize(GlobalObject& global_object)
+{
+    Object::initialize(global_object);
+}
+
+}

+ 22 - 0
Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.h

@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/Object.h>
+
+namespace JS::Temporal {
+
+class ZonedDateTimePrototype final : public Object {
+    JS_OBJECT(ZonedDateTimePrototype, Object);
+
+public:
+    explicit ZonedDateTimePrototype(GlobalObject&);
+    virtual void initialize(GlobalObject&) override;
+    virtual ~ZonedDateTimePrototype() override = default;
+};
+
+}

+ 39 - 0
Userland/Libraries/LibJS/Tests/builtins/Temporal/ZonedDateTime/ZonedDateTime.js

@@ -0,0 +1,39 @@
+describe("errors", () => {
+    test("called without new", () => {
+        expect(() => {
+            Temporal.ZonedDateTime();
+        }).toThrowWithMessage(
+            TypeError,
+            "Temporal.ZonedDateTime constructor must be called with 'new'"
+        );
+    });
+
+    test("out-of-range epoch nanoseconds value", () => {
+        expect(() => {
+            new Temporal.ZonedDateTime(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(() => {
+            new Temporal.ZonedDateTime(-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"
+        );
+    });
+});
+
+describe("normal behavior", () => {
+    test("length is 2", () => {
+        expect(Temporal.ZonedDateTime).toHaveLength(2);
+    });
+
+    test("basic functionality", () => {
+        const timeZone = new Temporal.TimeZone("UTC");
+        const zonedDateTime = new Temporal.ZonedDateTime(0n, timeZone);
+        expect(typeof zonedDateTime).toBe("object");
+        expect(zonedDateTime).toBeInstanceOf(Temporal.ZonedDateTime);
+        expect(Object.getPrototypeOf(zonedDateTime)).toBe(Temporal.ZonedDateTime.prototype);
+    });
+});