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
parent 4ad8ba11d5
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

@ -67,6 +67,10 @@
"Styles other than 'fractional', numeric', or '2-digit' may not be used in smaller units after being used in larger units") \
M(IntlNumberIsNaNOrOutOfRange, "Value {} is NaN or is not between {} and {}") \
M(IntlOptionUndefined, "Option {} must be defined when option {} is {}") \
M(IntlTemporalFormatIsNull, "Unable to determine format for {}") \
M(IntlTemporalFormatRangeTypeMismatch, "Cannot format a date-time range with different date-time types") \
M(IntlTemporalInvalidCalendar, "Cannot format {} with calendar '{}' in locale with calendar '{}'") \
M(IntlTemporalZonedDateTime, "Cannot format Temporal.ZonedDateTime, use Temporal.ZonedDateTime.prototype.toLocaleString") \
M(InvalidAssignToConst, "Invalid assignment to const variable") \
M(InvalidCodePoint, "Invalid code point {}, must be an integer no less than 0 and no greater than 0x10FFFF") \
M(InvalidEnumerationValue, "Invalid value '{}' for enumeration type '{}'") \

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);
});
}
}

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
*/
@ -12,6 +12,7 @@
#include <AK/Types.h>
#include <AK/Vector.h>
#include <LibJS/Runtime/Completion.h>
#include <LibJS/Runtime/Intl/DateTimeFormatConstructor.h>
#include <LibJS/Runtime/Object.h>
#include <LibJS/Runtime/VM.h>
#include <LibUnicode/DateTimeFormat.h>
@ -65,31 +66,96 @@ public:
Unicode::DateTimeFormat const& formatter() const { return *m_formatter; }
void set_formatter(NonnullOwnPtr<Unicode::DateTimeFormat> formatter) { m_formatter = move(formatter); }
Optional<Unicode::DateTimeFormat const&> temporal_plain_date_formatter();
void set_temporal_plain_date_format(Optional<Unicode::CalendarPattern> temporal_plain_date_format) { m_temporal_plain_date_format = move(temporal_plain_date_format); }
Optional<Unicode::DateTimeFormat const&> temporal_plain_year_month_formatter();
void set_temporal_plain_year_month_format(Optional<Unicode::CalendarPattern> temporal_plain_year_month_format) { m_temporal_plain_year_month_format = move(temporal_plain_year_month_format); }
Optional<Unicode::DateTimeFormat const&> temporal_plain_month_day_formatter();
void set_temporal_plain_month_day_format(Optional<Unicode::CalendarPattern> temporal_plain_month_day_format) { m_temporal_plain_month_day_format = move(temporal_plain_month_day_format); }
Optional<Unicode::DateTimeFormat const&> temporal_plain_time_formatter();
void set_temporal_plain_time_format(Optional<Unicode::CalendarPattern> temporal_plain_time_format) { m_temporal_plain_time_format = move(temporal_plain_time_format); }
Optional<Unicode::DateTimeFormat const&> temporal_plain_date_time_formatter();
void set_temporal_plain_date_time_format(Optional<Unicode::CalendarPattern> temporal_plain_date_time_format) { m_temporal_plain_date_time_format = move(temporal_plain_date_time_format); }
Optional<Unicode::DateTimeFormat const&> temporal_instant_formatter();
void set_temporal_instant_format(Optional<Unicode::CalendarPattern> temporal_instant_format) { m_temporal_instant_format = move(temporal_instant_format); }
void set_temporal_time_zone(String temporal_time_zone) { m_temporal_time_zone = move(temporal_time_zone); }
private:
DateTimeFormat(Object& prototype);
virtual void visit_edges(Visitor&) override;
String m_locale; // [[Locale]]
String m_calendar; // [[Calendar]]
String m_numbering_system; // [[NumberingSystem]]
String m_time_zone; // [[TimeZone]]
Optional<Unicode::DateTimeStyle> m_date_style; // [[DateStyle]]
Optional<Unicode::DateTimeStyle> m_time_style; // [[TimeStyle]]
Unicode::CalendarPattern m_date_time_format; // [[DateTimeFormat]]
GC::Ptr<NativeFunction> m_bound_format; // [[BoundFormat]]
String m_locale; // [[Locale]]
String m_calendar; // [[Calendar]]
String m_numbering_system; // [[NumberingSystem]]
String m_time_zone; // [[TimeZone]]
Optional<Unicode::DateTimeStyle> m_date_style; // [[DateStyle]]
Optional<Unicode::DateTimeStyle> m_time_style; // [[TimeStyle]]
Unicode::CalendarPattern m_date_time_format; // [[DateTimeFormat]]
Optional<Unicode::CalendarPattern> m_temporal_plain_date_format; // [[TemporalPlainDateFormat]]
Optional<Unicode::CalendarPattern> m_temporal_plain_year_month_format; // [[TemporalPlainYearMonthFormat]]
Optional<Unicode::CalendarPattern> m_temporal_plain_month_day_format; // [[TemporalPlainMonthDayFormat]]
Optional<Unicode::CalendarPattern> m_temporal_plain_time_format; // [[TemporalPlainTimeFormat]]
Optional<Unicode::CalendarPattern> m_temporal_plain_date_time_format; // [[TemporalPlainDateTimeFormat]]
Optional<Unicode::CalendarPattern> m_temporal_instant_format; // [[TemporalInstantFormat]]
GC::Ptr<NativeFunction> m_bound_format; // [[BoundFormat]]
// Non-standard. Stores the ICU date-time formatter for the Intl object's formatting options.
// Non-standard. Stores the ICU date-time formatters for the Intl object's formatting options.
OwnPtr<Unicode::DateTimeFormat> m_formatter;
OwnPtr<Unicode::DateTimeFormat> m_temporal_plain_date_formatter;
OwnPtr<Unicode::DateTimeFormat> m_temporal_plain_year_month_formatter;
OwnPtr<Unicode::DateTimeFormat> m_temporal_plain_month_day_formatter;
OwnPtr<Unicode::DateTimeFormat> m_temporal_plain_time_formatter;
OwnPtr<Unicode::DateTimeFormat> m_temporal_plain_date_time_formatter;
OwnPtr<Unicode::DateTimeFormat> m_temporal_instant_formatter;
String m_temporal_time_zone;
};
ThrowCompletionOr<Vector<Unicode::DateTimeFormat::Partition>> format_date_time_pattern(VM&, DateTimeFormat&, double time);
ThrowCompletionOr<Vector<Unicode::DateTimeFormat::Partition>> partition_date_time_pattern(VM&, DateTimeFormat&, double time);
ThrowCompletionOr<String> format_date_time(VM&, DateTimeFormat&, double time);
ThrowCompletionOr<GC::Ref<Array>> format_date_time_to_parts(VM&, DateTimeFormat&, double time);
ThrowCompletionOr<Vector<Unicode::DateTimeFormat::Partition>> partition_date_time_range_pattern(VM&, DateTimeFormat&, double start, double end);
ThrowCompletionOr<String> format_date_time_range(VM&, DateTimeFormat&, double start, double end);
ThrowCompletionOr<GC::Ref<Array>> format_date_time_range_to_parts(VM&, DateTimeFormat&, double start, double end);
using FormattableDateTime = Variant<
double,
GC::Ref<Temporal::PlainDate>,
GC::Ref<Temporal::PlainYearMonth>,
GC::Ref<Temporal::PlainMonthDay>,
GC::Ref<Temporal::PlainTime>,
GC::Ref<Temporal::PlainDateTime>,
GC::Ref<Temporal::ZonedDateTime>,
GC::Ref<Temporal::Instant>>;
// https://tc39.es/proposal-temporal/#datetimeformat-value-format-record
// NOTE: ICU does not support nanoseconds in its date-time formatter. Thus, we do do not store the epoch nanoseconds as
// a BigInt here. Instead, we store the epoch in milliseconds as a double.
struct ValueFormat {
Unicode::DateTimeFormat const& formatter; // [[Format]]
double epoch_milliseconds { 0 }; // [[EpochNanoseconds]]
};
Vector<Unicode::DateTimeFormat::Partition> format_date_time_pattern(ValueFormat const&);
ThrowCompletionOr<Vector<Unicode::DateTimeFormat::Partition>> partition_date_time_pattern(VM&, DateTimeFormat&, FormattableDateTime const&);
ThrowCompletionOr<String> format_date_time(VM&, DateTimeFormat&, FormattableDateTime const&);
ThrowCompletionOr<GC::Ref<Array>> format_date_time_to_parts(VM&, DateTimeFormat&, FormattableDateTime const&);
ThrowCompletionOr<Vector<Unicode::DateTimeFormat::Partition>> partition_date_time_range_pattern(VM&, DateTimeFormat&, FormattableDateTime const& start, FormattableDateTime const& end);
ThrowCompletionOr<String> format_date_time_range(VM&, DateTimeFormat&, FormattableDateTime const& start, FormattableDateTime const& end);
ThrowCompletionOr<GC::Ref<Array>> format_date_time_range_to_parts(VM&, DateTimeFormat&, FormattableDateTime const& start, FormattableDateTime const& end);
Optional<Unicode::CalendarPattern> get_date_time_format(Unicode::CalendarPattern const& options, OptionRequired, OptionDefaults, OptionInherit);
Unicode::CalendarPattern adjust_date_time_style_format(Unicode::CalendarPattern const& base_format, ReadonlySpan<Unicode::CalendarPattern::Field> allowed_options);
ThrowCompletionOr<FormattableDateTime> to_date_time_formattable(VM&, Value);
bool is_temporal_object(FormattableDateTime const&);
bool same_temporal_type(FormattableDateTime const&, FormattableDateTime const&);
ThrowCompletionOr<ValueFormat> handle_date_time_temporal_date(VM&, DateTimeFormat&, Temporal::PlainDate const&);
ThrowCompletionOr<ValueFormat> handle_date_time_temporal_year_month(VM&, DateTimeFormat&, Temporal::PlainYearMonth const&);
ThrowCompletionOr<ValueFormat> handle_date_time_temporal_month_day(VM&, DateTimeFormat&, Temporal::PlainMonthDay const&);
ThrowCompletionOr<ValueFormat> handle_date_time_temporal_time(VM&, DateTimeFormat&, Temporal::PlainTime const&);
ThrowCompletionOr<ValueFormat> handle_date_time_temporal_date_time(VM&, DateTimeFormat&, Temporal::PlainDateTime const&);
ValueFormat handle_date_time_temporal_instant(DateTimeFormat&, Temporal::Instant const&);
ThrowCompletionOr<ValueFormat> handle_date_time_others(VM&, DateTimeFormat&, double);
ThrowCompletionOr<ValueFormat> handle_date_time_value(VM&, DateTimeFormat&, FormattableDateTime const&);
template<typename Callback>
ThrowCompletionOr<void> for_each_calendar_field(VM& vm, Unicode::CalendarPattern& pattern, Callback&& callback)

