From 265e89367e66203702a04774feb01ed319820924 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Tue, 6 Jul 2021 23:53:27 +0100 Subject: [PATCH] LibJS: Start implementing Temporal.TimeZone Here we got our first Temporal object :^) This patch adds the TimeZone object itself, its constructor and prototype (currently empty), and a bunch of required abstract operations --- Userland/Libraries/LibJS/CMakeLists.txt | 4 + Userland/Libraries/LibJS/Forward.h | 3 +- .../Libraries/LibJS/Runtime/GlobalObject.cpp | 3 +- .../LibJS/Runtime/Temporal/ISO8601.cpp | 21 ++++ .../LibJS/Runtime/Temporal/ISO8601.h | 17 ++++ .../LibJS/Runtime/Temporal/Temporal.cpp | 2 + .../LibJS/Runtime/Temporal/TimeZone.cpp | 95 +++++++++++++++++++ .../LibJS/Runtime/Temporal/TimeZone.h | 43 +++++++++ .../Runtime/Temporal/TimeZoneConstructor.cpp | 79 +++++++++++++++ .../Runtime/Temporal/TimeZoneConstructor.h | 28 ++++++ .../Runtime/Temporal/TimeZonePrototype.cpp | 23 +++++ .../Runtime/Temporal/TimeZonePrototype.h | 22 +++++ 12 files changed, 338 insertions(+), 2 deletions(-) create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.h create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.cpp create mode 100644 Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.h diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index f3541f3d52..8634e41479 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -121,8 +121,12 @@ set(SOURCES Runtime/SymbolConstructor.cpp Runtime/SymbolObject.cpp Runtime/SymbolPrototype.cpp + Runtime/Temporal/ISO8601.cpp Runtime/Temporal/Now.cpp Runtime/Temporal/Temporal.cpp + Runtime/Temporal/TimeZone.cpp + Runtime/Temporal/TimeZoneConstructor.cpp + Runtime/Temporal/TimeZonePrototype.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 2d350e300a..01dd08b3e6 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -76,7 +76,8 @@ __JS_ENUMERATE(Float32Array, float32_array, Float32ArrayPrototype, Float32ArrayConstructor, float) \ __JS_ENUMERATE(Float64Array, float64_array, Float64ArrayPrototype, Float64ArrayConstructor, double) -#define JS_ENUMERATE_TEMPORAL_OBJECTS +#define JS_ENUMERATE_TEMPORAL_OBJECTS \ + __JS_ENUMERATE(TimeZone, time_zone, TimeZonePrototype, TimeZoneConstructor) #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 5f3acbcab2..27997dc4ec 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -68,6 +68,8 @@ #include #include #include +#include +#include #include #include #include @@ -243,7 +245,6 @@ void GlobalObject::visit_edges(Visitor& visitor) #define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ visitor.visit(m_##snake_name##_constructor); \ visitor.visit(m_##snake_name##_prototype); - JS_ENUMERATE_NATIVE_ERRORS JS_ENUMERATE_BUILTIN_TYPES #undef __JS_ENUMERATE diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp new file mode 100644 index 0000000000..737228becd --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace JS::Temporal { + +// 13.33 ISO 8601 grammar, https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar + +// TimeZoneNumericUTCOffset, https://tc39.es/proposal-temporal/#prod-TimeZoneNumericUTCOffset +bool is_valid_time_zone_numeric_utc_offset(String const&) +{ + // TODO: Implement me :^) + return false; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h new file mode 100644 index 0000000000..a800773d17 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/ISO8601.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Temporal { + +// 13.33 ISO 8601 grammar, https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar + +bool is_valid_time_zone_numeric_utc_offset(String const&); + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp index e99ca3b89c..7c410e702e 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace JS::Temporal { @@ -24,6 +25,7 @@ void Temporal::initialize(GlobalObject& global_object) u8 attr = Attribute::Writable | Attribute::Configurable; define_direct_property(vm.names.now, heap().allocate(global_object, global_object), attr); + define_direct_property(vm.names.TimeZone, global_object.temporal_time_zone_constructor(), attr); } } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp new file mode 100644 index 0000000000..f1ef6755af --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace JS::Temporal { + +// 11 Temporal.TimeZone Objects, https://tc39.es/proposal-temporal/#sec-temporal-timezone-objects +TimeZone::TimeZone(String identifier, Object& prototype) + : Object(prototype) + , m_identifier(move(identifier)) +{ +} + +// 11.1.1 IsValidTimeZoneName ( timeZone ), https://tc39.es/proposal-temporal/#sec-isvalidtimezonename +// NOTE: This is the minimum implementation of IsValidTimeZoneName, supporting only the "UTC" time zone. +bool is_valid_time_zone_name(String const& time_zone) +{ + // 1. Assert: Type(timeZone) is String. + + // 2. Let tzText be ! StringToCodePoints(timeZone). + // 3. Let tzUpperText be the result of toUppercase(tzText), according to the Unicode Default Case Conversion algorithm. + // 4. Let tzUpper be ! CodePointsToString(tzUpperText). + auto tz_upper = time_zone.to_uppercase(); + + // 5. If tzUpper and "UTC" are the same sequence of code points, return true. + if (tz_upper == "UTC") + return true; + + // 6. Return false. + return false; +} + +// 11.1.2 CanonicalizeTimeZoneName ( timeZone ), https://tc39.es/proposal-temporal/#sec-canonicalizetimezonename +// NOTE: This is the minimum implementation of CanonicalizeTimeZoneName, supporting only the "UTC" time zone. +String canonicalize_time_zone_name(String const& time_zone) +{ + // 1. Assert: Type(timeZone) is String. + + // 2. Assert: ! IsValidTimeZoneName(timeZone) is true. + VERIFY(is_valid_time_zone_name(time_zone)); + + // 3. Return "UTC". + return "UTC"; +} + +// 11.1.3 DefaultTimeZone ( ), https://tc39.es/proposal-temporal/#sec-defaulttimezone +// NOTE: This is the minimum implementation of DefaultTimeZone, supporting only the "UTC" time zone. +String default_time_zone() +{ + // 1. Return "UTC". + return "UTC"; +} + +// 11.6.2 CreateTemporalTimeZone ( identifier [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporaltimezone +Object* create_temporal_time_zone(GlobalObject& global_object, String const& identifier, FunctionObject* new_target) +{ + auto& vm = global_object.vm(); + + // 1. If newTarget is not present, set it to %Temporal.TimeZone%. + if (!new_target) + new_target = global_object.temporal_time_zone_constructor(); + + // 2. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.TimeZone.prototype%", « [[InitializedTemporalTimeZone]], [[Identifier]], [[OffsetNanoseconds]] »). + // 3. Set object.[[Identifier]] to identifier. + auto* object = ordinary_create_from_constructor(global_object, *new_target, &GlobalObject::temporal_time_zone_prototype, identifier); + if (vm.exception()) + return {}; + + // 4. If identifier satisfies the syntax of a TimeZoneNumericUTCOffset (see 13.33), then + if (is_valid_time_zone_numeric_utc_offset(identifier)) { + // TODO: + // a. Set object.[[OffsetNanoseconds]] to ! ParseTimeZoneOffsetString(identifier). + } + // 5. Else, + else { + // a. Assert: ! CanonicalizeTimeZoneName(identifier) is identifier. + VERIFY(canonicalize_time_zone_name(identifier) == identifier); + + // b. Set object.[[OffsetNanoseconds]] to undefined. + // NOTE: No-op. + } + + // 6. Return object. + return object; +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h new file mode 100644 index 0000000000..072a7ab456 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace JS::Temporal { + +class TimeZone final : public Object { + JS_OBJECT(TimeZone, Object); + + // Needs to store values in the range -8.64 * 10^21 to 8.64 * 10^21 + using OffsetType = double; + +public: + explicit TimeZone(String identifier, Object& prototype); + virtual ~TimeZone() override = default; + + String const& identifier() const { return m_identifier; } + Optional const& offset_nanoseconds() const { return m_offset_nanoseconds; } + void set_offset_nanoseconds(u32 offset_nanoseconds) { m_offset_nanoseconds = offset_nanoseconds; }; + +private: + // 11.5 Properties of Temporal.TimeZone Instances, https://tc39.es/proposal-temporal/#sec-properties-of-temporal-timezone-instances + + // [[Identifier]] + String m_identifier; + + // [[OffsetNanoseconds]] + Optional m_offset_nanoseconds; +}; + +bool is_valid_time_zone_name(String const& time_zone); +String canonicalize_time_zone_name(String const& time_zone); +String default_time_zone(); +Object* create_temporal_time_zone(GlobalObject&, String const& identifier, FunctionObject* new_target = nullptr); + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.cpp new file mode 100644 index 0000000000..ee49209269 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace JS::Temporal { + +// 11.2 The Temporal.TimeZone Constructor, https://tc39.es/proposal-temporal/#sec-temporal-timezone-constructor +TimeZoneConstructor::TimeZoneConstructor(GlobalObject& global_object) + : NativeFunction(vm().names.TimeZone.as_string(), *global_object.function_prototype()) +{ +} + +void TimeZoneConstructor::initialize(GlobalObject& global_object) +{ + NativeFunction::initialize(global_object); + + auto& vm = this->vm(); + + // 11.3.1 Temporal.TimeZone.prototype, https://tc39.es/proposal-temporal/#sec-temporal-timezone-prototype + define_direct_property(vm.names.prototype, global_object.temporal_time_zone_prototype(), 0); + + define_direct_property(vm.names.length, Value(1), Attribute::Configurable); +} + +// 11.2.1 Temporal.TimeZone ( identifier ), https://tc39.es/proposal-temporal/#sec-temporal.timezone +Value TimeZoneConstructor::call() +{ + auto& vm = this->vm(); + + // 1. If NewTarget is undefined, then + // a. Throw a TypeError exception. + vm.throw_exception(global_object(), ErrorType::ConstructorWithoutNew, "Temporal.TimeZone"); + return {}; +} + +// 11.2.1 Temporal.TimeZone ( identifier ), https://tc39.es/proposal-temporal/#sec-temporal.timezone +Value TimeZoneConstructor::construct(FunctionObject& new_target) +{ + auto& vm = this->vm(); + auto& global_object = this->global_object(); + + // 2. Set identifier to ? ToString(identifier). + auto identifier = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + + String canonical; + + // 3. If identifier satisfies the syntax of a TimeZoneNumericUTCOffset (see 13.33), then + if (is_valid_time_zone_numeric_utc_offset(identifier)) { + // TODO: + // a. Let offsetNanoseconds be ? ParseTimeZoneOffsetString(identifier). + // b. Let canonical be ! FormatTimeZoneOffsetString(offsetNanoseconds). + } + // 4. Else, + else { + // a. If ! IsValidTimeZoneName(identifier) is false, then + if (!is_valid_time_zone_name(identifier)) { + // i. Throw a RangeError exception. + vm.throw_exception(global_object); + return {}; + } + + // b. Let canonical be ! CanonicalizeTimeZoneName(identifier). + canonical = canonicalize_time_zone_name(identifier); + } + + // 5. Return ? CreateTemporalTimeZone(canonical, NewTarget). + return create_temporal_time_zone(global_object, canonical, &new_target); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.h b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.h new file mode 100644 index 0000000000..621a50ef5e --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneConstructor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Temporal { + +class TimeZoneConstructor final : public NativeFunction { + JS_OBJECT(TimeZoneConstructor, NativeFunction); + +public: + explicit TimeZoneConstructor(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~TimeZoneConstructor() 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/TimeZonePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.cpp new file mode 100644 index 0000000000..bd65c1d067 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace JS::Temporal { + +// 11.4 Properties of the Temporal.TimeZone Prototype Object, https://tc39.es/proposal-temporal/#sec-properties-of-the-temporal-timezone-prototype-object +TimeZonePrototype::TimeZonePrototype(GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void TimeZonePrototype::initialize(GlobalObject& global_object) +{ + Object::initialize(global_object); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.h b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.h new file mode 100644 index 0000000000..65a748e431 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace JS::Temporal { + +class TimeZonePrototype final : public Object { + JS_OBJECT(TimeZonePrototype, Object); + +public: + explicit TimeZonePrototype(GlobalObject&); + virtual void initialize(GlobalObject&) override; + virtual ~TimeZonePrototype() override = default; +}; + +}