Ver código fonte

LibJS: Start implementing Temporal.PlainDate

This commit adds the PlainDate object itself, its constructor and
prototype (currently empty), and several required abstract operations.
Idan Horowitz 4 anos atrás
pai
commit
cc00ccec41

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

@@ -135,6 +135,8 @@ set(SOURCES
     Runtime/Temporal/InstantPrototype.cpp
     Runtime/Temporal/Now.cpp
     Runtime/Temporal/PlainDate.cpp
+    Runtime/Temporal/PlainDateConstructor.cpp
+    Runtime/Temporal/PlainDatePrototype.cpp
     Runtime/Temporal/PlainDateTime.cpp
     Runtime/Temporal/PlainTime.cpp
     Runtime/Temporal/Temporal.cpp

+ 5 - 4
Userland/Libraries/LibJS/Forward.h

@@ -76,10 +76,11 @@
     __JS_ENUMERATE(Float32Array, float32_array, Float32ArrayPrototype, Float32ArrayConstructor, float)                          \
     __JS_ENUMERATE(Float64Array, float64_array, Float64ArrayPrototype, Float64ArrayConstructor, double)
 
-#define JS_ENUMERATE_TEMPORAL_OBJECTS                                          \
-    __JS_ENUMERATE(Calendar, calendar, CalendarPrototype, CalendarConstructor) \
-    __JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor) \
-    __JS_ENUMERATE(Instant, instant, InstantPrototype, InstantConstructor)     \
+#define JS_ENUMERATE_TEMPORAL_OBJECTS                                               \
+    __JS_ENUMERATE(Calendar, calendar, CalendarPrototype, CalendarConstructor)      \
+    __JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor)      \
+    __JS_ENUMERATE(Instant, instant, InstantPrototype, InstantConstructor)          \
+    __JS_ENUMERATE(PlainDate, plain_date, PlainDatePrototype, PlainDateConstructor) \
     __JS_ENUMERATE(TimeZone, time_zone, TimeZonePrototype, TimeZoneConstructor)
 
 #define JS_ENUMERATE_ITERATOR_PROTOTYPES                         \

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

