LibJS+LibUnicode: Integrate Temporal into Intl.DateTimeFormat

The gist is that we need to construct an ICU date-time formatter for
each possible Temporal type. This is of course going to be expensive.
So instead, we construct the configurations needed for the ICU objects
in the Intl.DateTimeFormat constructor, and defer creating the actual
ICU objects until they are needed.

Each formatting prototype can also now accept either a number (as they
already did), or any of the supported Temporal objects. These types may
not be mixed, and their properties (namely, their calendar) must align
with the Intl.DateTimeFormat object.
This commit is contained in:
Timothy Flynn 2024-11-27 16:09:41 -05:00 committed by Andreas Kling
commit ea503a4f68
Notes: github-actions[bot] 2024-11-29 08:53:47 +00:00
12 changed files with 1348 additions and 210 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -8,6 +8,14 @@
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/Intl/DateTimeFormat.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/Temporal/Instant.h>
#include <LibJS/Runtime/Temporal/PlainDate.h>
#include <LibJS/Runtime/Temporal/PlainDateTime.h>
#include <LibJS/Runtime/Temporal/PlainMonthDay.h>
#include <LibJS/Runtime/Temporal/PlainTime.h>
#include <LibJS/Runtime/Temporal/PlainYearMonth.h>
#include <LibJS/Runtime/Temporal/TimeZone.h>
#include <LibJS/Runtime/Temporal/ZonedDateTime.h>
#include <math.h>
namespace JS::Intl {
@ -26,52 +34,88 @@ void DateTimeFormat::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_bound_format);
}
// 11.5.5 FormatDateTimePattern ( dateTimeFormat, patternParts, x, rangeFormatOptions ), https://tc39.es/ecma402/#sec-formatdatetimepattern
ThrowCompletionOr<Vector<Unicode::DateTimeFormat::Partition>> format_date_time_pattern(VM& vm, DateTimeFormat& date_time_format, double time)
static Optional<Unicode::DateTimeFormat const&> get_or_create_formatter(StringView locale, StringView time_zone, OwnPtr<Unicode::DateTimeFormat>& formatter, Optional<Unicode::CalendarPattern> const& format)
{
// 1. Let x be TimeClip(x).
time = time_clip(time);
if (formatter)
return *formatter;
if (!format.has_value())
return {};
// 2. If x is NaN, throw a RangeError exception.
if (isnan(time))
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidTime);
formatter = Unicode::DateTimeFormat::create_for_pattern_options(locale, time_zone, *format);
return *formatter;
}
return date_time_format.formatter().format_to_parts(time);
Optional<Unicode::DateTimeFormat const&> 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<Unicode::DateTimeFormat const&> 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<Unicode::DateTimeFormat const&> 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<Unicode::DateTimeFormat const&> 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<Unicode::DateTimeFormat const&> 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<Unicode::DateTimeFormat const&> 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<Unicode::DateTimeFormat::Partition> 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
ThrowCompletionOr<Vector<Unicode::DateTimeFormat::Partition>> partition_date_time_pattern(VM& vm, DateTimeFormat& date_time_format, double time)
// 15.9.5 PartitionDateTimePattern ( dateTimeFormat, x ), https://tc39.es/proposal-temporal/#sec-partitiondatetimepattern
ThrowCompletionOr<Vector<Unicode::DateTimeFormat::Partition>> partition_date_time_pattern(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& time)
{
// 1. Let patternParts be PartitionPattern(dateTimeFormat.[[Pattern]]).
// 2. Let result be ? FormatDateTimePattern(dateTimeFormat, patternParts, x, undefined).
return format_date_time_pattern(vm, date_time_format, 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
ThrowCompletionOr<String> format_date_time(VM& vm, DateTimeFormat& date_time_format, double time)
// 15.9.6 FormatDateTime ( dateTimeFormat, x ), https://tc39.es/proposal-temporal/#sec-formatdatetime
ThrowCompletionOr<String> 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.
{
// NOTE: We short-circuit PartitionDateTimePattern as we do not need individual partitions. But we must still
// perform the time clip and NaN sanity checks from its call to FormatDateTimePattern.
// 1. Let xFormatRecord be ? HandleDateTimeValue(dateTimeFormat, x).
auto format_record = TRY(handle_date_time_value(vm, date_time_format, time));
// 1. Let x be TimeClip(x).
time = time_clip(time);
// 2. If x is NaN, throw a RangeError exception.
if (isnan(time))
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidTime);
result = format_record.formatter.format(format_record.epoch_milliseconds);
}
// 2. Let result be the empty String.
// 3. For each Record { [[Type]], [[Value]] } part in parts, do
// a. Set result to the string-concatenation of result and part.[[Value]].
// 4. Return result.
return date_time_format.formatter().format(time);
return result;
}
// 11.5.8 FormatDateTimeToParts ( dateTimeFormat, x ), https://tc39.es/ecma402/#sec-formatdatetimetoparts
ThrowCompletionOr<GC::Ref<Array>> format_date_time_to_parts(VM& vm, DateTimeFormat& date_time_format, double time)
// 15.9.7 FormatDateTimeToParts ( dateTimeFormat, x ), https://tc39.es/proposal-temporal/#sec-formatdatetimetoparts
ThrowCompletionOr<GC::Ref<Array>> format_date_time_to_parts(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& time)
{
auto& realm = *vm.current_realm();
@ -107,57 +151,58 @@ ThrowCompletionOr<GC::Ref<Array>> format_date_time_to_parts(VM& vm, DateTimeForm
}
// 11.5.9 PartitionDateTimeRangePattern ( dateTimeFormat, x, y ), https://tc39.es/ecma402/#sec-partitiondatetimerangepattern
ThrowCompletionOr<Vector<Unicode::DateTimeFormat::Partition>> partition_date_time_range_pattern(VM& vm, DateTimeFormat& date_time_format, double start, double end)
// 15.9.8 PartitionDateTimeRangePattern ( dateTimeFormat, x, y ), https://tc39.es/proposal-temporal/#sec-partitiondatetimerangepattern
ThrowCompletionOr<Vector<Unicode::DateTimeFormat::Partition>> partition_date_time_range_pattern(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& start, FormattableDateTime const& end)
{
// 1. Let x be TimeClip(x).
start = time_clip(start);
// 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<TypeError>(ErrorType::IntlTemporalFormatRangeTypeMismatch);
}
// 2. If x is NaN, throw a RangeError exception.
if (isnan(start))
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidTime);
// 2. Let xFormatRecord be ? HandleDateTimeValue(dateTimeFormat, x).
auto start_format_record = TRY(handle_date_time_value(vm, date_time_format, start));
// 3. Let y be TimeClip(y).
end = time_clip(end);
// 3. Let yFormatRecord be ? HandleDateTimeValue(dateTimeFormat, y).
auto end_format_record = TRY(handle_date_time_value(vm, date_time_format, end));
// 4. If y is NaN, throw a RangeError exception.
if (isnan(end))
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidTime);
return date_time_format.formatter().format_range_to_parts(start, 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
ThrowCompletionOr<String> format_date_time_range(VM& vm, DateTimeFormat& date_time_format, double start, double end)
// 15.9.9 FormatDateTimeRange ( dateTimeFormat, x, y ), https://tc39.es/proposal-temporal/#sec-formatdatetimerange
ThrowCompletionOr<String> format_date_time_range(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& start, FormattableDateTime const& end)
{
{
// NOTE: We short-circuit PartitionDateTimeRangePattern as we do not need individual partitions. But we must
// still perform the time clip and NaN sanity checks from its its first steps.
// 1. Let x be TimeClip(x).
start = time_clip(start);
// 2. If x is NaN, throw a RangeError exception.
if (isnan(start))
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidTime);
// 3. Let y be TimeClip(y).
end = time_clip(end);
// 4. If y is NaN, throw a RangeError exception.
if (isnan(end))
return vm.throw_completion<RangeError>(ErrorType::IntlInvalidTime);
}
// 1. Let parts be ? PartitionDateTimeRangePattern(dateTimeFormat, x, y).
// 2. Let result be the empty String.
// 3. For each Record { [[Type]], [[Value]], [[Source]] } part in parts, do
// a. Set result to the string-concatenation of result and part.[[Value]].
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<TypeError>(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 date_time_format.formatter().format_range(start, end);
return result;
}
// 11.5.11 FormatDateTimeRangeToParts ( dateTimeFormat, x, y ), https://tc39.es/ecma402/#sec-formatdatetimerangetoparts
ThrowCompletionOr<GC::Ref<Array>> format_date_time_range_to_parts(VM& vm, DateTimeFormat& date_time_format, double start, double end)
// 15.9.10 FormatDateTimeRangeToParts ( dateTimeFormat, x, y ), https://tc39.es/proposal-temporal/#sec-formatdatetimerangetoparts
ThrowCompletionOr<GC::Ref<Array>> format_date_time_range_to_parts(VM& vm, DateTimeFormat& date_time_format, FormattableDateTime const& start, FormattableDateTime const& end)
{
auto& realm = *vm.current_realm();
@ -195,4 +240,449 @@ ThrowCompletionOr<GC::Ref<Array>> format_date_time_range_to_parts(VM& vm, DateTi
return result;
}
// 15.9.1 GetDateTimeFormat ( formats, matcher, options, required, defaults, inherit ), https://tc39.es/proposal-temporal/#sec-getdatetimeformat
Optional<Unicode::CalendarPattern> get_date_time_format(Unicode::CalendarPattern const& options, OptionRequired required, OptionDefaults defaults, OptionInherit inherit)
{
using enum Unicode::CalendarPattern::Field;
auto required_options = [&]() -> ReadonlySpan<Unicode::CalendarPattern::Field> {
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<Unicode::CalendarPattern::Field> {
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.[[<prop>]] 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.[[<prop>]].
// b. If value is not undefined, then
if (option.has_value()) {
// i. Set formatOptions.[[<prop>]] to value.
format_option = *option;
// ii. Set needDefaults to false.
need_defaults = false;
}
return IterationDecision::Continue;
});
// 17. If anyPresent is true and needDefaults is true, return null.
if (any_present && need_defaults) {
// FIXME: Spec issue: We can hit this when setting `bestFormat`, which should never be null. Don't return for now.
// https://github.com/tc39/proposal-temporal/issues/3049
}
// 18. If needDefaults is true, then
if (need_defaults) {
// a. 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<decltype(format_option)>::ValueType;
if constexpr (IsSame<ValueType, Unicode::CalendarPatternStyle>) {
// i. Set formatOptions.[[<prop>]] to "numeric".
format_option = Unicode::CalendarPatternStyle::Numeric;
}
return IterationDecision::Continue;
});
// b. If defaults is ZONED-DATE-TIME, then
if (defaults == OptionDefaults::ZonedDateTime) {
// i. Set formatOptions.[[timeZoneName]] to "short".
format_options.time_zone_name = Unicode::CalendarPatternStyle::Short;
}
}
// 19. If matcher is "basic", then
// a. Let bestFormat be BasicFormatMatcher(formatOptions, formats).
// 20. Else,
// a. Let bestFormat be BestFitFormatMatcher(formatOptions, formats).
// 21. 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<Unicode::CalendarPattern::Field> 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<FormattableDateTime> 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<Temporal::Instant>(object))
return FormattableDateTime { static_cast<Temporal::Instant&>(object) };
if (is<Temporal::PlainDate>(object))
return FormattableDateTime { static_cast<Temporal::PlainDate&>(object) };
if (is<Temporal::PlainDateTime>(object))
return FormattableDateTime { static_cast<Temporal::PlainDateTime&>(object) };
if (is<Temporal::PlainMonthDay>(object))
return FormattableDateTime { static_cast<Temporal::PlainMonthDay&>(object) };
if (is<Temporal::PlainTime>(object))
return FormattableDateTime { static_cast<Temporal::PlainTime&>(object) };
if (is<Temporal::PlainYearMonth>(object))
return FormattableDateTime { static_cast<Temporal::PlainYearMonth&>(object) };
if (is<Temporal::ZonedDateTime>(object))
return FormattableDateTime { static_cast<Temporal::ZonedDateTime&>(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<double>();
}
// 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 Temporal::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<ValueFormat> 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<RangeError>(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<TypeError>(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<ValueFormat> 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<RangeError>(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<TypeError>(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<ValueFormat> 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<RangeError>(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<TypeError>(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<ValueFormat> 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<TypeError>(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<ValueFormat> 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<RangeError>(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<ValueFormat> 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<RangeError>(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<ValueFormat> 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::PlainDate> 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::PlainYearMonth> 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::PlainMonthDay> 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::PlainTime> 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<Temporal::PlainDateTime> 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<Temporal::Instant> instant) -> ThrowCompletionOr<ValueFormat> {
// 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<Temporal::ZonedDateTime>) -> ThrowCompletionOr<ValueFormat> {
// h. Throw a TypeError exception.
return vm.throw_completion<TypeError>(ErrorType::IntlTemporalZonedDateTime);
},
// 2. Return ? HandleDateTimeOthers(dateTimeFormat, x).
[&](double time) {
return handle_date_time_others(vm, date_time_format, time);
});
}
}