View file

@ -55,7 +55,7 @@ ThrowCompletionOr<GC::Ref<Object>> DateTimeFormatConstructor::construct(Function
auto locales = vm.argument(0);
auto options = vm.argument(1);
// 2. Let dateTimeFormat be ? CreateDateTimeFormat(newTarget, locales, options, any, date).
// 2. Let dateTimeFormat be ? CreateDateTimeFormat(newTarget, locales, options, ANY, DATE).
auto date_time_format = TRY(create_date_time_format(vm, new_target, locales, options, OptionRequired::Any, OptionDefaults::Date));
// 3. If the implementation supports the normative optional constructor mode of 4.3 Note 1, then
@ -83,8 +83,7 @@ JS_DEFINE_NATIVE_FUNCTION(DateTimeFormatConstructor::supported_locales_of)
// 11.1.2 CreateDateTimeFormat ( newTarget, locales, options, required, defaults ), https://tc39.es/ecma402/#sec-createdatetimeformat
// 15.7.1 CreateDateTimeFormat ( newTarget, locales, options, required, defaults [ , toLocaleStringTimeZone ] ), https://tc39.es/proposal-temporal/#sec-createdatetimeformat
// FIXME: Update the rest of this AO for Temporal once we have the required Temporal objects.
ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, FunctionObject& new_target, Value locales_value, Value options_value, OptionRequired required, OptionDefaults defaults)
ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, FunctionObject& new_target, Value locales_value, Value options_value, OptionRequired required, OptionDefaults defaults, Optional<String> const& to_locale_string_time_zone)
{
// 1. Let dateTimeFormat be ? OrdinaryCreateFromConstructor(newTarget, "%Intl.DateTimeFormat.prototype%", « [[InitializedDateTimeFormat]], [[Locale]], [[Calendar]], [[NumberingSystem]], [[TimeZone]], [[HourCycle]], [[DateStyle]], [[TimeStyle]], [[DateTimeFormat]], [[BoundFormat]] »).
auto date_time_format = TRY(ordinary_create_from_constructor<DateTimeFormat>(vm, new_target, &Intrinsics::intl_date_time_format_prototype));
@ -187,22 +186,37 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
hour_cycle_value = Unicode::default_hour_cycle(date_time_format->locale());
}
// 26. Let timeZone be ? Get(options, "timeZone").
// 26. Set dateTimeFormat.[[HourCycle]] to hc.
// NOTE: The [[HourCycle]] is stored and accessed from [[DateTimeFormat]].
// 27. Let timeZone be ? Get(options, "timeZone").
auto time_zone_value = TRY(options->get(vm.names.timeZone));
String time_zone;
// 27. If timeZone is undefined, then
// 28. If timeZone is undefined, then
if (time_zone_value.is_undefined()) {
// a. Set timeZone to SystemTimeZoneIdentifier().
time_zone = system_time_zone_identifier();
// a. If toLocaleStringTimeZone is present, then
if (to_locale_string_time_zone.has_value()) {
// i. Set timeZone to toLocaleStringTimeZone.
time_zone = *to_locale_string_time_zone;
}
// b. Else,
else {
// i. Set timeZone to SystemTimeZoneIdentifier().
time_zone = system_time_zone_identifier();
}
}
// 28. Else,
// 29. Else,
else {
// a. Set timeZone to ? ToString(timeZone).
// a. If toLocaleStringTimeZone is present, throw a TypeError exception.
if (to_locale_string_time_zone.has_value())
return vm.throw_completion<TypeError>(ErrorType::IntlInvalidDateTimeFormatOption, vm.names.timeZone, "a toLocaleString time zone"sv);
// b. Set timeZone to ? ToString(timeZone).
time_zone = TRY(time_zone_value.to_string(vm));
}
// 29. If IsTimeZoneOffsetString(timeZone) is true, then
// 30. If IsTimeZoneOffsetString(timeZone) is true, then
bool is_time_zone_offset_string = JS::is_offset_time_zone_identifier(time_zone);
if (is_time_zone_offset_string) {
@ -224,7 +238,7 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
// f. Set timeZone to FormatOffsetTimeZoneIdentifier(offsetMinutes).
time_zone = format_offset_time_zone_identifier(offset_minutes);
}
// 30. Else,
// 31. Else,
else {
// a. Let timeZoneIdentifierRecord be GetAvailableNamedTimeZoneIdentifier(timeZone).
auto time_zone_identifier_record = get_available_named_time_zone_identifier(time_zone);
@ -237,25 +251,28 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
time_zone = time_zone_identifier_record->primary_identifier;
}
// 31. Set dateTimeFormat.[[TimeZone]] to timeZone.
// 32. Set dateTimeFormat.[[TimeZone]] to timeZone.
date_time_format->set_time_zone(time_zone);
// NOTE: ICU requires time zone offset strings to be of the form "GMT+00:00"
if (is_time_zone_offset_string)
time_zone = MUST(String::formatted("GMT{}", time_zone));
// 32. Let formatOptions be a new Record.
// AD-HOC: We must store the massaged time zone for creating ICU formatters for Temporal objects.
date_time_format->set_temporal_time_zone(time_zone);
// 33. Let formatOptions be a new Record.
Unicode::CalendarPattern format_options {};
// 33. Set formatOptions.[[hourCycle]] to hc.
// 34. Set formatOptions.[[hourCycle]] to hc.
format_options.hour_cycle = hour_cycle_value;
format_options.hour12 = hour12_value;
// 34. Let hasExplicitFormatComponents be false.
// 35. Let hasExplicitFormatComponents be false.
// NOTE: Instead of using a boolean, we track any explicitly provided component name for nicer exception messages.
PropertyKey const* explicit_format_component = nullptr;
// 35. For each row of Table 16, except the header row, in table order, do
// 36. For each row of Table 16, except the header row, in table order, do
TRY(for_each_calendar_field(vm, format_options, [&](auto& option, PropertyKey const& property, auto const& values) -> ThrowCompletionOr<void> {
using ValueType = typename RemoveReference<decltype(option)>::ValueType;
@ -294,26 +311,28 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
return {};
}));
// 36. Let formatMatcher be ? GetOption(options, "formatMatcher", string, « "basic", "best fit" », "best fit").
// 37. Let formatMatcher be ? GetOption(options, "formatMatcher", string, « "basic", "best fit" », "best fit").
[[maybe_unused]] auto format_matcher = TRY(get_option(vm, *options, vm.names.formatMatcher, OptionType::String, AK::Array { "basic"sv, "best fit"sv }, "best fit"sv));
// 37. Let dateStyle be ? GetOption(options, "dateStyle", string, « "full", "long", "medium", "short" », undefined).
// 38. Let dateStyle be ? GetOption(options, "dateStyle", string, « "full", "long", "medium", "short" », undefined).
auto date_style = TRY(get_option(vm, *options, vm.names.dateStyle, OptionType::String, AK::Array { "full"sv, "long"sv, "medium"sv, "short"sv }, Empty {}));
// 38. Set dateTimeFormat.[[DateStyle]] to dateStyle.
// 39. Set dateTimeFormat.[[DateStyle]] to dateStyle.
if (!date_style.is_undefined())
date_time_format->set_date_style(date_style.as_string().utf8_string_view());
// 39. Let timeStyle be ? GetOption(options, "timeStyle", string, « "full", "long", "medium", "short" », undefined).
// 40. Let timeStyle be ? GetOption(options, "timeStyle", string, « "full", "long", "medium", "short" », undefined).
auto time_style = TRY(get_option(vm, *options, vm.names.timeStyle, OptionType::String, AK::Array { "full"sv, "long"sv, "medium"sv, "short"sv }, Empty {}));
// 40. Set dateTimeFormat.[[TimeStyle]] to timeStyle.
// 41. Set dateTimeFormat.[[TimeStyle]] to timeStyle.
if (!time_style.is_undefined())
date_time_format->set_time_style(time_style.as_string().utf8_string_view());
// 42. Let formats be resolvedLocaleData.[[formats]].[[<resolvedCalendar>]].
OwnPtr<Unicode::DateTimeFormat> formatter;
// 41. If dateStyle is not undefined or timeStyle is not undefined, then
// 43. If dateStyle is not undefined or timeStyle is not undefined, then
if (date_time_format->has_date_style() || date_time_format->has_time_style()) {
// a. If hasExplicitFormatComponents is true, then
if (explicit_format_component != nullptr) {
@ -342,93 +361,100 @@ ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM& vm, Funct
format_options.hour12,
date_time_format->date_style(),
date_time_format->time_style());
auto best_format = formatter->chosen_pattern();
using enum Unicode::CalendarPattern::Field;
// f. If dateStyle is not undefined, then
if (!date_style.is_undefined()) {
// i. Set dateTimeFormat.[[TemporalPlainDateFormat]] to AdjustDateTimeStyleFormat(formats, bestFormat, matcher, « [[weekday]], [[era]], [[year]], [[month]], [[day]] »).
auto temporal_plain_date_format = adjust_date_time_style_format(best_format, { { Weekday, Era, Year, Month, Day } });
date_time_format->set_temporal_plain_date_format(move(temporal_plain_date_format));
// ii. Set dateTimeFormat.[[TemporalPlainYearMonthFormat]] to AdjustDateTimeStyleFormat(formats, bestFormat, matcher, « [[era]], [[year]], [[month]] »).
auto temporal_plain_year_month_format = adjust_date_time_style_format(best_format, { { Era, Year, Month } });
date_time_format->set_temporal_plain_year_month_format(move(temporal_plain_year_month_format));
// iii. Set dateTimeFormat.[[TemporalPlainMonthDayFormat]] to AdjustDateTimeStyleFormat(formats, bestFormat, matcher, « [[month]], [[day]] »).
auto temporal_plain_month_day_format = adjust_date_time_style_format(best_format, { { Month, Day } });
date_time_format->set_temporal_plain_month_day_format(move(temporal_plain_month_day_format));
}
// g. Else,
else {
// i. Set dateTimeFormat.[[TemporalPlainDateFormat]] to null.
// ii. Set dateTimeFormat.[[TemporalPlainYearMonthFormat]] to null.
// iii. Set dateTimeFormat.[[TemporalPlainMonthDayFormat]] to null.
}
// h. If timeStyle is not undefined, then
if (!time_style.is_undefined()) {
// i. Set dateTimeFormat.[[TemporalPlainTimeFormat]] to AdjustDateTimeStyleFormat(formats, bestFormat, matcher, « [[dayPeriod]], [[hour]], [[minute]], [[second]], [[fractionalSecondDigits]] »).
auto temporal_plain_time_format = adjust_date_time_style_format(best_format, { { DayPeriod, Hour, Minute, Second, FractionalSecondDigits } });
date_time_format->set_temporal_plain_time_format(move(temporal_plain_time_format));
}
// i. Else,
else {
// i. Set dateTimeFormat.[[TemporalPlainTimeFormat]] to null.
}
// j. Set dateTimeFormat.[[TemporalPlainDateTimeFormat]] to AdjustDateTimeStyleFormat(formats, bestFormat, matcher, « [[weekday]], [[era]], [[year]], [[month]], [[day]], [[dayPeriod]], [[hour]], [[minute]], [[second]], [[fractionalSecondDigits]] »).
auto temporal_plain_date_time_format = adjust_date_time_style_format(best_format, { { Weekday, Era, Year, Month, Day, DayPeriod, Hour, Minute, Second, FractionalSecondDigits } });
date_time_format->set_temporal_plain_date_time_format(move(temporal_plain_date_time_format));
// k. Set dateTimeFormat.[[TemporalInstantFormat]] to bestFormat.
date_time_format->set_temporal_instant_format(move(best_format));
}
// 42. Else,
// 44. Else,
else {
// a. Let needDefaults be true.
bool needs_defaults = true;
// a. Let bestFormat be GetDateTimeFormat(formats, formatMatcher, formatOptions, required, defaults, ALL).
auto best_format = get_date_time_format(format_options, required, defaults, OptionInherit::All).release_value();
// b. If required is date or any, then
if (required == OptionRequired::Date || required == OptionRequired::Any) {
// i. For each property name prop of « "weekday", "year", "month", "day" », do
auto check_property_value = [&](auto const& value) {
// 1. Let value be formatOptions.[[<prop>]].
// 2. If value is not undefined, set needDefaults to false.
if (value.has_value())
needs_defaults = false;
};
// b. Set dateTimeFormat.[[TemporalPlainDateFormat]] to GetDateTimeFormat(formats, formatMatcher, formatOptions, DATE, DATE, RELEVANT).
auto temporal_plain_date_format = get_date_time_format(format_options, OptionRequired::Date, OptionDefaults::Date, OptionInherit::Relevant);
date_time_format->set_temporal_plain_date_format(move(temporal_plain_date_format));
check_property_value(format_options.weekday);
check_property_value(format_options.year);
check_property_value(format_options.month);
check_property_value(format_options.day);
// c. Set dateTimeFormat.[[TemporalPlainYearMonthFormat]] to GetDateTimeFormat(formats, formatMatcher, formatOptions, YEAR-MONTH, YEAR-MONTH, RELEVANT).
auto temporal_plain_year_month_format = get_date_time_format(format_options, OptionRequired::YearMonth, OptionDefaults::YearMonth, OptionInherit::Relevant);
date_time_format->set_temporal_plain_year_month_format(move(temporal_plain_year_month_format));
// d. Set dateTimeFormat.[[TemporalPlainMonthDayFormat]] to GetDateTimeFormat(formats, formatMatcher, formatOptions, MONTH-DAY, MONTH-DAY, RELEVANT).
auto temporal_plain_month_day_format = get_date_time_format(format_options, OptionRequired::MonthDay, OptionDefaults::MonthDay, OptionInherit::Relevant);
date_time_format->set_temporal_plain_month_day_format(move(temporal_plain_month_day_format));
// e. Set dateTimeFormat.[[TemporalPlainTimeFormat]] to GetDateTimeFormat(formats, formatMatcher, formatOptions, TIME, TIME, RELEVANT).
auto temporal_plain_time_format = get_date_time_format(format_options, OptionRequired::Time, OptionDefaults::Time, OptionInherit::Relevant);
date_time_format->set_temporal_plain_time_format(move(temporal_plain_time_format));
// f. Set dateTimeFormat.[[TemporalPlainDateTimeFormat]] to GetDateTimeFormat(formats, formatMatcher, formatOptions, ANY, ALL, RELEVANT).
auto temporal_plain_date_time_format = get_date_time_format(format_options, OptionRequired::Any, OptionDefaults::All, OptionInherit::Relevant);
date_time_format->set_temporal_plain_date_time_format(move(temporal_plain_date_time_format));
// g. If toLocaleStringTimeZone is present, then
if (to_locale_string_time_zone.has_value()) {
// i. Set dateTimeFormat.[[TemporalInstantFormat]] to GetDateTimeFormat(formats, formatMatcher, formatOptions, ANY, ZONED-DATE-TIME, ALL).
auto temporal_instant_format = get_date_time_format(format_options, OptionRequired::Any, OptionDefaults::ZonedDateTime, OptionInherit::All);
date_time_format->set_temporal_instant_format(move(temporal_instant_format));
}
// c. If required is time or any, then
if (required == OptionRequired::Time || required == OptionRequired::Any) {
// i. For each property name prop of « "dayPeriod", "hour", "minute", "second", "fractionalSecondDigits" », do
auto check_property_value = [&](auto const& value) {
// 1. Let value be formatOptions.[[<prop>]].
// 2. If value is not undefined, set needDefaults to false.
if (value.has_value())
needs_defaults = false;
};
check_property_value(format_options.day_period);
check_property_value(format_options.hour);
check_property_value(format_options.minute);
check_property_value(format_options.second);
check_property_value(format_options.fractional_second_digits);
}
// d. If needDefaults is true and defaults is either date or all, then
if (needs_defaults && (defaults == OptionDefaults::Date || defaults == OptionDefaults::All)) {
// i. For each property name prop of « "year", "month", "day" », do
auto set_property_value = [&](auto& value) {
// 1. Set formatOptions.[[<prop>]] to "numeric".
value = Unicode::CalendarPatternStyle::Numeric;
};
set_property_value(format_options.year);
set_property_value(format_options.month);
set_property_value(format_options.day);
}
// e. If needDefaults is true and defaults is either time or all, then
if (needs_defaults && (defaults == OptionDefaults::Time || defaults == OptionDefaults::All)) {
// i. For each property name prop of « "hour", "minute", "second" », do
auto set_property_value = [&](auto& value) {
// 1. Set formatOptions.[[<prop>]] to "numeric".
value = Unicode::CalendarPatternStyle::Numeric;
};
set_property_value(format_options.hour);
set_property_value(format_options.minute);
set_property_value(format_options.second);
}
// f. Let formats be resolvedLocaleData.[[formats]].[[<resolvedCalendar>]].
// g. If formatMatcher is "basic", then
// i. Let bestFormat be BasicFormatMatcher(formatOptions, formats).
// h. Else,
// i. Let bestFormat be BestFitFormatMatcher(formatOptions, formats).
else {
// i. Set dateTimeFormat.[[TemporalInstantFormat]] to GetDateTimeFormat(formats, formatMatcher, formatOptions, ANY, ALL, ALL).
auto temporal_instant_format = get_date_time_format(format_options, OptionRequired::Any, OptionDefaults::All, OptionInherit::All);
date_time_format->set_temporal_instant_format(move(temporal_instant_format));
}
formatter = Unicode::DateTimeFormat::create_for_pattern_options(
date_time_format->locale(),
time_zone,
format_options);
best_format);
}
// 43. Set dateTimeFormat.[[DateTimeFormat]] to bestFormat.
// 45. Set dateTimeFormat.[[DateTimeFormat]] to bestFormat.
date_time_format->set_date_time_format(formatter->chosen_pattern());
// 44. If bestFormat has a field [[hour]], then
// a. Set dateTimeFormat.[[HourCycle]] to hc.
// NOTE: The [[HourCycle]] is stored and accessed from [[DateTimeFormat]].
// Non-standard, create an ICU number formatter for this Intl object.
date_time_format->set_formatter(formatter.release_nonnull());
// 45. Return dateTimeFormat.
// 46. Return dateTimeFormat.
return date_time_format;
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2022, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -33,15 +33,25 @@ enum class OptionRequired {
Any,
Date,
Time,
YearMonth,
MonthDay,
};
enum class OptionDefaults {
All,
Date,
Time,
YearMonth,
MonthDay,
ZonedDateTime,
};
ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM&, FunctionObject& new_target, Value locales_value, Value options_value, OptionRequired, OptionDefaults);
enum class OptionInherit {
All,
Relevant,
};
ThrowCompletionOr<GC::Ref<DateTimeFormat>> create_date_time_format(VM&, FunctionObject& new_target, Value locales_value, Value options_value, OptionRequired, OptionDefaults, Optional<String> const& to_locale_string_time_zone = {});
String format_offset_time_zone_identifier(double offset_minutes);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2022, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -17,6 +17,7 @@ namespace JS::Intl {
GC_DEFINE_ALLOCATOR(DateTimeFormatFunction);
// 11.5.4 DateTime Format Functions, https://tc39.es/ecma402/#sec-datetime-format-functions
// 15.9.3 DateTime Format Functions, https://tc39.es/proposal-temporal/#sec-datetime-format-functions
GC::Ref<DateTimeFormatFunction> DateTimeFormatFunction::create(Realm& realm, DateTimeFormat& date_time_format)
{
return realm.create<DateTimeFormatFunction>(date_time_format, realm.intrinsics().function_prototype());
@ -42,26 +43,26 @@ ThrowCompletionOr<Value> DateTimeFormatFunction::call()
auto& vm = this->vm();
auto& realm = *vm.current_realm();
auto date = vm.argument(0);
auto date_value = vm.argument(0);
// 1. Let dtf be F.[[DateTimeFormat]].
// 2. Assert: Type(dtf) is Object and dtf has an [[InitializedDateTimeFormat]] internal slot.
double date_value;
FormattableDateTime date { 0 };
// 3. If date is not provided or is undefined, then
if (date.is_undefined()) {
if (date_value.is_undefined()) {
// a. Let x be ! Call(%Date.now%, undefined).
date_value = MUST(JS::call(vm, *realm.intrinsics().date_constructor_now_function(), js_undefined())).as_double();
date = MUST(JS::call(vm, *realm.intrinsics().date_constructor_now_function(), js_undefined())).as_double();
}
// 4. Else,
else {
// a. Let x be ? ToNumber(date).
date_value = TRY(date.to_number(vm)).as_double();
// a. Let x be ? ToDateTimeFormattable(date).
date = TRY(to_date_time_formattable(vm, date_value));
}
// 5. Return ? FormatDateTime(dtf, x).
auto formatted = TRY(format_date_time(vm, m_date_time_format, date_value));
auto formatted = TRY(format_date_time(vm, m_date_time_format, date));
return PrimitiveString::create(vm, move(formatted));
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2022, Tim Flynn <trflynn89@serenityos.org>
* Copyright (c) 2021-2024, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -66,84 +66,87 @@ JS_DEFINE_NATIVE_FUNCTION(DateTimeFormatPrototype::format)
}
// 11.3.4 Intl.DateTimeFormat.prototype.formatToParts ( date ), https://tc39.es/ecma402/#sec-Intl.DateTimeFormat.prototype.formatToParts
// 15.10.1 Intl.DateTimeFormat.prototype.formatToParts ( date ), https://tc39.es/proposal-temporal/#sec-Intl.DateTimeFormat.prototype.formatToParts
JS_DEFINE_NATIVE_FUNCTION(DateTimeFormatPrototype::format_to_parts)
{
auto& realm = *vm.current_realm();
auto date = vm.argument(0);
auto date_value = vm.argument(0);
// 1. Let dtf be the this value.
// 2. Perform ? RequireInternalSlot(dtf, [[InitializedDateTimeFormat]]).
auto date_time_format = TRY(typed_this_object(vm));
double date_value;
FormattableDateTime date { 0 };
// 3. If date is undefined, then
if (date.is_undefined()) {
if (date_value.is_undefined()) {
// a. Let x be ! Call(%Date.now%, undefined).
date_value = MUST(call(vm, *realm.intrinsics().date_constructor_now_function(), js_undefined())).as_double();
date = MUST(call(vm, *realm.intrinsics().date_constructor_now_function(), js_undefined())).as_double();
}
// 4. Else,
else {
// a. Let x be ? ToNumber(date).
date_value = TRY(date.to_number(vm)).as_double();
// a. Let x be ? ToDateTimeFormattable(date).
date = TRY(to_date_time_formattable(vm, date_value));
}
// 5. Return ? FormatDateTimeToParts(dtf, x).
return TRY(format_date_time_to_parts(vm, date_time_format, date_value));
return TRY(format_date_time_to_parts(vm, date_time_format, date));
}
// 11.3.5 Intl.DateTimeFormat.prototype.formatRange ( startDate, endDate ), https://tc39.es/ecma402/#sec-intl.datetimeformat.prototype.formatRange
// 15.10.2 Intl.DateTimeFormat.prototype.formatRange ( startDate, endDate ), https://tc39.es/proposal-temporal/#sec-intl.datetimeformat.prototype.formatRange
JS_DEFINE_NATIVE_FUNCTION(DateTimeFormatPrototype::format_range)
{
auto start_date = vm.argument(0);
auto end_date = vm.argument(1);
auto start_date_value = vm.argument(0);
auto end_date_value = vm.argument(1);
// 1. Let dtf be this value.
// 2. Perform ? RequireInternalSlot(dtf, [[InitializedDateTimeFormat]]).
auto date_time_format = TRY(typed_this_object(vm));
// 3. If startDate is undefined or endDate is undefined, throw a TypeError exception.
if (start_date.is_undefined())
if (start_date_value.is_undefined())
return vm.throw_completion<TypeError>(ErrorType::IsUndefined, "startDate"sv);
if (end_date.is_undefined())
if (end_date_value.is_undefined())
return vm.throw_completion<TypeError>(ErrorType::IsUndefined, "endDate"sv);
// 4. Let x be ? ToNumber(startDate).
auto start_date_number = TRY(start_date.to_number(vm)).as_double();
// 4. Let x be ? ToDateTimeFormattable(startDate).
auto start_date = TRY(to_date_time_formattable(vm, start_date_value));
// 5. Let y be ? ToNumber(endDate).
auto end_date_number = TRY(end_date.to_number(vm)).as_double();
// 5. Let y be ? ToDateTimeFormattable(endDate).
auto end_date = TRY(to_date_time_formattable(vm, end_date_value));
// 6. Return ? FormatDateTimeRange(dtf, x, y).
auto formatted = TRY(format_date_time_range(vm, date_time_format, start_date_number, end_date_number));
auto formatted = TRY(format_date_time_range(vm, date_time_format, start_date, end_date));
return PrimitiveString::create(vm, move(formatted));
}
// 11.3.6 Intl.DateTimeFormat.prototype.formatRangeToParts ( startDate, endDate ), https://tc39.es/ecma402/#sec-Intl.DateTimeFormat.prototype.formatRangeToParts
// 15.10.3 Intl.DateTimeFormat.prototype.formatRangeToParts ( startDate, endDate ), https://tc39.es/proposal-temporal/#sec-Intl.DateTimeFormat.prototype.formatRangeToParts
JS_DEFINE_NATIVE_FUNCTION(DateTimeFormatPrototype::format_range_to_parts)
{
auto start_date = vm.argument(0);
auto end_date = vm.argument(1);
auto start_date_value = vm.argument(0);
auto end_date_value = vm.argument(1);
// 1. Let dtf be this value.
// 2. Perform ? RequireInternalSlot(dtf, [[InitializedDateTimeFormat]]).
auto date_time_format = TRY(typed_this_object(vm));
// 3. If startDate is undefined or endDate is undefined, throw a TypeError exception.
if (start_date.is_undefined())
if (start_date_value.is_undefined())
return vm.throw_completion<TypeError>(ErrorType::IsUndefined, "startDate"sv);
if (end_date.is_undefined())
if (end_date_value.is_undefined())
return vm.throw_completion<TypeError>(ErrorType::IsUndefined, "endDate"sv);
// 4. Let x be ? ToNumber(startDate).
auto start_date_number = TRY(start_date.to_number(vm)).as_double();
// 4. Let x be ? ToDateTimeFormattable(startDate).
auto start_date = TRY(to_date_time_formattable(vm, start_date_value));
// 5. Let y be ? ToNumber(endDate).
auto end_date_number = TRY(end_date.to_number(vm)).as_double();
// 5. Let y be ? ToDateTimeFormattable(endDate).
auto end_date = TRY(to_date_time_formattable(vm, end_date_value));
// 6. Return ? FormatDateTimeRangeToParts(dtf, x, y).
return TRY(format_date_time_range_to_parts(vm, date_time_format, start_date_number, end_date_number));
return TRY(format_date_time_range_to_parts(vm, date_time_format, start_date, end_date));
}
// 11.3.7 Intl.DateTimeFormat.prototype.resolvedOptions ( ), https://tc39.es/ecma402/#sec-intl.datetimeformat.prototype.resolvedoptions

View file

@ -32,6 +32,49 @@ describe("errors", () => {
Intl.DateTimeFormat().format(8.65e15);
}).toThrowWithMessage(RangeError, "Time value must be between -8.64E15 and 8.64E15");
});
test("Temporal object must have same calendar", () => {
const formatter = new Intl.DateTimeFormat([], { calendar: "iso8601" });
expect(() => {
formatter.format(new Temporal.PlainDate(1972, 1, 1, "gregory"));
}).toThrowWithMessage(
RangeError,
"Cannot format Temporal.PlainDate with calendar 'gregory' in locale with calendar 'iso8601'"
);
expect(() => {
formatter.format(new Temporal.PlainYearMonth(1972, 1, "gregory"));
}).toThrowWithMessage(
RangeError,
"Cannot format Temporal.PlainYearMonth with calendar 'gregory' in locale with calendar 'iso8601'"
);
expect(() => {
formatter.format(new Temporal.PlainMonthDay(1, 1, "gregory"));
}).toThrowWithMessage(
RangeError,
"Cannot format Temporal.PlainMonthDay with calendar 'gregory' in locale with calendar 'iso8601'"
);
expect(() => {
formatter.format(
new Temporal.PlainDateTime(1972, 1, 1, 8, 45, 56, 123, 345, 789, "gregory")
);
}).toThrowWithMessage(
RangeError,
"Cannot format Temporal.PlainDateTime with calendar 'gregory' in locale with calendar 'iso8601'"
);
});
test("cannot format Temporal.ZonedDateTime", () => {
expect(() => {
new Intl.DateTimeFormat().format(new Temporal.ZonedDateTime(0n, "UTC"));
}).toThrowWithMessage(
TypeError,
"Cannot format Temporal.ZonedDateTime, use Temporal.ZonedDateTime.prototype.toLocaleString"
);
});
});
const d0 = Date.UTC(2021, 11, 7, 17, 40, 50, 456);
@ -575,3 +618,35 @@ describe("non-Gregorian calendars", () => {
expect(zh.format(d1)).toBe("1988戊辰年腊月十六 UTC 07:08:09");
});
});
describe("Temporal objects", () => {
const formatter = new Intl.DateTimeFormat("en", {
calendar: "iso8601",
timeZone: "UTC",
});
test("Temporal.PlainDate", () => {
const plainDate = new Temporal.PlainDate(1989, 1, 23);
expect(formatter.format(plainDate)).toBe("1/23/1989");
});
test("Temporal.PlainYearMonth", () => {
const plainYearMonth = new Temporal.PlainYearMonth(1989, 1);
expect(formatter.format(plainYearMonth)).toBe("1/1989");
});
test("Temporal.PlainMonthDay", () => {
const plainMonthDay = new Temporal.PlainMonthDay(1, 23);
expect(formatter.format(plainMonthDay)).toBe("1/23");
});
test("Temporal.PlainTime", () => {
const plainTime = new Temporal.PlainTime(8, 10, 51);
expect(formatter.format(plainTime)).toBe("8:10:51 AM");
});
test("Temporal.Instant", () => {
const instant = new Temporal.Instant(1732740069000000000n);
expect(formatter.format(instant)).toBe("11/27/2024, 8:41:09 PM");
});
});