@@ -78,6 +78,7 @@ namespace JS {
     P(buffer)                                \
     P(byteLength)                            \
     P(byteOffset)                            \
+    P(calendar)                              \
     P(call)                                  \
     P(callee)                                \
     P(caller)                                \

+ 1 - 0
Userland/Libraries/LibJS/Runtime/ErrorTypes.h

@@ -172,6 +172,7 @@
     M(TemporalInvalidDurationPropertyValue, "Invalid value for duration property '{}': must be an integer, got {}")                     \
     M(TemporalInvalidEpochNanoseconds, "Invalid epoch nanoseconds value, must be in range -86400 * 10^17 to 86400 * 10^17")             \
     M(TemporalInvalidISODate, "Invalid ISO date")                                                                                       \
+    M(TemporalInvalidPlainDate, "Invalid plain date")                                                                                   \
     M(TemporalInvalidTime, "Invalid time")                                                                                              \
     M(TemporalInvalidTimeZoneName, "Invalid time zone name")                                                                            \
     M(ThisHasNotBeenInitialized, "|this| has not been initialized")                                                                     \

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

@@ -75,6 +75,8 @@
 #include <LibJS/Runtime/Temporal/DurationPrototype.h>
 #include <LibJS/Runtime/Temporal/InstantConstructor.h>
 #include <LibJS/Runtime/Temporal/InstantPrototype.h>
+#include <LibJS/Runtime/Temporal/PlainDateConstructor.h>
+#include <LibJS/Runtime/Temporal/PlainDatePrototype.h>
 #include <LibJS/Runtime/Temporal/Temporal.h>
 #include <LibJS/Runtime/Temporal/TimeZoneConstructor.h>
 #include <LibJS/Runtime/Temporal/TimeZonePrototype.h>

+ 21 - 0
Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp

@@ -423,6 +423,27 @@ Optional<TemporalInstant> parse_temporal_instant_string(GlobalObject& global_obj
     return TemporalInstant { .year = result->year, .month = result->month, .day = result->day, .hour = result->hour, .minute = result->minute, .second = result->second, .millisecond = result->millisecond, .microsecond = result->microsecond, .nanosecond = result->nanosecond, .time_zone_offset = move(time_zone_result->offset) };
 }
 
+// 13.37 ParseTemporalCalendarString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporalcalendarstring
+String parse_temporal_calendar_string([[maybe_unused]] GlobalObject& global_object, [[maybe_unused]] String const& iso_string)
+{
+    // 1. Assert: Type(isoString) is String.
+
+    // 2. If isoString does not satisfy the syntax of a TemporalCalendarString (see 13.33), then
+    // a. Throw a RangeError exception.
+    // 3. Let id be the part of isoString produced by the CalendarName production, or undefined if not present.
+    Optional<StringView> id_part;
+    TODO();
+
+    // 4. If id is undefined, then
+    if (!id_part.has_value()) {
+        // a. Return "iso8601".
+        return "iso8601";
+    }
+
+    // 5. Return id.
+    return id_part.value();
+}
+
 // 13.40 ParseTemporalDurationString ( isoString ), https://tc39.es/proposal-temporal/#sec-temporal-parsetemporaldurationstring
 Optional<TemporalDuration> parse_temporal_duration_string(GlobalObject& global_object, String const& iso_string)
 {

+ 1 - 0
Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h

@@ -60,6 +60,7 @@ Optional<String> to_smallest_temporal_unit(GlobalObject&, Object& normalized_opt
 BigInt* round_number_to_increment(GlobalObject&, BigInt const&, u64 increment, String const& rounding_mode);
 Optional<ISODateTime> parse_iso_date_time(GlobalObject&, String const& iso_string);
 Optional<TemporalInstant> parse_temporal_instant_string(GlobalObject&, String const& iso_string);
+String parse_temporal_calendar_string(GlobalObject&, String const& iso_string);
 Optional<TemporalDuration> parse_temporal_duration_string(GlobalObject&, String const& iso_string);
 Optional<TemporalTimeZone> parse_temporal_time_zone_string(GlobalObject&, String const& iso_string);
 

+ 94 - 0
Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp

@@ -6,8 +6,10 @@
 
 #include <LibJS/Runtime/AbstractOperations.h>
 #include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Temporal/AbstractOperations.h>
 #include <LibJS/Runtime/Temporal/Calendar.h>
 #include <LibJS/Runtime/Temporal/CalendarConstructor.h>
+#include <LibJS/Runtime/Temporal/PlainDate.h>
 #include <LibJS/Runtime/Value.h>
 
 namespace JS::Temporal {
@@ -53,6 +55,98 @@ bool is_builtin_calendar(String const& identifier)
     return true;
 }
 
+// 12.1.3 GetBuiltinCalendar ( id )
+Calendar* get_builtin_calendar(GlobalObject& global_object, String const& identifier)
+{
+    auto& vm = global_object.vm();
+
+    // 1. If ! IsBuiltinCalendar(id) is false, throw a RangeError exception.
+    if (!is_builtin_calendar(identifier)) {
+        vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidCalendarIdentifier, identifier);
+        return {};
+    }
+
+    // 2. Return ? Construct(%Temporal.Calendar%, « id »).
+    MarkedValueList arguments(vm.heap());
+    arguments.append(js_string(vm, identifier));
+    auto calendar = vm.construct(*global_object.temporal_calendar_constructor(), *global_object.temporal_calendar_constructor(), move(arguments));
+    if (vm.exception())
+        return {};
+    return static_cast<Calendar*>(&calendar.as_object());
+}
+
+// 12.1.4 GetISO8601Calendar ( )
+Calendar* get_iso8601_calendar(GlobalObject& global_object)
+{
+    // 1. Return ? GetBuiltinCalendar("iso8601").
+    return get_builtin_calendar(global_object, "iso8601");
+}
+
+// 12.1.21 ToTemporalCalendar ( temporalCalendarLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalcalendar
+Object* to_temporal_calendar(GlobalObject& global_object, Value temporal_calendar_like)
+{
+    auto& vm = global_object.vm();
+
+    // 1. If Type(temporalCalendarLike) is Object, then
+    if (temporal_calendar_like.is_object()) {
+        auto& temporal_calendar_like_object = temporal_calendar_like.as_object();
+        // a. If temporalCalendarLike has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalTime]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then
+        // TODO: The rest of the Temporal built-ins
+        if (is<PlainDate>(temporal_calendar_like_object)) {
+            // i. Return temporalCalendarLike.[[Calendar]].
+            return &static_cast<PlainDate&>(temporal_calendar_like_object).calendar();
+        }
+
+        // b. If ? HasProperty(temporalCalendarLike, "calendar") is false, return temporalCalendarLike.
+        auto has_property = temporal_calendar_like_object.has_property(vm.names.calendar);
+        if (vm.exception())
+            return {};
+        if (!has_property)
+            return &temporal_calendar_like_object;
+
+        // c. Set temporalCalendarLike to ? Get(temporalCalendarLike, "calendar").
+        temporal_calendar_like = temporal_calendar_like_object.get(vm.names.calendar);
+        if (vm.exception())
+            return {};
+        // d. If Type(temporalCalendarLike) is Object and ? HasProperty(temporalCalendarLike, "calendar") is false, return temporalCalendarLike.
+        if (temporal_calendar_like.is_object()) {
+            has_property = temporal_calendar_like.as_object().has_property(vm.names.calendar);
+            if (vm.exception())
+                return {};
+            if (!has_property)
+                return &temporal_calendar_like.as_object();
+        }
+    }
+
+    // 2. Let identifier be ? ToString(temporalCalendarLike).
+    auto identifier = temporal_calendar_like.to_string(global_object);
+    if (vm.exception())
+        return {};
+
+    // 3. If ! IsBuiltinCalendar(identifier) is false, then
+    if (!is_builtin_calendar(identifier)) {
+        // a. Let identifier be ? ParseTemporalCalendarString(identifier).
+        identifier = parse_temporal_calendar_string(global_object, identifier);
+        if (vm.exception())
+            return {};
+    }
+
+    // 4. Return ? CreateTemporalCalendar(identifier).
+    return create_temporal_calendar(global_object, identifier);
+}
+
+// 12.1.22 ToTemporalCalendarWithISODefault ( temporalCalendarLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalcalendarwithisodefault
+Object* to_temporal_calendar_with_iso_default(GlobalObject& global_object, Value temporal_calendar_like)
+{
+    // 1. If temporalCalendarLike is undefined, then
+    if (temporal_calendar_like.is_undefined()) {
+        // a. Return ? GetISO8601Calendar().
+        return get_iso8601_calendar(global_object);
+    }
+    // 2. Return ? ToTemporalCalendar(temporalCalendarLike).
+    return to_temporal_calendar(global_object, temporal_calendar_like);
+}
+
 // 12.1.30 IsISOLeapYear ( year ), https://tc39.es/proposal-temporal/#sec-temporal-isisoleapyear
 bool is_iso_leap_year(i32 year)
 {

+ 4 - 0
Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h

@@ -30,6 +30,10 @@ private:
 
 Calendar* create_temporal_calendar(GlobalObject&, String const& identifier, FunctionObject* new_target = nullptr);
 bool is_builtin_calendar(String const& identifier);
+Calendar* get_builtin_calendar(GlobalObject&, String const& identifier);
+Calendar* get_iso8601_calendar(GlobalObject&);
+Object* to_temporal_calendar(GlobalObject&, Value);
+Object* to_temporal_calendar_with_iso_default(GlobalObject&, Value);
 bool is_iso_leap_year(i32 year);
 i32 iso_days_in_month(i32 year, i32 month);
 

+ 57 - 1
Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp

@@ -4,12 +4,68 @@
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
+#include <LibJS/Runtime/AbstractOperations.h>
+#include <LibJS/Runtime/GlobalObject.h>
 #include <LibJS/Runtime/Temporal/Calendar.h>
 #include <LibJS/Runtime/Temporal/PlainDate.h>
-#include <LibJS/Runtime/Value.h>
+#include <LibJS/Runtime/Temporal/PlainDateConstructor.h>
+#include <LibJS/Runtime/Temporal/PlainDateTime.h>
 
 namespace JS::Temporal {
 
+// 3 Temporal.PlainDate Objects, https://tc39.es/proposal-temporal/#sec-temporal-plaindate-objects
+PlainDate::PlainDate(i32 year, i32 month, i32 day, Object& calendar, Object& prototype)
+    : Object(prototype)
+    , m_iso_year(year)
+    , m_iso_month(month)
+    , m_iso_day(day)
+    , m_calendar(calendar)
+{
+}
+
+void PlainDate::visit_edges(Visitor& visitor)
+{
+    visitor.visit(&m_calendar);
+}
+
+// 3.5.1 CreateTemporalDate ( isoYear, isoMonth, isoDay, calendar [ , newTarget ] ), https://tc39.es/proposal-temporal/#sec-temporal-createtemporaldate
+PlainDate* create_temporal_date(GlobalObject& global_object, i32 iso_year, i32 iso_month, i32 iso_day, Object& calendar, FunctionObject* new_target)
+{
+    auto& vm = global_object.vm();
+
+    // 1. Assert: isoYear is an integer.
+    // 2. Assert: isoMonth is an integer.
+    // 3. Assert: isoDay is an integer.
+    // 4. Assert: Type(calendar) is Object.
+
+    // 5. If ! IsValidISODate(isoYear, isoMonth, isoDay) is false, throw a RangeError exception.
+    if (!is_valid_iso_date(iso_year, iso_month, iso_day)) {
+        vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidPlainDate);
+        return {};
+    }
+
+    // 6. If ! ISODateTimeWithinLimits(isoYear, isoMonth, isoDay, 12, 0, 0, 0, 0, 0) is false, throw a RangeError exception.
+    if (!iso_date_time_within_limits(global_object, iso_year, iso_month, iso_day, 12, 0, 0, 0, 0, 0)) {
+        vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidPlainDate);
+        return {};
+    }
+
+    // 7. If newTarget is not present, set it to %Temporal.PlainDate%.
+    if (!new_target)
+        new_target = global_object.temporal_plain_date_constructor();
+
+    // 8. Let object be ? OrdinaryCreateFromConstructor(newTarget, "%Temporal.PlainDate.prototype%", « [[InitializedTemporalDate]], [[ISOYear]], [[ISOMonth]], [[ISODay]], [[Calendar]] »).
+    // 9. Set object.[[ISOYear]] to isoYear.
+    // 10. Set object.[[ISOMonth]] to isoMonth.
+    // 11. Set object.[[ISODay]] to isoDay.
+    // 12. Set object.[[Calendar]] to calendar.
+    auto* object = ordinary_create_from_constructor<PlainDate>(global_object, *new_target, &GlobalObject::temporal_plain_date_prototype, iso_year, iso_month, iso_day, calendar);
+    if (vm.exception())
+        return {};
+
+    return object;
+}
+
 // 3.5.5 IsValidISODate ( year, month, day ), https://tc39.es/proposal-temporal/#sec-temporal-isvalidisodate
 bool is_valid_iso_date(i32 year, i32 month, i32 day)
 {

+ 24 - 0
Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.h

@@ -10,6 +10,30 @@
 
 namespace JS::Temporal {
 
+class PlainDate final : public Object {
+    JS_OBJECT(PlainDate, Object);
+
+public:
+    explicit PlainDate(i32 iso_year, i32 iso_month, i32 iso_day, Object& calendar, Object& prototype);
+    virtual ~PlainDate() override = default;
+
+    [[nodiscard]] i32 iso_year() const { return m_iso_year; }
+    [[nodiscard]] i32 iso_month() const { return m_iso_month; }
+    [[nodiscard]] i32 iso_day() const { return m_iso_day; }
+    [[nodiscard]] Object const& calendar() const { return m_calendar; }
+    [[nodiscard]] Object& calendar() { return m_calendar; }
+
+private:
+    virtual void visit_edges(Visitor&) override;
+
+    // 3.4 Properties of Temporal.PlainDate Instances, https://tc39.es/proposal-temporal/#sec-properties-of-temporal-plaindate-instances
+    i32 m_iso_year { 0 };  // [[ISOYear]]
+    i32 m_iso_month { 1 }; // [[ISOMonth]]
+    i32 m_iso_day { 1 };   // [[ISODay]]
+    Object& m_calendar;    // [[Calendar]]
+};
+
+PlainDate* create_temporal_date(GlobalObject&, i32 iso_year, i32 iso_month, i32 iso_day, Object& calendar, FunctionObject* new_target);
 bool is_valid_iso_date(i32 year, i32 month, i32 day);
 
 }

+ 96 - 0
Userland/Libraries/LibJS/Runtime/Temporal/PlainDateConstructor.cpp

@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/Checked.h>
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Temporal/Calendar.h>
+#include <LibJS/Runtime/Temporal/PlainDate.h>
+#include <LibJS/Runtime/Temporal/PlainDateConstructor.h>
+
+namespace JS::Temporal {
+
+// 3.1 The Temporal.PlainDate Constructor, https://tc39.es/proposal-temporal/#sec-temporal-plaindate-constructor
+PlainDateConstructor::PlainDateConstructor(GlobalObject& global_object)
+    : NativeFunction(vm().names.PlainDate.as_string(), *global_object.function_prototype())
+{
+}
+
+void PlainDateConstructor::initialize(GlobalObject& global_object)
+{
+    NativeFunction::initialize(global_object);
+
+    auto& vm = this->vm();
+
+    // 3.2.1 Temporal.PlainDate.prototype, https://tc39.es/proposal-temporal/#sec-temporal-plaindate-prototype
+    define_direct_property(vm.names.prototype, global_object.temporal_plain_date_prototype(), 0);
+
+    define_direct_property(vm.names.length, Value(0), Attribute::Configurable);
+}
+
+// 3.1.1 Temporal.PlainDate ( isoYear, isoMonth, isoDay [ , calendarLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindate
+Value PlainDateConstructor::call()
+{
+    auto& vm = this->vm();
+
+    // 1. If NewTarget is undefined, throw a TypeError exception.
+    vm.throw_exception<TypeError>(global_object(), ErrorType::ConstructorWithoutNew, "Temporal.PlainDate");
+    return {};
+}
+
+// 3.1.1 Temporal.PlainDate ( isoYear, isoMonth, isoDay [ , calendarLike ] ), https://tc39.es/proposal-temporal/#sec-temporal.plaindate
+Value PlainDateConstructor::construct(FunctionObject& new_target)
+{
+    auto& vm = this->vm();
+    auto& global_object = this->global_object();
+
+    // 2. Let y be ? ToIntegerOrInfinity(isoYear).
+    auto y = vm.argument(0).to_integer_or_infinity(global_object);
+    if (vm.exception())
+        return {};
+    // 3. If y is +∞ or -∞, throw a RangeError exception.
+    if (Value(y).is_infinity()) {
+        vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidPlainDate);
+        return {};
+    }
+
+    // 4. Let m be ? ToIntegerOrInfinity(isoMonth).
+    auto m = vm.argument(1).to_integer_or_infinity(global_object);
+    if (vm.exception())
+        return {};
+    // 5. If m is +∞ or -∞, throw a RangeError exception.
+    if (Value(m).is_infinity()) {
+        vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidPlainDate);
+        return {};
+    }
+
+    // 6. Let d be ? ToIntegerOrInfinity(isoDay).
+    auto d = vm.argument(2).to_integer_or_infinity(global_object);
+    if (vm.exception())
+        return {};
+    // 7. If d is +∞ or -∞, throw a RangeError exception.
+    if (Value(d).is_infinity()) {
+        vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidPlainDate);
+        return {};
+    }
+
+    // 8. Let calendar be ? ToTemporalCalendarWithISODefault(calendarLike).
+    auto* calendar = to_temporal_calendar_with_iso_default(global_object, vm.argument(3));
+    if (vm.exception())
+        return {};
+
+    // IMPLEMENTATION DEFINED: This is an optimization that allows us to treat these doubles as normal integers from this point onwards.
+    // This does not change the exposed behaviour as the call to CreateTemporalDate will immediately check that these values are valid
+    // ISO values (for years: -273975 - 273975, for months: 1 - 12, for days: 1 - 31) all of which are subsets of this check.
+    if (!AK::is_within_range<i32>(y) || !AK::is_within_range<i32>(m) || !AK::is_within_range<i32>(d)) {
+        vm.throw_exception<RangeError>(global_object, ErrorType::TemporalInvalidPlainDate);
+        return {};
+    }
+
+    // 9. Return ? CreateTemporalDate(y, m, d, calendar, NewTarget).
+    return create_temporal_date(global_object, y, m, d, *calendar, &new_target);
+}
+
+}

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

