diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index c9e29dcfae0..66fde5f5773 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -237,6 +237,7 @@ set(SOURCES Runtime/Temporal/Temporal.cpp Runtime/Temporal/TimeZone.cpp Runtime/Temporal/TimeZoneConstructor.cpp + Runtime/Temporal/TimeZoneMethods.cpp Runtime/Temporal/TimeZonePrototype.cpp Runtime/Temporal/ZonedDateTime.cpp Runtime/Temporal/ZonedDateTimeConstructor.cpp diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index c1c070ed6e9..df6d0ec9b04 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -292,6 +292,7 @@ struct CalendarMethods; struct DurationRecord; struct DateDurationRecord; struct TimeDurationRecord; +struct TimeZoneMethods; struct PartialDurationRecord; }; diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneMethods.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneMethods.cpp new file mode 100644 index 00000000000..0fbc568fab2 --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneMethods.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2024, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace JS::Temporal { + +// 11.5.2 CreateTimeZoneMethodsRecord ( timeZone, methods ), https://tc39.es/proposal-temporal/#sec-temporal-createtimezonemethodsrecord +ThrowCompletionOr create_time_zone_methods_record(VM& vm, Variant> time_zone, ReadonlySpan methods) +{ + // 1. Let record be the Time Zone Methods Record { [[Receiver]]: timeZone, [[GetOffsetNanosecondsFor]]: undefined, [[GetPossibleInstantsFor]]: undefined }. + TimeZoneMethods record { + .receiver = move(time_zone), + .get_offset_nanoseconds_for = nullptr, + .get_possible_instants_for = nullptr, + }; + + // 2. For each element methodName in methods, do + for (TimeZoneMethod method_name : methods) { + // a. Perform ? TimeZoneMethodsRecordLookup(record, methodName). + TRY(time_zone_methods_record_lookup(vm, record, method_name)); + } + + // 3. Return record. + return record; +} + +// 11.5.3 TimeZoneMethodsRecordLookup ( timeZoneRec, methodName ), https://tc39.es/proposal-temporal/#sec-temporal-timezonemethodsrecordlookup +ThrowCompletionOr time_zone_methods_record_lookup(VM& vm, TimeZoneMethods& time_zone_record, TimeZoneMethod method_name) +{ + auto& realm = *vm.current_realm(); + + // 1. Assert: TimeZoneMethodsRecordHasLookedUp(timeZoneRec, methodName) is false. + // 2. If methodName is GET-OFFSET-NANOSECONDS-FOR, then + // a. If timeZoneRec.[[Receiver]] is a String, then + // i. Set timeZoneRec.[[GetOffsetNanosecondsFor]] to %Temporal.TimeZone.prototype.getOffsetNanosecondsFor%. + // b. Else, + // i. Set timeZoneRec.[[GetOffsetNanosecondsFor]] to ? GetMethod(timeZoneRec.[[Receiver]], "getOffsetNanosecondsFor"). + // ii. If timeZoneRec.[[GetOffsetNanosecondsFor]] is undefined, throw a TypeError exception. + // 3. Else if methodName is GET-POSSIBLE-INSTANTS-FOR, then + // a. If timeZoneRec.[[Receiver]] is a String, then + // i. Set timeZoneRec.[[GetPossibleInstantsFor]] to %Temporal.TimeZone.prototype.getPossibleInstantsFor%. + // b. Else, + // i. Set timeZoneRec.[[GetPossibleInstantsFor]] to ? GetMethod(timeZoneRec.[[Receiver]], "getPossibleInstantsFor"). + // ii. If timeZoneRec.[[GetPossibleInstantsFor]] is undefined, throw a TypeError exception. + switch (method_name) { +#define __JS_ENUMERATE(PascalName, camelName, snake_name) \ + case TimeZoneMethod::PascalName: { \ + VERIFY(!time_zone_record.snake_name); \ + if (time_zone_record.receiver.has()) { \ + const auto& time_zone_prototype = *realm.intrinsics().temporal_time_zone_prototype(); \ + time_zone_record.snake_name = time_zone_prototype.get_without_side_effects(vm.names.camelName).as_function(); \ + } else { \ + Value time_zone { time_zone_record.receiver.get>() }; \ + time_zone_record.snake_name = TRY(time_zone.get_method(vm, vm.names.camelName)); \ + if (!time_zone_record.snake_name) \ + return vm.throw_completion(ErrorType::IsUndefined, #camelName##sv); \ + } \ + break; \ + } + JS_ENUMERATE_TIME_ZONE_METHODS +#undef __JS_ENUMERATE + } + // 4. Return UNUSED. + return {}; +} + +// 11.5.4 TimeZoneMethodsRecordHasLookedUp ( timeZoneRec, methodName ), https://tc39.es/proposal-temporal/#sec-temporal-timezonemethodsrecordhaslookedup +bool time_zone_methods_record_has_looked_up(TimeZoneMethods const& time_zone_record, TimeZoneMethod method_name) +{ + // 1. If methodName is GET-OFFSET-NANOSECONDS-FOR, then + // a. Let method be timeZoneRec.[[GetOffsetNanosecondsFor]]. + // 2. Else if methodName is GET-POSSIBLE-INSTANTS-FOR, then + // a. Let method be timeZoneRec.[[GetPossibleInstantsFor]]. + // 3. If method is undefined, return false. + // 4. Return true. + switch (method_name) { +#define __JS_ENUMERATE(PascalName, camelName, snake_name) \ + case TimeZoneMethod::PascalName: { \ + return time_zone_record.snake_name != nullptr; \ + } + JS_ENUMERATE_TIME_ZONE_METHODS +#undef __JS_ENUMERATE + } + VERIFY_NOT_REACHED(); +} + +// 11.5.5 TimeZoneMethodsRecordIsBuiltin ( timeZoneRec ), https://tc39.es/proposal-temporal/#sec-temporal-timezonemethodsrecordisbuiltin +bool time_zone_methods_record_is_builtin(TimeZoneMethods const& time_zone_record) +{ + // 1. If timeZoneRec.[[Receiver]] is a String, return true. + if (time_zone_record.receiver.has()) + return true; + + // 2. Return false. + return false; +} + +// 11.5.6 TimeZoneMethodsRecordCall ( timeZoneRec, methodName, arguments ), https://tc39.es/proposal-temporal/#sec-temporal-timezonemethodsrecordcall +ThrowCompletionOr time_zone_methods_record_call(VM& vm, TimeZoneMethods const& time_zone_record, TimeZoneMethod method_name, ReadonlySpan arguments) +{ + // 1. Assert: TimeZoneMethodsRecordHasLookedUp(timeZoneRec, methodName) is true. + VERIFY(time_zone_methods_record_has_looked_up(time_zone_record, method_name)); + + // 2. Let receiver be timeZoneRec.[[Receiver]]. + // 3. If TimeZoneMethodsRecordIsBuiltin(timeZoneRec) is true, then + // a. Set receiver to ! CreateTemporalTimeZone(timeZoneRec.[[Receiver]]). + GCPtr receiver; + if (time_zone_methods_record_is_builtin(time_zone_record)) + receiver = MUST(create_temporal_time_zone(vm, time_zone_record.receiver.get())); + else + receiver = time_zone_record.receiver.get>(); + + // 4. If methodName is GET-OFFSET-NANOSECONDS-FOR, then + // a. Return ? Call(timeZoneRec.[[GetOffsetNanosecondsFor]], receiver, arguments). + // 5. If methodName is GET-POSSIBLE-INSTANTS-FOR, then + // a. Return ? Call(timeZoneRec.[[GetPossibleInstantsFor]], receiver, arguments). + switch (method_name) { +#define __JS_ENUMERATE(PascalName, camelName, snake_name) \ + case TimeZoneMethod::PascalName: { \ + return TRY(call(vm, time_zone_record.snake_name, receiver, arguments)); \ + } + JS_ENUMERATE_TIME_ZONE_METHODS +#undef __JS_ENUMERATE + } + VERIFY_NOT_REACHED(); +} + +} diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneMethods.h b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneMethods.h new file mode 100644 index 00000000000..7eb309d84cc --- /dev/null +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZoneMethods.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024, Shannon Booth + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace JS::Temporal { + +// 11.5.1 Time Zone Methods Records, https://tc39.es/proposal-temporal/#sec-temporal-time-zone-methods-records +struct TimeZoneMethods { + // The time zone object, or a string indicating a built-in time zone. + Variant> receiver; // [[Reciever]] + + // The time zone's getOffsetNanosecondsFor method. For a built-in time zone this is always %Temporal.TimeZone.prototype.getOffsetNanosecondsFor%. + GCPtr get_offset_nanoseconds_for; // [[GetOffsetNanosecondsFor]] + + // The time zone's getPossibleInstantsFor method. For a built-in time zone this is always %Temporal.TimeZone.prototype.getPossibleInstantsFor%. + GCPtr get_possible_instants_for; // [[GetPossibleInstantsFor]] +}; + +#define JS_ENUMERATE_TIME_ZONE_METHODS \ + __JS_ENUMERATE(GetOffsetNanosecondsFor, getOffsetNanosecondsFor, get_offset_nanoseconds_for) \ + __JS_ENUMERATE(GetPossibleInstantsFor, getPossibleInstantsFor, get_possible_instants_for) + +enum class TimeZoneMethod { +#define __JS_ENUMERATE(PascalName, camelName, snake_name) \ + PascalName, + JS_ENUMERATE_TIME_ZONE_METHODS +#undef __JS_ENUMERATE +}; + +ThrowCompletionOr time_zone_methods_record_lookup(VM&, TimeZoneMethods&, TimeZoneMethod); +ThrowCompletionOr create_time_zone_methods_record(VM&, Variant> time_zone, ReadonlySpan); +bool time_zone_methods_record_has_looked_up(TimeZoneMethods const&, TimeZoneMethod); +bool time_zone_methods_record_is_builtin(TimeZoneMethods const&); +ThrowCompletionOr time_zone_methods_record_call(VM&, TimeZoneMethods const&, TimeZoneMethod, ReadonlySpan arguments); + +}