From 5fde02184d20649a7da1ba0c8792927328037a0d Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Sat, 30 Oct 2021 10:22:19 +0200 Subject: LibJS: Implement Temporal.TimeZone.prototype.getPossibleInstantsFor() --- .../Libraries/LibJS/Runtime/CommonPropertyNames.h | 1 + .../Libraries/LibJS/Runtime/Temporal/TimeZone.cpp | 14 +++++++ .../Libraries/LibJS/Runtime/Temporal/TimeZone.h | 2 + .../LibJS/Runtime/Temporal/TimeZonePrototype.cpp | 43 ++++++++++++++++++++++ .../LibJS/Runtime/Temporal/TimeZonePrototype.h | 1 + .../TimeZone.prototype.getPossibleInstantsFor.js | 31 ++++++++++++++++ 6 files changed, 92 insertions(+) create mode 100644 Userland/Libraries/LibJS/Tests/builtins/Temporal/TimeZone/TimeZone.prototype.getPossibleInstantsFor.js diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 069c356071..f59917044a 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -212,6 +212,7 @@ namespace JS { P(getOwnPropertyNames) \ P(getOwnPropertySymbols) \ P(getPlainDateTimeFor) \ + P(getPossibleInstantsFor) \ P(getPrototypeOf) \ P(getSeconds) \ P(getTime) \ diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp index fc5e801678..13205ebd06 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp @@ -154,6 +154,20 @@ ISODateTime get_iso_parts_from_epoch(BigInt const& epoch_nanoseconds) return { .year = year, .month = month, .day = day, .hour = hour, .minute = minute, .second = second, .millisecond = millisecond, .microsecond = microsecond, .nanosecond = nanosecond }; } +// 11.6.4 GetIANATimeZoneEpochValue ( timeZoneIdentifier, year, month, day, hour, minute, second, millisecond, microsecond, nanosecond ), https://tc39.es/proposal-temporal/#sec-temporal-getianatimezoneepochvalue +MarkedValueList get_iana_time_zone_epoch_value(GlobalObject& global_object, StringView time_zone_identifier, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond) +{ + // The abstract operation GetIANATimeZoneEpochValue is an implementation-defined algorithm that returns a List of integers. Each integer in the List represents a number of nanoseconds since the Unix epoch in UTC that may correspond to the given calendar date and wall-clock time in the IANA time zone identified by timeZoneIdentifier. + // When the input represents a local time repeating multiple times at a negative time zone transition (e.g. when the daylight saving time ends or the time zone offset is decreased due to a time zone rule change), the returned List will have more than one element. When the input represents a skipped local time at a positive time zone transition (e.g. when the daylight saving time starts or the time zone offset is increased due to a time zone rule change), the returned List will be empty. Otherwise, the returned List will have one element. + + VERIFY(time_zone_identifier == "UTC"sv); + // FIXME: MarkedValueList for T != Value would still be nice. + auto& vm = global_object.vm(); + auto list = MarkedValueList { vm.heap() }; + list.append(get_epoch_from_iso_parts(global_object, year, month, day, hour, minute, second, millisecond, microsecond, nanosecond)); + return list; +} + // 11.6.5 GetIANATimeZoneOffsetNanoseconds ( epochNanoseconds, timeZoneIdentifier ), https://tc39.es/proposal-temporal/#sec-temporal-getianatimezoneoffsetnanoseconds i64 get_iana_time_zone_offset_nanoseconds([[maybe_unused]] BigInt const& epoch_nanoseconds, [[maybe_unused]] String const& time_zone_identifier) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h index 8923d7fc93..93537bfd1e 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include #include @@ -38,6 +39,7 @@ String default_time_zone(); ThrowCompletionOr parse_temporal_time_zone(GlobalObject&, String const&); ThrowCompletionOr create_temporal_time_zone(GlobalObject&, String const& identifier, FunctionObject const* new_target = nullptr); ISODateTime get_iso_parts_from_epoch(BigInt const& epoch_nanoseconds); +MarkedValueList get_iana_time_zone_epoch_value(GlobalObject&, StringView time_zone_identifier, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond); i64 get_iana_time_zone_offset_nanoseconds(BigInt const& epoch_nanoseconds, String const& time_zone_identifier); ThrowCompletionOr parse_time_zone_offset_string(GlobalObject&, String const&); String format_time_zone_offset_string(double offset_nanoseconds); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.cpp index 9e11fe628f..b76255bdae 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -31,6 +32,7 @@ void TimeZonePrototype::initialize(GlobalObject& global_object) define_native_function(vm.names.getOffsetNanosecondsFor, get_offset_nanoseconds_for, 1, attr); define_native_function(vm.names.getOffsetStringFor, get_offset_string_for, 1, attr); define_native_function(vm.names.getPlainDateTimeFor, get_plain_date_time_for, 1, attr); + define_native_function(vm.names.getPossibleInstantsFor, get_possible_instants_for, 1, attr); define_native_function(vm.names.toString, to_string, 0, attr); define_native_function(vm.names.toJSON, to_json, 0, attr); @@ -97,6 +99,47 @@ JS_DEFINE_NATIVE_FUNCTION(TimeZonePrototype::get_plain_date_time_for) return TRY(builtin_time_zone_get_plain_date_time_for(global_object, time_zone, *instant, *calendar)); } +// 11.4.8 Temporal.TimeZone.prototype.getPossibleInstantsFor ( dateTime ), https://tc39.es/proposal-temporal/#sec-temporal.timezone.prototype.getpossibleinstantsfor +JS_DEFINE_NATIVE_FUNCTION(TimeZonePrototype::get_possible_instants_for) +{ + // 1. Let timeZone be the this value. + // 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimezone]]). + auto* time_zone = TRY(typed_this_object(global_object)); + + // 3. Set dateTime to ? ToTemporalDateTime(dateTime). + auto* date_time = TRY(to_temporal_date_time(global_object, vm.argument(0))); + + // 4. If timeZone.[[OffsetNanoseconds]] is not undefined, then + if (time_zone->offset_nanoseconds().has_value()) { + // a. Let epochNanoseconds be ! GetEpochFromISOParts(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]]). + auto* epoch_nanoseconds = get_epoch_from_iso_parts(global_object, date_time->iso_year(), date_time->iso_month(), date_time->iso_day(), date_time->iso_hour(), date_time->iso_minute(), date_time->iso_second(), date_time->iso_millisecond(), date_time->iso_microsecond(), date_time->iso_nanosecond()); + + // b. Let instant be ! CreateTemporalInstant(ℤ(epochNanoseconds − timeZone.[[OffsetNanoseconds]])). + auto* instant = MUST(create_temporal_instant(global_object, *js_bigint(vm, epoch_nanoseconds->big_integer().minus(Crypto::SignedBigInteger::create_from(*time_zone->offset_nanoseconds()))))); + + // c. Return ! CreateArrayFromList(« instant »). + return Array::create_from(global_object, { instant }); + } + + // 5. Let possibleEpochNanoseconds be ? GetIANATimeZoneEpochValue(timeZone.[[Identifier]], dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]]). + auto possible_epoch_nanoseconds = get_iana_time_zone_epoch_value(global_object, time_zone->identifier(), date_time->iso_year(), date_time->iso_month(), date_time->iso_day(), date_time->iso_hour(), date_time->iso_minute(), date_time->iso_second(), date_time->iso_millisecond(), date_time->iso_microsecond(), date_time->iso_nanosecond()); + + // 6. Let possibleInstants be a new empty List. + auto possible_instants = MarkedValueList { vm.heap() }; + + // 7. For each value epochNanoseconds in possibleEpochNanoseconds, do + for (auto& epoch_nanoseconds : possible_epoch_nanoseconds) { + // a. Let instant be ! CreateTemporalInstant(epochNanoseconds). + auto* instant = MUST(create_temporal_instant(global_object, epoch_nanoseconds.as_bigint())); + + // b. Append instant to possibleInstants. + possible_instants.append(instant); + } + + // 8. Return ! CreateArrayFromList(possibleInstants). + return Array::create_from(global_object, possible_instants); +} + // 11.4.11 Temporal.TimeZone.prototype.toString ( ), https://tc39.es/proposal-temporal/#sec-temporal.timezone.prototype.tostring JS_DEFINE_NATIVE_FUNCTION(TimeZonePrototype::to_string) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.h b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.h index 3137cb5ee3..f41e1193ba 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.h @@ -24,6 +24,7 @@ private: JS_DECLARE_NATIVE_FUNCTION(get_offset_nanoseconds_for); JS_DECLARE_NATIVE_FUNCTION(get_offset_string_for); JS_DECLARE_NATIVE_FUNCTION(get_plain_date_time_for); + JS_DECLARE_NATIVE_FUNCTION(get_possible_instants_for); JS_DECLARE_NATIVE_FUNCTION(to_string); JS_DECLARE_NATIVE_FUNCTION(to_json); }; diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/TimeZone/TimeZone.prototype.getPossibleInstantsFor.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/TimeZone/TimeZone.prototype.getPossibleInstantsFor.js new file mode 100644 index 0000000000..d9587c9d20 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Temporal/TimeZone/TimeZone.prototype.getPossibleInstantsFor.js @@ -0,0 +1,31 @@ +describe("correct behavior", () => { + test("length is 1", () => { + expect(Temporal.TimeZone.prototype.getPossibleInstantsFor).toHaveLength(1); + }); + + test("basic functionality", () => { + const timeZone = new Temporal.TimeZone("UTC"); + const plainDateTime = new Temporal.PlainDateTime(2021, 7, 6, 18, 14, 47); + const possibleInstants = timeZone.getPossibleInstantsFor(plainDateTime); + expect(possibleInstants).toBeInstanceOf(Array); + expect(possibleInstants).toHaveLength(1); + expect(possibleInstants[0].epochNanoseconds).toBe(1625595287000000000n); + }); + + test("custom offset", () => { + const timeZone = new Temporal.TimeZone("+01:30"); + const plainDateTime = new Temporal.PlainDateTime(2021, 7, 6, 18, 14, 47); + const possibleInstants = timeZone.getPossibleInstantsFor(plainDateTime); + expect(possibleInstants).toBeInstanceOf(Array); + expect(possibleInstants).toHaveLength(1); + expect(possibleInstants[0].epochNanoseconds).toBe(1625589887000000000n); + }); +}); + +describe("errors", () => { + test("this value must be a Temporal.TimeZone object", () => { + expect(() => { + Temporal.TimeZone.prototype.getPossibleInstantsFor.call("foo"); + }).toThrowWithMessage(TypeError, "Not an object of type Temporal.TimeZone"); + }); +}); -- cgit v1.2.3