From 029b6ad1a830169afa2044c84f4a60ae17c2c562 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sat, 23 Nov 2024 18:00:05 -0500 Subject: [PATCH] LibJS: Implement the Temporal.PlainDateTime constructor And the simple Temporal.PlainDateTime.prototype getters, so that the constructed Temporal.PlainDateTime may actually be validated. --- Libraries/LibJS/CMakeLists.txt | 2 + Libraries/LibJS/Forward.h | 1 + Libraries/LibJS/Print.cpp | 12 + Libraries/LibJS/Runtime/Intrinsics.cpp | 2 + .../Runtime/Temporal/AbstractOperations.cpp | 17 +- Libraries/LibJS/Runtime/Temporal/Calendar.cpp | 5 + .../LibJS/Runtime/Temporal/PlainDate.cpp | 19 +- .../LibJS/Runtime/Temporal/PlainDateTime.cpp | 135 +++++++++++ .../LibJS/Runtime/Temporal/PlainDateTime.h | 20 ++ .../Temporal/PlainDateTimeConstructor.cpp | 148 ++++++++++++ .../Temporal/PlainDateTimeConstructor.h | 34 +++ .../Temporal/PlainDateTimePrototype.cpp | 210 ++++++++++++++++++ .../Runtime/Temporal/PlainDateTimePrototype.h | 50 +++++ .../LibJS/Runtime/Temporal/PlainTime.cpp | 18 +- Libraries/LibJS/Runtime/Temporal/Temporal.cpp | 2 + .../PlainDateTime/PlainDateTime.compare.js | 13 ++ .../PlainDateTime/PlainDateTime.from.js | 171 ++++++++++++++ .../Temporal/PlainDateTime/PlainDateTime.js | 53 +++++ .../PlainDateTime.prototype.calendarId.js | 15 ++ .../PlainDateTime.prototype.day.js | 14 ++ .../PlainDateTime.prototype.dayOfWeek.js | 14 ++ .../PlainDateTime.prototype.dayOfYear.js | 14 ++ .../PlainDateTime.prototype.daysInMonth.js | 14 ++ .../PlainDateTime.prototype.daysInWeek.js | 14 ++ .../PlainDateTime.prototype.daysInYear.js | 14 ++ .../PlainDateTime.prototype.era.js | 14 ++ .../PlainDateTime.prototype.eraYear.js | 14 ++ .../PlainDateTime.prototype.hour.js | 14 ++ .../PlainDateTime.prototype.inLeapYear.js | 14 ++ .../PlainDateTime.prototype.microsecond.js | 14 ++ .../PlainDateTime.prototype.millisecond.js | 14 ++ .../PlainDateTime.prototype.minute.js | 14 ++ .../PlainDateTime.prototype.month.js | 14 ++ .../PlainDateTime.prototype.monthCode.js | 14 ++ .../PlainDateTime.prototype.monthsInYear.js | 14 ++ .../PlainDateTime.prototype.nanosecond.js | 14 ++ .../PlainDateTime.prototype.second.js | 14 ++ .../PlainDateTime.prototype.weekOfYear.js | 14 ++ .../PlainDateTime.prototype.year.js | 14 ++ .../PlainDateTime.prototype.yearOfWeek.js | 14 ++ .../PlainTime/PlainTime.prototype.with.js | 2 +- 41 files changed, 1210 insertions(+), 13 deletions(-) create mode 100644 Libraries/LibJS/Runtime/Temporal/PlainDateTimeConstructor.cpp create mode 100644 Libraries/LibJS/Runtime/Temporal/PlainDateTimeConstructor.h create mode 100644 Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.cpp create mode 100644 Libraries/LibJS/Runtime/Temporal/PlainDateTimePrototype.h create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.compare.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.from.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.calendarId.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.day.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.dayOfWeek.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.dayOfYear.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.daysInMonth.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.daysInWeek.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.daysInYear.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.era.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.eraYear.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.hour.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.inLeapYear.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.microsecond.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.millisecond.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.minute.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.month.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.monthCode.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.monthsInYear.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.nanosecond.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.second.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.weekOfYear.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.year.js create mode 100644 Libraries/LibJS/Tests/builtins/Temporal/PlainDateTime/PlainDateTime.prototype.yearOfWeek.js diff --git a/Libraries/LibJS/CMakeLists.txt b/Libraries/LibJS/CMakeLists.txt index c4a5692cece..a32237ebaa3 100644 --- a/Libraries/LibJS/CMakeLists.txt +++ b/Libraries/LibJS/CMakeLists.txt @@ -216,6 +216,8 @@ set(SOURCES Runtime/Temporal/PlainDateConstructor.cpp Runtime/Temporal/PlainDatePrototype.cpp Runtime/Temporal/PlainDateTime.cpp + Runtime/Temporal/PlainDateTimeConstructor.cpp + Runtime/Temporal/PlainDateTimePrototype.cpp Runtime/Temporal/PlainMonthDay.cpp Runtime/Temporal/PlainMonthDayConstructor.cpp Runtime/Temporal/PlainMonthDayPrototype.cpp diff --git a/Libraries/LibJS/Forward.h b/Libraries/LibJS/Forward.h index ac9f0e9e827..8633193c81a 100644 --- a/Libraries/LibJS/Forward.h +++ b/Libraries/LibJS/Forward.h @@ -90,6 +90,7 @@ #define JS_ENUMERATE_TEMPORAL_OBJECTS \ __JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor) \ __JS_ENUMERATE(PlainDate, plain_date, PlainDatePrototype, PlainDateConstructor) \ + __JS_ENUMERATE(PlainDateTime, plain_date_time, PlainDateTimePrototype, PlainDateTimeConstructor) \ __JS_ENUMERATE(PlainMonthDay, plain_month_day, PlainMonthDayPrototype, PlainMonthDayConstructor) \ __JS_ENUMERATE(PlainTime, plain_time, PlainTimePrototype, PlainTimeConstructor) \ __JS_ENUMERATE(PlainYearMonth, plain_year_month, PlainYearMonthPrototype, PlainYearMonthConstructor) diff --git a/Libraries/LibJS/Print.cpp b/Libraries/LibJS/Print.cpp index 746fca19821..fa9ea2468f9 100644 --- a/Libraries/LibJS/Print.cpp +++ b/Libraries/LibJS/Print.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -848,6 +849,15 @@ ErrorOr print_temporal_plain_date(JS::PrintContext& print_context, JS::Tem return {}; } +ErrorOr print_temporal_plain_date_time(JS::PrintContext& print_context, JS::Temporal::PlainDateTime const& plain_date_time, HashTable& seen_objects) +{ + TRY(print_type(print_context, "Temporal.PlainDateTime"sv)); + TRY(js_out(print_context, " \033[34;1m{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:03}{:03}{:03}\033[0m", plain_date_time.iso_date_time().iso_date.year, plain_date_time.iso_date_time().iso_date.month, plain_date_time.iso_date_time().iso_date.day, plain_date_time.iso_date_time().time.hour, plain_date_time.iso_date_time().time.minute, plain_date_time.iso_date_time().time.second, plain_date_time.iso_date_time().time.millisecond, plain_date_time.iso_date_time().time.microsecond, plain_date_time.iso_date_time().time.nanosecond)); + TRY(js_out(print_context, "\n calendar: ")); + TRY(print_value(print_context, JS::PrimitiveString::create(plain_date_time.vm(), plain_date_time.calendar()), seen_objects)); + return {}; +} + ErrorOr print_temporal_plain_month_day(JS::PrintContext& print_context, JS::Temporal::PlainMonthDay const& plain_month_day, HashTable& seen_objects) { TRY(print_type(print_context, "Temporal.PlainMonthDay"sv)); @@ -992,6 +1002,8 @@ ErrorOr print_value(JS::PrintContext& print_context, JS::Value value, Hash return print_temporal_duration(print_context, static_cast(object), seen_objects); if (is(object)) return print_temporal_plain_date(print_context, static_cast(object), seen_objects); + if (is(object)) + return print_temporal_plain_date_time(print_context, static_cast(object), seen_objects); if (is(object)) return print_temporal_plain_month_day(print_context, static_cast(object), seen_objects); if (is(object)) diff --git a/Libraries/LibJS/Runtime/Intrinsics.cpp b/Libraries/LibJS/Runtime/Intrinsics.cpp index 9c2783aa36d..e7b6e92ab81 100644 --- a/Libraries/LibJS/Runtime/Intrinsics.cpp +++ b/Libraries/LibJS/Runtime/Intrinsics.cpp @@ -103,6 +103,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index 6e1b5df7898..d53ea4af59b 100644 --- a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -431,11 +431,18 @@ ThrowCompletionOr get_temporal_relative_to_option(VM& vm, Object con return RelativeTo { .plain_relative_to = static_cast(object), .zoned_relative_to = {} }; } - // FIXME: c. If value has an [[InitializedTemporalDateTime]] internal slot, then - // FIXME: i. Let plainDate be ! CreateTemporalDate(value.[[ISODateTime]].[[ISODate]], value.[[Calendar]]). - // FIXME: ii. Return the Record { [[PlainRelativeTo]]: plainDate, [[ZonedRelativeTo]]: undefined }. + // c. If value has an [[InitializedTemporalDateTime]] internal slot, then + if (is(object)) { + auto const& plain_date_time = static_cast(object); - // FIXME: d. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(value). + // i. Let plainDate be ! CreateTemporalDate(value.[[ISODateTime]].[[ISODate]], value.[[Calendar]]). + auto plain_date = MUST(create_temporal_date(vm, plain_date_time.iso_date_time().iso_date, plain_date_time.calendar())); + + // ii. Return the Record { [[PlainRelativeTo]]: plainDate, [[ZonedRelativeTo]]: undefined }. + return RelativeTo { .plain_relative_to = plain_date, .zoned_relative_to = {} }; + } + + // d. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(value). calendar = TRY(get_temporal_calendar_identifier_with_iso_default(vm, object)); // e. Let fields be ? PrepareCalendarFields(calendar, value, « YEAR, MONTH, MONTH-CODE, DAY », « HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND, OFFSET, TIME-ZONE », «»). @@ -626,6 +633,8 @@ ThrowCompletionOr is_partial_temporal_object(VM& vm, Value value) // FIXME: Add the other types as we define them. if (is(object)) return false; + if (is(object)) + return false; if (is(object)) return false; if (is(object)) diff --git a/Libraries/LibJS/Runtime/Temporal/Calendar.cpp b/Libraries/LibJS/Runtime/Temporal/Calendar.cpp index 34083508d49..7b09860d079 100644 --- a/Libraries/LibJS/Runtime/Temporal/Calendar.cpp +++ b/Libraries/LibJS/Runtime/Temporal/Calendar.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -454,6 +455,8 @@ ThrowCompletionOr to_temporal_calendar_identifier(VM& vm, Value temporal // FIXME: Add the other calendar-holding types as we define them. if (is(temporal_calendar_object)) return static_cast(temporal_calendar_object).calendar(); + if (is(temporal_calendar_object)) + return static_cast(temporal_calendar_object).calendar(); if (is(temporal_calendar_object)) return static_cast(temporal_calendar_object).calendar(); if (is(temporal_calendar_object)) @@ -480,6 +483,8 @@ ThrowCompletionOr get_temporal_calendar_identifier_with_iso_default(VM& // FIXME: Add the other calendar-holding types as we define them. if (is(item)) return static_cast(item).calendar(); + if (is(item)) + return static_cast(item).calendar(); if (is(item)) return static_cast(item).calendar(); if (is(item)) diff --git a/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp b/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp index a81c01b8659..f93324dfcd4 100644 --- a/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp +++ b/Libraries/LibJS/Runtime/Temporal/PlainDate.cpp @@ -83,15 +83,26 @@ ThrowCompletionOr> to_temporal_date(VM& vm, Value item, Value // iii. Return ! CreateTemporalDate(item.[[ISODate]], item.[[Calendar]]). return MUST(create_temporal_date(vm, plain_date.iso_date(), plain_date.calendar())); } + // FIXME: b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then // FIXME: i. Let isoDateTime be GetISODateTimeFor(item.[[TimeZone]], item.[[EpochNanoseconds]]). // FIXME: ii. Let resolvedOptions be ? GetOptionsObject(options). // FIXME: iii. Perform ? GetTemporalOverflowOption(resolvedOptions). // FIXME: iv. Return ! CreateTemporalDate(isoDateTime.[[ISODate]], item.[[Calendar]]). - // FIXME: c. If item has an [[InitializedTemporalDateTime]] internal slot, then - // FIXME: i. Let resolvedOptions be ? GetOptionsObject(options). - // FIXME: ii. Perform ? GetTemporalOverflowOption(resolvedOptions). - // FIXME: iii. Return ! CreateTemporalDate(item.[[ISODateTime]].[[ISODate]], item.[[Calendar]]). + + // c. If item has an [[InitializedTemporalDateTime]] internal slot, then + if (is(object)) { + auto const& plain_date_time = static_cast(object); + + // i. Let resolvedOptions be ? GetOptionsObject(options). + auto resolved_options = TRY(get_options_object(vm, options)); + + // ii. Perform ? GetTemporalOverflowOption(resolvedOptions). + TRY(get_temporal_overflow_option(vm, resolved_options)); + + // iii. Return ! CreateTemporalDate(item.[[ISODateTime]].[[ISODate]], item.[[Calendar]]). + return MUST(create_temporal_date(vm, plain_date_time.iso_date_time().iso_date, plain_date_time.calendar())); + } // d. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(item). auto calendar = TRY(get_temporal_calendar_identifier_with_iso_default(vm, object)); diff --git a/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp b/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp index 001c2a3550a..b314249bacb 100644 --- a/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp +++ b/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp @@ -6,15 +6,26 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include #include #include +#include #include namespace JS::Temporal { +GC_DEFINE_ALLOCATOR(PlainDateTime); + +PlainDateTime::PlainDateTime(ISODateTime const& iso_date_time, String calendar, Object& prototype) + : Object(ConstructWithPrototypeTag::Tag, prototype) + , m_iso_date_time(iso_date_time) + , m_calendar(move(calendar)) +{ +} + // 5.5.3 CombineISODateAndTimeRecord ( isoDate, time ), https://tc39.es/proposal-temporal/#sec-temporal-combineisodateandtimerecord ISODateTime combine_iso_date_and_time_record(ISODate iso_date, Time time) { @@ -68,6 +79,106 @@ ThrowCompletionOr interpret_temporal_date_time_fields(VM& vm, Strin return combine_iso_date_and_time_record(iso_date, time); } +// 5.5.6 ToTemporalDateTime ( item [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaldatetime +ThrowCompletionOr> to_temporal_date_time(VM& vm, Value item, Value options) +{ + // 1. If options is not present, set options to undefined. + + // 2. If item is an Object, then + if (item.is_object()) { + auto const& object = item.as_object(); + + // a. If item has an [[InitializedTemporalDateTime]] internal slot, then + if (is(object)) { + auto const& plain_date_time = static_cast(object); + + // i. Let resolvedOptions be ? GetOptionsObject(options). + auto resolved_options = TRY(get_options_object(vm, options)); + + // ii. Perform ? GetTemporalOverflowOption(resolvedOptions). + TRY(get_temporal_overflow_option(vm, resolved_options)); + + // iii. Return ! CreateTemporalDateTime(item.[[ISODateTime]], item.[[Calendar]]). + return MUST(create_temporal_date_time(vm, plain_date_time.iso_date_time(), plain_date_time.calendar())); + } + + // FIXME: b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then + // FIXME: i. Let isoDateTime be GetISODateTimeFor(item.[[TimeZone]], item.[[EpochNanoseconds]]). + // FIXME: ii. Let resolvedOptions be ? GetOptionsObject(options). + // FIXME: iii. Perform ? GetTemporalOverflowOption(resolvedOptions). + // FIXME: iv. Return ! CreateTemporalDateTime(isoDateTime, item.[[Calendar]]). + + // c. If item has an [[InitializedTemporalDate]] internal slot, then + if (is(object)) { + auto const& plain_date = static_cast(object); + + // i. Let resolvedOptions be ? GetOptionsObject(options). + auto resolved_options = TRY(get_options_object(vm, options)); + + // ii. Perform ? GetTemporalOverflowOption(resolvedOptions). + TRY(get_temporal_overflow_option(vm, resolved_options)); + + // iii. Let isoDateTime be CombineISODateAndTimeRecord(item.[[ISODate]], MidnightTimeRecord()). + auto iso_date_time = combine_iso_date_and_time_record(plain_date.iso_date(), midnight_time_record()); + + // iv. Return ? CreateTemporalDateTime(isoDateTime, item.[[Calendar]]). + return TRY(create_temporal_date_time(vm, iso_date_time, plain_date.calendar())); + } + + // d. Let calendar be ? GetTemporalCalendarIdentifierWithISODefault(item). + auto calendar = TRY(get_temporal_calendar_identifier_with_iso_default(vm, object)); + + // e. Let fields be ? PrepareCalendarFields(calendar, item, « YEAR, MONTH, MONTH-CODE, DAY », « HOUR, MINUTE, SECOND, MILLISECOND, MICROSECOND, NANOSECOND », «»). + static constexpr auto calendar_field_names = to_array({ CalendarField::Year, CalendarField::Month, CalendarField::MonthCode, CalendarField::Day }); + static constexpr auto non_calendar_field_names = to_array({ CalendarField::Hour, CalendarField::Minute, CalendarField::Second, CalendarField::Millisecond, CalendarField::Microsecond, CalendarField::Nanosecond }); + auto fields = TRY(prepare_calendar_fields(vm, calendar, object, calendar_field_names, non_calendar_field_names, CalendarFieldList {})); + + // f. Let resolvedOptions be ? GetOptionsObject(options). + auto resolved_options = TRY(get_options_object(vm, options)); + + // g. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). + auto overflow = TRY(get_temporal_overflow_option(vm, resolved_options)); + + // h. Let result be ? InterpretTemporalDateTimeFields(calendar, fields, overflow). + auto result = TRY(interpret_temporal_date_time_fields(vm, calendar, fields, overflow)); + + // i. Return ? CreateTemporalDateTime(result, calendar). + return TRY(create_temporal_date_time(vm, result, move(calendar))); + } + + // 3. If item is not a String, throw a TypeError exception. + if (!item.is_string()) + return vm.throw_completion(ErrorType::TemporalInvalidPlainDateTime); + + // 4. Let result be ? ParseISODateTime(item, « TemporalDateTimeString[~Zoned] »). + auto result = TRY(parse_iso_date_time(vm, item.as_string().utf8_string_view(), { { Production::TemporalDateTimeString } })); + + // 5. If result.[[Time]] is START-OF-DAY, let time be MidnightTimeRecord(); else let time be result.[[Time]]. + auto time = result.time.has() ? midnight_time_record() : result.time.get