diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 467dc8e777..50e60386ac 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/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) \ diff --git a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h b/Userland/Libraries/LibJS/Runtime/ErrorTypes.h index db091065f8..687d7b373a 100644 --- a/Userland/Libraries/LibJS/Runtime/ErrorTypes.h +++ b/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 '{}'") \ diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp index bb76c7d0f4..01054b3e5b 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.cpp @@ -200,6 +200,18 @@ ThrowCompletionOr 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 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 to_temporal_rounding_mode(GlobalObject& global_object, Object const& normalized_options, String const& fallback) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h index c1f413b569..702a86730a 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/AbstractOperations.h @@ -90,6 +90,7 @@ ThrowCompletionOr get_option(GlobalObject&, Object const& options, Proper template ThrowCompletionOr> get_string_or_number_option(GlobalObject&, Object const& options, PropertyKey const& property, Vector const& string_values, NumberType minimum, NumberType maximum, Value fallback); ThrowCompletionOr to_temporal_overflow(GlobalObject&, Object const& normalized_options); +ThrowCompletionOr to_temporal_disambiguation(GlobalObject&, Object const& normalized_options); ThrowCompletionOr to_temporal_rounding_mode(GlobalObject&, Object const& normalized_options, String const& fallback); ThrowCompletionOr to_show_calendar_option(GlobalObject&, Object const& normalized_options); ThrowCompletionOr to_temporal_rounding_increment(GlobalObject&, Object const& normalized_options, Optional dividend, bool inclusive); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp index 7038e8cb40..73031d8398 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -135,6 +136,29 @@ ThrowCompletionOr 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 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(added_date_object)) + return vm.throw_completion(global_object, ErrorType::NotAnObjectOfType, "Temporal.PlainDate"); + + // 5. Return addedDate. + return static_cast(added_date_object); +} + // 12.1.9 CalendarYear ( calendar, dateLike ), https://tc39.es/proposal-temporal/#sec-temporal-calendaryear ThrowCompletionOr calendar_year(GlobalObject& global_object, Object& calendar, Object& date_like) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h index 0e96b2074a..387e2e6da8 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/Calendar.h @@ -36,6 +36,7 @@ ThrowCompletionOr get_builtin_calendar(GlobalObject&, String const& i Calendar* get_iso8601_calendar(GlobalObject&); ThrowCompletionOr> calendar_fields(GlobalObject&, Object& calendar, Vector const& field_names); ThrowCompletionOr calendar_merge_fields(GlobalObject&, Object& calendar, Object& fields, Object& additional_fields); +ThrowCompletionOr calendar_date_add(GlobalObject&, Object& calendar, PlainDate&, Duration&, Object* options, FunctionObject* date_add = nullptr); ThrowCompletionOr calendar_year(GlobalObject&, Object& calendar, Object& date_like); ThrowCompletionOr calendar_month(GlobalObject&, Object& calendar, Object& date_like); ThrowCompletionOr calendar_month_code(GlobalObject&, Object& calendar, Object& date_like); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp index f1727af140..2a9455db01 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, Idan Horowitz + * Copyright (c) 2021, Linus Groh * * SPDX-License-Identifier: BSD-2-Clause */ @@ -10,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -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 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 }; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h index 001bedb7c5..51ea00aca6 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainDateTime.h +++ b/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 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 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 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 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 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); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp index a08638837d..4304150311 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.cpp +++ b/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 day_length_ns) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h index 49867e8e97..3e394bdbef 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/PlainTime.h @@ -102,6 +102,7 @@ ThrowCompletionOr create_temporal_time(GlobalObject&, u8 hour, u8 mi ThrowCompletionOr 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 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 day_length_ns = {}); } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp index 958bc9d8ef..f8b06b9293 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -446,4 +447,177 @@ ThrowCompletionOr 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 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 disambiguate_possible_instants(GlobalObject& global_object, Vector const& possible_instants, Value time_zone, PlainDateTime& date_time, StringView disambiguation) +{ + // TODO: MarkedValueList would be nice, then we could pass a Vector 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(const_cast(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(const_cast(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(const_cast(instant.as_object())); + } + + // c. Assert: disambiguation is "reject". + VERIFY(disambiguation == "reject"sv); + + // d. Throw a RangeError exception. + return vm.throw_completion(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(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(global_object, ErrorType::TemporalDisambiguatePossibleInstantsEarlierZero); + + // e. Return possibleInstants[0]. + auto& instant = possible_instants_mvl[0]; + return &static_cast(const_cast(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(global_object, ErrorType::TemporalDisambiguatePossibleInstantsZero); + + // 20. Return possibleInstants[n − 1]. + auto& instant = possible_instants_mvl[n - 1]; + return &static_cast(const_cast(instant.as_object())); +} + +// 11.6.16 GetPossibleInstantsFor ( timeZone, dateTime ), https://tc39.es/proposal-temporal/#sec-temporal-getpossibleinstantsfor +ThrowCompletionOr 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(next_value.as_object())) { + // 1. Let completion be ThrowCompletion(a newly created TypeError object). + auto completion = vm.throw_completion(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) }; +} + } diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h index c54da0036c..8ed0e61ca7 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZone.h @@ -49,6 +49,9 @@ ThrowCompletionOr to_temporal_time_zone(GlobalObject&, Value temporal_t ThrowCompletionOr get_offset_nanoseconds_for(GlobalObject&, Value time_zone, Instant&); ThrowCompletionOr builtin_time_zone_get_offset_string_for(GlobalObject&, Value time_zone, Instant&); ThrowCompletionOr builtin_time_zone_get_plain_date_time_for(GlobalObject&, Value time_zone, Instant&, Object& calendar); +ThrowCompletionOr builtin_time_zone_get_instant_for(GlobalObject&, Value time_zone, PlainDateTime&, StringView disambiguation); +ThrowCompletionOr disambiguate_possible_instants(GlobalObject&, Vector const& possible_instants, Value time_zone, PlainDateTime&, StringView disambiguation); +ThrowCompletionOr get_possible_instants_for(GlobalObject&, Value time_zone, PlainDateTime&); bool is_valid_time_zone_numeric_utc_offset_syntax(String const&); diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.cpp b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.cpp index c1a6e0e516..abf53e0b5f 100644 --- a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -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) { diff --git a/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.h b/Userland/Libraries/LibJS/Runtime/Temporal/TimeZonePrototype.h index 8ae7323795..a84caad588 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_instant_for); JS_DECLARE_NATIVE_FUNCTION(get_possible_instants_for); JS_DECLARE_NATIVE_FUNCTION(get_next_transition); JS_DECLARE_NATIVE_FUNCTION(get_previous_transition); diff --git a/Userland/Libraries/LibJS/Tests/builtins/Temporal/TimeZone/TimeZone.prototype.getInstantFor.js b/Userland/Libraries/LibJS/Tests/builtins/Temporal/TimeZone/TimeZone.prototype.getInstantFor.js new file mode 100644 index 0000000000..07285475c9 --- /dev/null +++ b/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"); + }); +});