View file

@ -36,6 +36,82 @@ describe("errors", () => {
}).toThrowWithMessage(RangeError, "Time value must be between -8.64E15 and 8.64E15");
});
});
test("Temporal object must have same calendar", () => {
const formatter = new Intl.DateTimeFormat([], { calendar: "iso8601" });
expect(() => {
const plainDate = new Temporal.PlainDate(1972, 1, 1, "gregory");
formatter.formatRange(plainDate, plainDate);
}).toThrowWithMessage(
RangeError,
"Cannot format Temporal.PlainDate with calendar 'gregory' in locale with calendar 'iso8601'"
);
expect(() => {
const plainYearMonth = new Temporal.PlainYearMonth(1972, 1, "gregory");
formatter.formatRange(plainYearMonth, plainYearMonth);
}).toThrowWithMessage(
RangeError,
"Cannot format Temporal.PlainYearMonth with calendar 'gregory' in locale with calendar 'iso8601'"
);
expect(() => {
const plainMonthDay = new Temporal.PlainMonthDay(1, 1, "gregory");
formatter.formatRange(plainMonthDay, plainMonthDay);
}).toThrowWithMessage(
RangeError,
"Cannot format Temporal.PlainMonthDay with calendar 'gregory' in locale with calendar 'iso8601'"
);
expect(() => {
const plainDateTime = new Temporal.PlainDateTime(
1972,
1,
1,
8,
45,
56,
123,
345,
789,
"gregory"
);
formatter.formatRange(plainDateTime, plainDateTime);
}).toThrowWithMessage(
RangeError,
"Cannot format Temporal.PlainDateTime with calendar 'gregory' in locale with calendar 'iso8601'"
);
});
test("cannot format Temporal.ZonedDateTime", () => {
expect(() => {
const zonedDateTime = new Temporal.ZonedDateTime(0n, "UTC");
new Intl.DateTimeFormat().formatRange(zonedDateTime, zonedDateTime);
}).toThrowWithMessage(
TypeError,
"Cannot format Temporal.ZonedDateTime, use Temporal.ZonedDateTime.prototype.toLocaleString"
);
});
test("cannot mix Temporal object types", () => {
expect(() => {
const plainDate = new Temporal.PlainDate(1972, 1, 1, "gregory");
new Intl.DateTimeFormat().formatRange(plainDate, 0);
}).toThrowWithMessage(
TypeError,
"Cannot format a date-time range with different date-time types"
);
expect(() => {
const plainYearMonth = new Temporal.PlainYearMonth(1972, 1, "gregory");
const plainMonthDay = new Temporal.PlainMonthDay(1, 1, "gregory");
new Intl.DateTimeFormat().formatRange(plainYearMonth, plainMonthDay);
}).toThrowWithMessage(
TypeError,
"Cannot format a date-time range with different date-time types"
);
});
});
const d0 = Date.UTC(1989, 0, 23, 7, 8, 9, 45);
@ -220,3 +296,42 @@ describe("dateStyle + timeStyle", () => {
});
});
});
describe("Temporal objects", () => {
const formatter = new Intl.DateTimeFormat("en", {
calendar: "iso8601",
timeZone: "UTC",
});
test("Temporal.PlainDate", () => {
const plainDate1 = new Temporal.PlainDate(1989, 1, 23);
const plainDate2 = new Temporal.PlainDate(2024, 11, 27);
expect(formatter.formatRange(plainDate1, plainDate2)).toBe("1/23/1989 11/27/2024");
});
test("Temporal.PlainYearMonth", () => {
const plainYearMonth1 = new Temporal.PlainYearMonth(1989, 1);
const plainYearMonth2 = new Temporal.PlainYearMonth(2024, 11);
expect(formatter.formatRange(plainYearMonth1, plainYearMonth2)).toBe("1/1989 11/2024");
});
test("Temporal.PlainMonthDay", () => {
const plainMonthDay1 = new Temporal.PlainMonthDay(1, 23);
const plainMonthDay2 = new Temporal.PlainMonthDay(11, 27);
expect(formatter.formatRange(plainMonthDay1, plainMonthDay2)).toBe("1/23 11/27");
});
test("Temporal.PlainTime", () => {
const plainTime1 = new Temporal.PlainTime(8, 10, 51);
const plainTime2 = new Temporal.PlainTime(20, 41, 9);
expect(formatter.formatRange(plainTime1, plainTime2)).toBe("8:10:51 AM 8:41:09 PM");
});
test("Temporal.Instant", () => {
const instant1 = new Temporal.Instant(601546251000000000n);
const instant2 = new Temporal.Instant(1732740069000000000n);
expect(formatter.formatRange(instant1, instant2)).toBe(
"1/23/1989, 8:10:51 AM 11/27/2024, 8:41:09 PM"
);
});
});