@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <LibJS/Runtime/NativeFunction.h>
+
+namespace JS::Temporal {
+
+class PlainDateConstructor final : public NativeFunction {
+    JS_OBJECT(PlainDateConstructor, NativeFunction);
+
+public:
+    explicit PlainDateConstructor(GlobalObject&);
+    virtual void initialize(GlobalObject&) override;
+    virtual ~PlainDateConstructor() 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/PlainDatePrototype.cpp

@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Temporal/PlainDatePrototype.h>
+
+namespace JS::Temporal {
+
+// 3.3 Properties of the Temporal.PlainDate Prototype Object, https://tc39.es/proposal-temporal/#sec-properties-of-the-temporal-plaindate-prototype-object
+PlainDatePrototype::PlainDatePrototype(GlobalObject& global_object)
+    : Object(*global_object.object_prototype())
+{
+}
+
+void PlainDatePrototype::initialize(GlobalObject& global_object)
+{
+    Object::initialize(global_object);
+}
+
+}

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

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

+ 28 - 0
Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp

@@ -41,4 +41,32 @@ BigInt* get_epoch_from_iso_parts(GlobalObject& global_object, i32 year, i32 mont
     return js_bigint(vm.heap(), Crypto::SignedBigInteger::create_from(static_cast<i64>(ms.as_double())).multiplied_by(Crypto::UnsignedBigInteger { 1'000'000 }).plus(Crypto::SignedBigInteger::create_from((i64)microsecond * 1000)).plus(Crypto::SignedBigInteger(nanosecond)));
 }
 
+// -864 * 10^19 - 864 * 10^14
+const auto DATETIME_NANOSECONDS_MIN = "-8640086400000000000000"_sbigint;
+// +864 * 10^19 + 864 * 10^14
+const auto DATETIME_NANOSECONDS_MAX = "8640086400000000000000"_sbigint;
+
+// 5.5.2 ISODateTimeWithinLimits ( year, month, day, hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/proposal-temporal/#sec-temporal-isodatetimewithinlimits
+bool iso_date_time_within_limits(GlobalObject& global_object, i32 year, i32 month, i32 day, i32 hour, i32 minute, i32 second, i32 millisecond, i32 microsecond, i32 nanosecond)
+{
+    // 1. Assert: year, month, day, hour, minute, second, millisecond, microsecond, and nanosecond are integers.
+
+    // 2. Let ns be ! GetEpochFromISOParts(year, month, day, hour, minute, second, millisecond, microsecond, nanosecond).
+    auto ns = get_epoch_from_iso_parts(global_object, year, month, day, hour, minute, second, millisecond, microsecond, nanosecond);
+
+    // 3. If ns ≤ -8.64 × 10^21 - 8.64 × 10^16, then
+    if (ns->big_integer() <= DATETIME_NANOSECONDS_MIN) {
+        // a. Return false.
+        return false;
+    }
+
+    // 4. If ns ≥ 8.64 × 10^21 + 8.64 × 10^16, then
+    if (ns->big_integer() >= DATETIME_NANOSECONDS_MAX) {
+        // a. Return false.
+        return false;
+    }
+    // 5. Return true.
+    return true;
+}
+
 }

+ 1 - 0
Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h

@@ -11,5 +11,6 @@
 namespace JS::Temporal {
 
 BigInt* get_epoch_from_iso_parts(GlobalObject&, i32 year, i32 month, i32 day, i32 hour, i32 minute, i32 second, i32 millisecond, i32 microsecond, i32 nanosecond);
+bool iso_date_time_within_limits(GlobalObject&, i32 year, i32 month, i32 day, i32 hour, i32 minute, i32 second, i32 millisecond, i32 microsecond, i32 nanosecond);
 
 }

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

@@ -9,6 +9,7 @@
 #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/Temporal.h>
 #include <LibJS/Runtime/Temporal/TimeZoneConstructor.h>
 
@@ -31,6 +32,7 @@ void Temporal::initialize(GlobalObject& global_object)
     define_direct_property(vm.names.Calendar, global_object.temporal_calendar_constructor(), attr);
     define_direct_property(vm.names.Duration, global_object.temporal_duration_constructor(), attr);
     define_direct_property(vm.names.Instant, global_object.temporal_instant_constructor(), attr);
+    define_direct_property(vm.names.PlainDate, global_object.temporal_plain_date_constructor(), attr);
     define_direct_property(vm.names.TimeZone, global_object.temporal_time_zone_constructor(), attr);
 }
 

