Преглед на файлове

LibJS: Implement Temporal.TimeZone.prototype.getInstantFor()

Linus Groh преди 3 години
родител
ревизия
97f6c6029f

+ 2 - 0
Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h

@@ -133,6 +133,7 @@ namespace JS {
     P(deleteProperty)                        \
     P(deref)                                 \
     P(description)                           \
+    P(disambiguation)                        \
     P(done)                                  \
     P(dotAll)                                \
     P(encodeURI)                             \
@@ -198,6 +199,7 @@ namespace JS {
     P(getFloat64)                            \
     P(getFullYear)                           \
     P(getHours)                              \
+    P(getInstantFor)                         \
     P(getInt8)                               \
     P(getInt16)                              \
     P(getInt32)                              \

+ 4 - 0
Userland/Libraries/LibJS/Runtime/ErrorTypes.h

@@ -196,6 +196,10 @@
     M(StringRawCannotConvert, "Cannot convert property 'raw' to object from {}")                                                        \
     M(StringRepeatCountMustBe, "repeat count must be a {} number")                                                                      \
     M(TemporalAmbiguousMonthOfPlainMonthDay, "Accessing month of PlainMonthDay is ambiguous, use monthCode instead")                    \
+    M(TemporalDisambiguatePossibleInstantsEarlierZero, "Cannot disambiguate zero possible instants with mode \"earlier\"")              \
+    M(TemporalDisambiguatePossibleInstantsRejectMoreThanOne, "Cannot disambiguate two or more possible instants with mode \"reject\"")  \
+    M(TemporalDisambiguatePossibleInstantsRejectZero, "Cannot disambiguate zero possible instants with mode \"reject\"")                \
+    M(TemporalDisambiguatePossibleInstantsZero, "Cannot disambiguate zero possible instants")                                           \
     M(TemporalDuplicateCalendarField, "Duplicate calendar field '{}'")                                                                  \
     M(TemporalInvalidCalendar, "Invalid calendar")                                                                                      \
     M(TemporalInvalidCalendarFieldName, "Invalid calendar field '{}'")                                                                  \

+ 12 - 0
Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp

@@ -200,6 +200,18 @@ ThrowCompletionOr<String> to_temporal_overflow(GlobalObject& global_object, Obje
     return option.as_string().string();
 }
 
+// 13.7 ToTemporalDisambiguation ( normalizedOptions ), https://tc39.es/proposal-temporal/#sec-temporal-totemporaldisambiguation
+ThrowCompletionOr<String> to_temporal_disambiguation(GlobalObject& global_object, Object const& normalized_options)
+{
+    auto& vm = global_object.vm();
+
+    // 1. Return ? GetOption(normalizedOptions, "disambiguation", « String », « "compatible", "earlier", "later", "reject" », "compatible").
+    auto option = TRY(get_option(global_object, normalized_options, vm.names.disambiguation, { OptionType::String }, { "compatible"sv, "earlier"sv, "later"sv, "reject"sv }, js_string(vm, "compatible")));
+
+    VERIFY(option.is_string());
+    return option.as_string().string();
+}
+
 // 13.8 ToTemporalRoundingMode ( normalizedOptions, fallback ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalroundingmode
 ThrowCompletionOr<String> to_temporal_rounding_mode(GlobalObject& global_object, Object const& normalized_options, String const& fallback)
 {

+ 1 - 0
Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h

@@ -90,6 +90,7 @@ ThrowCompletionOr<Value> get_option(GlobalObject&, Object const& options, Proper
 template<typename NumberType>
 ThrowCompletionOr<Variant<String, NumberType>> get_string_or_number_option(GlobalObject&, Object const& options, PropertyKey const& property, Vector<StringView> const& string_values, NumberType minimum, NumberType maximum, Value fallback);
 ThrowCompletionOr<String> to_temporal_overflow(GlobalObject&, Object const& normalized_options);
+ThrowCompletionOr<String> to_temporal_disambiguation(GlobalObject&, Object const& normalized_options);
 ThrowCompletionOr<String> to_temporal_rounding_mode(GlobalObject&, Object const& normalized_options, String const& fallback);
 ThrowCompletionOr<String> to_show_calendar_option(GlobalObject&, Object const& normalized_options);
 ThrowCompletionOr<u64> to_temporal_rounding_increment(GlobalObject&, Object const& normalized_options, Optional<double> dividend, bool inclusive);

+ 24 - 0
Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp

@@ -12,6 +12,7 @@
 #include <LibJS/Runtime/Temporal/AbstractOperations.h>
 #include <LibJS/Runtime/Temporal/Calendar.h>
 #include <LibJS/Runtime/Temporal/CalendarConstructor.h>
+#include <LibJS/Runtime/Temporal/Duration.h>
 #include <LibJS/Runtime/Temporal/PlainDate.h>
 #include <LibJS/Runtime/Temporal/PlainDateTime.h>
 #include <LibJS/Runtime/Temporal/PlainMonthDay.h>
@@ -135,6 +136,29 @@ ThrowCompletionOr<Object*> calendar_merge_fields(GlobalObject& global_object, Ob
     return &result.as_object();
 }
 
+// 12.1.7 CalendarDateAdd ( calendar, date, duration, options [ , dateAdd ] ), https://tc39.es/proposal-temporal/#sec-temporal-calendardateadd
+ThrowCompletionOr<PlainDate*> calendar_date_add(GlobalObject& global_object, Object& calendar, PlainDate& date, Duration& duration, Object* options, FunctionObject* date_add)
+{
+    auto& vm = global_object.vm();
+
+    // 1. Assert: Type(calendar) is Object.
+
+    // 2. If dateAdd is not present, set dateAdd to ? GetMethod(calendar, "dateAdd").
+    if (!date_add)
+        date_add = TRY(Value(&calendar).get_method(global_object, vm.names.dateAdd));
+
+    // 3. Let addedDate be ? Call(dateAdd, calendar, « date, duration, options »).
+    auto added_date = TRY(call(global_object, date_add ?: js_undefined(), &calendar, &date, &duration, options ?: js_undefined()));
+
+    // 4. Perform ? RequireInternalSlot(addedDate, [[InitializedTemporalDate]]).
+    auto* added_date_object = TRY(added_date.to_object(global_object));
+    if (!is<PlainDate>(added_date_object))
+        return vm.throw_completion<TypeError>(global_object, ErrorType::NotAnObjectOfType, "Temporal.PlainDate");
+
+    // 5. Return addedDate.
+    return static_cast<PlainDate*>(added_date_object);
+}
+
 // 12.1.9 CalendarYear ( calendar, dateLike ), https://tc39.es/proposal-temporal/#sec-temporal-calendaryear
 ThrowCompletionOr<double> calendar_year(GlobalObject& global_object, Object& calendar, Object& date_like)
 {

+ 1 - 0
Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h

@@ -36,6 +36,7 @@ ThrowCompletionOr<Calendar*> get_builtin_calendar(GlobalObject&, String const& i
 Calendar* get_iso8601_calendar(GlobalObject&);
 ThrowCompletionOr<Vector<String>> calendar_fields(GlobalObject&, Object& calendar, Vector<StringView> const& field_names);
 ThrowCompletionOr<Object*> calendar_merge_fields(GlobalObject&, Object& calendar, Object& fields, Object& additional_fields);
+ThrowCompletionOr<PlainDate*> calendar_date_add(GlobalObject&, Object& calendar, PlainDate&, Duration&, Object* options, FunctionObject* date_add = nullptr);
 ThrowCompletionOr<double> calendar_year(GlobalObject&, Object& calendar, Object& date_like);
 ThrowCompletionOr<double> calendar_month(GlobalObject&, Object& calendar, Object& date_like);
 ThrowCompletionOr<String> calendar_month_code(GlobalObject&, Object& calendar, Object& date_like);

+ 23 - 0
Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp

@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
+ * Copyright (c) 2021, Linus Groh <linusg@serenityos.org>
  *
  * SPDX-License-Identifier: BSD-2-Clause
  */
@@ -10,6 +11,7 @@
 #include <LibJS/Runtime/GlobalObject.h>
 #include <LibJS/Runtime/Temporal/AbstractOperations.h>
 #include <LibJS/Runtime/Temporal/Calendar.h>
+#include <LibJS/Runtime/Temporal/Duration.h>
 #include <LibJS/Runtime/Temporal/Instant.h>
 #include <LibJS/Runtime/Temporal/PlainDate.h>
 #include <LibJS/Runtime/Temporal/PlainDateTime.h>
@@ -310,4 +312,25 @@ i8 compare_iso_date_time(i32 year1, u8 month1, u8 day1, u8 hour1, u8 minute1, u8
     return compare_temporal_time(hour1, minute1, second1, millisecond1, microsecond1, nanosecond1, hour2, minute2, second2, millisecond2, microsecond2, nanosecond2);
 }
 
+// 5.5.9 AddDateTime ( year, month, day, hour, minute, second, millisecond, microsecond, nanosecond, calendar, years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, options ), https://tc39.es/proposal-temporal/#sec-temporal-adddatetime
+ThrowCompletionOr<TemporalPlainDateTime> add_date_time(GlobalObject& global_object, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Object& calendar, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Object* options)
+{
+    // 1. Assert: year, month, day, hour, minute, second, millisecond, microsecond, and nanosecond are integers.
+
+    // 2. Let timeResult be ! AddTime(hour, minute, second, millisecond, microsecond, nanosecond, hours, minutes, seconds, milliseconds, microseconds, nanoseconds).
+    auto time_result = add_time(hour, minute, second, millisecond, microsecond, nanosecond, hours, minutes, seconds, milliseconds, microseconds, nanoseconds);
+
+    // 3. Let datePart be ? CreateTemporalDate(year, month, day, calendar).
+    auto* date_part = TRY(create_temporal_date(global_object, year, month, day, calendar));
+
+    // 4. Let dateDuration be ? CreateTemporalDuration(years, months, weeks, days + timeResult.[[Days]], 0, 0, 0, 0, 0, 0).
+    auto* date_duration = TRY(create_temporal_duration(global_object, years, months, weeks, days + time_result.days, 0, 0, 0, 0, 0, 0));
+
+    // 5. Let addedDate be ? CalendarDateAdd(calendar, datePart, dateDuration, options).
+    auto* added_date = TRY(calendar_date_add(global_object, calendar, *date_part, *date_duration, options));
+
+    // 6. Return the Record { [[Year]]: addedDate.[[ISOYear]], [[Month]]: addedDate.[[ISOMonth]], [[Day]]: addedDate.[[ISODay]], [[Hour]]: timeResult.[[Hour]], [[Minute]]: timeResult.[[Minute]], [[Second]]: timeResult.[[Second]], [[Millisecond]]: timeResult.[[Millisecond]], [[Microsecond]]: timeResult.[[Microsecond]], [[Nanosecond]]: timeResult.[[Nanosecond]] }.
+    return TemporalPlainDateTime { .year = added_date->iso_year(), .month = added_date->iso_month(), .day = added_date->iso_day(), .hour = time_result.hour, .minute = time_result.minute, .second = time_result.second, .millisecond = time_result.millisecond, .microsecond = time_result.microsecond, .nanosecond = time_result.nanosecond };
+}
+
 }

+ 14 - 0
Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h

@@ -49,6 +49,19 @@ private:
     Object& m_calendar;          // [[Calendar]]
 };
 
+// Used by AddDateTime to temporarily hold values
+struct TemporalPlainDateTime {
+    i32 year;
+    u8 month;
+    u8 day;
+    u8 hour;
+    u8 minute;
+    u8 second;
+    u16 millisecond;
+    u16 microsecond;
+    u16 nanosecond;
+};
+
 BigInt* get_epoch_from_iso_parts(GlobalObject&, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond);
 bool iso_date_time_within_limits(GlobalObject&, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond);
 ThrowCompletionOr<ISODateTime> interpret_temporal_date_time_fields(GlobalObject&, Object& calendar, Object& fields, Object& options);
@@ -57,5 +70,6 @@ ISODateTime balance_iso_date_time(i32 year, u8 month, u8 day, u8 hour, u8 minute
 ThrowCompletionOr<PlainDateTime*> create_temporal_date_time(GlobalObject&, i32 iso_year, u8 iso_month, u8 iso_day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Object& calendar, FunctionObject const* new_target = nullptr);
 ThrowCompletionOr<String> temporal_date_time_to_string(GlobalObject&, i32 iso_year, u8 iso_month, u8 iso_day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Value calendar, Variant<StringView, u8> const& precision, StringView show_calendar);
 i8 compare_iso_date_time(i32 year1, u8 month1, u8 day1, u8 hour1, u8 minute1, u8 second1, u16 millisecond1, u16 microsecond1, u16 nanosecond1, i32 year2, u8 month2, u8 day2, u8 hour2, u8 minute2, u8 second2, u16 millisecond2, u16 microsecond2, u16 nanosecond2);
+ThrowCompletionOr<TemporalPlainDateTime> add_date_time(GlobalObject&, i32 year, u8 month, u8 day, u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Object& calendar, double years, double months, double weeks, double days, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds, Object* options);
 
 }

+ 30 - 0
Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp

@@ -452,6 +452,36 @@ i8 compare_temporal_time(u8 hour1, u8 minute1, u8 second1, u16 millisecond1, u16
     return 0;
 }
 
+// 4.5.12 AddTime ( hour, minute, second, millisecond, microsecond, nanosecond, hours, minutes, seconds, milliseconds, microseconds, nanoseconds ), https://tc39.es/proposal-temporal/#sec-temporal-addtime
+DaysAndTime add_time(u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds)
+{
+    // 1. Assert: hour, minute, second, millisecond, microsecond, nanosecond, hours, minutes, seconds, milliseconds, microseconds, and nanoseconds are integers.
+    VERIFY(hours == trunc(hours) && minutes == trunc(minutes) && seconds == trunc(seconds) && milliseconds == trunc(milliseconds) && microseconds == trunc(microseconds) && nanoseconds == trunc(nanoseconds));
+
+    // FIXME: balance_time() should probably take double arguments. In fact, pretty much every balance_foo() needed to take doubles at some point.
+
+    // 2. Let hour be hour + hours.
+    i64 hour_ = hour + hours;
+
+    // 3. Let minute be minute + minutes.
+    i64 minute_ = minute + minutes;
+
+    // 4. Let second be second + seconds.
+    i64 second_ = second + seconds;
+
+    // 5. Let millisecond be millisecond + milliseconds.
+    i64 millisecond_ = millisecond + milliseconds;
+
+    // 6. Let microsecond be microsecond + microseconds.
+    i64 microsecond_ = microsecond + microseconds;
+
+    // 7. Let nanosecond be nanosecond + nanoseconds.
+    i64 nanosecond_ = nanosecond + nanoseconds;
+
+    // 8. Return ! BalanceTime(hour, minute, second, millisecond, microsecond, nanosecond).
+    return balance_time(hour_, minute_, second_, millisecond_, microsecond_, nanosecond_);
+}
+
 // 4.5.13 RoundTime ( hour, minute, second, millisecond, microsecond, nanosecond, increment, unit, roundingMode [ , dayLengthNs ] ), https://tc39.es/proposal-temporal/#sec-temporal-roundtime
 DaysAndTime round_time(u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, u64 increment, StringView unit, StringView rounding_mode, Optional<double> day_length_ns)
 {

+ 1 - 0
Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h

@@ -102,6 +102,7 @@ ThrowCompletionOr<PlainTime*> create_temporal_time(GlobalObject&, u8 hour, u8 mi
 ThrowCompletionOr<UnregulatedTemporalTime> to_temporal_time_record(GlobalObject&, Object const& temporal_time_like);
 String temporal_time_to_string(u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, Variant<StringView, u8> const& precision);
 i8 compare_temporal_time(u8 hour1, u8 minute1, u8 second1, u16 millisecond1, u16 microsecond1, u16 nanosecond1, u8 hour2, u8 minute2, u8 second2, u16 millisecond2, u16 microsecond2, u16 nanosecond2);
+DaysAndTime add_time(u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, double hours, double minutes, double seconds, double milliseconds, double microseconds, double nanoseconds);
 DaysAndTime round_time(u8 hour, u8 minute, u8 second, u16 millisecond, u16 microsecond, u16 nanosecond, u64 increment, StringView unit, StringView rounding_mode, Optional<double> day_length_ns = {});
 
 }

+ 174 - 0
Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp

@@ -9,6 +9,7 @@
 #include <LibJS/Runtime/AbstractOperations.h>
 #include <LibJS/Runtime/Date.h>
 #include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/IteratorOperations.h>
 #include <LibJS/Runtime/Temporal/AbstractOperations.h>
 #include <LibJS/Runtime/Temporal/Instant.h>
 #include <LibJS/Runtime/Temporal/PlainDateTime.h>
@@ -446,4 +447,177 @@ ThrowCompletionOr<PlainDateTime*> builtin_time_zone_get_plain_date_time_for(Glob
     return create_temporal_date_time(global_object, result.year, result.month, result.day, result.hour, result.minute, result.second, result.millisecond, result.microsecond, result.nanosecond, calendar);
 }
 
+// 11.6.14 BuiltinTimeZoneGetInstantFor ( timeZone, dateTime, disambiguation ), https://tc39.es/proposal-temporal/#sec-temporal-builtintimezonegetinstantfor
+ThrowCompletionOr<Instant*> builtin_time_zone_get_instant_for(GlobalObject& global_object, Value time_zone, PlainDateTime& date_time, StringView disambiguation)
+{
+    // 1. Assert: dateTime has an [[InitializedTemporalDateTime]] internal slot.
+
+    // 2. Let possibleInstants be ? GetPossibleInstantsFor(timeZone, dateTime).
+    auto possible_instants = TRY(get_possible_instants_for(global_object, time_zone, date_time));
+
+    // 3. Return ? DisambiguatePossibleInstants(possibleInstants, timeZone, dateTime, disambiguation).
+    return disambiguate_possible_instants(global_object, possible_instants, time_zone, date_time, disambiguation);
+}
+
+// 11.6.15 DisambiguatePossibleInstants ( possibleInstants, timeZone, dateTime, disambiguation ), https://tc39.es/proposal-temporal/#sec-temporal-disambiguatepossibleinstants
+ThrowCompletionOr<Instant*> disambiguate_possible_instants(GlobalObject& global_object, Vector<Value> const& possible_instants, Value time_zone, PlainDateTime& date_time, StringView disambiguation)
+{
+    // TODO: MarkedValueList<T> would be nice, then we could pass a Vector<Instant*> here and wouldn't need the casts...
+
+    auto& vm = global_object.vm();
+
+    // 1. Assert: dateTime has an [[InitializedTemporalDateTime]] internal slot.
+
+    // 2. Let n be possibleInstants's length.
+    auto n = possible_instants.size();
+
+    // 3. If n = 1, then
+    if (n == 1) {
+        // a. Return possibleInstants[0].
+        auto& instant = possible_instants[0];
+        return &static_cast<Instant&>(const_cast<Object&>(instant.as_object()));
+    }
+
+    // 4. If n ≠ 0, then
+    if (n != 0) {
+        // a. If disambiguation is "earlier" or "compatible", then
+        if (disambiguation.is_one_of("earlier"sv, "compatible"sv)) {
+            // i. Return possibleInstants[0].
+            auto& instant = possible_instants[0];
+            return &static_cast<Instant&>(const_cast<Object&>(instant.as_object()));
+        }
+
+        // b. If disambiguation is "later", then
+        if (disambiguation == "later"sv) {
+            // i. Return possibleInstants[n − 1].
+            auto& instant = possible_instants[n - 1];
+            return &static_cast<Instant&>(const_cast<Object&>(instant.as_object()));
+        }
+
+        // c. Assert: disambiguation is "reject".
+        VERIFY(disambiguation == "reject"sv);
+
+        // d. Throw a RangeError exception.
+        return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalDisambiguatePossibleInstantsRejectMoreThanOne);
+    }
+
+    // 5. Assert: n = 0.
+    VERIFY(n == 0);
+
+    // 6. If disambiguation is "reject", then
+    if (disambiguation == "reject"sv) {
+        // a. Throw a RangeError exception.
+        return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalDisambiguatePossibleInstantsRejectZero);
+    }
+
+    // 7. 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());
+
+    // 8. Let dayBefore be ! CreateTemporalInstant(epochNanoseconds − 8.64 × 10^13).
+    auto* day_before = MUST(create_temporal_instant(global_object, *js_bigint(vm, epoch_nanoseconds->big_integer().minus("86400000000000"_sbigint))));
+
+    // 9. Let dayAfter be ! CreateTemporalInstant(epochNanoseconds + 8.64 × 10^13).
+    auto* day_after = MUST(create_temporal_instant(global_object, *js_bigint(vm, epoch_nanoseconds->big_integer().plus("86400000000000"_sbigint))));
+
+    // 10. Let offsetBefore be ? GetOffsetNanosecondsFor(timeZone, dayBefore).
+    auto offset_before = TRY(get_offset_nanoseconds_for(global_object, time_zone, *day_before));
+
+    // 11. Let offsetAfter be ? GetOffsetNanosecondsFor(timeZone, dayAfter).
+    auto offset_after = TRY(get_offset_nanoseconds_for(global_object, time_zone, *day_after));
+
+    // 12. Let nanoseconds be offsetAfter − offsetBefore.
+    auto nanoseconds = offset_after - offset_before;
+
+    // 13. If disambiguation is "earlier", then
+    if (disambiguation == "earlier"sv) {
+        TODO();
+        // a. Let earlier be ? AddDateTime(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]], dateTime.[[Calendar]], 0, 0, 0, 0, 0, 0, 0, 0, 0, −nanoseconds, undefined).
+        auto earlier = TRY(add_date_time(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(), date_time.calendar(), 0, 0, 0, 0, 0, 0, 0, 0, 0, -nanoseconds, nullptr));
+
+        // b. Let earlierDateTime be ! CreateTemporalDateTime(earlier.[[Year]], earlier.[[Month]], earlier.[[Day]], earlier.[[Hour]], earlier.[[Minute]], earlier.[[Second]], earlier.[[Millisecond]], earlier.[[Microsecond]], earlier.[[Nanosecond]], dateTime.[[Calendar]]).
+        auto* earlier_date_time = MUST(create_temporal_date_time(global_object, earlier.year, earlier.month, earlier.day, earlier.hour, earlier.minute, earlier.second, earlier.millisecond, earlier.microsecond, earlier.nanosecond, date_time.calendar()));
+
+        // c. Set possibleInstants to ? GetPossibleInstantsFor(timeZone, earlierDateTime).
+        auto possible_instants_mvl = TRY(get_possible_instants_for(global_object, time_zone, *earlier_date_time));
+
+        // d. If possibleInstants is empty, throw a RangeError exception.
+        if (possible_instants_mvl.is_empty())
+            return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalDisambiguatePossibleInstantsEarlierZero);
+
+        // e. Return possibleInstants[0].
+        auto& instant = possible_instants_mvl[0];
+        return &static_cast<Instant&>(const_cast<Object&>(instant.as_object()));
+    }
+
+    // 14. Assert: disambiguation is "compatible" or "later".
+    VERIFY(disambiguation.is_one_of("compatible"sv, "later"sv));
+
+    // 15. Let later be ? AddDateTime(dateTime.[[ISOYear]], dateTime.[[ISOMonth]], dateTime.[[ISODay]], dateTime.[[ISOHour]], dateTime.[[ISOMinute]], dateTime.[[ISOSecond]], dateTime.[[ISOMillisecond]], dateTime.[[ISOMicrosecond]], dateTime.[[ISONanosecond]], dateTime.[[Calendar]], 0, 0, 0, 0, 0, 0, 0, 0, 0, nanoseconds, undefined).
+    auto later = TRY(add_date_time(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(), date_time.calendar(), 0, 0, 0, 0, 0, 0, 0, 0, 0, nanoseconds, nullptr));
+
+    // 16. Let laterDateTime be ! CreateTemporalDateTime(later.[[Year]], later.[[Month]], later.[[Day]], later.[[Hour]], later.[[Minute]], later.[[Second]], later.[[Millisecond]], later.[[Microsecond]], later.[[Nanosecond]], dateTime.[[Calendar]]).
+    auto* later_date_time = MUST(create_temporal_date_time(global_object, later.year, later.month, later.day, later.hour, later.minute, later.second, later.millisecond, later.microsecond, later.nanosecond, date_time.calendar()));
+
+    // 17. Set possibleInstants to ? GetPossibleInstantsFor(timeZone, laterDateTime).
+    auto possible_instants_mvl = TRY(get_possible_instants_for(global_object, time_zone, *later_date_time));
+
+    // 18. Set n to possibleInstants's length.
+    n = possible_instants_mvl.size();
+
+    // 19. If n = 0, throw a RangeError exception.
+    if (n == 0)
+        return vm.throw_completion<RangeError>(global_object, ErrorType::TemporalDisambiguatePossibleInstantsZero);
+
+    // 20. Return possibleInstants[n − 1].
+    auto& instant = possible_instants_mvl[n - 1];
+    return &static_cast<Instant&>(const_cast<Object&>(instant.as_object()));
+}
+
+// 11.6.16 GetPossibleInstantsFor ( timeZone, dateTime ), https://tc39.es/proposal-temporal/#sec-temporal-getpossibleinstantsfor
+ThrowCompletionOr<MarkedValueList> get_possible_instants_for(GlobalObject& global_object, Value time_zone, PlainDateTime& date_time)
+{
+    auto& vm = global_object.vm();
+
+    // 1. Assert: dateTime has an [[InitializedTemporalDateTime]] internal slot.
+
+    // 2. Let possibleInstants be ? Invoke(timeZone, "getPossibleInstantsFor", « dateTime »).
+    auto possible_instants = TRY(time_zone.invoke(global_object, vm.names.getPossibleInstantsFor, &date_time));
+
+    // 3. Let iteratorRecord be ? GetIterator(possibleInstants, sync).
+    auto* iterator = TRY(get_iterator(global_object, possible_instants, IteratorHint::Sync));
+
+    // 4. Let list be a new empty List.
+    auto list = MarkedValueList { vm.heap() };
+
+    // 5. Let next be true.
+    Object* next = nullptr;
+
+    // 6. Repeat, while next is not false,
+    do {
+        // a. Set next to ? IteratorStep(iteratorRecord).
+        next = TRY(iterator_step(global_object, *iterator));
+
+        // b. If next is not false, then
+        if (next) {
+            // i. Let nextValue be ? IteratorValue(next).
+            auto next_value = TRY(iterator_value(global_object, *next));
+
+            // ii. If Type(nextValue) is not Object or nextValue does not have an [[InitializedTemporalInstant]] internal slot, then
+            if (!next_value.is_object() || !is<Instant>(next_value.as_object())) {
+                // 1. Let completion be ThrowCompletion(a newly created TypeError object).
+                auto completion = vm.throw_completion<TypeError>(global_object, ErrorType::NotAnObjectOfType, "Temporal.Instant");
+
+                // 2. Return ? IteratorClose(iteratorRecord, completion).
+                return iterator_close(*iterator, move(completion));
+            }
+
+            // iii. Append nextValue to the end of the List list.
+            list.append(next_value);
+        }
+    } while (next != nullptr);
+
+    // 7. Return list.
+    return { move(list) };
+}
+
 }

+ 3 - 0
Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h

@@ -49,6 +49,9 @@ ThrowCompletionOr<Object*> to_temporal_time_zone(GlobalObject&, Value temporal_t
 ThrowCompletionOr<double> get_offset_nanoseconds_for(GlobalObject&, Value time_zone, Instant&);
 ThrowCompletionOr<String> builtin_time_zone_get_offset_string_for(GlobalObject&, Value time_zone, Instant&);
 ThrowCompletionOr<PlainDateTime*> builtin_time_zone_get_plain_date_time_for(GlobalObject&, Value time_zone, Instant&, Object& calendar);
+ThrowCompletionOr<Instant*> builtin_time_zone_get_instant_for(GlobalObject&, Value time_zone, PlainDateTime&, StringView disambiguation);
+ThrowCompletionOr<Instant*> disambiguate_possible_instants(GlobalObject&, Vector<Value> const& possible_instants, Value time_zone, PlainDateTime&, StringView disambiguation);
+ThrowCompletionOr<MarkedValueList> get_possible_instants_for(GlobalObject&, Value time_zone, PlainDateTime&);
 
 bool is_valid_time_zone_numeric_utc_offset_syntax(String const&);
 

+ 22 - 0
Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.cpp

@@ -7,6 +7,7 @@
 #include <AK/TypeCasts.h>
 #include <LibJS/Runtime/Array.h>
 #include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/Temporal/AbstractOperations.h>
 #include <LibJS/Runtime/Temporal/Calendar.h>
 #include <LibJS/Runtime/Temporal/Instant.h>
 #include <LibJS/Runtime/Temporal/PlainDateTime.h>
@@ -32,6 +33,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.getInstantFor, get_instant_for, 1, attr);
     define_native_function(vm.names.getPossibleInstantsFor, get_possible_instants_for, 1, attr);
     define_native_function(vm.names.getNextTransition, get_next_transition, 1, attr);
     define_native_function(vm.names.getPreviousTransition, get_previous_transition, 1, attr);
@@ -101,6 +103,26 @@ 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.7 Temporal.TimeZone.prototype.getInstantFor ( dateTime [ , options ] ), https://tc39.es/proposal-temporal/#sec-temporal.timezone.prototype.getinstantfor
+JS_DEFINE_NATIVE_FUNCTION(TimeZonePrototype::get_instant_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. Set options to ? GetOptionsObject(options).
+    auto* options = TRY(get_options_object(global_object, vm.argument(1)));
+
+    // 5. Let disambiguation be ? ToTemporalDisambiguation(options).
+    auto disambiguation = TRY(to_temporal_disambiguation(global_object, *options));
+
+    // 6. Return ? BuiltinTimeZoneGetInstantFor(timeZone, dateTime, disambiguation).
+    return TRY(builtin_time_zone_get_instant_for(global_object, time_zone, *date_time, disambiguation));
+}
+
 // 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 - 0
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_instant_for);
     JS_DECLARE_NATIVE_FUNCTION(get_possible_instants_for);
     JS_DECLARE_NATIVE_FUNCTION(get_next_transition);
     JS_DECLARE_NATIVE_FUNCTION(get_previous_transition);

+ 29 - 0
Userland/Libraries/LibJS/Tests/builtins/Temporal/TimeZone/TimeZone.prototype.getInstantFor.js

@@ -0,0 +1,29 @@
+describe("correct behavior", () => {
+    test("length is 1", () => {
+        expect(Temporal.TimeZone.prototype.getInstantFor).toHaveLength(1);
+    });
+
+    test("basic functionality", () => {
+        const timeZone = new Temporal.TimeZone("UTC");
+        const plainDateTime = new Temporal.PlainDateTime(2021, 7, 6, 18, 14, 47);
+        const instant = timeZone.getInstantFor(plainDateTime);
+        expect(instant).toBeInstanceOf(Temporal.Instant);
+        expect(instant.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 instant = timeZone.getInstantFor(plainDateTime);
+        expect(instant).toBeInstanceOf(Temporal.Instant);
+        expect(instant.epochNanoseconds).toBe(1625589887000000000n);
+    });
+});
+
+describe("errors", () => {
+    test("this value must be a Temporal.TimeZone object", () => {
+        expect(() => {
+            Temporal.TimeZone.prototype.getInstantFor.call("foo");
+        }).toThrowWithMessage(TypeError, "Not an object of type Temporal.TimeZone");
+    });
+});