View file

@ -36,6 +36,82 @@ describe("errors", () => {
}).toThrowWithMessage(RangeError, "Time value must be between -8.64E15 and 8.64E15");
});
});
test("Temporal object must have same calendar", () => {
const formatter = new Intl.DateTimeFormat([], { calendar: "iso8601" });
expect(() => {
const plainDate = new Temporal.PlainDate(1972, 1, 1, "gregory");
formatter.formatRangeToParts(plainDate, plainDate);
}).toThrowWithMessage(
RangeError,
"Cannot format Temporal.PlainDate with calendar 'gregory' in locale with calendar 'iso8601'"
);
expect(() => {
const plainYearMonth = new Temporal.PlainYearMonth(1972, 1, "gregory");
formatter.formatRangeToParts(plainYearMonth, plainYearMonth);
}).toThrowWithMessage(
RangeError,
"Cannot format Temporal.PlainYearMonth with calendar 'gregory' in locale with calendar 'iso8601'"
);
expect(() => {
const plainMonthDay = new Temporal.PlainMonthDay(1, 1, "gregory");
formatter.formatRangeToParts(plainMonthDay, plainMonthDay);
}).toThrowWithMessage(
RangeError,
"Cannot format Temporal.PlainMonthDay with calendar 'gregory' in locale with calendar 'iso8601'"
);
expect(() => {
const plainDateTime = new Temporal.PlainDateTime(
1972,
1,
1,
8,
45,
56,
123,
345,
789,
"gregory"
);
formatter.formatRangeToParts(plainDateTime, plainDateTime);
}).toThrowWithMessage(
RangeError,
"Cannot format Temporal.PlainDateTime with calendar 'gregory' in locale with calendar 'iso8601'"
);
});
test("cannot format Temporal.ZonedDateTime", () => {
expect(() => {
const zonedDateTime = new Temporal.ZonedDateTime(0n, "UTC");
new Intl.DateTimeFormat().formatRangeToParts(zonedDateTime, zonedDateTime);
}).toThrowWithMessage(
TypeError,
"Cannot format Temporal.ZonedDateTime, use Temporal.ZonedDateTime.prototype.toLocaleString"
);
});
test("cannot mix Temporal object types", () => {
expect(() => {
const plainDate = new Temporal.PlainDate(1972, 1, 1, "gregory");
new Intl.DateTimeFormat().formatRangeToParts(plainDate, 0);
}).toThrowWithMessage(
TypeError,
"Cannot format a date-time range with different date-time types"
);
expect(() => {
const plainYearMonth = new Temporal.PlainYearMonth(1972, 1, "gregory");
const plainMonthDay = new Temporal.PlainMonthDay(1, 1, "gregory");
new Intl.DateTimeFormat().formatRangeToParts(plainYearMonth, plainMonthDay);
}).toThrowWithMessage(
TypeError,
"Cannot format a date-time range with different date-time types"
);
});
});
const d0 = Date.UTC(1989, 0, 23, 7, 8, 9, 45);
@ -633,3 +709,112 @@ describe("timeStyle", () => {
]);
});
});
describe("Temporal objects", () => {
const formatter = new Intl.DateTimeFormat("en", {
calendar: "iso8601",
timeZone: "UTC",
});
test("Temporal.PlainDate", () => {
const plainDate1 = new Temporal.PlainDate(1989, 1, 23);
const plainDate2 = new Temporal.PlainDate(2024, 11, 27);
expect(formatter.formatRangeToParts(plainDate1, plainDate2)).toEqual([
{ type: "month", value: "1", source: "startRange" },
{ type: "literal", value: "/", source: "startRange" },
{ type: "day", value: "23", source: "startRange" },
{ type: "literal", value: "/", source: "startRange" },
{ type: "year", value: "1989", source: "startRange" },
{ type: "literal", value: " ", source: "shared" },
{ type: "month", value: "11", source: "endRange" },
{ type: "literal", value: "/", source: "endRange" },
{ type: "day", value: "27", source: "endRange" },
{ type: "literal", value: "/", source: "endRange" },
{ type: "year", value: "2024", source: "endRange" },
]);
});
test("Temporal.PlainYearMonth", () => {
const plainYearMonth1 = new Temporal.PlainYearMonth(1989, 1);
const plainYearMonth2 = new Temporal.PlainYearMonth(2024, 11);
expect(formatter.formatRangeToParts(plainYearMonth1, plainYearMonth2)).toEqual([
{ type: "month", value: "1", source: "startRange" },
{ type: "literal", value: "/", source: "startRange" },
{ type: "year", value: "1989", source: "startRange" },
{ type: "literal", value: " ", source: "shared" },
{ type: "month", value: "11", source: "endRange" },
{ type: "literal", value: "/", source: "endRange" },
{ type: "year", value: "2024", source: "endRange" },
]);
});
test("Temporal.PlainMonthDay", () => {
const plainMonthDay1 = new Temporal.PlainMonthDay(1, 23);
const plainMonthDay2 = new Temporal.PlainMonthDay(11, 27);
expect(formatter.formatRangeToParts(plainMonthDay1, plainMonthDay2)).toEqual([
{ type: "month", value: "1", source: "startRange" },
{ type: "literal", value: "/", source: "startRange" },
{ type: "day", value: "23", source: "startRange" },
{ type: "literal", value: " ", source: "shared" },
{ type: "month", value: "11", source: "endRange" },
{ type: "literal", value: "/", source: "endRange" },
{ type: "day", value: "27", source: "endRange" },
]);
});
test("Temporal.PlainTime", () => {
const plainTime1 = new Temporal.PlainTime(8, 10, 51);
const plainTime2 = new Temporal.PlainTime(20, 41, 9);
expect(formatter.formatRangeToParts(plainTime1, plainTime2)).toEqual([
{ type: "hour", value: "8", source: "startRange" },
{ type: "literal", value: ":", source: "startRange" },
{ type: "minute", value: "10", source: "startRange" },
{ type: "literal", value: ":", source: "startRange" },
{ type: "second", value: "51", source: "startRange" },
{ type: "literal", value: " ", source: "startRange" },
{ type: "dayPeriod", value: "AM", source: "startRange" },
{ type: "literal", value: " ", source: "shared" },
{ type: "hour", value: "8", source: "endRange" },
{ type: "literal", value: ":", source: "endRange" },
{ type: "minute", value: "41", source: "endRange" },
{ type: "literal", value: ":", source: "endRange" },
{ type: "second", value: "09", source: "endRange" },
{ type: "literal", value: " ", source: "endRange" },
{ type: "dayPeriod", value: "PM", source: "endRange" },
]);
});
test("Temporal.Instant", () => {
const instant1 = new Temporal.Instant(601546251000000000n);
const instant2 = new Temporal.Instant(1732740069000000000n);
expect(formatter.formatRangeToParts(instant1, instant2)).toEqual([
{ type: "month", value: "1", source: "startRange" },
{ type: "literal", value: "/", source: "startRange" },
{ type: "day", value: "23", source: "startRange" },
{ type: "literal", value: "/", source: "startRange" },
{ type: "year", value: "1989", source: "startRange" },
{ type: "literal", value: ", ", source: "startRange" },
{ type: "hour", value: "8", source: "startRange" },
{ type: "literal", value: ":", source: "startRange" },
{ type: "minute", value: "10", source: "startRange" },
{ type: "literal", value: ":", source: "startRange" },
{ type: "second", value: "51", source: "startRange" },
{ type: "literal", value: " ", source: "startRange" },
{ type: "dayPeriod", value: "AM", source: "startRange" },
{ type: "literal", value: " ", source: "shared" },
{ type: "month", value: "11", source: "endRange" },
{ type: "literal", value: "/", source: "endRange" },
{ type: "day", value: "27", source: "endRange" },
{ type: "literal", value: "/", source: "endRange" },
{ type: "year", value: "2024", source: "endRange" },
{ type: "literal", value: ", ", source: "endRange" },
{ type: "hour", value: "8", source: "endRange" },
{ type: "literal", value: ":", source: "endRange" },
{ type: "minute", value: "41", source: "endRange" },
{ type: "literal", value: ":", source: "endRange" },
{ type: "second", value: "09", source: "endRange" },
{ type: "literal", value: " ", source: "endRange" },
{ type: "dayPeriod", value: "PM", source: "endRange" },
]);
});
});

