/* * Copyright (c) 2021-2025, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace JS::Intl { GC_DEFINE_ALLOCATOR(DateTimeFormat); // 11 DateTimeFormat Objects, https://tc39.es/ecma402/#datetimeformat-objects DateTimeFormat::DateTimeFormat(Object& prototype) : Object(ConstructWithPrototypeTag::Tag, prototype) { } void DateTimeFormat::visit_edges(Cell::Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_bound_format); } static Optional get_or_create_formatter(StringView locale, StringView time_zone, OwnPtr& formatter, Optional const& format) { if (formatter) return *formatter; if (!format.has_value()) return {}; formatter = Unicode::DateTimeFormat::create_for_pattern_options(locale, time_zone, *format); return *formatter; } Optional DateTimeFormat::temporal_plain_date_formatter() { return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_date_formatter, m_temporal_plain_date_format); } Optional DateTimeFormat::temporal_plain_year_month_formatter() { return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_year_month_formatter, m_temporal_plain_year_month_format); } Optional DateTimeFormat::temporal_plain_month_day_formatter() { return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_month_day_formatter, m_temporal_plain_month_day_format); } Optional DateTimeFormat::temporal_plain_time_formatter() { return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_time_formatter, m_temporal_plain_time_format); } Optional DateTimeFormat::temporal_plain_date_time_formatter() { return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_plain_date_time_formatter, m_temporal_plain_date_time_format); } Optional DateTimeFormat::temporal_instant_formatter() { return get_or_create_formatter(m_locale, m_temporal_time_zone, m_temporal_instant_formatter, m_temporal_instant_format); } // 11.5.5 FormatDateTimePattern ( dateTimeFormat, patternParts, x, rangeFormatOptions ), https://tc39.es/ecma402/#sec-formatdatetimepattern // 15.9.4 FormatDateTimePattern ( dateTimeFormat, format, pattern, x, epochNanoseconds ), https://tc39.es/proposal-temporal/#sec-formatdatetimepattern Vector format_date_time_pattern(ValueFormat const& format_record) { return format_record.formatter.format_to_parts(format_record.epoch_milliseconds); } // 11.5.6 PartitionDateTimePattern ( dateTimeFormat, x ), https://tc39.es/ecma402/#sec-partitiondatetimepattern // 15.9.5 PartitionDateTimePattern ( dateTimeFormat, x ), https://tc39.es/proposal-temporal/#sec-partitiondatetimepattern ThrowCompletionOr> partition_date_time_pattern(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& time) { // 1. Let xFormatRecord be ? HandleDateTimeValue(dateTimeFormat, x). auto format_record = TRY(handle_date_time_value(vm, date_time_format, time)); // 5. Let result be ? FormatDateTimePattern(dateTimeFormat, format, pattern, xFormatRecord.[[EpochNanoseconds]]). return format_date_time_pattern(format_record); } // 11.5.7 FormatDateTime ( dateTimeFormat, x ), https://tc39.es/ecma402/#sec-formatdatetime // 15.9.6 FormatDateTime ( dateTimeFormat, x ), https://tc39.es/proposal-temporal/#sec-formatdatetime ThrowCompletionOr format_date_time(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& time) { // 1. Let parts be ? PartitionDateTimePattern(dateTimeFormat, x). // 2. Let result be the empty String. String result; // NOTE: We short-circuit PartitionDateTimePattern as we do not need individual partitions. { // 1. Let xFormatRecord be ? HandleDateTimeValue(dateTimeFormat, x). auto format_record = TRY(handle_date_time_value(vm, date_time_format, time)); result = format_record.formatter.format(format_record.epoch_milliseconds); } // 4. Return result. return result; } // 11.5.8 FormatDateTimeToParts ( dateTimeFormat, x ), https://tc39.es/ecma402/#sec-formatdatetimetoparts // 15.9.7 FormatDateTimeToParts ( dateTimeFormat, x ), https://tc39.es/proposal-temporal/#sec-formatdatetimetoparts ThrowCompletionOr> format_date_time_to_parts(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& time) { auto& realm = *vm.current_realm(); // 1. Let parts be ? PartitionDateTimePattern(dateTimeFormat, x). auto parts = TRY(partition_date_time_pattern(vm, date_time_format, time)); // 2. Let result be ! ArrayCreate(0). auto result = MUST(Array::create(realm, 0)); // 3. Let n be 0. size_t n = 0; // 4. For each Record { [[Type]], [[Value]] } part in parts, do for (auto& part : parts) { // a. Let O be OrdinaryObjectCreate(%Object.prototype%). auto object = Object::create(realm, realm.intrinsics().object_prototype()); // b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]). MUST(object->create_data_property_or_throw(vm.names.type, PrimitiveString::create(vm, part.type))); // c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]). MUST(object->create_data_property_or_throw(vm.names.value, PrimitiveString::create(vm, move(part.value)))); // d. Perform ! CreateDataProperty(result, ! ToString(n), O). MUST(result->create_data_property_or_throw(n, object)); // e. Increment n by 1. ++n; } // 5. Return result. return result; } // 11.5.9 PartitionDateTimeRangePattern ( dateTimeFormat, x, y ), https://tc39.es/ecma402/#sec-partitiondatetimerangepattern // 15.9.8 PartitionDateTimeRangePattern ( dateTimeFormat, x, y ), https://tc39.es/proposal-temporal/#sec-partitiondatetimerangepattern ThrowCompletionOr> partition_date_time_range_pattern(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& start, FormattableDateTime const& end) { // 1. If IsTemporalObject(x) is true or IsTemporalObject(y) is true, then if (is_temporal_object(start) || is_temporal_object(end)) { // a. If SameTemporalType(x, y) is false, throw a TypeError exception. if (!same_temporal_type(start, end)) return vm.throw_completion(ErrorType::IntlTemporalFormatRangeTypeMismatch); } // 2. Let xFormatRecord be ? HandleDateTimeValue(dateTimeFormat, x). auto start_format_record = TRY(handle_date_time_value(vm, date_time_format, start)); // 3. Let yFormatRecord be ? HandleDateTimeValue(dateTimeFormat, y). auto end_format_record = TRY(handle_date_time_value(vm, date_time_format, end)); return start_format_record.formatter.format_range_to_parts(start_format_record.epoch_milliseconds, end_format_record.epoch_milliseconds); } // 11.5.10 FormatDateTimeRange ( dateTimeFormat, x, y ), https://tc39.es/ecma402/#sec-formatdatetimerange // 15.9.9 FormatDateTimeRange ( dateTimeFormat, x, y ), https://tc39.es/proposal-temporal/#sec-formatdatetimerange ThrowCompletionOr format_date_time_range(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& start, FormattableDateTime const& end) { // 1. Let parts be ? PartitionDateTimeRangePattern(dateTimeFormat, x, y). // 2. Let result be the empty String. String result; // NOTE: We short-circuit PartitionDateTimeRangePattern as we do not need individual partitions. { // 1. If IsTemporalObject(x) is true or IsTemporalObject(y) is true, then if (is_temporal_object(start) || is_temporal_object(end)) { // a. If SameTemporalType(x, y) is false, throw a TypeError exception. if (!same_temporal_type(start, end)) return vm.throw_completion(ErrorType::IntlTemporalFormatRangeTypeMismatch); } // 2. Let xFormatRecord be ? HandleDateTimeValue(dateTimeFormat, x). auto start_format_record = TRY(handle_date_time_value(vm, date_time_format, start)); // 3. Let yFormatRecord be ? HandleDateTimeValue(dateTimeFormat, y). auto end_format_record = TRY(handle_date_time_value(vm, date_time_format, end)); result = start_format_record.formatter.format_range(start_format_record.epoch_milliseconds, end_format_record.epoch_milliseconds); } // 4. Return result. return result; } // 11.5.11 FormatDateTimeRangeToParts ( dateTimeFormat, x, y ), https://tc39.es/ecma402/#sec-formatdatetimerangetoparts // 15.9.10 FormatDateTimeRangeToParts ( dateTimeFormat, x, y ), https://tc39.es/proposal-temporal/#sec-formatdatetimerangetoparts ThrowCompletionOr> format_date_time_range_to_parts(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& start, FormattableDateTime const& end) { auto& realm = *vm.current_realm(); // 1. Let parts be ? PartitionDateTimeRangePattern(dateTimeFormat, x, y). auto parts = TRY(partition_date_time_range_pattern(vm, date_time_format, start, end)); // 2. Let result be ! ArrayCreate(0). auto result = MUST(Array::create(realm, 0)); // 3. Let n be 0. size_t n = 0; // 4. For each Record { [[Type]], [[Value]], [[Source]] } part in parts, do for (auto& part : parts) { // a. Let O be OrdinaryObjectCreate(%ObjectPrototype%). auto object = Object::create(realm, realm.intrinsics().object_prototype()); // b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]). MUST(object->create_data_property_or_throw(vm.names.type, PrimitiveString::create(vm, part.type))); // c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]). MUST(object->create_data_property_or_throw(vm.names.value, PrimitiveString::create(vm, move(part.value)))); // d. Perform ! CreateDataPropertyOrThrow(O, "source", part.[[Source]]). MUST(object->create_data_property_or_throw(vm.names.source, PrimitiveString::create(vm, part.source))); // e. Perform ! CreateDataProperty(result, ! ToString(n), O). MUST(result->create_data_property_or_throw(n, object)); // f. Increment n by 1. ++n; } // 5. Return result. return result; } // 15.9.1 GetDateTimeFormat ( formats, matcher, options, required, defaults, inherit ), https://tc39.es/proposal-temporal/#sec-getdatetimeformat Optional get_date_time_format(Unicode::CalendarPattern const& options, OptionRequired required, OptionDefaults defaults, OptionInherit inherit) { using enum Unicode::CalendarPattern::Field; auto required_options = [&]() -> ReadonlySpan { static constexpr auto date_fields = AK::Array { Weekday, Year, Month, Day }; static constexpr auto time_fields = AK::Array { DayPeriod, Hour, Minute, Second, FractionalSecondDigits }; static constexpr auto year_month_fields = AK::Array { Year, Month }; static constexpr auto month_day_fields = AK::Array { Month, Day }; static constexpr auto any_fields = AK::Array { Weekday, Year, Month, Day, DayPeriod, Hour, Minute, Second, FractionalSecondDigits }; switch (required) { // 1. If required is DATE, then case OptionRequired::Date: // a. Let requiredOptions be « "weekday", "year", "month", "day" ». return date_fields; // 2. Else if required is TIME, then case OptionRequired::Time: // a. Let requiredOptions be « "dayPeriod", "hour", "minute", "second", "fractionalSecondDigits" ». return time_fields; // 3. Else if required is YEAR-MONTH, then case OptionRequired::YearMonth: // a. Let requiredOptions be « "year", "month" ». return year_month_fields; // 4. Else if required is MONTH-DAY, then case OptionRequired::MonthDay: // a. Let requiredOptions be « "month", "day" ». return month_day_fields; // 5. Else, case OptionRequired::Any: // a. Assert: required is ANY. // b. Let requiredOptions be « "weekday", "year", "month", "day", "dayPeriod", "hour", "minute", "second", "fractionalSecondDigits" ». return any_fields; } VERIFY_NOT_REACHED(); }(); auto default_options = [&]() -> ReadonlySpan { static constexpr auto date_fields = AK::Array { Year, Month, Day }; static constexpr auto time_fields = AK::Array { Hour, Minute, Second }; static constexpr auto year_month_fields = AK::Array { Year, Month }; static constexpr auto month_day_fields = AK::Array { Month, Day }; static constexpr auto all_fields = AK::Array { Year, Month, Day, Hour, Minute, Second }; switch (defaults) { // 6. If defaults is DATE, then case OptionDefaults::Date: // a. Let defaultOptions be « "year", "month", "day" ». return date_fields; // 7. Else if defaults is TIME, then case OptionDefaults::Time: // a. Let defaultOptions be « "hour", "minute", "second" ». return time_fields; // 8. Else if defaults is YEAR-MONTH, then case OptionDefaults::YearMonth: // a. Let defaultOptions be « "year", "month" ». return year_month_fields; // 9. Else if defaults is MONTH-DAY, then case OptionDefaults::MonthDay: // a. Let defaultOptions be « "month", "day" ». return month_day_fields; // 10. Else, case OptionDefaults::ZonedDateTime: case OptionDefaults::All: // a. Assert: defaults is ZONED-DATE-TIME or ALL. // b. Let defaultOptions be « "year", "month", "day", "hour", "minute", "second" ». return all_fields; } VERIFY_NOT_REACHED(); }(); Unicode::CalendarPattern format_options {}; // 11. If inherit is ALL, then if (inherit == OptionInherit::All) { // a. Let formatOptions be a copy of options. format_options = options; } // 12. Else, else { // a. Let formatOptions be a new Record. // b. If required is one of DATE, YEAR-MONTH, or ANY, then if (required == OptionRequired::Date || required == OptionRequired::YearMonth || required == OptionRequired::Any) { // i. Set formatOptions.[[era]] to options.[[era]]. format_options.era = options.era; } // c. If required is TIME or ANY, then if (required == OptionRequired::Time || required == OptionRequired::Any) { // i. Set formatOptions.[[hourCycle]] to options.[[hourCycle]]. format_options.hour_cycle = options.hour_cycle; format_options.hour12 = options.hour12; } } // 13. Let anyPresent be false. auto any_present = false; // 14. For each property name prop of « "weekday", "year", "month", "day", "era", "dayPeriod", "hour", "minute", "second", "fractionalSecondDigits" », do static constexpr auto all_fields = AK::Array { Weekday, Year, Month, Day, Era, DayPeriod, Hour, Minute, Second, FractionalSecondDigits }; options.for_each_calendar_field_zipped_with(format_options, all_fields, [&](auto const& option, auto&) { // a. If options.[[]] is not undefined, set anyPresent to true. if (option.has_value()) { any_present = true; return IterationDecision::Break; } return IterationDecision::Continue; }); // 15. Let needDefaults be true. auto need_defaults = true; // 16. For each property name prop of requiredOptions, do options.for_each_calendar_field_zipped_with(format_options, required_options, [&](auto const& option, auto& format_option) { // a. Let value be options.[[]]. // b. If value is not undefined, then if (option.has_value()) { // i. Set formatOptions.[[]] to value. format_option = *option; // ii. Set needDefaults to false. need_defaults = false; } return IterationDecision::Continue; }); // 17. If needDefaults is true, then if (need_defaults) { // a. If anyPresent is true and inherit is RELEVANT, return null. if (any_present && inherit == OptionInherit::Relevant) return {}; // b. For each property name prop of defaultOptions, do options.for_each_calendar_field_zipped_with(format_options, default_options, [&](auto const&, auto& format_option) { using ValueType = typename RemoveCVReference::ValueType; if constexpr (IsSame) { // i. Set formatOptions.[[]] to "numeric". format_option = Unicode::CalendarPatternStyle::Numeric; } return IterationDecision::Continue; }); // c. If defaults is ZONED-DATE-TIME and formatOptions.[[timeZoneName]] is undefined, then if (defaults == OptionDefaults::ZonedDateTime && !format_options.time_zone_name.has_value()) { // i. Set formatOptions.[[timeZoneName]] to "short". format_options.time_zone_name = Unicode::CalendarPatternStyle::Short; } } // 18. If matcher is "basic", then // a. Let bestFormat be BasicFormatMatcher(formatOptions, formats). // 19. Else, // a. Let bestFormat be BestFitFormatMatcher(formatOptions, formats). // 20. Return bestFormat. return format_options; } // 15.9.2 AdjustDateTimeStyleFormat ( formats, baseFormat, matcher, allowedOptions ), https://tc39.es/proposal-temporal/#sec-adjustdatetimestyleformat Unicode::CalendarPattern adjust_date_time_style_format(Unicode::CalendarPattern const& base_format, ReadonlySpan allowed_options) { // 1. Let formatOptions be a new Record. Unicode::CalendarPattern format_options; // 2. For each field name fieldName of allowedOptions, do base_format.for_each_calendar_field_zipped_with(format_options, allowed_options, [&](auto const& base_option, auto& format_option) { // a. Set the field of formatOptions whose name is fieldName to the value of the field of baseFormat whose name is fieldName. format_option = base_option; return IterationDecision::Continue; }); // 3. If matcher is "basic", then // a. Let bestFormat be BasicFormatMatcher(formatOptions, formats). // 4. Else, // a. Let bestFormat be BestFitFormatMatcher(formatOptions, formats). // 5. Return bestFormat. return format_options; } // 15.9.11 ToDateTimeFormattable ( value ), https://tc39.es/proposal-temporal/#sec-todatetimeformattable ThrowCompletionOr to_date_time_formattable(VM& vm, Value value) { // 1. If IsTemporalObject(value) is true, return value. if (value.is_object()) { auto& object = value.as_object(); if (is(object)) return FormattableDateTime { static_cast(object) }; if (is(object)) return FormattableDateTime { static_cast(object) }; if (is(object)) return FormattableDateTime { static_cast(object) }; if (is(object)) return FormattableDateTime { static_cast(object) }; if (is(object)) return FormattableDateTime { static_cast(object) }; if (is(object)) return FormattableDateTime { static_cast(object) }; if (is(object)) return FormattableDateTime { static_cast(object) }; } // 2. Return ? ToNumber(value). return FormattableDateTime { TRY(value.to_number(vm)).as_double() }; } // 15.9.12 IsTemporalObject ( value ), https://tc39.es/proposal-temporal/#sec-temporal-istemporalobject bool is_temporal_object(FormattableDateTime const& value) { // 1. If value is not an Object, then // a. Return false. // 2. If value does not have an [[InitializedTemporalDate]], [[InitializedTemporalTime]], [[InitializedTemporalDateTime]], // [[InitializedTemporalZonedDateTime]], [[InitializedTemporalYearMonth]], [[InitializedTemporalMonthDay]], or // [[InitializedTemporalInstant]] internal slot, then // a. Return false. // 3. Return true. return !value.has(); } // 15.9.13 SameTemporalType ( x, y ), https://tc39.es/proposal-temporal/#sec-temporal-istemporalobject bool same_temporal_type(FormattableDateTime const& x, FormattableDateTime const& y) { // 1. If either of IsTemporalObject(x) or IsTemporalObject(y) is false, return false. if (!is_temporal_object(x) || !is_temporal_object(y)) return false; // 2. If x has an [[InitializedTemporalDate]] internal slot and y does not, return false. // 3. If x has an [[InitializedTemporalTime]] internal slot and y does not, return false. // 4. If x has an [[InitializedTemporalDateTime]] internal slot and y does not, return false. // 5. If x has an [[InitializedTemporalZonedDateTime]] internal slot and y does not, return false. // 6. If x has an [[InitializedTemporalYearMonth]] internal slot and y does not, return false. // 7. If x has an [[InitializedTemporalMonthDay]] internal slot and y does not, return false. // 8. If x has an [[InitializedTemporalInstant]] internal slot and y does not, return false. // 9. Return true. return x.index() == y.index(); } static double to_epoch_milliseconds(Crypto::SignedBigInteger const& epoch_nanoseconds) { return big_floor(epoch_nanoseconds, Temporal::NANOSECONDS_PER_MILLISECOND).to_double(); } // 15.9.15 HandleDateTimeTemporalDate ( dateTimeFormat, temporalDate ), https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporaldate ThrowCompletionOr handle_date_time_temporal_date(VM& vm, DateTimeFormat& date_time_format, Temporal::PlainDate const& temporal_date) { // 1. If temporalDate.[[Calendar]] is not dateTimeFormat.[[Calendar]] or "iso8601", throw a RangeError exception. if (!temporal_date.calendar().is_one_of(date_time_format.calendar(), "iso8601"sv)) return vm.throw_completion(ErrorType::IntlTemporalInvalidCalendar, "Temporal.PlainDate"sv, temporal_date.calendar(), date_time_format.calendar()); // 2. Let isoDateTime be CombineISODateAndTimeRecord(temporalDate.[[ISODate]], NoonTimeRecord()). auto iso_date_time = Temporal::combine_iso_date_and_time_record(temporal_date.iso_date(), Temporal::noon_time_record()); // 3. Let epochNs be ? GetEpochNanosecondsFor(dateTimeFormat.[[TimeZone]], isoDateTime, COMPATIBLE). auto epoch_nanoseconds = TRY(Temporal::get_epoch_nanoseconds_for(vm, date_time_format.time_zone(), iso_date_time, Temporal::Disambiguation::Compatible)); // 4. Let format be dateTimeFormat.[[TemporalPlainDateFormat]]. auto formatter = date_time_format.temporal_plain_date_formatter(); // 5. If format is null, throw a TypeError exception. if (!formatter.has_value()) return vm.throw_completion(ErrorType::IntlTemporalFormatIsNull, "Temporal.PlainDate"sv); // 6. Return Value Format Record { [[Format]]: format, [[EpochNanoseconds]]: epochNs }. return ValueFormat { .formatter = *formatter, .epoch_milliseconds = to_epoch_milliseconds(epoch_nanoseconds) }; } // 15.9.16 HandleDateTimeTemporalYearMonth ( dateTimeFormat, temporalYearMonth ), https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporalyearmonth ThrowCompletionOr handle_date_time_temporal_year_month(VM& vm, DateTimeFormat& date_time_format, Temporal::PlainYearMonth const& temporal_year_month) { // 1. If temporalYearMonth.[[Calendar]] is not equal to dateTimeFormat.[[Calendar]], then if (temporal_year_month.calendar() != date_time_format.calendar()) { // a. Throw a RangeError exception. return vm.throw_completion(ErrorType::IntlTemporalInvalidCalendar, "Temporal.PlainYearMonth"sv, temporal_year_month.calendar(), date_time_format.calendar()); } // 2. Let isoDateTime be CombineISODateAndTimeRecord(temporalYearMonth.[[ISODate]], NoonTimeRecord()). auto iso_date_time = Temporal::combine_iso_date_and_time_record(temporal_year_month.iso_date(), Temporal::noon_time_record()); // 3. Let epochNs be ? GetEpochNanosecondsFor(dateTimeFormat.[[TimeZone]], isoDateTime, COMPATIBLE). auto epoch_nanoseconds = TRY(Temporal::get_epoch_nanoseconds_for(vm, date_time_format.time_zone(), iso_date_time, Temporal::Disambiguation::Compatible)); // 4. Let format be dateTimeFormat.[[TemporalPlainYearMonthFormat]]. auto formatter = date_time_format.temporal_plain_year_month_formatter(); // 5. If format is null, throw a TypeError exception. if (!formatter.has_value()) return vm.throw_completion(ErrorType::IntlTemporalFormatIsNull, "Temporal.PlainYearMonth"sv); // 6. Return Value Format Record { [[Format]]: format, [[EpochNanoseconds]]: epochNs }. return ValueFormat { .formatter = *formatter, .epoch_milliseconds = to_epoch_milliseconds(epoch_nanoseconds) }; } // 15.9.17 HandleDateTimeTemporalMonthDay ( dateTimeFormat, temporalMonthDay ), https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporalmonthday ThrowCompletionOr handle_date_time_temporal_month_day(VM& vm, DateTimeFormat& date_time_format, Temporal::PlainMonthDay const& temporal_month_day) { // 1. If temporalMonthDay.[[Calendar]] is not equal to dateTimeFormat.[[Calendar]], then if (temporal_month_day.calendar() != date_time_format.calendar()) { // a. Throw a RangeError exception. return vm.throw_completion(ErrorType::IntlTemporalInvalidCalendar, "Temporal.PlainMonthDay"sv, temporal_month_day.calendar(), date_time_format.calendar()); } // 2. Let isoDateTime be CombineISODateAndTimeRecord(temporalMonthDay.[[ISODate]], NoonTimeRecord()). auto iso_date_time = Temporal::combine_iso_date_and_time_record(temporal_month_day.iso_date(), Temporal::noon_time_record()); // 3. Let epochNs be ? GetEpochNanosecondsFor(dateTimeFormat.[[TimeZone]], isoDateTime, COMPATIBLE). auto epoch_nanoseconds = TRY(Temporal::get_epoch_nanoseconds_for(vm, date_time_format.time_zone(), iso_date_time, Temporal::Disambiguation::Compatible)); // 4. Let format be dateTimeFormat.[[TemporalPlainMonthDayFormat]]. auto formatter = date_time_format.temporal_plain_month_day_formatter(); // 5. If format is null, throw a TypeError exception. if (!formatter.has_value()) return vm.throw_completion(ErrorType::IntlTemporalFormatIsNull, "Temporal.PlainMonthDay"sv); // 6. Return Value Format Record { [[Format]]: format, [[EpochNanoseconds]]: epochNs }. return ValueFormat { .formatter = *formatter, .epoch_milliseconds = to_epoch_milliseconds(epoch_nanoseconds) }; } // 15.9.18 HandleDateTimeTemporalTime ( dateTimeFormat, temporalTime ), https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporaltime ThrowCompletionOr handle_date_time_temporal_time(VM& vm, DateTimeFormat& date_time_format, Temporal::PlainTime const& temporal_time) { // 1. Let isoDate be CreateISODateRecord(1970, 1, 1). auto iso_date = Temporal::create_iso_date_record(1970, 1, 1); // 2. Let isoDateTime be CombineISODateAndTimeRecord(isoDate, temporalTime.[[Time]]). auto iso_date_time = Temporal::combine_iso_date_and_time_record(iso_date, temporal_time.time()); // 3. Let epochNs be ? GetEpochNanosecondsFor(dateTimeFormat.[[TimeZone]], isoDateTime, COMPATIBLE). auto epoch_nanoseconds = TRY(Temporal::get_epoch_nanoseconds_for(vm, date_time_format.time_zone(), iso_date_time, Temporal::Disambiguation::Compatible)); // 4. Let format be dateTimeFormat.[[TemporalPlainTimeFormat]]. auto formatter = date_time_format.temporal_plain_time_formatter(); // 5. If format is null, throw a TypeError exception. if (!formatter.has_value()) return vm.throw_completion(ErrorType::IntlTemporalFormatIsNull, "Temporal.PlainTime"sv); // 6. Return Value Format Record { [[Format]]: format, [[EpochNanoseconds]]: epochNs }. return ValueFormat { .formatter = *formatter, .epoch_milliseconds = to_epoch_milliseconds(epoch_nanoseconds) }; } // 15.9.19 HandleDateTimeTemporalDateTime ( dateTimeFormat, dateTime ), https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporaldatetime ThrowCompletionOr handle_date_time_temporal_date_time(VM& vm, DateTimeFormat& date_time_format, Temporal::PlainDateTime const& date_time) { // 1. If dateTime.[[Calendar]] is not "iso8601" and not equal to dateTimeFormat.[[Calendar]], then if (!date_time.calendar().is_one_of(date_time_format.calendar(), "iso8601"sv)) { // a. Throw a RangeError exception. return vm.throw_completion(ErrorType::IntlTemporalInvalidCalendar, "Temporal.PlainDateTime"sv, date_time.calendar(), date_time_format.calendar()); } // 2. Let epochNs be ? GetEpochNanosecondsFor(dateTimeFormat.[[TimeZone]], dateTime.[[ISODateTime]], COMPATIBLE). auto epoch_nanoseconds = TRY(Temporal::get_epoch_nanoseconds_for(vm, date_time_format.time_zone(), date_time.iso_date_time(), Temporal::Disambiguation::Compatible)); // 3. Let format be dateTimeFormat.[[TemporalPlainDateTimeFormat]]. auto formatter = date_time_format.temporal_plain_date_time_formatter(); VERIFY(formatter.has_value()); // 4. Return Value Format Record { [[Format]]: format, [[EpochNanoseconds]]: epochNs }. return ValueFormat { .formatter = *formatter, .epoch_milliseconds = to_epoch_milliseconds(epoch_nanoseconds) }; } // 15.9.20 HandleDateTimeTemporalInstant ( dateTimeFormat, instant ), https://tc39.es/proposal-temporal/#sec-temporal-handledatetimetemporalinstant ValueFormat handle_date_time_temporal_instant(DateTimeFormat& date_time_format, Temporal::Instant const& instant) { // 1. Let format be dateTimeFormat.[[TemporalInstantFormat]]. auto formatter = date_time_format.temporal_instant_formatter(); VERIFY(formatter.has_value()); // 2. Return Value Format Record { [[Format]]: format, [[EpochNanoseconds]]: instant.[[EpochNanoseconds]] }. return ValueFormat { .formatter = *formatter, .epoch_milliseconds = to_epoch_milliseconds(instant.epoch_nanoseconds()->big_integer()) }; } // 15.9.21 HandleDateTimeOthers ( dateTimeFormat, x ), https://tc39.es/proposal-temporal/#sec-temporal-handledatetimeothers ThrowCompletionOr handle_date_time_others(VM& vm, DateTimeFormat& date_time_format, double time) { // 1. Set x to TimeClip(x). time = time_clip(time); // 2. If x is NaN, throw a RangeError exception. if (isnan(time)) return vm.throw_completion(ErrorType::IntlInvalidTime); // 3. Let epochNanoseconds be ℤ(ℝ(x) × 10**6). // 4. Let format be dateTimeFormat.[[DateTimeFormat]]. auto const& formatter = date_time_format.formatter(); // 5. Return Value Format Record { [[Format]]: format, [[EpochNanoseconds]]: epochNanoseconds }. return ValueFormat { .formatter = formatter, .epoch_milliseconds = time }; } // 15.9.22 HandleDateTimeValue ( dateTimeFormat, x ), https://tc39.es/proposal-temporal/#sec-temporal-handledatetimevalue ThrowCompletionOr handle_date_time_value(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& formattable) { return formattable.visit( // 1. If x is an Object, then // a. If x has an [[InitializedTemporalDate]] internal slot, then [&](GC::Ref temporal_date) { // i. Return ? HandleDateTimeTemporalDate(dateTimeFormat, x). return handle_date_time_temporal_date(vm, date_time_format, temporal_date); }, // b. If x has an [[InitializedTemporalYearMonth]] internal slot, then [&](GC::Ref temporal_year_month) { // i. Return ? HandleDateTimeTemporalYearMonth(dateTimeFormat, x). return handle_date_time_temporal_year_month(vm, date_time_format, temporal_year_month); }, // c. If x has an [[InitializedTemporalMonthDay]] internal slot, then [&](GC::Ref temporal_month_day) { // i. Return ? HandleDateTimeTemporalMonthDay(dateTimeFormat, x). return handle_date_time_temporal_month_day(vm, date_time_format, temporal_month_day); }, // d. If x has an [[InitializedTemporalTime]] internal slot, then [&](GC::Ref temporal_time) { // i. Return ? HandleDateTimeTemporalTime(dateTimeFormat, x). return handle_date_time_temporal_time(vm, date_time_format, temporal_time); }, // e. If x has an [[InitializedTemporalDateTime]] internal slot, then [&](GC::Ref date_time) { // i. Return ? HandleDateTimeTemporalDateTime(dateTimeFormat, x). return handle_date_time_temporal_date_time(vm, date_time_format, date_time); }, // f. If x has an [[InitializedTemporalInstant]] internal slot, then [&](GC::Ref instant) -> ThrowCompletionOr { // i. Return HandleDateTimeTemporalInstant(dateTimeFormat, x). return handle_date_time_temporal_instant(date_time_format, instant); }, // g. Assert: x has an [[InitializedTemporalZonedDateTime]] internal slot. [&](GC::Ref) -> ThrowCompletionOr { // h. Throw a TypeError exception. return vm.throw_completion(ErrorType::IntlTemporalZonedDateTime); }, // 2. Return ? HandleDateTimeOthers(dateTimeFormat, x). [&](double time) { return handle_date_time_others(vm, date_time_format, time); }); } }