From cfb77b66e51a04892b791e88eae92a9c02d76bdc Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Sun, 1 Aug 2021 17:20:11 +0100 Subject: [PATCH] LibJS: Start implementing Temporal.ZonedDateTime This commit adds the ZonedDateTime object itself, its constructor and prototype (currently empty), and the CreateTemporalZonedDateTime abstract operation. --- Userland/Libraries/LibJS/CMakeLists.txt | 3 + Userland/Libraries/LibJS/Forward.h | 3 +- .../Libraries/LibJS/Runtime/GlobalObject.cpp | 2 + .../LibJS/Runtime/Temporal/Temporal.cpp | 2 + .../LibJS/Runtime/Temporal/ZonedDateTime.cpp | 61 +++++++++++++++ .../LibJS/Runtime/Temporal/ZonedDateTime.h | 39 ++++++++++ .../Temporal/ZonedDateTimeConstructor.cpp | 76 +++++++++++++++++++ .../Temporal/ZonedDateTimeConstructor.h | 28 +++++++ .../Temporal/ZonedDateTimePrototype.cpp | 23 ++++++ .../Runtime/Temporal/ZonedDateTimePrototype.h | 22 ++++++ .../Temporal/ZonedDateTime/ZonedDateTime.js | 39 ++++++++++ 11 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.h create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.h create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Temporal/ZonedDateTime/ZonedDateTime.js diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index 008ee31f03d..cc1fea744fc 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/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 diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index 4d73840641d..5e6bfded46f 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/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) \ diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp index bdaaa84c077..45f3f4b03e3 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -84,6 +84,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp index 1cb1e0dcc74..b043791d495 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp @@ -14,6 +14,7 @@ #include #include #include +#include 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); } } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp new file mode 100644 index 00000000000..a5652cd2202 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +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(global_object, *new_target, &GlobalObject::temporal_time_zone_prototype, epoch_nanoseconds, time_zone, calendar); + if (vm.exception()) + return {}; + + // 10. Return object. + return object; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.h b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.h new file mode 100644 index 00000000000..67cf49f7791 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +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); + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.cpp new file mode 100644 index 00000000000..a895d26f00c --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +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(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(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); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h new file mode 100644 index 00000000000..bad97618265 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimeConstructor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +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; } +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp new file mode 100644 index 00000000000..daf738ef1f3 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +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); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.h b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.h new file mode 100644 index 00000000000..64be549401c --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ZonedDateTimePrototype.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +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; +}; + +} diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/ZonedDateTime/ZonedDateTime.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/ZonedDateTime/ZonedDateTime.js new file mode 100644 index 00000000000..f65d7247d4e --- /dev/null +++ b/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); + }); +});