diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index 3a75eb8f3e1..53c3ecd4b43 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/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 diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index 977c8960ed7..62c59cab964 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/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 \ diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 23de5d001d1..f8976b6b060 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/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) \ diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index 9f21032df36..08d9e6e69a0 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/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") \ diff --git a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp index 3a1fad31d9b..1e3c12402a7 100644 --- a/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/GlobalObject.cpp @@ -75,6 +75,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index dafc0302a8e..1e0e76b025e 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -423,6 +423,27 @@ Optional 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 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 parse_temporal_duration_string(GlobalObject& global_object, String const& iso_string) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h index c02ccf196b3..d7667ce9514 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h @@ -60,6 +60,7 @@ Optional to_smallest_temporal_unit(GlobalObject&, Object& normalized_opt BigInt* round_number_to_increment(GlobalObject&, BigInt const&, u64 increment, String const& rounding_mode); Optional parse_iso_date_time(GlobalObject&, String const& iso_string); Optional parse_temporal_instant_string(GlobalObject&, String const& iso_string); +String parse_temporal_calendar_string(GlobalObject&, String const& iso_string); Optional parse_temporal_duration_string(GlobalObject&, String const& iso_string); Optional parse_temporal_time_zone_string(GlobalObject&, String const& iso_string); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp index aa53db94bbb..f839eedd64d 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp @@ -6,8 +6,10 @@ #include #include +#include #include #include +#include #include 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(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.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(temporal_calendar_like_object)) { + // i. Return temporalCalendarLike.[[Calendar]]. + return &static_cast(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) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h index e7b8ba18390..d729fdba92f 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h +++ b/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); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp index bf18c9a5c23..efe78699f87 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp @@ -4,12 +4,68 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include #include #include -#include +#include +#include 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(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(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(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) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.h index 8471958743d..fde86d4bd3c 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDate.h +++ b/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); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateConstructor.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateConstructor.cpp new file mode 100644 index 00000000000..73e7545467f --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateConstructor.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2021, Idan Horowitz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +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(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(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(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(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(y) || !AK::is_within_range(m) || !AK::is_within_range(d)) { + vm.throw_exception(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); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateConstructor.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateConstructor.h new file mode 100644 index 00000000000..830330412de --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateConstructor.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Idan Horowitz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +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; } +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDatePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDatePrototype.cpp new file mode 100644 index 00000000000..354719d72bb --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDatePrototype.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021, Idan Horowitz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +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); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDatePrototype.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDatePrototype.h new file mode 100644 index 00000000000..88c5fbd4b92 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDatePrototype.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021, Idan Horowitz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +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; +}; + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp index 3bb99eded65..b047d0b19ca 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp +++ b/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(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; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h index 77f4b0a46c2..f5b6452464e 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h +++ b/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); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp index 34c0ecda944..4fa31a57d9f 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Temporal.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -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); } diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainDate/PlainDate.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/PlainDate/PlainDate.js new file mode 100644 index 00000000000..70e43991192 --- /dev/null +++ b/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); + }); +});