View file

@ -28,6 +28,49 @@ describe("errors", () => {
Intl.DateTimeFormat().formatToParts(8.65e15);
}).toThrowWithMessage(RangeError, "Time value must be between -8.64E15 and 8.64E15");
});
test("Temporal object must have same calendar", () => {
const formatter = new Intl.DateTimeFormat([], { calendar: "iso8601" });
expect(() => {
formatter.formatToParts(new Temporal.PlainDate(1972, 1, 1, "gregory"));
}).toThrowWithMessage(
RangeError,
"Cannot format Temporal.PlainDate with calendar 'gregory' in locale with calendar 'iso8601'"
);
expect(() => {
formatter.formatToParts(new Temporal.PlainYearMonth(1972, 1, "gregory"));
}).toThrowWithMessage(
RangeError,
"Cannot format Temporal.PlainYearMonth with calendar 'gregory' in locale with calendar 'iso8601'"
);
expect(() => {
formatter.formatToParts(new Temporal.PlainMonthDay(1, 1, "gregory"));
}).toThrowWithMessage(
RangeError,
"Cannot format Temporal.PlainMonthDay with calendar 'gregory' in locale with calendar 'iso8601'"
);
expect(() => {
formatter.formatToParts(
new Temporal.PlainDateTime(1972, 1, 1, 8, 45, 56, 123, 345, 789, "gregory")
);
}).toThrowWithMessage(
RangeError,
"Cannot format Temporal.PlainDateTime with calendar 'gregory' in locale with calendar 'iso8601'"
);
});
test("cannot format Temporal.ZonedDateTime", () => {
expect(() => {
new Intl.DateTimeFormat().formatToParts(new Temporal.ZonedDateTime(0n, "UTC"));
}).toThrowWithMessage(
TypeError,
"Cannot format Temporal.ZonedDateTime, use Temporal.ZonedDateTime.prototype.toLocaleString"
);
});
});
const d = Date.UTC(1989, 0, 23, 7, 8, 9, 45);
@ -275,3 +318,71 @@ describe("special cases", () => {
]);
});
});
describe("Temporal objects", () => {
const formatter = new Intl.DateTimeFormat("en", {
calendar: "iso8601",
timeZone: "UTC",
});
test("Temporal.PlainDate", () => {
const plainDate = new Temporal.PlainDate(1989, 1, 23);
expect(formatter.formatToParts(plainDate)).toEqual([
{ type: "month", value: "1" },
{ type: "literal", value: "/" },
{ type: "day", value: "23" },
{ type: "literal", value: "/" },
{ type: "year", value: "1989" },
]);
});
test("Temporal.PlainYearMonth", () => {
const plainYearMonth = new Temporal.PlainYearMonth(1989, 1);
expect(formatter.formatToParts(plainYearMonth)).toEqual([
{ type: "month", value: "1" },
{ type: "literal", value: "/" },
{ type: "year", value: "1989" },
]);
});
test("Temporal.PlainMonthDay", () => {
const plainMonthDay = new Temporal.PlainMonthDay(1, 23);
expect(formatter.formatToParts(plainMonthDay)).toEqual([
{ type: "month", value: "1" },
{ type: "literal", value: "/" },
{ type: "day", value: "23" },
]);
});
test("Temporal.PlainTime", () => {
const plainTime = new Temporal.PlainTime(8, 10, 51);
expect(formatter.formatToParts(plainTime)).toEqual([
{ type: "hour", value: "8" },
{ type: "literal", value: ":" },
{ type: "minute", value: "10" },
{ type: "literal", value: ":" },
{ type: "second", value: "51" },
{ type: "literal", value: " " },
{ type: "dayPeriod", value: "AM" },
]);
});
test("Temporal.Instant", () => {
const instant = new Temporal.Instant(1732740069000000000n);
expect(formatter.formatToParts(instant)).toEqual([
{ type: "month", value: "11" },
{ type: "literal", value: "/" },
{ type: "day", value: "27" },
{ type: "literal", value: "/" },
{ type: "year", value: "2024" },
{ type: "literal", value: ", " },
{ type: "hour", value: "8" },
{ type: "literal", value: ":" },
{ type: "minute", value: "41" },
{ type: "literal", value: ":" },
{ type: "second", value: "09" },
{ type: "literal", value: " " },
{ type: "dayPeriod", value: "PM" },
]);
});
});