+ 53 - 0
Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainDate/PlainDate.js

@@ -0,0 +1,53 @@
+describe("errors", () => {
+    test("called without new", () => {
+        expect(() => {
+            Temporal.PlainDate();
+        }).toThrowWithMessage(
+            TypeError,
+            "Temporal.PlainDate constructor must be called with 'new'"
+        );
+    });
+
+    test("cannot pass Infinity", () => {
+        expect(() => {
+            new Temporal.PlainDate(Infinity);
+        }).toThrowWithMessage(RangeError, "Invalid plain date");
+        expect(() => {
+            new Temporal.PlainDate(0, Infinity);
+        }).toThrowWithMessage(RangeError, "Invalid plain date");
+        expect(() => {
+            new Temporal.PlainDate(0, 0, Infinity);
+        }).toThrowWithMessage(RangeError, "Invalid plain date");
+        expect(() => {
+            new Temporal.PlainDate(-Infinity);
+        }).toThrowWithMessage(RangeError, "Invalid plain date");
+        expect(() => {
+            new Temporal.PlainDate(0, -Infinity);
+        }).toThrowWithMessage(RangeError, "Invalid plain date");
+        expect(() => {
+            new Temporal.PlainDate(0, 0, -Infinity);
+        }).toThrowWithMessage(RangeError, "Invalid plain date");
+    });
+
+    test("cannot pass invalid ISO month/day", () => {
+        expect(() => {
+            new Temporal.PlainDate(0, 0, 1);
+        }).toThrowWithMessage(RangeError, "Invalid plain date");
+        expect(() => {
+            new Temporal.PlainDate(0, 1, 0);
+        }).toThrowWithMessage(RangeError, "Invalid plain date");
+    });
+});
+
+describe("normal behavior", () => {
+    test("length is 0", () => {
+        expect(Temporal.PlainDate).toHaveLength(0);
+    });
+
+    test("basic functionality", () => {
+        const plainDate = new Temporal.PlainDate(2021, 7, 19);
+        expect(typeof plainDate).toBe("object");
+        expect(plainDate).toBeInstanceOf(Temporal.PlainDate);
+        expect(Object.getPrototypeOf(plainDate)).toBe(Temporal.PlainDate.prototype);
+    });
+});