diff --git a/Libraries/LibJS/CMakeLists.txt b/Libraries/LibJS/CMakeLists.txt index 8638f11d4c1..119fa9a6b12 100644 --- a/Libraries/LibJS/CMakeLists.txt +++ b/Libraries/LibJS/CMakeLists.txt @@ -232,6 +232,9 @@ set(SOURCES Runtime/Temporal/PlainYearMonthPrototype.cpp Runtime/Temporal/Temporal.cpp Runtime/Temporal/TimeZone.cpp + Runtime/Temporal/ZonedDateTime.cpp + Runtime/Temporal/ZonedDateTimeConstructor.cpp + Runtime/Temporal/ZonedDateTimePrototype.cpp Runtime/TypedArray.cpp Runtime/TypedArrayConstructor.cpp Runtime/TypedArrayPrototype.cpp diff --git a/Libraries/LibJS/Forward.h b/Libraries/LibJS/Forward.h index 1f573fdaf16..4f6e37fb234 100644 --- a/Libraries/LibJS/Forward.h +++ b/Libraries/LibJS/Forward.h @@ -87,14 +87,15 @@ __JS_ENUMERATE(RelativeTimeFormat, relative_time_format, RelativeTimeFormatPrototype, RelativeTimeFormatConstructor) \ __JS_ENUMERATE(Segmenter, segmenter, SegmenterPrototype, SegmenterConstructor) -#define JS_ENUMERATE_TEMPORAL_OBJECTS \ - __JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor) \ - __JS_ENUMERATE(Instant, instant, InstantPrototype, InstantConstructor) \ - __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) +#define JS_ENUMERATE_TEMPORAL_OBJECTS \ + __JS_ENUMERATE(Duration, duration, DurationPrototype, DurationConstructor) \ + __JS_ENUMERATE(Instant, instant, InstantPrototype, InstantConstructor) \ + __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) \ + __JS_ENUMERATE(ZonedDateTime, zoned_date_time, ZonedDateTimePrototype, ZonedDateTimeConstructor) #define JS_ENUMERATE_BUILTIN_NAMESPACE_OBJECTS \ __JS_ENUMERATE(AtomicsObject, atomics) \ diff --git a/Libraries/LibJS/Print.cpp b/Libraries/LibJS/Print.cpp index 66aacd7987a..24c449cc227 100644 --- a/Libraries/LibJS/Print.cpp +++ b/Libraries/LibJS/Print.cpp @@ -54,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -893,6 +894,18 @@ ErrorOr print_temporal_plain_year_month(JS::PrintContext& print_context, J return {}; } +ErrorOr print_temporal_zoned_date_time(JS::PrintContext& print_context, JS::Temporal::ZonedDateTime const& zoned_date_time, HashTable& seen_objects) +{ + TRY(print_type(print_context, "Temporal.ZonedDateTime"sv)); + TRY(js_out(print_context, "\n epochNanoseconds: ")); + TRY(print_value(print_context, zoned_date_time.epoch_nanoseconds(), seen_objects)); + TRY(js_out(print_context, "\n timeZone: ")); + TRY(print_value(print_context, JS::PrimitiveString::create(zoned_date_time.vm(), zoned_date_time.time_zone()), seen_objects)); + TRY(js_out(print_context, "\n calendar: ")); + TRY(print_value(print_context, JS::PrimitiveString::create(zoned_date_time.vm(), zoned_date_time.calendar()), seen_objects)); + return {}; +} + ErrorOr print_boolean_object(JS::PrintContext& print_context, JS::BooleanObject const& boolean_object, HashTable& seen_objects) { TRY(print_type(print_context, "Boolean"sv)); @@ -1022,6 +1035,8 @@ ErrorOr print_value(JS::PrintContext& print_context, JS::Value value, Hash return print_temporal_plain_time(print_context, static_cast(object), seen_objects); if (is(object)) return print_temporal_plain_year_month(print_context, static_cast(object), seen_objects); + if (is(object)) + return print_temporal_zoned_date_time(print_context, static_cast(object), seen_objects); return print_object(print_context, object, seen_objects); } diff --git a/Libraries/LibJS/Runtime/Intrinsics.cpp b/Libraries/LibJS/Runtime/Intrinsics.cpp index 451a60cfd6c..613646c02ae 100644 --- a/Libraries/LibJS/Runtime/Intrinsics.cpp +++ b/Libraries/LibJS/Runtime/Intrinsics.cpp @@ -114,6 +114,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 6087da701ee..addbd5cb440 100644 --- a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -21,6 +21,7 @@ #include #include #include +#include namespace JS::Temporal { @@ -100,6 +101,29 @@ ThrowCompletionOr get_temporal_overflow_option(VM& vm, Object const& o return Overflow::Reject; } +// 13.7 GetTemporalDisambiguationOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporaldisambiguationoption +ThrowCompletionOr get_temporal_disambiguation_option(VM& vm, Object const& options) +{ + // 1. Let stringValue be ? GetOption(options, "disambiguation", STRING, « "compatible", "earlier", "later", "reject" », "compatible"). + auto string_value = TRY(get_option(vm, options, vm.names.disambiguation, OptionType::String, { "compatible"sv, "earlier"sv, "later"sv, "reject"sv }, "compatible"sv)); + auto string_view = string_value.as_string().utf8_string_view(); + + // 2. If stringValue is "compatible", return COMPATIBLE. + if (string_view == "compatible"sv) + return Disambiguation::Compatible; + + // 3. If stringValue is "earlier", return EARLIER. + if (string_view == "earlier"sv) + return Disambiguation::Earlier; + + // 4. If stringValue is "later", return LATER. + if (string_view == "later"sv) + return Disambiguation::Later; + + // 5. Return REJECT. + return Disambiguation::Reject; +} + // 13.8 NegateRoundingMode ( roundingMode ), https://tc39.es/proposal-temporal/#sec-temporal-negateroundingmode RoundingMode negate_rounding_mode(RoundingMode rounding_mode) { @@ -123,22 +147,64 @@ RoundingMode negate_rounding_mode(RoundingMode rounding_mode) return rounding_mode; } +// 13.9 GetTemporalOffsetOption ( options, fallback ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporaloffsetoption +ThrowCompletionOr get_temporal_offset_option(VM& vm, Object const& options, OffsetOption fallback) +{ + auto string_fallback = [&]() { + switch (fallback) { + // 1. If fallback is PREFER, let stringFallback be "prefer". + case OffsetOption::Prefer: + return "prefer"sv; + // 2. Else if fallback is USE, let stringFallback be "use". + case OffsetOption::Use: + return "use"sv; + // 3. Else if fallback is IGNORE, let stringFallback be "ignore". + case OffsetOption::Ignore: + return "ignore"sv; + // 4. Else, let stringFallback be "reject". + case OffsetOption::Reject: + return "reject"sv; + } + VERIFY_NOT_REACHED(); + }(); + + // 5. Let stringValue be ? GetOption(options, "offset", STRING, « "prefer", "use", "ignore", "reject" », stringFallback). + auto string_value = TRY(get_option(vm, options, vm.names.offset, OptionType::String, { "prefer"sv, "use"sv, "ignore"sv, "reject"sv }, string_fallback)); + auto string_view = string_value.as_string().utf8_string_view(); + + // 6. If stringValue is "prefer", return PREFER. + if (string_view == "prefer"sv) + return OffsetOption::Prefer; + + // 7. If stringValue is "use", return USE. + if (string_view == "use"sv) + return OffsetOption::Use; + + // 8. If stringValue is "ignore", return IGNORE. + if (string_view == "ignore"sv) + return OffsetOption::Ignore; + + // 9. Return REJECT. + return OffsetOption::Reject; +} + // 13.10 GetTemporalShowCalendarNameOption ( options ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalshowcalendarnameoption ThrowCompletionOr get_temporal_show_calendar_name_option(VM& vm, Object const& options) { // 1. Let stringValue be ? GetOption(options, "calendarName", STRING, « "auto", "always", "never", "critical" », "auto"). auto string_value = TRY(get_option(vm, options, vm.names.calendarName, OptionType::String, { "auto"sv, "always"sv, "never"sv, "critical"sv }, "auto"sv)); + auto string_view = string_value.as_string().utf8_string_view(); // 2. If stringValue is "always", return ALWAYS. - if (string_value.as_string().utf8_string_view() == "always"sv) + if (string_view == "always"sv) return ShowCalendar::Always; // 3. If stringValue is "never", return NEVER. - if (string_value.as_string().utf8_string_view() == "never"sv) + if (string_view == "never"sv) return ShowCalendar::Never; // 4. If stringValue is "critical", return CRITICAL. - if (string_value.as_string().utf8_string_view() == "critical"sv) + if (string_view == "critical"sv) return ShowCalendar::Critical; // 5. Return AUTO. @@ -630,7 +696,6 @@ ThrowCompletionOr is_partial_temporal_object(VM& vm, Value value) // 2. If value has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], // [[InitializedTemporalTime]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal // slot, return false. - // FIXME: Add the other types as we define them. if (is(object)) return false; if (is(object)) @@ -641,6 +706,8 @@ ThrowCompletionOr is_partial_temporal_object(VM& vm, Value value) return false; if (is(object)) return false; + if (is(object)) + return false; // 3. Let calendarProperty be ? Get(value, "calendar"). auto calendar_property = TRY(object.get(vm.names.calendar)); diff --git a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h index 6ccd2acb898..89afe895af4 100644 --- a/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h +++ b/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h @@ -33,11 +33,25 @@ enum class DateType { YearMonth, }; +enum class Disambiguation { + Compatible, + Earlier, + Later, + Reject, +}; + enum class DurationOperation { Since, Until, }; +enum class OffsetOption { + Prefer, + Use, + Ignore, + Reject, +}; + enum class Overflow { Constrain, Reject, @@ -144,7 +158,9 @@ double iso_date_to_epoch_days(double year, double month, double date); double epoch_days_to_epoch_ms(double day, double time); ThrowCompletionOr check_iso_days_range(VM&, ISODate const&); ThrowCompletionOr get_temporal_overflow_option(VM&, Object const& options); +ThrowCompletionOr get_temporal_disambiguation_option(VM&, Object const& options); RoundingMode negate_rounding_mode(RoundingMode); +ThrowCompletionOr get_temporal_offset_option(VM&, Object const& options, OffsetOption fallback); ThrowCompletionOr get_temporal_show_calendar_name_option(VM&, Object const& options); ThrowCompletionOr validate_temporal_rounding_increment(VM&, u64 increment, u64 dividend, bool inclusive); ThrowCompletionOr get_temporal_fractional_second_digits_option(VM&, Object const& options); diff --git a/Libraries/LibJS/Runtime/Temporal/Calendar.cpp b/Libraries/LibJS/Runtime/Temporal/Calendar.cpp index f65e26192f6..b88b063bc06 100644 --- a/Libraries/LibJS/Runtime/Temporal/Calendar.cpp +++ b/Libraries/LibJS/Runtime/Temporal/Calendar.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -452,7 +453,6 @@ ThrowCompletionOr to_temporal_calendar_identifier(VM& vm, Value temporal // [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] // internal slot, then // i. Return temporalCalendarLike.[[Calendar]]. - // 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)) @@ -461,6 +461,8 @@ ThrowCompletionOr to_temporal_calendar_identifier(VM& vm, Value temporal 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(); } // 2. If temporalCalendarLike is not a String, throw a TypeError exception. @@ -480,7 +482,6 @@ ThrowCompletionOr get_temporal_calendar_identifier_with_iso_default(VM& // 1. If item has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], // [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then // a. Return item.[[Calendar]]. - // FIXME: Add the other calendar-holding types as we define them. if (is(item)) return static_cast(item).calendar(); if (is(item)) @@ -489,6 +490,8 @@ ThrowCompletionOr get_temporal_calendar_identifier_with_iso_default(VM& return static_cast(item).calendar(); if (is(item)) return static_cast(item).calendar(); + if (is(item)) + return static_cast(item).calendar(); // 2. Let calendarLike be ? Get(item, "calendar"). auto calendar_like = TRY(item.get(vm.names.calendar)); diff --git a/Libraries/LibJS/Runtime/Temporal/Temporal.cpp b/Libraries/LibJS/Runtime/Temporal/Temporal.cpp index d33dd274b4a..85e93573eac 100644 --- a/Libraries/LibJS/Runtime/Temporal/Temporal.cpp +++ b/Libraries/LibJS/Runtime/Temporal/Temporal.cpp @@ -15,6 +15,7 @@ #include #include #include +#include namespace JS::Temporal { @@ -44,6 +45,7 @@ void Temporal::initialize(Realm& realm) define_intrinsic_accessor(vm.names.PlainMonthDay, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_month_day_constructor(); }); define_intrinsic_accessor(vm.names.PlainTime, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_time_constructor(); }); define_intrinsic_accessor(vm.names.PlainYearMonth, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_plain_year_month_constructor(); }); + define_intrinsic_accessor(vm.names.ZonedDateTime, attr, [](auto& realm) -> Value { return realm.intrinsics().temporal_zoned_date_time_constructor(); }); } } diff --git a/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp b/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp index 8e602aebd90..fb9e58159bc 100644 --- a/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp +++ b/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include namespace JS::Temporal { @@ -96,6 +97,39 @@ String format_offset_time_zone_identifier(i64 offset_minutes, Optional= 0 ? '+' : '-'; + + // 2. Let absoluteNanoseconds be abs(offsetNanoseconds). + auto absolute_nanoseconds = static_cast(abs(offset_nanoseconds)); + + // 3. Let hour be floor(absoluteNanoseconds / (3600 × 10**9)). + auto hour = floor(absolute_nanoseconds / 3'600'000'000'000.0); + + // 4. Let minute be floor(absoluteNanoseconds / (60 × 10**9)) modulo 60. + auto minute = modulo(floor(absolute_nanoseconds / 60'000'000'000.0), 60.0); + + // 5. Let second be floor(absoluteNanoseconds / 10**9) modulo 60. + auto second = modulo(floor(absolute_nanoseconds / 1'000'000'000.0), 60.0); + + // 6. Let subSecondNanoseconds be absoluteNanoseconds modulo 10**9. + auto sub_second_nanoseconds = modulo(absolute_nanoseconds, 1'000'000'000.0); + + // 7. If second = 0 and subSecondNanoseconds = 0, let precision be MINUTE; otherwise, let precision be AUTO. + SecondsStringPrecision::Precision precision { Auto {} }; + if (second == 0 && sub_second_nanoseconds == 0) + precision = SecondsStringPrecision::Minute {}; + + // 8. Let timeString be FormatTimeString(hour, minute, second, subSecondNanoseconds, precision). + auto time_string = format_time_string(hour, minute, second, sub_second_nanoseconds, precision); + + // 9. Return the string-concatenation of sign and timeString. + return MUST(String::formatted("{}{}", sign, time_string)); +} + // 11.1.7 FormatDateTimeUTCOffsetRounded ( offsetNanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-formatdatetimeutcoffsetrounded String format_date_time_utc_offset_rounded(i64 offset_nanoseconds) { @@ -117,8 +151,13 @@ ThrowCompletionOr to_temporal_time_zone_identifier(VM& vm, Value tempora { // 1. If temporalTimeZoneLike is an Object, then if (temporal_time_zone_like.is_object()) { - // FIXME: a. If temporalTimeZoneLike has an [[InitializedTemporalZonedDateTime]] internal slot, then - // FIXME: i. Return temporalTimeZoneLike.[[TimeZone]]. + auto const& object = temporal_time_zone_like.as_object(); + + // a. If temporalTimeZoneLike has an [[InitializedTemporalZonedDateTime]] internal slot, then + if (is(object)) { + // i. Return temporalTimeZoneLike.[[TimeZone]]. + return static_cast(object).time_zone(); + } } // 2. If temporalTimeZoneLike is not a String, throw a TypeError exception. @@ -297,6 +336,23 @@ ThrowCompletionOr> get_possible_epoch_nanosecon return possible_epoch_nanoseconds; } +// 11.1.14 GetStartOfDay ( timeZone, isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-getstartofday +ThrowCompletionOr get_start_of_day(VM& vm, StringView time_zone, ISODate iso_date) +{ + // 1. Let isoDateTime be CombineISODateAndTimeRecord(isoDate, MidnightTimeRecord()). + auto iso_date_time = combine_iso_date_and_time_record(iso_date, midnight_time_record()); + + // 2. Let possibleEpochNs be ? GetPossibleEpochNanoseconds(timeZone, isoDateTime). + auto possible_epoch_nanoseconds = TRY(get_possible_epoch_nanoseconds(vm, time_zone, iso_date_time)); + + // 3. If possibleEpochNs is not empty, return possibleEpochNs[0]. + if (!possible_epoch_nanoseconds.is_empty()) + return move(possible_epoch_nanoseconds[0]); + + // FIXME: GetNamedTimeZoneEpochNanoseconds currently does not produce zero instants. + TODO(); +} + // 11.1.16 ParseTimeZoneIdentifier ( identifier ), https://tc39.es/proposal-temporal/#sec-parsetimezoneidentifier ThrowCompletionOr parse_time_zone_identifier(VM& vm, StringView identifier) { diff --git a/Libraries/LibJS/Runtime/Temporal/TimeZone.h b/Libraries/LibJS/Runtime/Temporal/TimeZone.h index 3a0948eea7a..1952ca05954 100644 --- a/Libraries/LibJS/Runtime/Temporal/TimeZone.h +++ b/Libraries/LibJS/Runtime/Temporal/TimeZone.h @@ -21,15 +21,9 @@ struct TimeZone { Optional offset_minutes; }; -enum class Disambiguation { - Compatible, - Earlier, - Later, - Reject, -}; - ISODateTime get_iso_parts_from_epoch(Crypto::SignedBigInteger const& epoch_nanoseconds); String format_offset_time_zone_identifier(i64 offset_minutes, Optional = {}); +String format_utc_offset_nanoseconds(i64 offset_nanoseconds); String format_date_time_utc_offset_rounded(i64 offset_nanoseconds); ThrowCompletionOr to_temporal_time_zone_identifier(VM&, Value temporal_time_zone_like); i64 get_offset_nanoseconds_for(StringView time_zone, Crypto::SignedBigInteger const& epoch_nanoseconds); @@ -38,6 +32,7 @@ ThrowCompletionOr to_temporal_time_zone_identifier(VM&, StringView tempo ThrowCompletionOr get_epoch_nanoseconds_for(VM&, StringView time_zone, ISODateTime const&, Disambiguation); ThrowCompletionOr disambiguate_possible_epoch_nanoseconds(VM&, Vector possible_epoch_ns, StringView time_zone, ISODateTime const&, Disambiguation); ThrowCompletionOr> get_possible_epoch_nanoseconds(VM&, StringView time_zone, ISODateTime const&); +ThrowCompletionOr get_start_of_day(VM&, StringView time_zone, ISODate); ThrowCompletionOr parse_time_zone_identifier(VM&, StringView identifier); TimeZone parse_time_zone_identifier(StringView identifier); TimeZone parse_time_zone_identifier(ParseResult const&); diff --git a/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp b/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp new file mode 100644 index 00000000000..7fab84a3f79 --- /dev/null +++ b/Libraries/LibJS/Runtime/Temporal/ZonedDateTime.cpp @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2021-2023, Linus Groh + * Copyright (c) 2021-2023, Luke Wilde + * Copyright (c) 2024, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace JS::Temporal { + +GC_DEFINE_ALLOCATOR(ZonedDateTime); + +// 6 Temporal.ZonedDateTime Objects, https://tc39.es/proposal-temporal/#sec-temporal-zoneddatetime-objects +ZonedDateTime::ZonedDateTime(BigInt const& epoch_nanoseconds, String time_zone, String calendar, Object& prototype) + : Object(ConstructWithPrototypeTag::Tag, prototype) + , m_epoch_nanoseconds(epoch_nanoseconds) + , m_time_zone(move(time_zone)) + , m_calendar(move(calendar)) +{ +} + +void ZonedDateTime::visit_edges(Cell::Visitor& visitor) +{ + Base::visit_edges(visitor); + visitor.visit(m_epoch_nanoseconds); +} + +// 6.5.1 InterpretISODateTimeOffset ( isoDate, time, offsetBehaviour, offsetNanoseconds, timeZone, disambiguation, offsetOption, matchBehaviour ), https://tc39.es/proposal-temporal/#sec-temporal-interpretisodatetimeoffset +ThrowCompletionOr interpret_iso_date_time_offset(VM& vm, ISODate iso_date, Variant const& time_or_start_of_day, OffsetBehavior offset_behavior, double offset_nanoseconds, StringView time_zone, Disambiguation disambiguation, OffsetOption offset_option, MatchBehavior match_behavior) +{ + // 1. If time is START-OF-DAY, then + if (time_or_start_of_day.has()) { + // a. Assert: offsetBehaviour is WALL. + VERIFY(offset_behavior == OffsetBehavior::Wall); + + // b. Assert: offsetNanoseconds is 0. + VERIFY(offset_nanoseconds == 0); + + // c. Return ? GetStartOfDay(timeZone, isoDate). + return TRY(get_start_of_day(vm, time_zone, iso_date)); + } + + auto time = time_or_start_of_day.get