View file

@ -6,6 +6,7 @@
#pragma once
#include <AK/IterationDecision.h>
#include <AK/Optional.h>
#include <AK/String.h>
#include <AK/StringView.h>
@ -60,9 +61,60 @@ CalendarPatternStyle calendar_pattern_style_from_string(StringView style);
StringView calendar_pattern_style_to_string(CalendarPatternStyle style);
struct CalendarPattern {
enum class Field {
Era,
Year,
Month,
Weekday,
Day,
DayPeriod,
Hour,
Minute,
Second,
FractionalSecondDigits,
TimeZoneName,
};
static CalendarPattern create_from_pattern(StringView);
String to_pattern() const;
template<typename Callback>
void for_each_calendar_field_zipped_with(CalendarPattern& other, ReadonlySpan<Field> filter, Callback&& callback) const
{
auto invoke_callback_for_field = [&](auto field) {
switch (field) {
case Field::Era:
return callback(era, other.era);
case Field::Year:
return callback(year, other.year);
case Field::Month:
return callback(month, other.month);
case Field::Weekday:
return callback(weekday, other.weekday);
case Field::Day:
return callback(day, other.day);
case Field::DayPeriod:
return callback(day_period, other.day_period);
case Field::Hour:
return callback(hour, other.hour);
case Field::Minute:
return callback(minute, other.minute);
case Field::Second:
return callback(second, other.second);
case Field::FractionalSecondDigits:
return callback(fractional_second_digits, other.fractional_second_digits);
case Field::TimeZoneName:
return callback(time_zone_name, other.time_zone_name);
}
VERIFY_NOT_REACHED();
};
for (auto field : filter) {
if (invoke_callback_for_field(field) == IterationDecision::Break)
break;
}
}
Optional<HourCycle> hour_cycle;
Optional<bool> hour12;