2024-11-20 12:59:15 -05:00
/*
* Copyright ( c ) 2021 , Idan Horowitz < idan . horowitz @ serenityos . org >
* Copyright ( c ) 2021 - 2023 , Linus Groh < linusg @ serenityos . org >
* Copyright ( c ) 2023 - 2024 , Shannon Booth < shannon @ serenityos . org >
2026-01-13 12:31:52 -05:00
* Copyright ( c ) 2024 - 2026 , Tim Flynn < trflynn89 @ ladybird . org >
2024-11-20 12:59:15 -05:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2026-02-14 11:46:19 -05:00
# include <AK/GenericShorthands.h>
2024-11-20 12:59:15 -05:00
# include <AK/NonnullRawPtr.h>
# include <AK/QuickSort.h>
# include <LibJS/Runtime/Temporal/Calendar.h>
# include <LibJS/Runtime/Temporal/DateEquations.h>
2024-11-21 13:21:29 -05:00
# include <LibJS/Runtime/Temporal/Duration.h>
2024-11-20 12:59:15 -05:00
# include <LibJS/Runtime/Temporal/PlainDate.h>
2024-11-23 18:00:05 -05:00
# include <LibJS/Runtime/Temporal/PlainDateTime.h>
2024-11-20 12:59:15 -05:00
# include <LibJS/Runtime/Temporal/PlainMonthDay.h>
2024-11-21 11:22:32 -05:00
# include <LibJS/Runtime/Temporal/PlainYearMonth.h>
2024-11-20 12:59:15 -05:00
# include <LibJS/Runtime/Temporal/TimeZone.h>
2024-11-24 20:42:47 -05:00
# include <LibJS/Runtime/Temporal/ZonedDateTime.h>
2024-11-20 12:59:15 -05:00
# include <LibJS/Runtime/VM.h>
# include <LibUnicode/Locale.h>
# include <LibUnicode/UnicodeKeywords.h>
namespace JS : : Temporal {
2026-03-06 07:52:58 -05:00
String ISO8601_CALENDAR = " iso8601 " _string ;
2024-11-20 12:59:15 -05:00
enum class CalendarFieldConversion {
ToIntegerWithTruncation ,
ToMonthCode ,
ToOffsetString ,
ToPositiveIntegerWithTruncation ,
ToString ,
ToTemporalTimeZoneIdentifier ,
} ;
// https://tc39.es/proposal-temporal/#table-temporal-calendar-fields-record-fields
# define JS_ENUMERATE_CALENDAR_FIELDS \
__JS_ENUMERATE ( CalendarField : : Era , era , vm . names . era , CalendarFieldConversion : : ToString ) \
__JS_ENUMERATE ( CalendarField : : EraYear , era_year , vm . names . eraYear , CalendarFieldConversion : : ToIntegerWithTruncation ) \
__JS_ENUMERATE ( CalendarField : : Year , year , vm . names . year , CalendarFieldConversion : : ToIntegerWithTruncation ) \
__JS_ENUMERATE ( CalendarField : : Month , month , vm . names . month , CalendarFieldConversion : : ToPositiveIntegerWithTruncation ) \
__JS_ENUMERATE ( CalendarField : : MonthCode , month_code , vm . names . monthCode , CalendarFieldConversion : : ToMonthCode ) \
__JS_ENUMERATE ( CalendarField : : Day , day , vm . names . day , CalendarFieldConversion : : ToPositiveIntegerWithTruncation ) \
__JS_ENUMERATE ( CalendarField : : Hour , hour , vm . names . hour , CalendarFieldConversion : : ToIntegerWithTruncation ) \
__JS_ENUMERATE ( CalendarField : : Minute , minute , vm . names . minute , CalendarFieldConversion : : ToIntegerWithTruncation ) \
__JS_ENUMERATE ( CalendarField : : Second , second , vm . names . second , CalendarFieldConversion : : ToIntegerWithTruncation ) \
__JS_ENUMERATE ( CalendarField : : Millisecond , millisecond , vm . names . millisecond , CalendarFieldConversion : : ToIntegerWithTruncation ) \
__JS_ENUMERATE ( CalendarField : : Microsecond , microsecond , vm . names . microsecond , CalendarFieldConversion : : ToIntegerWithTruncation ) \
__JS_ENUMERATE ( CalendarField : : Nanosecond , nanosecond , vm . names . nanosecond , CalendarFieldConversion : : ToIntegerWithTruncation ) \
2024-12-05 09:39:00 -05:00
__JS_ENUMERATE ( CalendarField : : Offset , offset_string , vm . names . offset , CalendarFieldConversion : : ToOffsetString ) \
2024-11-20 12:59:15 -05:00
__JS_ENUMERATE ( CalendarField : : TimeZone , time_zone , vm . names . timeZone , CalendarFieldConversion : : ToTemporalTimeZoneIdentifier )
struct CalendarFieldData {
CalendarField key ;
NonnullRawPtr < PropertyKey > property ;
CalendarFieldConversion conversion ;
} ;
static Vector < CalendarFieldData > sorted_calendar_fields ( VM & vm , CalendarFieldList fields )
{
auto data_for_field = [ & ] ( auto field ) - > CalendarFieldData {
switch ( field ) {
# define __JS_ENUMERATE(enumeration, field_name, property_key, conversion) \
case enumeration : \
return { enumeration , property_key , conversion } ;
JS_ENUMERATE_CALENDAR_FIELDS
# undef __JS_ENUMERATE
}
VERIFY_NOT_REACHED ( ) ;
} ;
Vector < CalendarFieldData > result ;
result . ensure_capacity ( fields . size ( ) ) ;
for ( auto field : fields )
result . unchecked_append ( data_for_field ( field ) ) ;
quick_sort ( result , [ ] ( auto const & lhs , auto const & rhs ) {
2025-08-02 19:27:29 -04:00
return lhs . property - > as_string ( ) < rhs . property - > as_string ( ) ;
2024-11-20 12:59:15 -05:00
} ) ;
return result ;
}
template < typename T >
static void set_field_value ( CalendarField field , CalendarFields & fields , T & & value )
{
switch ( field ) {
# define __JS_ENUMERATE(enumeration, field_name, property_key, conversion) \
case enumeration : \
if constexpr ( IsAssignable < decltype ( fields . field_name ) , RemoveCVReference < T > > ) \
fields . field_name = value ; \
return ;
JS_ENUMERATE_CALENDAR_FIELDS
# undef __JS_ENUMERATE
}
VERIFY_NOT_REACHED ( ) ;
}
static void set_default_field_value ( CalendarField field , CalendarFields & fields )
{
CalendarFields default_ { } ;
switch ( field ) {
# define __JS_ENUMERATE(enumeration, field_name, property_key, conversion) \
case enumeration : \
fields . field_name = default_ . field_name ; \
return ;
JS_ENUMERATE_CALENDAR_FIELDS
# undef __JS_ENUMERATE
}
VERIFY_NOT_REACHED ( ) ;
}
2026-03-08 11:39:46 -04:00
// Table 1: Calendar types described in CLDR, https://tc39.es/proposal-intl-era-monthcode/#table-calendar-types
static constexpr auto CLDR_CALENDAR_TYPES = to_array ( {
" buddhist " sv ,
" chinese " sv ,
" coptic " sv ,
" dangi " sv ,
" ethioaa " sv ,
" ethiopic " sv ,
" ethiopic-amete-alem " sv ,
" gregory " sv ,
" hebrew " sv ,
" indian " sv ,
" islamic-civil " sv ,
" islamic-tbla " sv ,
" islamic-umalqura " sv ,
" islamicc " sv ,
" iso8601 " sv ,
" japanese " sv ,
" persian " sv ,
" roc " sv ,
} ) ;
// Table 2: Eras, https://tc39.es/proposal-intl-era-monthcode/#table-eras
struct CalendarEraData {
enum class Kind : u8 {
Epoch ,
Offset ,
Negative ,
} ;
StringView calendar ;
StringView era ;
StringView alias ;
Optional < i32 > minimum_era_year ;
Optional < i32 > maximum_era_year ;
Kind kind ;
Optional < i32 > offset ;
// NB: This column is not in the spec table, but is needed to handle calendars with mid-year era transitions.
Optional < ISODate > iso_era_start ;
} ;
static constexpr auto CALENDAR_ERA_DATA = to_array < CalendarEraData > ( {
// clang-format off
{ " buddhist " sv , " be " sv , { } , { } , { } , CalendarEraData : : Kind : : Epoch , { } , { } } ,
{ " coptic " sv , " am " sv , { } , { } , { } , CalendarEraData : : Kind : : Epoch , { } , { } } ,
{ " ethioaa " sv , " aa " sv , { } , { } , { } , CalendarEraData : : Kind : : Epoch , { } , { } } ,
{ " ethiopic " sv , " am " sv , { } , 1 , { } , CalendarEraData : : Kind : : Epoch , { } , { } } ,
{ " ethiopic " sv , " aa " sv , { } , { } , 5500 , CalendarEraData : : Kind : : Offset , - 5499 , { } } ,
{ " gregory " sv , " ce " sv , " ad " sv , 1 , { } , CalendarEraData : : Kind : : Epoch , { } , { } } ,
{ " gregory " sv , " bce " sv , " bc " sv , 1 , { } , CalendarEraData : : Kind : : Negative , { } , { } } ,
{ " hebrew " sv , " am " sv , { } , { } , { } , CalendarEraData : : Kind : : Epoch , { } , { } } ,
{ " indian " sv , " shaka " sv , { } , { } , { } , CalendarEraData : : Kind : : Epoch , { } , { } } ,
{ " islamic-civil " sv , " ah " sv , { } , 1 , { } , CalendarEraData : : Kind : : Epoch , { } , { } } ,
{ " islamic-civil " sv , " bh " sv , { } , 1 , { } , CalendarEraData : : Kind : : Negative , { } , { } } ,
{ " islamic-tbla " sv , " ah " sv , { } , 1 , { } , CalendarEraData : : Kind : : Epoch , { } , { } } ,
{ " islamic-tbla " sv , " bh " sv , { } , 1 , { } , CalendarEraData : : Kind : : Negative , { } , { } } ,
{ " islamic-umalqura " sv , " ah " sv , { } , 1 , { } , CalendarEraData : : Kind : : Epoch , { } , { } } ,
{ " islamic-umalqura " sv , " bh " sv , { } , 1 , { } , CalendarEraData : : Kind : : Negative , { } , { } } ,
{ " japanese " sv , " reiwa " sv , { } , 1 , { } , CalendarEraData : : Kind : : Offset , 2019 , { { 2019 , 5 , 1 } } } ,
{ " japanese " sv , " heisei " sv , { } , 1 , 31 , CalendarEraData : : Kind : : Offset , 1989 , { { 1989 , 1 , 8 } } } ,
{ " japanese " sv , " showa " sv , { } , 1 , 64 , CalendarEraData : : Kind : : Offset , 1926 , { { 1926 , 12 , 25 } } } ,
{ " japanese " sv , " taisho " sv , { } , 1 , 15 , CalendarEraData : : Kind : : Offset , 1912 , { { 1912 , 7 , 30 } } } ,
{ " japanese " sv , " meiji " sv , { } , 1 , 45 , CalendarEraData : : Kind : : Offset , 1868 , { { 1873 , 1 , 1 } } } ,
{ " japanese " sv , " ce " sv , " ad " sv , 1 , 1872 , CalendarEraData : : Kind : : Epoch , { } , { } } ,
{ " japanese " sv , " bce " sv , " bc " sv , 1 , { } , CalendarEraData : : Kind : : Negative , { } , { } } ,
{ " persian " sv , " ap " sv , { } , { } , { } , CalendarEraData : : Kind : : Epoch , { } , { } } ,
{ " roc " sv , " roc " sv , { } , 1 , { } , CalendarEraData : : Kind : : Epoch , { } , { } } ,
{ " roc " sv , " broc " sv , { } , 1 , { } , CalendarEraData : : Kind : : Negative , { } , { } } ,
// clang-format on
} ) ;
// Table 3: Additional Month Codes in Calendars, https://tc39.es/proposal-intl-era-monthcode/#table-additional-month-codes
struct AdditionalMonthCodes {
enum class Leap : u8 {
SkipBackward ,
SkipForward ,
} ;
StringView calendar ;
ReadonlySpan < StringView > additional_month_codes ;
Optional < Leap > leap_to_common_month_transformation ;
} ;
static constexpr auto ALL_LEAP_MONTH_CODES = to_array ( { " M01L " sv , " M02L " sv , " M03L " sv , " M04L " sv , " M05L " sv , " M06L " sv , " M07L " sv , " M08L " sv , " M09L " sv , " M10L " sv , " M11L " sv , " M12L " sv } ) ;
static constexpr auto THIRTEENTH_MONTH_CODES = to_array ( { " M13 " sv } ) ;
static constexpr auto HEBREW_ADAR_I_MONTH_CODES = to_array ( { " M05L " sv } ) ;
static constexpr auto ADDITIONAL_MONTH_CODES = to_array < AdditionalMonthCodes > ( {
{ " chinese " sv , ALL_LEAP_MONTH_CODES , AdditionalMonthCodes : : Leap : : SkipBackward } ,
{ " coptic " sv , THIRTEENTH_MONTH_CODES , { } } ,
{ " dangi " sv , ALL_LEAP_MONTH_CODES , AdditionalMonthCodes : : Leap : : SkipBackward } ,
{ " ethioaa " sv , THIRTEENTH_MONTH_CODES , { } } ,
{ " ethiopic " sv , THIRTEENTH_MONTH_CODES , { } } ,
{ " hebrew " sv , HEBREW_ADAR_I_MONTH_CODES , AdditionalMonthCodes : : Leap : : SkipForward } ,
} ) ;
// Table 6: "chinese" and "dangi" Calendars ISO Reference Years, https://tc39.es/proposal-intl-era-monthcode/#chinese-dangi-iso-reference-years
struct ISOReferenceYears {
StringView month_code ;
Optional < i32 > days_1_to_29 ;
Optional < i32 > day_30 ;
} ;
static constexpr auto CHINESE_AND_DANGI_ISO_REFERENCE_YEARS = to_array < ISOReferenceYears > ( {
{ " M01 " sv , 1972 , 1970 } ,
{ " M01L " sv , { } , { } } ,
{ " M02 " sv , 1972 , 1972 } ,
{ " M02L " sv , 1947 , { } } ,
{ " M03 " sv , 1972 , 0 } , // Day=30 depends on the calendar and is handled below.
{ " M03L " sv , 1966 , 1955 } ,
{ " M04 " sv , 1972 , 1970 } ,
{ " M04L " sv , 1963 , 1944 } ,
{ " M05 " sv , 1972 , 1972 } ,
{ " M05L " sv , 1971 , 1952 } ,
{ " M06 " sv , 1972 , 1971 } ,
{ " M06L " sv , 1960 , 1941 } ,
{ " M07 " sv , 1972 , 1972 } ,
{ " M07L " sv , 1968 , 1938 } ,
{ " M08 " sv , 1972 , 1971 } ,
{ " M08L " sv , 1957 , { } } ,
{ " M09 " sv , 1972 , 1972 } ,
{ " M09L " sv , 2014 , { } } ,
{ " M10 " sv , 1972 , 1972 } ,
{ " M10L " sv , 1984 , { } } ,
{ " M11 " sv , 1972 , 1970 } ,
{ " M11L " sv , 0 , { } } , // The reference year for days 1-10 and days 11-29 differ and is handled below.
{ " M12 " sv , 1972 , 1972 } ,
{ " M12L " sv , { } , { } } ,
} ) ;
static Optional < i32 > chinese_or_dangi_reference_year ( String const & calendar , StringView month_code , u8 day )
{
auto row = find_value ( CHINESE_AND_DANGI_ISO_REFERENCE_YEARS , [ & ] ( auto const & row ) { return row . month_code = = month_code ; } ) ;
VERIFY ( row . has_value ( ) ) ;
if ( day > = 1 & & day < 30 ) {
if ( month_code = = " M11L " sv )
return day < = 10 ? 2033 : 2034 ;
return row - > days_1_to_29 ;
}
if ( day = = 30 ) {
if ( month_code = = " M03 " sv )
return calendar = = " chinese " sv ? 1966 : 1968 ;
return row - > day_30 ;
}
return { } ;
}
2024-11-20 12:59:15 -05:00
// 12.1.1 CanonicalizeCalendar ( id ), https://tc39.es/proposal-temporal/#sec-temporal-canonicalizecalendar
ThrowCompletionOr < String > canonicalize_calendar ( VM & vm , StringView id )
{
// 1. Let calendars be AvailableCalendars().
auto const & calendars = available_calendars ( ) ;
// 2. If calendars does not contain the ASCII-lowercase of id, throw a RangeError exception.
for ( auto const & calendar : calendars ) {
if ( calendar . equals_ignoring_ascii_case ( id ) ) {
// 3. Return CanonicalizeUValue("ca", id).
return Unicode : : canonicalize_unicode_extension_values ( " ca " sv , id ) ;
}
}
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidCalendarIdentifier , id ) ;
}
// 12.1.2 AvailableCalendars ( ), https://tc39.es/proposal-temporal/#sec-availablecalendars
2026-03-08 11:39:46 -04:00
// 1.1.1 AvailableCalendars ( ), https://tc39.es/proposal-intl-era-monthcode/#sup-availablecalendars
2024-11-20 12:59:15 -05:00
Vector < String > const & available_calendars ( )
{
// The implementation-defined abstract operation AvailableCalendars takes no arguments and returns a List of calendar
// types. The returned List is sorted according to lexicographic code unit order, and contains unique calendar types
// in canonical form (12.1) identifying the calendars for which the implementation provides the functionality of
2026-03-08 11:39:46 -04:00
// Intl.DateTimeFormat objects, including their aliases (e.g., both "islamicc" and "islamic-civil"). The List must
// consist of the "Calendar Type" value of every row of Table 1, except the header row.
static auto calendars = [ ] ( ) {
auto calendars = Unicode : : available_calendars ( ) ;
for ( auto calendar : CLDR_CALENDAR_TYPES ) {
if ( ! calendars . contains_slow ( calendar ) )
calendars . append ( String : : from_utf8_without_validation ( calendar . bytes ( ) ) ) ;
}
quick_sort ( calendars ) ;
return calendars ;
} ( ) ;
return calendars ;
2024-11-20 12:59:15 -05:00
}
2025-08-14 11:00:44 -04:00
// 12.2.1 ParseMonthCode ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-parsemonthcode
ThrowCompletionOr < MonthCode > parse_month_code ( VM & vm , Value argument )
{
// 1. Let monthCode be ? ToPrimitive(argument, STRING).
auto month_code = TRY ( argument . to_primitive ( vm , Value : : PreferredType : : String ) ) ;
// 2. If monthCode is not a String, throw a TypeError exception.
if ( ! month_code . is_string ( ) )
return vm . throw_completion < TypeError > ( ErrorType : : NotAString , month_code ) ;
return parse_month_code ( vm , month_code . as_string ( ) . utf8_string_view ( ) ) ;
}
// 12.2.1 ParseMonthCode ( argument ), https://tc39.es/proposal-temporal/#sec-temporal-parsemonthcode
ThrowCompletionOr < MonthCode > parse_month_code ( VM & vm , StringView month_code )
{
// 3. If ParseText(StringToCodePoints(monthCode), MonthCode) is a List of errors, throw a RangeError exception.
2026-03-12 09:35:00 -04:00
if ( auto result = Unicode : : parse_month_code ( month_code ) ; result . has_value ( ) )
return result . release_value ( ) ;
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidMonthCode ) ;
2025-08-14 11:00:44 -04:00
}
// 12.3.3 PrepareCalendarFields ( calendar, fields, calendarFieldNames, nonCalendarFieldNames, requiredFieldNames ), https://tc39.es/proposal-temporal/#sec-temporal-preparecalendarfields
2026-03-06 07:52:58 -05:00
ThrowCompletionOr < CalendarFields > prepare_calendar_fields ( VM & vm , String const & calendar , Object const & fields , CalendarFieldList calendar_field_names , CalendarFieldList non_calendar_field_names , CalendarFieldListOrPartial required_field_names )
2024-11-20 12:59:15 -05:00
{
// 1. Assert: If requiredFieldNames is a List, requiredFieldNames contains zero or one of each of the elements of
// calendarFieldNames and nonCalendarFieldNames.
// 2. Let fieldNames be the list-concatenation of calendarFieldNames and nonCalendarFieldNames.
Vector < CalendarField > field_names ;
field_names . append ( calendar_field_names . data ( ) , calendar_field_names . size ( ) ) ;
field_names . append ( non_calendar_field_names . data ( ) , non_calendar_field_names . size ( ) ) ;
// 3. Let extraFieldNames be CalendarExtraFields(calendar, calendarFieldNames).
auto extra_field_names = calendar_extra_fields ( calendar , calendar_field_names ) ;
// 4. Set fieldNames to the list-concatenation of fieldNames and extraFieldNames.
field_names . extend ( move ( extra_field_names ) ) ;
// 5. Assert: fieldNames contains no duplicate elements.
// 6. Let result be a Calendar Fields Record with all fields equal to UNSET.
auto result = CalendarFields : : unset ( ) ;
// 7. Let any be false.
auto any = false ;
// 8. Let sortedPropertyNames be a List whose elements are the values in the Property Key column of Table 19
// corresponding to the elements of fieldNames, sorted according to lexicographic code unit order.
auto sorted_property_names = sorted_calendar_fields ( vm , field_names ) ;
// 9. For each property name property of sortedPropertyNames, do
for ( auto const & [ key , property , conversion ] : sorted_property_names ) {
// a. Let key be the value in the Enumeration Key column of Table 19 corresponding to the row whose Property Key value is property.
// b. Let value be ? Get(fields, property).
auto value = TRY ( fields . get ( property ) ) ;
// c. If value is not undefined, then
if ( ! value . is_undefined ( ) ) {
// i. Set any to true.
any = true ;
// ii. Let Conversion be the Conversion value of the same row.
switch ( conversion ) {
// iii. If Conversion is TO-INTEGER-WITH-TRUNCATION, then
case CalendarFieldConversion : : ToIntegerWithTruncation :
// 1. Set value to ? ToIntegerWithTruncation(value).
// 2. Set value to 𝔽 (value).
set_field_value ( key , result , TRY ( to_integer_with_truncation ( vm , value , ErrorType : : TemporalInvalidCalendarFieldName , * property ) ) ) ;
break ;
// iv. Else if Conversion is TO-POSITIVE-INTEGER-WITH-TRUNCATION, then
case CalendarFieldConversion : : ToPositiveIntegerWithTruncation :
// 1. Set value to ? ToPositiveIntegerWithTruncation(value).
// 2. Set value to 𝔽 (value).
set_field_value ( key , result , TRY ( to_positive_integer_with_truncation ( vm , value , ErrorType : : TemporalInvalidCalendarFieldName , * property ) ) ) ;
break ;
// v. Else if Conversion is TO-STRING, then
case CalendarFieldConversion : : ToString :
// 1. Set value to ? ToString(value).
set_field_value ( key , result , TRY ( value . to_string ( vm ) ) ) ;
break ;
// vi. Else if Conversion is TO-TEMPORAL-TIME-ZONE-IDENTIFIER, then
case CalendarFieldConversion : : ToTemporalTimeZoneIdentifier :
// 1. Set value to ? ToTemporalTimeZoneIdentifier(value).
set_field_value ( key , result , TRY ( to_temporal_time_zone_identifier ( vm , value ) ) ) ;
break ;
// vii. Else if Conversion is TO-MONTH-CODE, then
2025-08-14 11:00:44 -04:00
case CalendarFieldConversion : : ToMonthCode : {
// 1. Let parsed be ? ParseMonthCode(value).
auto parsed = TRY ( parse_month_code ( vm , value ) ) ;
// 2. Set value to CreateMonthCode(parsed.[[MonthNumber]], parsed.[[IsLeapMonth]]).
2026-03-12 09:35:00 -04:00
set_field_value ( key , result , Unicode : : create_month_code ( parsed . month_number , parsed . is_leap_month ) ) ;
2025-08-14 11:00:44 -04:00
2024-11-20 12:59:15 -05:00
break ;
2025-08-14 11:00:44 -04:00
}
2024-11-20 12:59:15 -05:00
// viii. Else,
case CalendarFieldConversion : : ToOffsetString :
// 1. Assert: Conversion is TO-OFFSET-STRING.
// 2. Set value to ? ToOffsetString(value).
set_field_value ( key , result , TRY ( to_offset_string ( vm , value ) ) ) ;
break ;
}
// ix. Set result's field whose name is given in the Field Name column of the same row to value.
}
// d. Else if requiredFieldNames is a List, then
else if ( auto const * required = required_field_names . get_pointer < CalendarFieldList > ( ) ) {
2026-02-14 12:42:36 -05:00
// i. If requiredFieldNames contains key, throw a TypeError exception.
if ( required - > contains_slow ( key ) )
2024-11-20 12:59:15 -05:00
return vm . throw_completion < TypeError > ( ErrorType : : MissingRequiredProperty , * property ) ;
// ii. Set result's field whose name is given in the Field Name column of the same row to the corresponding
// Default value of the same row.
set_default_field_value ( key , result ) ;
}
}
2026-02-14 12:42:36 -05:00
// 10. If requiredFieldNames is PARTIAL and any is false, throw a TypeError exception.
if ( required_field_names . has < Partial > ( ) & & ! any )
2024-11-20 12:59:15 -05:00
return vm . throw_completion < TypeError > ( ErrorType : : TemporalObjectMustBePartialTemporalObject ) ;
// 11. Return result.
return result ;
}
2025-08-14 11:00:44 -04:00
// 12.3.4 CalendarFieldKeysPresent ( fields ), https://tc39.es/proposal-temporal/#sec-temporal-calendarfieldkeyspresent
2024-11-20 18:08:07 -05:00
Vector < CalendarField > calendar_field_keys_present ( CalendarFields const & fields )
{
// 1. Let list be « ».
Vector < CalendarField > list ;
auto handle_field = [ & ] ( auto enumeration_key , auto const & value ) {
// a. Let value be fields' field whose name is given in the Field Name column of the row.
// b. Let enumerationKey be the value in the Enumeration Key column of the row.
// c. If value is not unset, append enumerationKey to list.
if ( value . has_value ( ) )
list . append ( enumeration_key ) ;
} ;
// 2. For each row of Table 19, except the header row, do
# define __JS_ENUMERATE(enumeration, field_name, property_key, conversion) \
handle_field ( enumeration , fields . field_name ) ;
JS_ENUMERATE_CALENDAR_FIELDS
# undef __JS_ENUMERATE
// 3. Return list.
return list ;
}
2025-08-14 11:00:44 -04:00
// 12.3.5 CalendarMergeFields ( calendar, fields, additionalFields ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmergefields
2026-03-06 07:52:58 -05:00
CalendarFields calendar_merge_fields ( String const & calendar , CalendarFields const & fields , CalendarFields const & additional_fields )
2024-11-20 18:08:07 -05:00
{
// 1. Let additionalKeys be CalendarFieldKeysPresent(additionalFields).
auto additional_keys = calendar_field_keys_present ( additional_fields ) ;
// 2. Let overriddenKeys be CalendarFieldKeysToIgnore(calendar, additionalKeys).
auto overridden_keys = calendar_field_keys_to_ignore ( calendar , additional_keys ) ;
// 3. Let merged be a Calendar Fields Record with all fields set to unset.
auto merged = CalendarFields : : unset ( ) ;
// 4. Let fieldsKeys be CalendarFieldKeysPresent(fields).
auto fields_keys = calendar_field_keys_present ( fields ) ;
auto merge_field = [ & ] ( auto key , auto & merged_field , auto const & fields_field , auto const & additional_fields_field ) {
// a. Let key be the value in the Enumeration Key column of the row.
// b. If fieldsKeys contains key and overriddenKeys does not contain key, then
if ( fields_keys . contains_slow ( key ) & & ! overridden_keys . contains_slow ( key ) ) {
// i. Let propValue be fields' field whose name is given in the Field Name column of the row.
// ii. Set merged's field whose name is given in the Field Name column of the row to propValue.
merged_field = fields_field ;
}
// c. If additionalKeys contains key, then
if ( additional_keys . contains_slow ( key ) ) {
// i. Let propValue be additionalFields' field whose name is given in the Field Name column of the row.
// ii. Set merged's field whose name is given in the Field Name column of the row to propValue.
merged_field = additional_fields_field ;
}
} ;
// 5. For each row of Table 19, except the header row, do
# define __JS_ENUMERATE(enumeration, field_name, property_key, conversion) \
merge_field ( enumeration , merged . field_name , fields . field_name , additional_fields . field_name ) ;
JS_ENUMERATE_CALENDAR_FIELDS
# undef __JS_ENUMERATE
// 6. Return merged.
return merged ;
}
2025-08-14 11:00:44 -04:00
// 12.3.6 NonISODateAdd ( calendar, isoDate, duration, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-nonisodateadd
2026-03-08 11:39:46 -04:00
// 4.1.18 NonISODateAdd ( calendar, isoDate, duration, overflow ), https://tc39.es/proposal-intl-era-monthcode/#sup-temporal-nonisodateadd
2026-03-06 07:52:58 -05:00
ThrowCompletionOr < ISODate > non_iso_date_add ( VM & vm , String const & calendar , ISODate iso_date , DateDuration const & duration , Overflow overflow )
2025-08-05 10:10:01 -04:00
{
2026-03-08 11:39:46 -04:00
// 1. Let parts be CalendarISOToDate(calendar, isoDate).
auto parts = non_iso_calendar_iso_to_date ( calendar , iso_date ) ;
// 2. Let y0 be parts.[[Year]] + duration.[[Years]].
auto y0 = parts . year + static_cast < i32 > ( duration . years ) ;
// 3. Let m0 be MonthCodeToOrdinal(calendar, y0, ? ConstrainMonthCode(calendar, y0, parts.[[MonthCode]], overflow)).
auto m0 = month_code_to_ordinal ( calendar , y0 , TRY ( constrain_month_code ( vm , calendar , y0 , parts . month_code , overflow ) ) ) ;
// 4. Let endOfMonth be BalanceNonISODate(calendar, y0, m0 + duration.[[Months]] + 1, 0).
auto end_of_month = balance_non_iso_date ( calendar , y0 , static_cast < i32 > ( m0 + duration . months + 1 ) , 0 ) ;
// 5. Let baseDay be parts.[[Day]].
auto base_day = parts . day ;
u8 regulated_day = 0 ;
// 6. If baseDay ≤ endOfMonth.[[Day]], then
if ( base_day < = end_of_month . day ) {
// a. Let regulatedDay be baseDay.
regulated_day = base_day ;
}
// 7. Else,
else {
// a. If overflow is REJECT, throw a RangeError exception.
if ( overflow = = Overflow : : Reject )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidISODate ) ;
// b. Let regulatedDay be endOfMonth.[[Day]].
regulated_day = end_of_month . day ;
}
// 8. Let balancedDate be BalanceNonISODate(calendar, endOfMonth.[[Year]], endOfMonth.[[Month]], regulatedDay + 7 * duration.[[Weeks]] + duration.[[Days]]).
auto balanced_date = balance_non_iso_date ( calendar , end_of_month . year , end_of_month . month , static_cast < i32 > ( regulated_day + ( 7 * duration . weeks ) + duration . days ) ) ;
// 9. Let result be ? CalendarIntegersToISO(calendar, balancedDate.[[Year]], balancedDate.[[Month]], balancedDate.[[Day]]).
auto result = TRY ( calendar_integers_to_iso ( vm , calendar , balanced_date . year , balanced_date . month , balanced_date . day ) ) ;
// 10. If ISODateWithinLimits(result) is false, throw a RangeError exception.
// NB: This is handled by the caller, CalendarDateAdd.
// 11. Return result.
return result ;
2025-08-05 10:10:01 -04:00
}
2025-08-14 11:00:44 -04:00
// 12.3.7 CalendarDateAdd ( calendar, isoDate, duration, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendardateadd
2026-03-06 07:52:58 -05:00
ThrowCompletionOr < ISODate > calendar_date_add ( VM & vm , String const & calendar , ISODate iso_date , DateDuration const & duration , Overflow overflow )
2024-11-21 13:21:29 -05:00
{
ISODate result ;
// 1. If calendar is "iso8601", then
2026-03-06 07:52:58 -05:00
if ( calendar = = ISO8601_CALENDAR ) {
2024-11-21 13:21:29 -05:00
// a. Let intermediate be BalanceISOYearMonth(isoDate.[[Year]] + duration.[[Years]], isoDate.[[Month]] + duration.[[Months]]).
auto intermediate = balance_iso_year_month ( static_cast < double > ( iso_date . year ) + duration . years , static_cast < double > ( iso_date . month ) + duration . months ) ;
// b. Set intermediate to ? RegulateISODate(intermediate.[[Year]], intermediate.[[Month]], isoDate.[[Day]], overflow).
auto intermediate_date = TRY ( regulate_iso_date ( vm , intermediate . year , intermediate . month , iso_date . day , overflow ) ) ;
2026-01-13 12:31:52 -05:00
// c. Let days be duration.[[Days]] + 7 × duration.[[Weeks]].
auto days = duration . days + ( 7 * duration . weeks ) ;
2024-11-21 13:21:29 -05:00
2026-01-13 12:31:52 -05:00
// d. Let result be AddDaysToISODate(intermediate, days).
result = add_days_to_iso_date ( intermediate_date , days ) ;
2024-11-21 13:21:29 -05:00
}
// 2. Else,
else {
2025-08-05 10:10:01 -04:00
// a. Let result be ? NonISODateAdd(calendar, isoDate, duration, overflow).
2026-03-08 11:39:46 -04:00
result = TRY ( non_iso_date_add ( vm , calendar , iso_date , duration , overflow ) ) ;
2024-11-21 13:21:29 -05:00
}
// 3. If ISODateWithinLimits(result) is false, throw a RangeError exception.
if ( ! iso_date_within_limits ( result ) )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidISODate ) ;
// 4. Return result.
return result ;
}
2025-08-14 11:00:44 -04:00
// 12.3.8 NonISODateUntil ( calendar, one, two, largestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-nonisodateuntil
2026-03-08 11:39:46 -04:00
// 4.1.19 NonISODateUntil ( calendar, one, two, largestUnit ), https://tc39.es/proposal-intl-era-monthcode/#sup-temporal-nonisodateuntil
2026-03-06 07:52:58 -05:00
DateDuration non_iso_date_until ( VM & vm , String const & calendar , ISODate one , ISODate two , Unit largest_unit )
2025-08-05 10:10:01 -04:00
{
2026-03-08 11:39:46 -04:00
// 1. Let sign be -1 × CompareISODate(one, two).
auto sign = compare_iso_date ( one , two ) ;
sign * = - 1 ;
// 2. If sign = 0, return ZeroDateDuration().
if ( sign = = 0 )
return zero_date_duration ( vm ) ;
// OPTIMIZATION: For DAY and WEEK largest units, calendar days equal ISO days. We can compute the difference
// directly from ISO epoch days without any calendar arithmetic.
if ( largest_unit = = Unit : : Day | | largest_unit = = Unit : : Week ) {
auto days = iso_date_to_epoch_days ( two . year , two . month - 1 , two . day ) - iso_date_to_epoch_days ( one . year , one . month - 1 , one . day ) ;
double weeks = 0 ;
if ( largest_unit = = Unit : : Week ) {
weeks = trunc ( days / 7.0 ) ;
days = fmod ( days , 7.0 ) ;
}
return MUST ( create_date_duration_record ( vm , 0 , 0 , weeks , days ) ) ;
}
// OPTIMIZATION: Pre-compute calendar dates for `from` and `to` to avoid expensive redundant conversions.
auto calendar_one = non_iso_calendar_iso_to_date ( calendar , one ) ;
auto calendar_two = non_iso_calendar_iso_to_date ( calendar , two ) ;
// 3. Let years be 0.
double years = 0 ;
// OPTIMIZATION: If the largestUnit is MONTH, we want to skip ahead to the correct year. If implemented in exact
// accordance with the spec, we could enter the second NonISODateSurpasses loop below with a very
// large number of months to traverse.
// 4. If largestUnit is YEAR, then
if ( largest_unit = = Unit : : Year ) {
// OPTIMIZATION: Skip ahead by estimating the year difference from calendar dates to avoid a large number of
// iterations in the NonISODateSurpasses loop below.
auto estimated_years = calendar_two . year - calendar_one . year ;
if ( estimated_years ! = 0 )
estimated_years - = sign ;
// a. Let candidateYears be sign.
double candidate_years = estimated_years ? estimated_years : static_cast < double > ( sign ) ;
// b. Repeat, while NonISODateSurpasses(calendar, sign, one, two, candidateYears, 0, 0, 0) is false,
while ( ! non_iso_date_surpasses ( vm , calendar , sign , calendar_one , calendar_two , candidate_years , 0 , 0 , 0 ) ) {
// i. Set years to candidateYears.
years = candidate_years ;
// ii. Set candidateYears to candidateYears + sign.
candidate_years + = sign ;
}
}
// 5. Let months be 0.
double months = 0 ;
// 6. If largestUnit is YEAR or largestUnit is MONTH, then
if ( largest_unit = = Unit : : Year | | largest_unit = = Unit : : Month ) {
// a. Let candidateMonths be sign.
double candidate_months = sign ;
// OPTIMIZATION: Skip ahead by estimating the month difference from calendar dates to avoid a large number of
// iterations in the NonISODateSurpasses loop below.
if ( largest_unit = = Unit : : Month ) {
auto estimated_months = ( ( calendar_two . year - calendar_one . year ) * 12 ) + ( calendar_two . month - calendar_one . month ) ;
if ( estimated_months ! = 0 )
estimated_months - = sign ;
if ( estimated_months ! = 0 )
candidate_months = estimated_months ;
}
// b. Repeat, while NonISODateSurpasses(calendar, sign, one, two, years, candidateMonths, 0, 0) is false,
while ( ! non_iso_date_surpasses ( vm , calendar , sign , calendar_one , calendar_two , years , candidate_months , 0 , 0 ) ) {
// i. Set months to candidateMonths.
months = candidate_months ;
// ii. Set candidateMonths to candidateMonths + sign.
candidate_months + = sign ;
}
}
// 9. Let days be 0.
double days = 0 ;
// 10. Let candidateDays be sign.
double candidate_days = sign ;
// 11. Repeat, while NonISODateSurpasses(calendar, sign, one, two, years, months, weeks, candidateDays) is false,
while ( ! non_iso_date_surpasses ( vm , calendar , sign , calendar_one , calendar_two , years , months , 0 , candidate_days ) ) {
// a. Set days to candidateDays.
days = candidate_days ;
// b. Set candidateDays to candidateDays + sign.
candidate_days + = sign ;
}
// 12. Return ! CreateDateDurationRecord(years, months, weeks, days).
return MUST ( create_date_duration_record ( vm , years , months , 0 , days ) ) ;
2025-08-05 10:10:01 -04:00
}
2025-08-14 11:00:44 -04:00
// 12.3.9 CalendarDateUntil ( calendar, one, two, largestUnit ), https://tc39.es/proposal-temporal/#sec-temporal-calendardateuntil
2026-03-06 07:52:58 -05:00
DateDuration calendar_date_until ( VM & vm , String const & calendar , ISODate one , ISODate two , Unit largest_unit )
2024-11-21 13:21:29 -05:00
{
2026-01-16 07:09:32 -05:00
// 1. Let sign be CompareISODate(one, two).
auto sign = compare_iso_date ( one , two ) ;
2026-02-14 12:42:36 -05:00
// 2. If sign = 0, return ZeroDateDuration().
if ( sign = = 0 )
2026-01-16 07:09:32 -05:00
return zero_date_duration ( vm ) ;
// 3. If calendar is "iso8601", then
2026-03-06 07:52:58 -05:00
if ( calendar = = ISO8601_CALENDAR ) {
2026-01-16 07:09:32 -05:00
// a. Set sign to -sign.
2024-11-21 13:21:29 -05:00
sign * = - 1 ;
2026-01-16 07:09:32 -05:00
// b. Let years be 0.
2024-11-21 13:21:29 -05:00
double years = 0 ;
2026-01-16 07:09:32 -05:00
// d. Let months be 0.
2024-11-22 12:50:52 -05:00
double months = 0 ;
// OPTIMIZATION: If the largestUnit is MONTH, we want to skip ahead to the correct year. If implemented in exact
// accordance with the spec, we could enter the second ISODateSurpasses loop below with a very large
// number of months to traverse.
2026-01-16 07:09:32 -05:00
// c. If largestUnit is YEAR, then
2026-02-14 11:46:19 -05:00
// e. If largestUnit is either YEAR or MONTH, then
2024-11-22 12:50:52 -05:00
if ( largest_unit = = Unit : : Year | | largest_unit = = Unit : : Month ) {
2026-01-16 07:09:32 -05:00
// c.i. Let candidateYears be sign.
2024-11-22 12:50:52 -05:00
auto candidate_years = two . year - one . year ;
if ( candidate_years ! = 0 )
candidate_years - = sign ;
2024-11-21 13:21:29 -05:00
2026-01-16 07:09:32 -05:00
// c.ii. Repeat, while ISODateSurpasses(sign, one, two, candidateYears, 0, 0, 0) is false,
2025-08-05 10:28:13 -04:00
while ( ! iso_date_surpasses ( vm , sign , one , two , candidate_years , 0 , 0 , 0 ) ) {
2024-11-21 13:21:29 -05:00
// 1. Set years to candidateYears.
years = candidate_years ;
// 2. Set candidateYears to candidateYears + sign.
candidate_years + = sign ;
}
2026-01-16 07:09:32 -05:00
// e.i. Let candidateMonths be sign.
2024-11-21 13:21:29 -05:00
double candidate_months = sign ;
2026-01-16 07:09:32 -05:00
// e.ii. Repeat, while ISODateSurpasses(sign, one, two, years, candidateMonths, 0, 0) is false,
2025-08-05 10:28:13 -04:00
while ( ! iso_date_surpasses ( vm , sign , one , two , years , candidate_months , 0 , 0 ) ) {
2024-11-21 13:21:29 -05:00
// 1. Set months to candidateMonths.
months = candidate_months ;
// 2. Set candidateMonths to candidateMonths + sign.
candidate_months + = sign ;
}
2024-11-22 12:50:52 -05:00
if ( largest_unit = = Unit : : Month ) {
months + = years * 12.0 ;
years = 0.0 ;
}
2024-11-21 13:21:29 -05:00
}
2026-01-16 07:09:32 -05:00
// f. Let weeks be 0.
2024-11-21 13:21:29 -05:00
double weeks = 0 ;
2024-11-22 12:50:52 -05:00
// OPTIMIZATION: If the largestUnit is DAY, we do not want to enter an ISODateSurpasses loop. The loop would have
// us increment the intermediate ISOYearMonth one day at time, which will take an extremely long
// time if the difference is a large number of years. Instead, we can compute the day difference,
// and convert to weeks if needed.
2025-08-05 10:28:13 -04:00
auto year_month = balance_iso_year_month ( static_cast < double > ( one . year ) + years , static_cast < double > ( one . month ) + months ) ;
auto regulated_date = MUST ( regulate_iso_date ( vm , year_month . year , year_month . month , one . day , Overflow : : Constrain ) ) ;
2024-11-21 13:21:29 -05:00
2025-08-05 10:28:13 -04:00
auto days = iso_date_to_epoch_days ( two . year , two . month - 1 , two . day ) - iso_date_to_epoch_days ( regulated_date . year , regulated_date . month - 1 , regulated_date . day ) ;
2024-11-21 13:21:29 -05:00
2024-11-22 12:50:52 -05:00
if ( largest_unit = = Unit : : Week ) {
weeks = trunc ( days / 7.0 ) ;
days = fmod ( days , 7.0 ) ;
2024-11-21 13:21:29 -05:00
}
2026-01-16 07:09:32 -05:00
// k. Return ! CreateDateDurationRecord(years, months, weeks, days).
2024-11-21 13:21:29 -05:00
return MUST ( create_date_duration_record ( vm , years , months , weeks , days ) ) ;
}
2026-01-16 07:09:32 -05:00
// 4. Return NonISODateUntil(calendar, one, two, largestUnit).
2025-08-05 10:10:01 -04:00
return non_iso_date_until ( vm , calendar , one , two , largest_unit ) ;
2024-11-21 13:21:29 -05:00
}
2025-08-14 11:00:44 -04:00
// 12.3.10 ToTemporalCalendarIdentifier ( temporalCalendarLike ), https://tc39.es/proposal-temporal/#sec-temporal-totemporalcalendaridentifier
2024-11-20 12:59:15 -05:00
ThrowCompletionOr < String > to_temporal_calendar_identifier ( VM & vm , Value temporal_calendar_like )
{
2026-02-14 12:42:36 -05:00
// 1. If temporalCalendarLike is an Object and temporalCalendarLike has an [[InitializedTemporalDate]],
// [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or
// [[InitializedTemporalZonedDateTime]] internal slot, return temporalCalendarLike.[[Calendar]].
2026-02-27 08:39:44 -05:00
if ( auto plain_date = temporal_calendar_like . as_if < PlainDate > ( ) )
return plain_date - > calendar ( ) ;
if ( auto plain_date_time = temporal_calendar_like . as_if < PlainDateTime > ( ) )
return plain_date_time - > calendar ( ) ;
if ( auto plain_month_day = temporal_calendar_like . as_if < PlainMonthDay > ( ) )
return plain_month_day - > calendar ( ) ;
if ( auto plain_year_month = temporal_calendar_like . as_if < PlainYearMonth > ( ) )
return plain_year_month - > calendar ( ) ;
if ( auto zoned_date_time = temporal_calendar_like . as_if < ZonedDateTime > ( ) )
return zoned_date_time - > calendar ( ) ;
2024-11-20 12:59:15 -05:00
// 2. If temporalCalendarLike is not a String, throw a TypeError exception.
if ( ! temporal_calendar_like . is_string ( ) )
return vm . throw_completion < TypeError > ( ErrorType : : TemporalInvalidCalendar ) ;
// 3. Let identifier be ? ParseTemporalCalendarString(temporalCalendarLike).
auto identifier = TRY ( parse_temporal_calendar_string ( vm , temporal_calendar_like . as_string ( ) . utf8_string ( ) ) ) ;
// 4. Return ? CanonicalizeCalendar(identifier).
return TRY ( canonicalize_calendar ( vm , identifier ) ) ;
}
2025-08-14 11:00:44 -04:00
// 12.3.11 GetTemporalCalendarIdentifierWithISODefault ( item ), https://tc39.es/proposal-temporal/#sec-temporal-gettemporalcalendarslotvaluewithisodefault
2024-11-20 12:59:15 -05:00
ThrowCompletionOr < String > get_temporal_calendar_identifier_with_iso_default ( VM & vm , Object const & item )
{
// 1. If item has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]],
// [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then
// a. Return item.[[Calendar]].
2026-02-14 12:42:36 -05:00
if ( auto const * plain_date = as_if < PlainDate > ( item ) )
return plain_date - > calendar ( ) ;
if ( auto const * plain_date_time = as_if < PlainDateTime > ( item ) )
return plain_date_time - > calendar ( ) ;
if ( auto const * plain_month_day = as_if < PlainMonthDay > ( item ) )
return plain_month_day - > calendar ( ) ;
if ( auto const * plain_year_month = as_if < PlainYearMonth > ( item ) )
return plain_year_month - > calendar ( ) ;
if ( auto const * zoned_date_time = as_if < ZonedDateTime > ( item ) )
return zoned_date_time - > calendar ( ) ;
2024-11-20 12:59:15 -05:00
// 2. Let calendarLike be ? Get(item, "calendar").
auto calendar_like = TRY ( item . get ( vm . names . calendar ) ) ;
2026-02-14 12:42:36 -05:00
// 3. If calendarLike is undefined, return "iso8601".
if ( calendar_like . is_undefined ( ) )
2024-11-20 12:59:15 -05:00
return " iso8601 " _string ;
// 4. Return ? ToTemporalCalendarIdentifier(calendarLike).
return TRY ( to_temporal_calendar_identifier ( vm , calendar_like ) ) ;
}
2025-08-14 11:00:44 -04:00
// 12.3.12 CalendarDateFromFields ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendardatefromfields
2026-03-06 07:52:58 -05:00
ThrowCompletionOr < ISODate > calendar_date_from_fields ( VM & vm , String const & calendar , CalendarFields & fields , Overflow overflow )
2024-11-21 13:21:29 -05:00
{
// 1. Perform ? CalendarResolveFields(calendar, fields, DATE).
TRY ( calendar_resolve_fields ( vm , calendar , fields , DateType : : Date ) ) ;
// 2. Let result be ? CalendarDateToISO(calendar, fields, overflow).
auto result = TRY ( calendar_date_to_iso ( vm , calendar , fields , overflow ) ) ;
// 3. If ISODateWithinLimits(result) is false, throw a RangeError exception.
if ( ! iso_date_within_limits ( result ) )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidISODate ) ;
// 4. Return result.
return result ;
}
2025-08-14 11:00:44 -04:00
// 12.3.13 CalendarYearMonthFromFields ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendaryearmonthfromfields
2026-03-06 07:52:58 -05:00
ThrowCompletionOr < ISODate > calendar_year_month_from_fields ( VM & vm , String const & calendar , CalendarFields & fields , Overflow overflow )
2024-11-21 11:22:32 -05:00
{
2025-11-06 11:14:05 -05:00
// 1. Set fields.[[Day]] to 1.
fields . day = 1 ;
2024-11-21 11:22:32 -05:00
2025-11-06 11:14:05 -05:00
// 2. Perform ? CalendarResolveFields(calendar, fields, YEAR-MONTH).
TRY ( calendar_resolve_fields ( vm , calendar , fields , DateType : : YearMonth ) ) ;
2024-11-21 11:22:32 -05:00
2025-11-06 11:14:05 -05:00
// 3. Let result be ? CalendarDateToISO(calendar, fields, overflow).
2024-11-21 11:22:32 -05:00
auto result = TRY ( calendar_date_to_iso ( vm , calendar , fields , overflow ) ) ;
2025-11-06 11:14:05 -05:00
// 4. If ISOYearMonthWithinLimits(result) is false, throw a RangeError exception.
2024-11-21 11:22:32 -05:00
if ( ! iso_year_month_within_limits ( result ) )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidISODate ) ;
2025-11-06 11:14:05 -05:00
// 5. Return result.
2024-11-21 11:22:32 -05:00
return result ;
}
2025-08-14 11:00:44 -04:00
// 12.3.14 CalendarMonthDayFromFields ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdayfromfields
2026-03-06 07:52:58 -05:00
ThrowCompletionOr < ISODate > calendar_month_day_from_fields ( VM & vm , String const & calendar , CalendarFields & fields , Overflow overflow )
2024-11-20 12:59:15 -05:00
{
// 1. Perform ? CalendarResolveFields(calendar, fields, MONTH-DAY).
TRY ( calendar_resolve_fields ( vm , calendar , fields , DateType : : MonthDay ) ) ;
// 2. Let result be ? CalendarMonthDayToISOReferenceDate(calendar, fields, overflow).
auto result = TRY ( calendar_month_day_to_iso_reference_date ( vm , calendar , fields , overflow ) ) ;
2026-01-16 07:11:37 -05:00
// 3. Assert: ISODateWithinLimits(result) is true.
VERIFY ( iso_date_within_limits ( result ) ) ;
2024-11-20 12:59:15 -05:00
// 4. Return result.
return result ;
}
2025-08-14 11:00:44 -04:00
// 12.3.15 FormatCalendarAnnotation ( id, showCalendar ), https://tc39.es/proposal-temporal/#sec-temporal-formatcalendarannotation
2024-11-20 14:57:18 -05:00
String format_calendar_annotation ( StringView id , ShowCalendar show_calendar )
{
// 1. If showCalendar is NEVER, return the empty String.
if ( show_calendar = = ShowCalendar : : Never )
return String { } ;
// 2. If showCalendar is AUTO and id is "iso8601", return the empty String.
2026-03-06 07:52:58 -05:00
if ( show_calendar = = ShowCalendar : : Auto & & id = = ISO8601_CALENDAR )
2024-11-20 14:57:18 -05:00
return String { } ;
// 3. If showCalendar is CRITICAL, let flag be "!"; else, let flag be the empty String.
auto flag = show_calendar = = ShowCalendar : : Critical ? " ! " sv : " " sv ;
// 4. Return the string-concatenation of "[", flag, "u-ca=", id, and "]".
return MUST ( String : : formatted ( " [{}u-ca={}] " , flag , id ) ) ;
}
2025-08-14 11:00:44 -04:00
// 12.3.16 CalendarEquals ( one, two ), https://tc39.es/proposal-temporal/#sec-temporal-calendarequals
2024-11-20 18:08:07 -05:00
bool calendar_equals ( StringView one , StringView two )
{
// 1. If CanonicalizeUValue("ca", one) is CanonicalizeUValue("ca", two), return true.
// 2. Return false.
return Unicode : : canonicalize_unicode_extension_values ( " ca " sv , one )
= = Unicode : : canonicalize_unicode_extension_values ( " ca " sv , two ) ;
}
2025-08-14 11:00:44 -04:00
// 12.3.17 ISODaysInMonth ( year, month ), https://tc39.es/proposal-temporal/#sec-temporal-isodaysinmonth
2024-11-20 12:59:15 -05:00
u8 iso_days_in_month ( double year , double month )
{
2026-02-14 11:46:19 -05:00
// 1. If month is one of 1, 3, 5, 7, 8, 10, or 12, return 31.
if ( first_is_one_of ( month , 1 , 3 , 5 , 7 , 8 , 10 , 12 ) )
2024-11-20 12:59:15 -05:00
return 31 ;
2026-02-14 11:46:19 -05:00
// 2. If month is one of 4, 6, 9, or 11, return 30.
if ( first_is_one_of ( month , 4 , 6 , 9 , 11 ) )
2024-11-20 12:59:15 -05:00
return 30 ;
// 3. Assert: month is 2.
VERIFY ( month = = 2 ) ;
// 4. Return 28 + MathematicalInLeapYear(EpochTimeForYear(year)).
return 28 + mathematical_in_leap_year ( epoch_time_for_year ( year ) ) ;
}
2025-08-14 11:00:44 -04:00
// 12.3.18 ISOWeekOfYear ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isoweekofyear
2024-11-26 08:41:52 -05:00
YearWeek iso_week_of_year ( ISODate iso_date )
2024-11-20 12:59:15 -05:00
{
// 1. Let year be isoDate.[[Year]].
auto year = iso_date . year ;
// 2. Let wednesday be 3.
static constexpr auto wednesday = 3 ;
// 3. Let thursday be 4.
static constexpr auto thursday = 4 ;
// 4. Let friday be 5.
static constexpr auto friday = 5 ;
// 5. Let saturday be 6.
static constexpr auto saturday = 6 ;
// 6. Let daysInWeek be 7.
static constexpr auto days_in_week = 7 ;
// 7. Let maxWeekNumber be 53.
static constexpr auto max_week_number = 53 ;
// 8. Let dayOfYear be ISODayOfYear(isoDate).
auto day_of_year = iso_day_of_year ( iso_date ) ;
// 9. Let dayOfWeek be ISODayOfWeek(isoDate).
auto day_of_week = iso_day_of_week ( iso_date ) ;
// 10. Let week be floor((dayOfYear + daysInWeek - dayOfWeek + wednesday) / daysInWeek).
auto week = floor ( static_cast < double > ( day_of_year + days_in_week - day_of_week + wednesday ) / static_cast < double > ( days_in_week ) ) ;
// 11. If week < 1, then
if ( week < 1 ) {
// a. NOTE: This is the last week of the previous year.
// b. Let jan1st be CreateISODateRecord(year, 1, 1).
auto jan1st = create_iso_date_record ( year , 1 , 1 ) ;
// c. Let dayOfJan1st be ISODayOfWeek(jan1st).
auto day_of_jan1st = iso_day_of_week ( jan1st ) ;
// d. If dayOfJan1st = friday, then
if ( day_of_jan1st = = friday ) {
// i. Return Year-Week Record { [[Week]]: maxWeekNumber, [[Year]]: year - 1 }.
return { . week = max_week_number , . year = year - 1 } ;
}
// e. If dayOfJan1st = saturday, and MathematicalInLeapYear(EpochTimeForYear(year - 1)) = 1, then
if ( day_of_jan1st = = saturday & & mathematical_in_leap_year ( epoch_time_for_year ( year - 1 ) ) = = 1 ) {
// i. Return Year-Week Record { [[Week]]: maxWeekNumber. [[Year]]: year - 1 }.
return { . week = max_week_number , . year = year - 1 } ;
}
// f. Return Year-Week Record { [[Week]]: maxWeekNumber - 1, [[Year]]: year - 1 }.
return { . week = max_week_number - 1 , . year = year - 1 } ;
}
// 12. If week = maxWeekNumber, then
if ( week = = max_week_number ) {
// a. Let daysInYear be MathematicalDaysInYear(year).
auto days_in_year = mathematical_days_in_year ( year ) ;
// b. Let daysLaterInYear be daysInYear - dayOfYear.
auto days_later_in_year = days_in_year - day_of_year ;
// c. Let daysAfterThursday be thursday - dayOfWeek.
auto days_after_thursday = thursday - day_of_week ;
// d. If daysLaterInYear < daysAfterThursday, then
if ( days_later_in_year < days_after_thursday ) {
// i. Return Year-Week Record { [[Week]]: 1, [[Year]]: year + 1 }.
return { . week = 1 , . year = year + 1 } ;
}
}
// 13. Return Year-Week Record { [[Week]]: week, [[Year]]: year }.
return { . week = week , . year = year } ;
}
2025-08-14 11:00:44 -04:00
// 12.3.19 ISODayOfYear ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isodayofyear
2024-11-26 08:41:52 -05:00
u16 iso_day_of_year ( ISODate iso_date )
2024-11-20 12:59:15 -05:00
{
// 1. Let epochDays be ISODateToEpochDays(isoDate.[[Year]], isoDate.[[Month]] - 1, isoDate.[[Day]]).
auto epoch_days = iso_date_to_epoch_days ( iso_date . year , iso_date . month - 1 , iso_date . day ) ;
// 2. Return EpochTimeToDayInYear(EpochDaysToEpochMs(epochDays, 0)) + 1.
return epoch_time_to_day_in_year ( epoch_days_to_epoch_ms ( epoch_days , 0 ) ) + 1 ;
}
2025-08-14 11:00:44 -04:00
// 12.3.20 ISODayOfWeek ( isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-isodayofweek
2024-11-26 08:41:52 -05:00
u8 iso_day_of_week ( ISODate iso_date )
2024-11-20 12:59:15 -05:00
{
// 1. Let epochDays be ISODateToEpochDays(isoDate.[[Year]], isoDate.[[Month]] - 1, isoDate.[[Day]]).
auto epoch_days = iso_date_to_epoch_days ( iso_date . year , iso_date . month - 1 , iso_date . day ) ;
// 2. Let dayOfWeek be EpochTimeToWeekDay(EpochDaysToEpochMs(epochDays, 0)).
auto day_of_week = epoch_time_to_week_day ( epoch_days_to_epoch_ms ( epoch_days , 0 ) ) ;
// 3. If dayOfWeek = 0, return 7.
if ( day_of_week = = 0 )
return 7 ;
// 4. Return dayOfWeek.
return day_of_week ;
}
2025-08-14 11:00:44 -04:00
// 12.3.21 NonISOCalendarDateToISO ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-nonisocalendardatetoiso
2026-03-08 11:39:46 -04:00
// 4.1.20 NonISOCalendarDateToISO ( calendar, fields, overflow ), https://tc39.es/proposal-intl-era-monthcode/#sup-temporal-nonisocalendardatetoiso
2026-03-06 07:52:58 -05:00
ThrowCompletionOr < ISODate > non_iso_calendar_date_to_iso ( VM & vm , String const & calendar , CalendarFields const & fields , Overflow overflow )
2025-08-05 10:10:01 -04:00
{
2026-03-08 11:39:46 -04:00
// 1. Assert: fields.[[Year]], fields.[[Month]], and fields.[[Day]] are not UNSET.
VERIFY ( fields . year . has_value ( ) ) ;
VERIFY ( fields . month . has_value ( ) ) ;
VERIFY ( fields . day . has_value ( ) ) ;
// 2. If fields.[[MonthCode]] is not UNSET, then
if ( fields . month_code . has_value ( ) ) {
// a. Perform ? ConstrainMonthCode(calendar, fields.[[Year]], fields.[[MonthCode]], overflow).
( void ) TRY ( constrain_month_code ( vm , calendar , * fields . year , * fields . month_code , overflow ) ) ;
}
// 3. Let monthsInYear be CalendarMonthsInYear(calendar, fields.[[Year]]).
auto months_in_year = calendar_months_in_year ( calendar , * fields . year ) ;
u8 month = 0 ;
u8 day = 0 ;
// 4. If fields.[[Month]] > monthsInYear, then
if ( * fields . month > months_in_year ) {
// a. If overflow is REJECT, throw a RangeError exception.
if ( overflow = = Overflow : : Reject )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidCalendarFieldName , " month " sv ) ;
// b. Let month be monthsInYear.
month = months_in_year ;
}
// 5. Else,
else {
// a. Let month be fields.[[Month]].
month = static_cast < u8 > ( * fields . month ) ;
}
// 6. Let daysInMonth be CalendarDaysInMonth(calendar, fields.[[Year]], fields.[[Month]]).
// FIXME: Spec issue: We should use the `month` value that we just constrained.
auto days_in_month = calendar_days_in_month ( calendar , * fields . year , month ) ;
// 7. If fields.[[Day]] > daysInMonth, then
if ( * fields . day > days_in_month ) {
// a. If overflow is REJECT, throw a RangeError exception.
if ( overflow = = Overflow : : Reject )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidCalendarFieldName , " day " sv ) ;
// b. Let day be daysInMonth.
day = days_in_month ;
}
// 8. Else,
else {
// a. Let day be fields.[[Day]].
day = static_cast < u8 > ( * fields . day ) ;
}
// 9. Return ? CalendarIntegersToISO(calendar, fields.[[Year]], month, day).
return TRY ( calendar_integers_to_iso ( vm , calendar , * fields . year , month , day ) ) ;
2025-08-05 10:10:01 -04:00
}
2025-08-14 11:00:44 -04:00
// 12.3.22 CalendarDateToISO ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendardatetoiso
2026-03-06 07:52:58 -05:00
ThrowCompletionOr < ISODate > calendar_date_to_iso ( VM & vm , String const & calendar , CalendarFields const & fields , Overflow overflow )
2024-11-21 11:22:32 -05:00
{
// 1. If calendar is "iso8601", then
2026-03-06 07:52:58 -05:00
if ( calendar = = ISO8601_CALENDAR ) {
2024-11-21 11:22:32 -05:00
// a. Assert: fields.[[Year]], fields.[[Month]], and fields.[[Day]] are not UNSET.
VERIFY ( fields . year . has_value ( ) ) ;
VERIFY ( fields . month . has_value ( ) ) ;
VERIFY ( fields . day . has_value ( ) ) ;
// b. Return ? RegulateISODate(fields.[[Year]], fields.[[Month]], fields.[[Day]], overflow).
return TRY ( regulate_iso_date ( vm , * fields . year , * fields . month , * fields . day , overflow ) ) ;
}
2025-08-05 10:10:01 -04:00
// 2. Return ? NonISOCalendarDateToISO(calendar, fields, overflow).
return non_iso_calendar_date_to_iso ( vm , calendar , fields , overflow ) ;
}
2025-08-14 11:00:44 -04:00
// 12.3.23 NonISOMonthDayToISOReferenceDate ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-nonisomonthdaytoisoreferencedate
2026-03-08 11:39:46 -04:00
// 4.1.21 NonISOMonthDayToISOReferenceDate ( calendar, fields, overflow ), https://tc39.es/proposal-intl-era-monthcode/#sup-temporal-nonisomonthdaytoisoreferencedate
2026-03-06 07:52:58 -05:00
ThrowCompletionOr < ISODate > non_iso_month_day_to_iso_reference_date ( VM & vm , String const & calendar , CalendarFields const & fields , Overflow overflow )
2025-08-05 10:10:01 -04:00
{
2026-03-08 11:39:46 -04:00
// 1. Assert: fields.[[Day]] is not UNSET.
VERIFY ( fields . day . has_value ( ) ) ;
u8 day = 0 ;
u8 month = 0 ;
u8 days_in_month = 0 ;
String month_code ;
// 2. If fields.[[Year]] is not UNSET, then
if ( fields . year . has_value ( ) ) {
// a. Assert: fields.[[Month]] is not UNSET.
VERIFY ( fields . month . has_value ( ) ) ;
// b. If there exists no combination of inputs such that ! CalendarIntegersToISO(calendar, fields.[[Year]], ..., ...)
// would return an ISO Date Record isoDate for which ISODateWithinLimits(isoDate) is true, throw a RangeError exception.
// c. NOTE: The above step exists so as not to require calculating whether the month and day described in fields
// exist in user-provided years arbitrarily far in the future or past.
2026-04-14 16:00:37 -04:00
if ( auto iso_date = calendar_integers_to_iso ( vm , calendar , * fields . year , 1 , 1 ) ; iso_date . is_error ( ) | | ! iso_date_within_limits ( iso_date . value ( ) ) )
2026-03-08 11:39:46 -04:00
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidISODate ) ;
// d. Let monthsInYear be CalendarMonthsInYear(calendar, fields.[[Year]]).
auto months_in_year = calendar_months_in_year ( calendar , * fields . year ) ;
// e. If fields.[[Month]] > monthsInYear, then
if ( * fields . month > months_in_year ) {
// i. If overflow is REJECT, throw a RangeError exception.
if ( overflow = = Overflow : : Reject )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidCalendarFieldName , " month " sv ) ;
// ii. Let month be monthsInYear.
month = months_in_year ;
}
// f. Else,
else {
// i. Let month be fields.[[Month]].
month = static_cast < u8 > ( * fields . month ) ;
}
// g. If fields.[[MonthCode]] is UNSET, then
if ( ! fields . month_code . has_value ( ) ) {
// i. Let fieldsISODate be ! CalendarIntegersToISO(calendar, fields.[[Year]], month, 1).
auto fields_iso_date = MUST ( calendar_integers_to_iso ( vm , calendar , * fields . year , month , 1 ) ) ;
// ii. Let monthCode be NonISOCalendarISOToDate(calendar, fieldsISODate).[[MonthCode]].
month_code = non_iso_calendar_iso_to_date ( calendar , fields_iso_date ) . month_code ;
}
// h. Else,
else {
// i. Let monthCode be ? ConstrainMonthCode(calendar, fields.[[Year]], fields.[[MonthCode]], overflow).
month_code = TRY ( constrain_month_code ( vm , calendar , * fields . year , * fields . month_code , overflow ) ) ;
}
// i. Let daysInMonth be CalendarDaysInMonth(calendar, fields.[[Year]], month).
days_in_month = calendar_days_in_month ( calendar , * fields . year , month ) ;
}
// 3. Else,
else {
// a. Assert: fields.[[MonthCode]] is not UNSET.
VERIFY ( fields . month_code . has_value ( ) ) ;
// b. Let monthCode be fields.[[MonthCode]].
month_code = * fields . month_code ;
// c. If calendar is "chinese" or "dangi", let daysInMonth be 30; else, let daysInMonth be the maximum number of
// days in the month described by monthCode in any year.
days_in_month = calendar . is_one_of ( " chinese " sv , " dangi " sv ) ? 30 : Unicode : : calendar_max_days_in_month_code ( calendar , month_code ) ;
}
// 4. If fields.[[Day]] > daysInMonth, then
if ( * fields . day > days_in_month ) {
// a. If overflow is REJECT, throw a RangeError exception.
if ( overflow = = Overflow : : Reject )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidCalendarFieldName , " day " sv ) ;
// b. Let day be daysInMonth.
day = days_in_month ;
}
// 5. Else,
else {
// a. Let day be fields.[[Day]].
day = static_cast < u8 > ( * fields . day ) ;
}
auto is_chinese_or_dangi = calendar . is_one_of ( " chinese " sv , " dangi " sv ) ;
// 6. If calendar is "chinese" or "dangi", then
if ( is_chinese_or_dangi ) {
// a. NOTE: This special case handles combinations of month and day that theoretically could occur but are not
// known to have occurred historically and cannot be accurately calculated to occur in the future, even if it
// may be possible to construct a PlainDate with such combinations due to inaccurate approximations. This is
// explicitly mentioned here because as time goes on, these dates may become known to have occurred
// historically, or may be more accurately calculated to occur in the future.
// b. Let row be the row in Table 6 with a value in the "Month Code" column matching monthCode.
auto reference_year = chinese_or_dangi_reference_year ( calendar , month_code , day ) ;
// c. If the "Reference Year (Days 1-29)" column of row is "—", or day = 30 and the "Reference Year (Day 30)"
// column of row is "—", then
if ( ! reference_year . has_value ( ) ) {
// i. If overflow is REJECT, throw a RangeError exception.
if ( overflow = = Overflow : : Reject )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidCalendarFieldName , " monthCode " sv ) ;
// ii. Set monthCode to CreateMonthCode(! ParseMonthCode(monthCode).[[MonthNumber]], false).
if ( month_code . ends_with ( ' L ' ) )
month_code = MUST ( month_code . trim ( " L " sv , TrimMode : : Right ) ) ;
}
}
// 7. Let referenceYear be the ISO reference year for monthCode and day as described above. If calendar is "chinese"
// or "dangi", the reference years in Table 6 are to be used.
// 8. Return the latest possible ISO Date Record isoDate such that isoDate.[[Year]] = referenceYear and
// NonISOCalendarISOToDate(calendar, isoDate) returns a Calendar Date Record whose [[MonthCode]] and [[Day]]
// field values respectively equal monthCode and day.
auto result = [ & ] ( ) - > Optional < ISODate > {
if ( is_chinese_or_dangi ) {
auto reference_year = chinese_or_dangi_reference_year ( calendar , month_code , day ) ;
2026-03-12 12:00:06 -04:00
return Unicode : : iso_year_and_month_code_to_iso_date ( calendar , * reference_year , month_code , day ) ;
2026-03-08 11:39:46 -04:00
}
for ( i32 iso_year = 1972 ; iso_year > = 1900 ; - - iso_year ) {
2026-03-12 12:00:06 -04:00
if ( auto result = Unicode : : iso_year_and_month_code_to_iso_date ( calendar , iso_year , month_code , day ) ; result . has_value ( ) )
2026-03-08 11:39:46 -04:00
return result ;
}
for ( i32 iso_year = 1973 ; iso_year < = 2035 ; + + iso_year ) {
2026-03-12 12:00:06 -04:00
if ( auto result = Unicode : : iso_year_and_month_code_to_iso_date ( calendar , iso_year , month_code , day ) ; result . has_value ( ) )
2026-03-08 11:39:46 -04:00
return result ;
}
return { } ;
} ( ) ;
VERIFY ( result . has_value ( ) ) ;
return * result ;
2024-11-21 11:22:32 -05:00
}
2025-08-14 11:00:44 -04:00
// 12.3.24 CalendarMonthDayToISOReferenceDate ( calendar, fields, overflow ), https://tc39.es/proposal-temporal/#sec-temporal-calendarmonthdaytoisoreferencedate
2026-03-06 07:52:58 -05:00
ThrowCompletionOr < ISODate > calendar_month_day_to_iso_reference_date ( VM & vm , String const & calendar , CalendarFields const & fields , Overflow overflow )
2024-11-20 12:59:15 -05:00
{
// 1. If calendar is "iso8601", then
2026-03-06 07:52:58 -05:00
if ( calendar = = ISO8601_CALENDAR ) {
2024-11-20 12:59:15 -05:00
// a. Assert: fields.[[Month]] and fields.[[Day]] are not UNSET.
VERIFY ( fields . month . has_value ( ) ) ;
VERIFY ( fields . day . has_value ( ) ) ;
// b. Let referenceISOYear be 1972 (the first ISO 8601 leap year after the epoch).
static constexpr i32 reference_iso_year = 1972 ;
// c. If fields.[[Year]] is UNSET, let year be referenceISOYear; else let year be fields.[[Year]].
auto year = ! fields . year . has_value ( ) ? reference_iso_year : * fields . year ;
// d. Let result be ? RegulateISODate(year, fields.[[Month]], fields.[[Day]], overflow).
auto result = TRY ( regulate_iso_date ( vm , year , * fields . month , * fields . day , overflow ) ) ;
// e. Return CreateISODateRecord(referenceISOYear, result.[[Month]], result.[[Day]]).
return create_iso_date_record ( reference_iso_year , result . month , result . day ) ;
}
2025-08-05 10:10:01 -04:00
// 2. Return ? NonISOMonthDayToISOReferenceDate(calendar, fields, overflow).
return non_iso_month_day_to_iso_reference_date ( vm , calendar , fields , overflow ) ;
2024-11-20 12:59:15 -05:00
}
2025-08-14 11:00:44 -04:00
// 12.3.25 NonISOCalendarISOToDate ( calendar, isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-nonisocalendarisotodate
2026-03-06 07:52:58 -05:00
CalendarDate non_iso_calendar_iso_to_date ( String const & calendar , ISODate iso_date )
2025-08-05 10:10:01 -04:00
{
2026-03-08 11:39:46 -04:00
auto result = Unicode : : iso_date_to_calendar_date ( calendar , iso_date ) ;
for ( auto const & row : CALENDAR_ERA_DATA ) {
if ( row . calendar ! = calendar )
continue ;
i32 era_year = 0 ;
switch ( row . kind ) {
case CalendarEraData : : Kind : : Epoch :
era_year = result . year ;
break ;
case CalendarEraData : : Kind : : Negative :
era_year = 1 - result . year ;
break ;
case CalendarEraData : : Kind : : Offset :
era_year = result . year - * row . offset + 1 ;
break ;
}
if ( row . minimum_era_year . has_value ( ) & & era_year < * row . minimum_era_year )
continue ;
if ( row . maximum_era_year . has_value ( ) & & era_year > * row . maximum_era_year )
continue ;
if ( row . iso_era_start . has_value ( ) & & compare_iso_date ( iso_date , * row . iso_era_start ) < 0 )
continue ;
result . era = String : : from_utf8_without_validation ( row . era . bytes ( ) ) ;
result . era_year = era_year ;
break ;
}
return result ;
2025-08-05 10:10:01 -04:00
}
2025-08-14 11:00:44 -04:00
// 12.3.26 CalendarISOToDate ( calendar, isoDate ), https://tc39.es/proposal-temporal/#sec-temporal-calendarisotodate
2026-03-06 07:52:58 -05:00
CalendarDate calendar_iso_to_date ( String const & calendar , ISODate iso_date )
2024-11-20 12:59:15 -05:00
{
// 1. If calendar is "iso8601", then
2026-03-06 07:52:58 -05:00
if ( calendar = = ISO8601_CALENDAR ) {
2025-08-14 11:00:44 -04:00
// a. If MathematicalInLeapYear(EpochTimeForYear(isoDate.[[Year]])) = 1, let inLeapYear be true; else let inLeapYear be false.
2024-11-20 12:59:15 -05:00
auto in_leap_year = mathematical_in_leap_year ( epoch_time_for_year ( iso_date . year ) ) = = 1 ;
2025-08-14 11:00:44 -04:00
// b. Return Calendar Date Record { [[Era]]: undefined, [[EraYear]]: undefined, [[Year]]: isoDate.[[Year]],
// [[Month]]: isoDate.[[Month]], [[MonthCode]]: CreateMonthCode(isoDate.[[Month]], false), [[Day]]: isoDate.[[Day]],
// [[DayOfWeek]]: ISODayOfWeek(isoDate), [[DayOfYear]]: ISODayOfYear(isoDate), [[WeekOfYear]]: ISOWeekOfYear(isoDate),
// [[DaysInWeek]]: 7, [[DaysInMonth]]: ISODaysInMonth(isoDate.[[Year]], isoDate.[[Month]]),
// [[DaysInYear]]: MathematicalDaysInYear(isoDate.[[Year]]), [[MonthsInYear]]: 12, [[InLeapYear]]: inLeapYear }.
2024-11-20 12:59:15 -05:00
return CalendarDate {
. era = { } ,
. era_year = { } ,
. year = iso_date . year ,
. month = iso_date . month ,
2026-03-12 09:35:00 -04:00
. month_code = Unicode : : create_month_code ( iso_date . month , false ) ,
2024-11-20 12:59:15 -05:00
. day = iso_date . day ,
. day_of_week = iso_day_of_week ( iso_date ) ,
. day_of_year = iso_day_of_year ( iso_date ) ,
. week_of_year = iso_week_of_year ( iso_date ) ,
. days_in_week = 7 ,
. days_in_month = iso_days_in_month ( iso_date . year , iso_date . month ) ,
. days_in_year = mathematical_days_in_year ( iso_date . year ) ,
. months_in_year = 12 ,
. in_leap_year = in_leap_year ,
} ;
}
2025-08-05 10:10:01 -04:00
// 2. Return NonISOCalendarISOToDate(calendar, isoDate).
return non_iso_calendar_iso_to_date ( calendar , iso_date ) ;
2024-11-20 12:59:15 -05:00
}
2025-08-14 11:00:44 -04:00
// 12.3.27 CalendarExtraFields ( calendar, fields ), https://tc39.es/proposal-temporal/#sec-temporal-calendarextrafields
2026-03-08 11:39:46 -04:00
// 4.1.22 CalendarExtraFields ( calendar, fields ), https://tc39.es/proposal-intl-era-monthcode/#sup-temporal-calendarextrafields
Vector < CalendarField > calendar_extra_fields ( String const & calendar , CalendarFieldList fields )
2024-11-20 12:59:15 -05:00
{
2026-03-08 11:39:46 -04:00
// 1. If fields contains an element equal to YEAR and CalendarSupportsEra(calendar) is true, then
if ( fields . contains_slow ( CalendarField : : Year ) & & calendar_supports_era ( calendar ) ) {
// a. Return « ERA, ERA-YEAR ».
return { CalendarField : : Era , CalendarField : : EraYear } ;
}
2024-11-20 12:59:15 -05:00
2026-03-08 11:39:46 -04:00
// 2. Return an empty List.
2024-11-20 12:59:15 -05:00
return { } ;
}
2025-08-14 11:00:44 -04:00
// 12.3.28 NonISOFieldKeysToIgnore ( calendar, keys ), https://tc39.es/proposal-temporal/#sec-temporal-nonisofieldkeystoignore
2026-03-08 11:39:46 -04:00
// 4.1.23 NonISOFieldKeysToIgnore ( calendar, keys ), https://tc39.es/proposal-intl-era-monthcode/#sup-temporal-nonisofieldkeystoignore
2026-03-06 07:52:58 -05:00
Vector < CalendarField > non_iso_field_keys_to_ignore ( String const & calendar , ReadonlySpan < CalendarField > keys )
2025-08-05 10:10:01 -04:00
{
2026-03-08 11:39:46 -04:00
// 1. Let ignoredKeys be a copy of keys.
Vector < CalendarField > ignored_keys { keys } ;
// 2. For each element key of keys, do
for ( auto key : keys ) {
// a. If key is MONTH, append MONTH-CODE to ignoredKeys.
if ( key = = CalendarField : : Month )
ignored_keys . append ( CalendarField : : MonthCode ) ;
// b. If key is MONTH-CODE, append month.
if ( key = = CalendarField : : MonthCode )
ignored_keys . append ( CalendarField : : Month ) ;
// c. If key is one of ERA, ERA-YEAR, or YEAR and CalendarSupportsEra(calendar) is true, then
if ( first_is_one_of ( key , CalendarField : : Era , CalendarField : : EraYear , CalendarField : : Year ) & & calendar_supports_era ( calendar ) ) {
// i. Append ERA, ERA-YEAR, and YEAR to ignoredKeys.
ignored_keys . append ( CalendarField : : Era ) ;
ignored_keys . append ( CalendarField : : EraYear ) ;
ignored_keys . append ( CalendarField : : Year ) ;
}
// d. If key is one of DAY, MONTH, or MONTH-CODE and CalendarHasMidYearEras(calendar) is true, then
if ( first_is_one_of ( key , CalendarField : : Day , CalendarField : : Month , CalendarField : : MonthCode ) & & calendar_has_mid_year_eras ( calendar ) ) {
// i. Append ERA and ERA-YEAR to ignoredKeys.
ignored_keys . append ( CalendarField : : Era ) ;
ignored_keys . append ( CalendarField : : EraYear ) ;
}
}
// 3. NOTE: While ignoredKeys can have duplicate elements, this is not intended to be meaningful. This specification
// only checks whether particular keys are or are not members of the list.
// 4. Return ignoredKeys.
return ignored_keys ;
2025-08-05 10:10:01 -04:00
}
2025-08-14 11:00:44 -04:00
// 12.3.29 CalendarFieldKeysToIgnore ( calendar, keys ), https://tc39.es/proposal-temporal/#sec-temporal-calendarfieldkeystoignore
2026-03-06 07:52:58 -05:00
Vector < CalendarField > calendar_field_keys_to_ignore ( String const & calendar , ReadonlySpan < CalendarField > keys )
2024-11-20 18:08:07 -05:00
{
// 1. If calendar is "iso8601", then
2026-03-06 07:52:58 -05:00
if ( calendar = = ISO8601_CALENDAR ) {
2026-02-14 11:27:25 -05:00
// a. Let ignoredKeys be a new empty List.
2024-11-20 18:08:07 -05:00
Vector < CalendarField > ignored_keys ;
// b. For each element key of keys, do
for ( auto key : keys ) {
// i. Append key to ignoredKeys.
ignored_keys . append ( key ) ;
// ii. If key is MONTH, append MONTH-CODE to ignoredKeys.
if ( key = = CalendarField : : Month )
ignored_keys . append ( CalendarField : : MonthCode ) ;
// iii. Else if key is MONTH-CODE, append MONTH to ignoredKeys.
else if ( key = = CalendarField : : MonthCode )
ignored_keys . append ( CalendarField : : Month ) ;
}
// c. NOTE: While ignoredKeys can have duplicate elements, this is not intended to be meaningful. This specification
// only checks whether particular keys are or are not members of the list.
// d. Return ignoredKeys.
return ignored_keys ;
}
2025-08-05 10:10:01 -04:00
// 2. Return NonISOFieldKeysToIgnore(calendar, keys).
return non_iso_field_keys_to_ignore ( calendar , keys ) ;
}
2025-08-14 11:00:44 -04:00
// 12.3.30 NonISOResolveFields ( calendar, fields, type ), https://tc39.es/proposal-temporal/#sec-temporal-nonisoresolvefields
2026-03-08 11:39:46 -04:00
// 4.1.24 NonISOResolveFields ( calendar, fields, type ), https://tc39.es/proposal-intl-era-monthcode/#sup-temporal-nonisoresolvefields
2026-03-06 07:52:58 -05:00
ThrowCompletionOr < void > non_iso_resolve_fields ( VM & vm , String const & calendar , CalendarFields & fields , DateType type )
2025-08-05 10:10:01 -04:00
{
2026-03-08 11:39:46 -04:00
// 1. Let needsYear be false.
// 2. If type is DATE or type is YEAR-MONTH, set needsYear to true.
// 3. If fields.[[MonthCode]] is UNSET, set needsYear to true.
// 4. If fields.[[Month]] is not UNSET, set needsYear to true.
auto needs_year = type = = DateType : : Date | | type = = DateType : : YearMonth
| | ! fields . month_code . has_value ( )
| | fields . month . has_value ( ) ;
// 5. Let needsOrdinalMonth be false.
// 6. If fields.[[Year]] is not UNSET, set needsOrdinalMonth to true.
// 7. If fields.[[EraYear]] is not UNSET, set needsOrdinalMonth to true.
auto needs_ordinal_month = fields . year . has_value ( ) | | fields . era_year . has_value ( ) ;
// 8. Let needsDay be false.
// 9. If type is DATE or type is MONTH-DAY, set needsDay to true.
auto needs_day = type = = DateType : : Date | | type = = DateType : : MonthDay ;
// 10. If needsYear is true, then
if ( needs_year ) {
// a. If fields.[[Year]] is UNSET, then
if ( ! fields . year . has_value ( ) ) {
// i. If CalendarSupportsEra(calendar) is false, throw a TypeError exception.
if ( ! calendar_supports_era ( calendar ) )
return vm . throw_completion < TypeError > ( ErrorType : : MissingRequiredProperty , " year " sv ) ;
// ii. If fields.[[Era]] is UNSET or fields.[[EraYear]] is UNSET, throw a TypeError exception.
if ( ! fields . era . has_value ( ) | | ! fields . era_year . has_value ( ) )
return vm . throw_completion < TypeError > ( ErrorType : : MissingRequiredProperty , " era " sv ) ;
}
}
// 11. If CalendarSupportsEra(calendar) is true, then
if ( calendar_supports_era ( calendar ) ) {
// a. If fields.[[Era]] is not UNSET and fields.[[EraYear]] is UNSET, throw a TypeError exception.
if ( fields . era . has_value ( ) & & ! fields . era_year . has_value ( ) )
return vm . throw_completion < TypeError > ( ErrorType : : MissingRequiredProperty , " eraYear " sv ) ;
// b. If fields.[[EraYear]] is not UNSET and fields.[[Era]] is UNSET, throw a TypeError exception.
if ( fields . era_year . has_value ( ) & & ! fields . era . has_value ( ) )
return vm . throw_completion < TypeError > ( ErrorType : : MissingRequiredProperty , " era " sv ) ;
}
// 12. If needsDay is true and fields.[[Day]] is UNSET, throw a TypeError exception.
if ( needs_day & & ! fields . day . has_value ( ) )
return vm . throw_completion < TypeError > ( ErrorType : : MissingRequiredProperty , " day " sv ) ;
// 13. If fields.[[Month]] is UNSET and fields.[[MonthCode]] is UNSET, throw a TypeError exception.
if ( ! fields . month . has_value ( ) & & ! fields . month_code . has_value ( ) )
return vm . throw_completion < TypeError > ( ErrorType : : MissingRequiredProperty , " month " sv ) ;
// 14. If CalendarSupportsEra(calendar) is true and fields.[[EraYear]] is not UNSET, then
if ( calendar_supports_era ( calendar ) & & fields . era_year . has_value ( ) ) {
// a. Let canonicalEra be CanonicalizeEraInCalendar(calendar, fields.[[Era]]).
auto canonical_era = canonicalize_era_in_calendar ( calendar , * fields . era ) ;
// b. If canonicalEra is undefined, throw a RangeError exception.
if ( ! canonical_era . has_value ( ) )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidCalendarFieldName , " era " sv ) ;
// c. Let arithmeticYear be CalendarDateArithmeticYearForEraYear(calendar, canonicalEra, fields.[[EraYear]]).
auto arithmetic_year = calendar_date_arithmetic_year_for_era_year ( calendar , * canonical_era , * fields . era_year ) ;
// d. If fields.[[Year]] is not UNSET, and fields.[[Year]] ≠ arithmeticYear, throw a RangeError exception.
if ( fields . year . has_value ( ) & & * fields . year ! = arithmetic_year )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidCalendarFieldName , " year " sv ) ;
// e. Set fields.[[Year]] to arithmeticYear.
fields . year = arithmetic_year ;
}
// 15. Set fields.[[Era]] to UNSET.
fields . era = { } ;
// 16. Set fields.[[EraYear]] to UNSET.
fields . era_year = { } ;
// 17. NOTE: fields.[[Era]] and fields.[[EraYear]] are erased in order to allow a lenient interpretation of
// out-of-bounds values, which is particularly useful for consistent interpretation of dates in calendars with
// regnal eras.
// 18. If fields.[[MonthCode]] is not UNSET, then
if ( fields . month_code . has_value ( ) ) {
// a. If IsValidMonthCodeForCalendar(calendar, fields.[[MonthCode]]) is false, throw a RangeError exception.
if ( ! is_valid_month_code_for_calendar ( calendar , * fields . month_code ) )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidCalendarFieldName , " monthCode " sv ) ;
// b. If fields.[[Year]] is not UNSET, then
if ( fields . year . has_value ( ) ) {
// i. If YearContainsMonthCode(calendar, fields.[[Year]], fields.[[MonthCode]]) is true, let constrainedMonthCode be fields.[[MonthCode]];
// else let constrainedMonthCode be ! ConstrainMonthCode(calendar, fields.[[Year]], fields.[[MonthCode]], CONSTRAIN).
auto constrained_month_code = year_contains_month_code ( calendar , * fields . year , * fields . month_code )
? * fields . month_code
: MUST ( constrain_month_code ( vm , calendar , * fields . year , * fields . month_code , Overflow : : Constrain ) ) ;
// ii. Let month be MonthCodeToOrdinal(calendar, fields.[[Year]], constrainedMonthCode).
auto month = month_code_to_ordinal ( calendar , * fields . year , constrained_month_code ) ;
// iii. If fields.[[Month]] is not UNSET and fields.[[Month]] ≠ month, throw a RangeError exception.
if ( fields . month . has_value ( ) & & * fields . month ! = month )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidCalendarFieldName , " month " sv ) ;
// iv. Set fields.[[Month]] to month.
fields . month = month ;
// v. NOTE: fields.[[MonthCode]] is intentionally not overwritten with constrainedMonthCode. Pending the
// "overflow" parameter in CalendarDateToISO or CalendarMonthDayToISOReferenceDate, a month code not
// occurring in fields.[[Year]] may cause that operation to throw. However, if fields.[[Month]] is
// present, it must agree with the constrained month code.
}
}
// 19. Assert: fields.[[Era]] and fields.[[EraYear]] are UNSET.
VERIFY ( ! fields . era . has_value ( ) ) ;
VERIFY ( ! fields . era_year . has_value ( ) ) ;
// 20. Assert: If needsYear is true, fields.[[Year]] is not UNSET.
if ( needs_year )
VERIFY ( fields . year . has_value ( ) ) ;
// 21. Assert: If needsOrdinalMonth is true, fields.[[Month]] is not UNSET.
if ( needs_ordinal_month )
VERIFY ( fields . month . has_value ( ) ) ;
// 22. Assert: If needsDay is true, fields.[[Day]] is not UNSET.
if ( needs_day )
VERIFY ( fields . day . has_value ( ) ) ;
// 23. Return unused.
return { } ;
2024-11-20 18:08:07 -05:00
}
2025-08-14 11:00:44 -04:00
// 12.3.31 CalendarResolveFields ( calendar, fields, type ), https://tc39.es/proposal-temporal/#sec-temporal-calendarresolvefields
2026-03-06 07:52:58 -05:00
ThrowCompletionOr < void > calendar_resolve_fields ( VM & vm , String const & calendar , CalendarFields & fields , DateType type )
2024-11-20 12:59:15 -05:00
{
// 1. If calendar is "iso8601", then
2026-03-06 07:52:58 -05:00
if ( calendar = = ISO8601_CALENDAR ) {
2026-02-14 10:47:40 -05:00
// a. Let needsYear be false.
// b. If type is either DATE or YEAR-MONTH, set needsYear to true.
auto needs_year = type = = DateType : : Date | | type = = DateType : : YearMonth ;
2024-11-20 12:59:15 -05:00
2026-02-14 10:47:40 -05:00
// c. Let needsDay be false.
// d. If type is either DATE or MONTH-DAY, set needsDay to true.
auto needs_day = type = = DateType : : Date | | type = = DateType : : MonthDay ;
2024-11-20 12:59:15 -05:00
2026-02-14 10:47:40 -05:00
// e. If needsYear is true and fields.[[Year]] is UNSET, throw a TypeError exception.
if ( needs_year & & ! fields . year . has_value ( ) )
return vm . throw_completion < TypeError > ( ErrorType : : MissingRequiredProperty , " year " sv ) ;
2024-11-20 12:59:15 -05:00
2026-02-14 10:47:40 -05:00
// f. If needsDay is true and fields.[[Day]] is UNSET, throw a TypeError exception.
if ( needs_day & & ! fields . day . has_value ( ) )
return vm . throw_completion < TypeError > ( ErrorType : : MissingRequiredProperty , " day " sv ) ;
2024-11-20 12:59:15 -05:00
2026-02-14 10:47:40 -05:00
// g. If fields.[[Month]] is UNSET and fields.[[MonthCode]] is UNSET, throw a TypeError exception.
if ( ! fields . month . has_value ( ) & & ! fields . month_code . has_value ( ) )
return vm . throw_completion < TypeError > ( ErrorType : : MissingRequiredProperty , " month " sv ) ;
2024-11-20 12:59:15 -05:00
2026-02-14 10:47:40 -05:00
// h. If fields.[[MonthCode]] is not UNSET, then
if ( fields . month_code . has_value ( ) ) {
// i. Let parsedMonthCode be ! ParseMonthCode(fields.[[MonthCode]]).
2026-03-12 09:35:00 -04:00
auto parsed_month_code = Unicode : : parse_month_code ( * fields . month_code ) . release_value ( ) ;
2024-11-20 12:59:15 -05:00
2026-02-14 10:47:40 -05:00
// ii. If parsedMonthCode.[[IsLeapMonth]] is true, throw a RangeError exception.
if ( parsed_month_code . is_leap_month )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidCalendarFieldName , " monthCode " sv ) ;
2024-11-20 12:59:15 -05:00
2026-02-14 10:47:40 -05:00
// iii. Let month be parsedMonthCode.[[MonthNumber]].
auto month = parsed_month_code . month_number ;
2024-11-20 12:59:15 -05:00
2026-02-14 10:47:40 -05:00
// iv. If month > 12, throw a RangeError exception.
if ( month > 12 )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidCalendarFieldName , " monthCode " sv ) ;
2024-11-20 12:59:15 -05:00
2026-02-14 10:47:40 -05:00
// v. If fields.[[Month]] is not UNSET and fields.[[Month]] ≠ month, throw a RangeError exception.
if ( fields . month . has_value ( ) & & fields . month ! = month )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidCalendarFieldName , " month " sv ) ;
2024-11-20 12:59:15 -05:00
2026-02-14 10:47:40 -05:00
// vi. Set fields.[[Month]] to month.
fields . month = month ;
}
2024-11-20 12:59:15 -05:00
}
// 2. Else,
else {
2025-08-05 10:10:01 -04:00
// a. Perform ? NonISOResolveFields(calendar, fields, type).
return TRY ( non_iso_resolve_fields ( vm , calendar , fields , type ) ) ;
2024-11-20 12:59:15 -05:00
}
// 3. Return UNUSED.
return { } ;
}
2026-03-08 11:39:46 -04:00
// 4.1.1 CalendarSupportsEra ( calendar ), https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-calendarsupportsera
bool calendar_supports_era ( String const & calendar )
{
// 1. If calendar is listed in the "Calendar" column of Table 2, return true.
// 2. If calendar is listed in the "Calendar Type" column of Table 1, return false.
// 3. Return an implementation-defined value.
return find_value ( CALENDAR_ERA_DATA , [ & ] ( auto const & row ) { return row . calendar = = calendar ; } ) . has_value ( ) ;
}
// 4.1.2 CanonicalizeEraInCalendar ( calendar, era ), https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-canonicalizeeraincalendar
Optional < StringView > canonicalize_era_in_calendar ( String const & calendar , StringView era )
{
// 1. For each row of Table 2, except the header row, do
for ( auto const & row : CALENDAR_ERA_DATA ) {
// a. Let cal be the Calendar value of the current row.
// b. If cal is equal to calendar, then
if ( row . calendar = = calendar ) {
// i. Let canonicalName be the Era value of the current row.
auto canonical_name = row . era ;
// ii. If canonicalName is equal to era, return canonicalName.
if ( canonical_name = = era )
return canonical_name ;
// iii. Let aliases be a List whose elements are the strings given in the "Aliases" column of the row.
// iv. If aliases contains era, return canonicalName.
if ( row . alias = = era )
return canonical_name ;
}
}
// 2. If calendar is listed in the "Calendar Type" column of Table 1, return undefined.
// 3. Return an implementation-defined value.
return { } ;
}
// 4.1.3 CalendarHasMidYearEras ( calendar ), https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-calendarhasmidyeareras
bool calendar_has_mid_year_eras ( String const & calendar )
{
// 1. If calendar is "japanese", return true.
// 2. If calendar is listed in the "Calendar Type" column of Table 1, return false.
// 3. Return an implementation-defined value.
return calendar = = " japanese " sv ;
}
// 4.1.4 IsValidMonthCodeForCalendar ( calendar, monthCode ), https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-isvalidmonthcodeforcalendar
bool is_valid_month_code_for_calendar ( String const & calendar , StringView month_code )
{
// 1. Let commonMonthCodes be « "M01", "M02", "M03", "M04", "M05", "M06", "M07", "M08", "M09", "M10", "M11", "M12" ».
// 2. If commonMonthCodes contains monthCode, return true.
if ( month_code . is_one_of ( " M01 " sv , " M02 " sv , " M03 " sv , " M04 " sv , " M05 " sv , " M06 " sv , " M07 " sv , " M08 " sv , " M09 " sv , " M10 " sv , " M11 " sv , " M12 " sv ) )
return true ;
// 3. If calendar is listed in the "Calendar" column of Table 3, then
if ( auto row = find_value ( ADDITIONAL_MONTH_CODES , [ & ] ( auto const & row ) { return row . calendar = = calendar ; } ) ; row . has_value ( ) ) {
// a. Let r be the row in Table 3 with a value in the Calendar column matching calendar.
// b. Let specialMonthCodes be a List whose elements are the strings given in the "Additional Month Codes" column of r.
// c. If specialMonthCodes contains monthCode, return true.
// d. Return false.
return row - > additional_month_codes . contains_slow ( month_code ) ;
}
// 4. If calendar is listed in the "Calendar Type" column of Table 1, return false.
// 5. Return an implementation-defined value.
return false ;
}
// 4.1.5 YearContainsMonthCode ( calendar, arithmeticYear, monthCode ), https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-yearcontainsmonthcode
bool year_contains_month_code ( String const & calendar , i32 arithmetic_year , StringView month_code )
{
// 1. Assert: IsValidMonthCodeForCalendar(calendar, monthCode) is true.
VERIFY ( is_valid_month_code_for_calendar ( calendar , month_code ) ) ;
// 2. If ! ParseMonthCode(monthCode).[[IsLeap]] is false, return true.
2026-03-12 09:35:00 -04:00
if ( ! Unicode : : parse_month_code ( month_code ) - > is_leap_month )
2026-03-08 11:39:46 -04:00
return true ;
// 3. Return whether the leap month indicated by monthCode exists in the year arithmeticYear in calendar, using
// calendar-dependent behaviour.
2026-03-10 09:19:29 -04:00
return Unicode : : calendar_year_contains_month_code ( calendar , arithmetic_year , month_code ) ;
2026-03-08 11:39:46 -04:00
}
// 4.1.6 ConstrainMonthCode ( calendar, arithmeticYear, monthCode, overflow ), https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-constrainmonthcode
ThrowCompletionOr < String > constrain_month_code ( VM & vm , String const & calendar , i32 arithmetic_year , String const & month_code , Overflow overflow )
{
// 1. Assert: IsValidMonthCodeForCalendar(calendar, monthCode) is true.
VERIFY ( is_valid_month_code_for_calendar ( calendar , month_code ) ) ;
// 2. If YearContainsMonthCode(calendar, arithmeticYear, monthCode) is true, return monthCode.
if ( year_contains_month_code ( calendar , arithmetic_year , month_code ) )
return month_code ;
// 3. If overflow is REJECT, throw a RangeError exception.
if ( overflow = = Overflow : : Reject )
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidMonthCode ) ;
// 4. Assert: calendar is listed in the "Calendar" column of Table 3.
// 5. Let r be the row in Table 3 with a value in the Calendar column matching calendar.
auto row = find_value ( ADDITIONAL_MONTH_CODES , [ & ] ( auto const & row ) { return row . calendar = = calendar ; } ) ;
VERIFY ( row . has_value ( ) ) ;
// 6. Let shiftType be the value given in the "Leap to Common Month Transformation" column of r.
// 7. If shiftType is SKIP-BACKWARD, then
if ( row - > leap_to_common_month_transformation = = AdditionalMonthCodes : : Leap : : SkipBackward ) {
// a. Return CreateMonthCode(! ParseMonthCode(monthCode).[[MonthNumber]], false).
return MUST ( month_code . trim ( " L " sv , TrimMode : : Right ) ) ;
}
// 8. Else,
// a. Assert: monthCode is "M05L".
VERIFY ( month_code = = " M05L " ) ;
// b. Return "M06".
return " M06 " _string ;
}
// 4.1.7 MonthCodeToOrdinal ( calendar, arithmeticYear, monthCode ), https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-monthcodetoordinal
u8 month_code_to_ordinal ( String const & calendar , i32 arithmetic_year , StringView month_code )
{
// 1. Assert: YearContainsMonthCode(calendar, arithmeticYear, monthCode) is true.
VERIFY ( year_contains_month_code ( calendar , arithmetic_year , month_code ) ) ;
// 2. Let monthsBefore be 0.
auto months_before = 0 ;
// 3. Let number be 1.
auto number = 1 ;
// 4. Let isLeap be false.
auto is_leap = false ;
// 5. Let r be the row in Table 3 which the calendar is in the Calendar column.
auto row = find_value ( ADDITIONAL_MONTH_CODES , [ & ] ( auto const & row ) { return row . calendar = = calendar ; } ) ;
// 6. If the "Leap to Common Month Transformation" column of r is empty, then
if ( ! row . has_value ( ) | | ! row - > leap_to_common_month_transformation . has_value ( ) ) {
// a. Return ! ParseMonthCode(monthCode).[[MonthNumber]].
2026-03-12 09:35:00 -04:00
return Unicode : : parse_month_code ( month_code ) - > month_number ;
2026-03-08 11:39:46 -04:00
}
// 7. Assert: The "Additional Month Codes" column of r does not contain "M00L" or "M13".
VERIFY ( ! row - > additional_month_codes . contains_slow ( " M00L " sv ) ) ;
VERIFY ( ! row - > additional_month_codes . contains_slow ( " M13 " sv ) ) ;
// 8. Assert: This algorithm will return before the following loop terminates by failing its condition.
// 9. Repeat, while number ≤ 12,
while ( number < = 12 ) {
// a. Let currentMonthCode be CreateMonthCode(number, isLeap).
2026-03-12 09:35:00 -04:00
auto current_month_code = Unicode : : create_month_code ( number , is_leap ) ;
2026-03-08 11:39:46 -04:00
// b. If IsValidMonthCodeForCalendar(calendar, currentMonthCode) is true and YearContainsMonthCode(calendar, arithmeticYear, currentMonthCode) is true, then
if ( is_valid_month_code_for_calendar ( calendar , current_month_code ) & & year_contains_month_code ( calendar , arithmetic_year , current_month_code ) ) {
// i. Set monthsBefore to monthsBefore + 1.
+ + months_before ;
}
// c. If currentMonthCode is monthCode, then
if ( current_month_code = = month_code ) {
// i. Return monthsBefore.
return months_before ;
}
// d. If isLeap is false, then
// i. Set isLeap to true.
// e. Else,
// i. Set isLeap to false.
// ii. Set number to number + 1.
if ( exchange ( is_leap , ! is_leap ) )
+ + number ;
}
VERIFY_NOT_REACHED ( ) ;
}
// 4.1.8 CalendarDaysInMonth ( calendar, arithmeticYear, ordinalMonth ), https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-calendardaysinmonth
u8 calendar_days_in_month ( String const & calendar , i32 arithmetic_year , u8 ordinal_month )
{
// 1. Let isoDate be ! CalendarIntegersToISO(calendar, arithmeticYear, ordinalMonth, 1).
// 2. Return CalendarISOToDate(calendar, isoDate).[[DaysInMonth]].
return Unicode : : calendar_days_in_month ( calendar , arithmetic_year , ordinal_month ) ;
}
// 4.1.12 CalendarDateArithmeticYearForEraYear ( calendar, era, eraYear ), https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-calendardatearithmeticyearforerayear
i32 calendar_date_arithmetic_year_for_era_year ( String const & calendar , StringView era , i32 era_year )
{
// 1. Let era be CanonicalizeEraInCalendar(calendar, era).
// 2. Assert: era is not undefined.
era = canonicalize_era_in_calendar ( calendar , era ) . release_value ( ) ;
// 3. If calendar is not listed in the "Calendar Type" column of Table 1, return an implementation-defined value.
// 4. Let r be the row in Table 2 with a value in the Calendar column matching calendar and a value in the Era
// column matching era.
auto row = find_value ( CALENDAR_ERA_DATA , [ & ] ( auto const & row ) { return row . calendar = = calendar & & row . era = = era ; } ) ;
if ( ! row . has_value ( ) )
return era_year ;
// 5. Let eraKind be the value given in the "Era Kind" column of r.
auto era_kind = row - > kind ;
// 6. Let offset be the value given in the "Offset" column of r.
auto offset = row - > offset ;
switch ( era_kind ) {
// 7. If eraKind is EPOCH, return eraYear.
case CalendarEraData : : Kind : : Epoch :
return era_year ;
// 8. If eraKind is NEGATIVE, return 1 - eraYear.
case CalendarEraData : : Kind : : Negative :
return 1 - era_year ;
// 9. Assert: eraKind is OFFSET.
case CalendarEraData : : Kind : : Offset :
// 10. Assert: offset is not undefined.
VERIFY ( offset . has_value ( ) ) ;
// 11. Return offset + eraYear - 1.
return * offset + era_year - 1 ;
}
VERIFY_NOT_REACHED ( ) ;
}
// 4.1.13 CalendarIntegersToISO ( calendar, arithmeticYear, ordinalMonth, day ), https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-calendarintegerstoiso
ThrowCompletionOr < ISODate > calendar_integers_to_iso ( VM & vm , String const & calendar , i32 arithmetic_year , u8 ordinal_month , u8 day )
{
// 1. If arithmeticYear, ordinalMonth, and day do not form a valid date in calendar, throw a RangeError exception.
// 2. Let isoDate be an ISO Date Record such that CalendarISOToDate(calendar, isoDate) returns a Calendar Date Record
// whose [[Year]], [[Month]], and [[Day]] field values respectively equal arithmeticYear, ordinalMonth, and day.
// 3. NOTE: No known calendars have repeated dates that would cause isoDate to be ambiguous between two ISO Date Records.
// 4. Return isoDate.
if ( auto iso_date = Unicode : : calendar_date_to_iso_date ( calendar , arithmetic_year , ordinal_month , day ) ; iso_date . has_value ( ) )
return create_iso_date_record ( iso_date - > year , iso_date - > month , iso_date - > day ) ;
return vm . throw_completion < RangeError > ( ErrorType : : TemporalInvalidISODate ) ;
}
// 4.1.15 CalendarMonthsInYear ( calendar, arithmeticYear ), https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-calendarmonthsinyear
u8 calendar_months_in_year ( String const & calendar , i32 arithmetic_year )
{
// 1. Let isoDate be ! CalendarIntegersToISO(calendar, arithmeticYear, 1, 1).
// 2. Return CalendarISOToDate(calendar, isoDate).[[MonthsInYear]].
return Unicode : : calendar_months_in_year ( calendar , arithmetic_year ) ;
}
// 4.1.16 BalanceNonISODate ( calendar, arithmeticYear, ordinalMonth, day ), https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-balancenonisodate
BalancedDate balance_non_iso_date ( String const & calendar , i32 arithmetic_year , i32 ordinal_month , i32 day )
{
// 1. Let resolvedYear be arithmeticYear.
auto resolved_year = arithmetic_year ;
// 2. Let resolvedMonth be ordinalMonth.
auto resolved_month = ordinal_month ;
// 3. Let monthsInYear be CalendarMonthsInYear(calendar, resolvedYear).
auto months_in_year = calendar_months_in_year ( calendar , resolved_year ) ;
// 4. Repeat, while resolvedMonth ≤ 0,
while ( resolved_month < = 0 ) {
// a. Set resolvedYear to resolvedYear - 1.
- - resolved_year ;
// b. Set monthsInYear to CalendarMonthsInYear(calendar, resolvedYear).
months_in_year = calendar_months_in_year ( calendar , resolved_year ) ;
// c. Set resolvedMonth to resolvedMonth + monthsInYear.
resolved_month + = months_in_year ;
}
// 5. Repeat, while resolvedMonth > monthsInYear,
while ( resolved_month > months_in_year ) {
// a. Set resolvedMonth to resolvedMonth - monthsInYear.
resolved_month - = months_in_year ;
// b. Set resolvedYear to resolvedYear + 1.
+ + resolved_year ;
// c. Set monthsInYear to CalendarMonthsInYear(calendar, resolvedYear).
months_in_year = calendar_months_in_year ( calendar , resolved_year ) ;
}
// 6. Let resolvedDay be day.
auto resolved_day = day ;
// 7. Let daysInMonth be CalendarDaysInMonth(calendar, resolvedYear, resolvedMonth).
auto days_in_month = calendar_days_in_month ( calendar , resolved_year , resolved_month ) ;
// 8. Repeat, while resolvedDay ≤ 0,
while ( resolved_day < = 0 ) {
// a. Set resolvedMonth to resolvedMonth - 1.
- - resolved_month ;
// b. If resolvedMonth is 0, then
if ( resolved_month = = 0 ) {
// i. Set resolvedYear to resolvedYear - 1.
- - resolved_year ;
// ii. Set monthsInYear to CalendarMonthsInYear(calendar, resolvedYear).
months_in_year = calendar_months_in_year ( calendar , resolved_year ) ;
// iii. Set resolvedMonth to monthsInYear.
resolved_month = months_in_year ;
}
// c. Set daysInMonth to CalendarDaysInMonth(calendar, resolvedYear, resolvedMonth).
days_in_month = calendar_days_in_month ( calendar , resolved_year , resolved_month ) ;
// d. Set resolvedDay to resolvedDay + daysInMonth.
resolved_day + = days_in_month ;
}
// 9. Repeat, while resolvedDay > daysInMonth,
while ( resolved_day > days_in_month ) {
// a. Set resolvedDay to resolvedDay - daysInMonth.
resolved_day - = days_in_month ;
// b. Set resolvedMonth to resolvedMonth + 1.
+ + resolved_month ;
// c. If resolvedMonth > monthsInYear, then
if ( resolved_month > months_in_year ) {
// i. Set resolvedYear to resolvedYear + 1.
+ + resolved_year ;
// ii. Set monthsInYear to CalendarMonthsInYear(calendar, resolvedYear).
months_in_year = calendar_months_in_year ( calendar , resolved_year ) ;
// iii. Set resolvedMonth to 1.
resolved_month = 1 ;
}
// d. Set daysInMonth to CalendarDaysInMonth(calendar, resolvedYear, resolvedMonth).
days_in_month = calendar_days_in_month ( calendar , resolved_year , resolved_month ) ;
}
// 10. Return the Record { [[Year]]: resolvedYear, [[Month]]: resolvedMonth, [[Day]]: resolvedDay }.
return { . year = resolved_year , . month = static_cast < u8 > ( resolved_month ) , . day = static_cast < u8 > ( resolved_day ) } ;
}
// 4.1.17 NonISODateSurpasses ( calendar, sign, fromIsoDate, toIsoDate, years, months, weeks, days ), https://tc39.es/proposal-intl-era-monthcode/#sec-temporal-nonisodatesurpasses
// NB: The only caller to this function is NonISODateUntil, which precomputes the calendar dates.
bool non_iso_date_surpasses ( VM & vm , String const & calendar , i8 sign , CalendarDate const & from_calendar_date , CalendarDate const & to_calendar_date , double years , double months , double weeks , double days )
{
// 1. Let parts be CalendarISOToDate(calendar, fromIsoDate).
auto const & parts = from_calendar_date ;
// 2. Let calDate2 be CalendarISOToDate(calendar, toIsoDate).
auto const & calendar_date_2 = to_calendar_date ;
// 3. Let y0 be parts.[[Year]] + years.
auto y0 = parts . year + static_cast < i32 > ( years ) ;
// 4. If CompareSurpasses(sign, y0, parts.[[MonthCode]], parts.[[Day]], calDate2) is true, return true.
if ( compare_surpasses ( sign , y0 , parts . month_code , parts . day , calendar_date_2 ) )
return true ;
// 5. Let m0 be MonthCodeToOrdinal(calendar, y0, ! ConstrainMonthCode(calendar, y0, parts.[[MonthCode]], CONSTRAIN)).
auto m0 = month_code_to_ordinal ( calendar , y0 , MUST ( constrain_month_code ( vm , calendar , y0 , parts . month_code , Overflow : : Constrain ) ) ) ;
// 6. Let monthsAdded be BalanceNonISODate(calendar, y0, m0 + months, 1).
auto months_added = balance_non_iso_date ( calendar , y0 , m0 + static_cast < i32 > ( months ) , 1 ) ;
// 7. If CompareSurpasses(sign, monthsAdded.[[Year]], monthsAdded.[[Month]], parts.[[Day]], calDate2) is true, return true.
if ( compare_surpasses ( sign , months_added . year , months_added . month , parts . day , calendar_date_2 ) )
return true ;
// 8. If weeks = 0 and days = 0, return false.
if ( weeks = = 0 & & days = = 0 )
return false ;
// 9. Let endOfMonth be BalanceNonISODate(calendar, monthsAdded.[[Year]], monthsAdded.[[Month]] + 1, 0).
auto end_of_month = balance_non_iso_date ( calendar , months_added . year , months_added . month + 1 , 0 ) ;
// 10. Let baseDay be parts.[[Day]].
auto base_day = parts . day ;
// 11. If baseDay ≤ endOfMonth.[[Day]], then
// a. Let regulatedDay be baseDay.
// 12. Else,
// a. Let regulatedDay be endOfMonth.[[Day]].
auto regulated_day = base_day < = end_of_month . day ? base_day : end_of_month . day ;
// 13. Let daysInWeek be 7 (the number of days in a week for all supported calendars).
static constexpr auto days_in_week = 7 ;
// 14. Let balancedDate be BalanceNonISODate(calendar, endOfMonth.[[Year]], endOfMonth.[[Month]], regulatedDay + daysInWeek * weeks + days).
auto balanced_date = balance_non_iso_date ( calendar , end_of_month . year , end_of_month . month , static_cast < i32 > ( regulated_day + ( days_in_week * weeks ) + days ) ) ;
// 15. Return CompareSurpasses(sign, balancedDate.[[Year]], balancedDate.[[Month]], balancedDate.[[Day]], calDate2).
return compare_surpasses ( sign , balanced_date . year , balanced_date . month , balanced_date . day , calendar_date_2 ) ;
}
2024-11-20 12:59:15 -05:00
}