2021-11-18 10:30:31 -05:00
/*
2023-01-27 10:18:11 -05:00
* Copyright ( c ) 2021 - 2023 , Tim Flynn < trflynn89 @ serenityos . org >
2021-11-18 10:30:31 -05:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2022-07-21 14:41:47 -04:00
# include <AK/Find.h>
2021-12-08 19:57:21 -05:00
# include <AK/IterationDecision.h>
2021-11-28 17:55:47 -05:00
# include <AK/NumericLimits.h>
2022-11-23 13:41:50 +01:00
# include <AK/Utf16View.h>
2021-12-06 12:26:49 -05:00
# include <LibJS/Runtime/AbstractOperations.h>
2021-12-07 21:49:29 -05:00
# include <LibJS/Runtime/Array.h>
2021-12-06 12:26:49 -05:00
# include <LibJS/Runtime/Date.h>
2021-11-18 10:30:31 -05:00
# include <LibJS/Runtime/Intl/DateTimeFormat.h>
2021-12-06 12:26:49 -05:00
# include <LibJS/Runtime/Intl/NumberFormat.h>
# include <LibJS/Runtime/Intl/NumberFormatConstructor.h>
# include <LibJS/Runtime/NativeFunction.h>
2023-01-27 10:18:11 -05:00
# include <LibJS/Runtime/ThrowableStringBuilder.h>
2021-12-06 12:26:49 -05:00
# include <LibJS/Runtime/Utf16String.h>
2022-09-02 12:11:30 -04:00
# include <LibLocale/Locale.h>
# include <LibLocale/NumberFormat.h>
2021-12-06 12:26:49 -05:00
# include <math.h>
2021-11-18 10:30:31 -05:00
namespace JS : : Intl {
2022-11-05 12:22:00 -04:00
static Crypto : : SignedBigInteger const s_one_million_bigint { 1'000'000 } ;
2021-11-18 10:30:31 -05:00
// 11 DateTimeFormat Objects, https://tc39.es/ecma402/#datetimeformat-objects
DateTimeFormat : : DateTimeFormat ( Object & prototype )
2022-12-14 12:17:58 +01:00
: Object ( ConstructWithPrototypeTag : : Tag , prototype )
2021-11-18 10:30:31 -05:00
{
}
2021-12-06 12:26:49 -05:00
void DateTimeFormat : : visit_edges ( Cell : : Visitor & visitor )
{
Base : : visit_edges ( visitor ) ;
if ( m_bound_format )
visitor . visit ( m_bound_format ) ;
}
2021-11-18 10:30:31 -05:00
DateTimeFormat : : Style DateTimeFormat : : style_from_string ( StringView style )
{
if ( style = = " full " sv )
return Style : : Full ;
if ( style = = " long " sv )
return Style : : Long ;
if ( style = = " medium " sv )
return Style : : Medium ;
if ( style = = " short " sv )
return Style : : Short ;
VERIFY_NOT_REACHED ( ) ;
}
StringView DateTimeFormat : : style_to_string ( Style style )
{
switch ( style ) {
case Style : : Full :
return " full " sv ;
case Style : : Long :
return " long " sv ;
case Style : : Medium :
return " medium " sv ;
case Style : : Short :
return " short " sv ;
default :
VERIFY_NOT_REACHED ( ) ;
}
}
2023-07-21 22:13:44 -04:00
// 11.5.1 DateTimeStyleFormat ( dateStyle, timeStyle, styles ), https://tc39.es/ecma402/#sec-date-time-style-format
2023-01-27 10:18:11 -05:00
ThrowCompletionOr < Optional < : : Locale : : CalendarPattern > > date_time_style_format ( VM & vm , StringView data_locale , DateTimeFormat & date_time_format )
2021-11-28 17:55:47 -05:00
{
2022-09-02 12:01:10 -04:00
: : Locale : : CalendarPattern time_format { } ;
: : Locale : : CalendarPattern date_format { } ;
2021-11-28 17:55:47 -05:00
2023-01-27 10:18:11 -05:00
auto get_pattern = [ & ] ( auto type , auto style ) - > ThrowCompletionOr < Optional < : : Locale : : CalendarPattern > > {
auto formats = TRY_OR_THROW_OOM ( vm , : : Locale : : get_calendar_format ( data_locale , date_time_format . calendar ( ) , type ) ) ;
2021-11-28 17:55:47 -05:00
if ( formats . has_value ( ) ) {
switch ( style ) {
case DateTimeFormat : : Style : : Full :
return formats - > full_format ;
case DateTimeFormat : : Style : : Long :
return formats - > long_format ;
case DateTimeFormat : : Style : : Medium :
return formats - > medium_format ;
case DateTimeFormat : : Style : : Short :
return formats - > short_format ;
}
}
2023-01-27 10:18:11 -05:00
return OptionalNone { } ;
2021-11-28 17:55:47 -05:00
} ;
// 1. If timeStyle is not undefined, then
if ( date_time_format . has_time_style ( ) ) {
// a. Assert: timeStyle is one of "full", "long", "medium", or "short".
// b. Let timeFormat be styles.[[TimeFormat]].[[<timeStyle>]].
2023-01-27 10:18:11 -05:00
auto pattern = MUST_OR_THROW_OOM ( get_pattern ( : : Locale : : CalendarFormatType : : Time , date_time_format . time_style ( ) ) ) ;
2021-11-28 17:55:47 -05:00
if ( ! pattern . has_value ( ) )
2023-01-27 10:18:11 -05:00
return OptionalNone { } ;
2021-11-28 17:55:47 -05:00
time_format = pattern . release_value ( ) ;
}
// 2. If dateStyle is not undefined, then
if ( date_time_format . has_date_style ( ) ) {
// a. Assert: dateStyle is one of "full", "long", "medium", or "short".
// b. Let dateFormat be styles.[[DateFormat]].[[<dateStyle>]].
2023-01-27 10:18:11 -05:00
auto pattern = MUST_OR_THROW_OOM ( get_pattern ( : : Locale : : CalendarFormatType : : Date , date_time_format . date_style ( ) ) ) ;
2021-11-28 17:55:47 -05:00
if ( ! pattern . has_value ( ) )
2023-01-27 10:18:11 -05:00
return OptionalNone { } ;
2021-11-28 17:55:47 -05:00
date_format = pattern . release_value ( ) ;
}
// 3. If dateStyle is not undefined and timeStyle is not undefined, then
if ( date_time_format . has_date_style ( ) & & date_time_format . has_time_style ( ) ) {
// a. Let format be a new Record.
2022-09-02 12:01:10 -04:00
: : Locale : : CalendarPattern format { } ;
2021-11-28 17:55:47 -05:00
// b. Add to format all fields from dateFormat except [[pattern]] and [[rangePatterns]].
2021-12-07 08:40:06 -05:00
format . for_each_calendar_field_zipped_with ( date_format , [ ] ( auto & format_field , auto const & date_format_field , auto ) {
2021-11-28 17:55:47 -05:00
format_field = date_format_field ;
} ) ;
// c. Add to format all fields from timeFormat except [[pattern]], [[rangePatterns]], [[pattern12]], and [[rangePatterns12]], if present.
2021-12-07 08:40:06 -05:00
format . for_each_calendar_field_zipped_with ( time_format , [ ] ( auto & format_field , auto const & time_format_field , auto ) {
2021-11-28 17:55:47 -05:00
if ( time_format_field . has_value ( ) )
format_field = time_format_field ;
} ) ;
// d. Let connector be styles.[[DateTimeFormat]].[[<dateStyle>]].
2023-01-27 10:18:11 -05:00
auto connector = MUST_OR_THROW_OOM ( get_pattern ( : : Locale : : CalendarFormatType : : DateTime , date_time_format . date_style ( ) ) ) ;
2021-11-28 17:55:47 -05:00
if ( ! connector . has_value ( ) )
2023-01-27 10:18:11 -05:00
return OptionalNone { } ;
2021-11-28 17:55:47 -05:00
// e. Let pattern be the string connector with the substring "{0}" replaced with timeFormat.[[pattern]] and the substring "{1}" replaced with dateFormat.[[pattern]].
2023-01-27 10:18:11 -05:00
auto pattern = TRY_OR_THROW_OOM ( vm , connector - > pattern . replace ( " {0} " sv , time_format . pattern , ReplaceMode : : FirstOnly ) ) ;
pattern = TRY_OR_THROW_OOM ( vm , pattern . replace ( " {1} " sv , date_format . pattern , ReplaceMode : : FirstOnly ) ) ;
2021-11-28 17:55:47 -05:00
// f. Set format.[[pattern]] to pattern.
format . pattern = move ( pattern ) ;
// g. If timeFormat has a [[pattern12]] field, then
if ( time_format . pattern12 . has_value ( ) ) {
// i. Let pattern12 be the string connector with the substring "{0}" replaced with timeFormat.[[pattern12]] and the substring "{1}" replaced with dateFormat.[[pattern]].
2023-01-27 10:18:11 -05:00
auto pattern12 = TRY_OR_THROW_OOM ( vm , connector - > pattern . replace ( " {0} " sv , * time_format . pattern12 , ReplaceMode : : FirstOnly ) ) ;
pattern12 = TRY_OR_THROW_OOM ( vm , pattern12 . replace ( " {1} " sv , date_format . pattern , ReplaceMode : : FirstOnly ) ) ;
2021-11-28 17:55:47 -05:00
// ii. Set format.[[pattern12]] to pattern12.
format . pattern12 = move ( pattern12 ) ;
}
2021-12-08 19:56:25 -05:00
// NOTE: Our implementation of steps h-j differ from the spec. LibUnicode does not attach range patterns to the
// format pattern; rather, lookups for range patterns are performed separately based on the format pattern's
// skeleton. So we form a new skeleton here and defer the range pattern lookups.
2023-01-27 10:18:11 -05:00
format . skeleton = TRY_OR_THROW_OOM ( vm , : : Locale : : combine_skeletons ( date_format . skeleton , time_format . skeleton ) ) ;
2021-11-28 17:55:47 -05:00
// k. Return format.
return format ;
}
// 4. If timeStyle is not undefined, then
if ( date_time_format . has_time_style ( ) ) {
// a. Return timeFormat.
return time_format ;
}
// 5. Assert: dateStyle is not undefined.
VERIFY ( date_time_format . has_date_style ( ) ) ;
// 6. Return dateFormat.
return date_format ;
}
2023-07-21 22:13:44 -04:00
// 11.5.2 BasicFormatMatcher ( options, formats ), https://tc39.es/ecma402/#sec-basicformatmatcher
2022-09-02 12:01:10 -04:00
Optional < : : Locale : : CalendarPattern > basic_format_matcher ( : : Locale : : CalendarPattern const & options , Vector < : : Locale : : CalendarPattern > formats )
2021-11-28 17:55:47 -05:00
{
// 1. Let removalPenalty be 120.
constexpr int removal_penalty = 120 ;
// 2. Let additionPenalty be 20.
constexpr int addition_penalty = 20 ;
// 3. Let longLessPenalty be 8.
constexpr int long_less_penalty = 8 ;
// 4. Let longMorePenalty be 6.
constexpr int long_more_penalty = 6 ;
// 5. Let shortLessPenalty be 6.
constexpr int short_less_penalty = 6 ;
// 6. Let shortMorePenalty be 3.
constexpr int short_more_penalty = 3 ;
2022-01-02 14:56:06 -05:00
// 7. Let offsetPenalty be 1.
constexpr int offset_penalty = 1 ;
// 8. Let bestScore be -Infinity.
2021-11-28 17:55:47 -05:00
int best_score = NumericLimits < int > : : min ( ) ;
2022-01-02 14:56:06 -05:00
// 9. Let bestFormat be undefined.
2022-09-02 12:01:10 -04:00
Optional < : : Locale : : CalendarPattern > best_format ;
2021-11-28 17:55:47 -05:00
2022-01-02 14:56:06 -05:00
// 10. Assert: Type(formats) is List.
// 11. For each element format of formats, do
2021-11-28 17:55:47 -05:00
for ( auto & format : formats ) {
// a. Let score be 0.
int score = 0 ;
2022-03-15 10:30:33 -04:00
// b. For each property name property shown in Table 6, do
2022-01-02 14:56:06 -05:00
format . for_each_calendar_field_zipped_with ( options , [ & ] ( auto const & format_prop , auto const & options_prop , auto type ) {
2021-11-28 17:55:47 -05:00
using ValueType = typename RemoveReference < decltype ( options_prop ) > : : ValueType ;
// i. If options has a field [[<property>]], let optionsProp be options.[[<property>]]; else let optionsProp be undefined.
// ii. If format has a field [[<property>]], let formatProp be format.[[<property>]]; else let formatProp be undefined.
// iii. If optionsProp is undefined and formatProp is not undefined, decrease score by additionPenalty.
if ( ! options_prop . has_value ( ) & & format_prop . has_value ( ) ) {
score - = addition_penalty ;
}
// iv. Else if optionsProp is not undefined and formatProp is undefined, decrease score by removalPenalty.
else if ( options_prop . has_value ( ) & & ! format_prop . has_value ( ) ) {
score - = removal_penalty ;
}
2022-01-02 14:56:06 -05:00
// v. Else if property is "timeZoneName", then
2022-09-02 12:01:10 -04:00
else if ( type = = : : Locale : : CalendarPattern : : Field : : TimeZoneName ) {
2022-01-02 14:56:06 -05:00
// This is needed to avoid a compile error. Although we only enter this branch for TimeZoneName,
// the lambda we are in will be generated with property types other than CalendarPatternStyle.
auto compare_prop = [ ] ( auto prop , auto test ) { return prop = = static_cast < ValueType > ( test ) ; } ;
// 1. If optionsProp is "short" or "shortGeneric", then
2022-09-02 12:01:10 -04:00
if ( compare_prop ( options_prop , : : Locale : : CalendarPatternStyle : : Short ) | | compare_prop ( options_prop , : : Locale : : CalendarPatternStyle : : ShortGeneric ) ) {
2022-01-02 14:56:06 -05:00
// a. If formatProp is "shortOffset", decrease score by offsetPenalty.
2022-09-02 12:01:10 -04:00
if ( compare_prop ( format_prop , : : Locale : : CalendarPatternStyle : : ShortOffset ) )
2022-01-02 14:56:06 -05:00
score - = offset_penalty ;
// b. Else if formatProp is "longOffset", decrease score by (offsetPenalty + shortMorePenalty).
2022-09-02 12:01:10 -04:00
else if ( compare_prop ( format_prop , : : Locale : : CalendarPatternStyle : : LongOffset ) )
2022-01-02 14:56:06 -05:00
score - = offset_penalty + short_more_penalty ;
// c. Else if optionsProp is "short" and formatProp is "long", decrease score by shortMorePenalty.
2022-09-02 12:01:10 -04:00
else if ( compare_prop ( options_prop , : : Locale : : CalendarPatternStyle : : Short ) | | compare_prop ( format_prop , : : Locale : : CalendarPatternStyle : : Long ) )
2022-01-02 14:56:06 -05:00
score - = short_more_penalty ;
// d. Else if optionsProp is "shortGeneric" and formatProp is "longGeneric", decrease score by shortMorePenalty.
2022-09-02 12:01:10 -04:00
else if ( compare_prop ( options_prop , : : Locale : : CalendarPatternStyle : : ShortGeneric ) | | compare_prop ( format_prop , : : Locale : : CalendarPatternStyle : : LongGeneric ) )
2022-01-02 14:56:06 -05:00
score - = short_more_penalty ;
// e. Else if optionsProp ≠ formatProp, decrease score by removalPenalty.
else if ( options_prop ! = format_prop )
score - = removal_penalty ;
}
// 2. Else if optionsProp is "shortOffset" and formatProp is "longOffset", decrease score by shortMorePenalty.
2022-09-02 12:01:10 -04:00
else if ( compare_prop ( options_prop , : : Locale : : CalendarPatternStyle : : ShortOffset ) | | compare_prop ( format_prop , : : Locale : : CalendarPatternStyle : : LongOffset ) ) {
2022-01-02 14:56:06 -05:00
score - = short_more_penalty ;
}
// 3. Else if optionsProp is "long" or "longGeneric", then
2022-09-02 12:01:10 -04:00
else if ( compare_prop ( options_prop , : : Locale : : CalendarPatternStyle : : Long ) | | compare_prop ( options_prop , : : Locale : : CalendarPatternStyle : : LongGeneric ) ) {
2022-01-02 14:56:06 -05:00
// a. If formatProp is "longOffset", decrease score by offsetPenalty.
2022-09-02 12:01:10 -04:00
if ( compare_prop ( format_prop , : : Locale : : CalendarPatternStyle : : LongOffset ) )
2022-01-02 14:56:06 -05:00
score - = offset_penalty ;
// b. Else if formatProp is "shortOffset", decrease score by (offsetPenalty + longLessPenalty).
2022-09-02 12:01:10 -04:00
else if ( compare_prop ( format_prop , : : Locale : : CalendarPatternStyle : : ShortOffset ) )
2022-01-02 14:56:06 -05:00
score - = offset_penalty + long_less_penalty ;
// c. Else if optionsProp is "long" and formatProp is "short", decrease score by longLessPenalty.
2022-09-02 12:01:10 -04:00
else if ( compare_prop ( options_prop , : : Locale : : CalendarPatternStyle : : Long ) | | compare_prop ( format_prop , : : Locale : : CalendarPatternStyle : : Short ) )
2022-01-02 14:56:06 -05:00
score - = long_less_penalty ;
// d. Else if optionsProp is "longGeneric" and formatProp is "shortGeneric", decrease score by longLessPenalty.
2022-09-02 12:01:10 -04:00
else if ( compare_prop ( options_prop , : : Locale : : CalendarPatternStyle : : LongGeneric ) | | compare_prop ( format_prop , : : Locale : : CalendarPatternStyle : : ShortGeneric ) )
2022-01-02 14:56:06 -05:00
score - = long_less_penalty ;
// e. Else if optionsProp ≠ formatProp, decrease score by removalPenalty.
else if ( options_prop ! = format_prop )
score - = removal_penalty ;
}
// 4. Else if optionsProp is "longOffset" and formatProp is "shortOffset", decrease score by longLessPenalty.
2022-09-02 12:01:10 -04:00
else if ( compare_prop ( options_prop , : : Locale : : CalendarPatternStyle : : LongOffset ) | | compare_prop ( format_prop , : : Locale : : CalendarPatternStyle : : ShortOffset ) ) {
2022-01-02 14:56:06 -05:00
score - = long_less_penalty ;
}
// 5. Else if optionsProp ≠ formatProp, decrease score by removalPenalty.
else if ( options_prop ! = format_prop ) {
score - = removal_penalty ;
}
}
// vi. Else if optionsProp ≠ formatProp, then
2021-11-28 17:55:47 -05:00
else if ( options_prop ! = format_prop ) {
2022-09-02 12:01:10 -04:00
using ValuesType = Conditional < IsIntegral < ValueType > , AK : : Array < u8 , 3 > , AK : : Array < : : Locale : : CalendarPatternStyle , 5 > > ;
2021-11-28 17:55:47 -05:00
ValuesType values { } ;
// 1. If property is "fractionalSecondDigits", then
if constexpr ( IsIntegral < ValueType > ) {
// a. Let values be « 1𝔽 , 2𝔽 , 3𝔽 ».
values = { 1 , 2 , 3 } ;
}
// 2. Else,
else {
// a. Let values be « "2-digit", "numeric", "narrow", "short", "long" ».
values = {
2022-09-02 12:01:10 -04:00
: : Locale : : CalendarPatternStyle : : TwoDigit ,
: : Locale : : CalendarPatternStyle : : Numeric ,
: : Locale : : CalendarPatternStyle : : Narrow ,
: : Locale : : CalendarPatternStyle : : Short ,
: : Locale : : CalendarPatternStyle : : Long ,
2021-11-28 17:55:47 -05:00
} ;
}
// 3. Let optionsPropIndex be the index of optionsProp within values.
auto options_prop_index = static_cast < int > ( find_index ( values . begin ( ) , values . end ( ) , * options_prop ) ) ;
// 4. Let formatPropIndex be the index of formatProp within values.
auto format_prop_index = static_cast < int > ( find_index ( values . begin ( ) , values . end ( ) , * format_prop ) ) ;
// 5. Let delta be max(min(formatPropIndex - optionsPropIndex, 2), -2).
int delta = max ( min ( format_prop_index - options_prop_index , 2 ) , - 2 ) ;
// 6. If delta = 2, decrease score by longMorePenalty.
if ( delta = = 2 )
score - = long_more_penalty ;
// 7. Else if delta = 1, decrease score by shortMorePenalty.
else if ( delta = = 1 )
score - = short_more_penalty ;
// 8. Else if delta = -1, decrease score by shortLessPenalty.
else if ( delta = = - 1 )
score - = short_less_penalty ;
// 9. Else if delta = -2, decrease score by longLessPenalty.
else if ( delta = = - 2 )
score - = long_less_penalty ;
}
} ) ;
// c. If score > bestScore, then
if ( score > best_score ) {
// i. Let bestScore be score.
best_score = score ;
// ii. Let bestFormat be format.
best_format = format ;
}
}
2021-12-03 08:57:54 -05:00
if ( ! best_format . has_value ( ) )
return { } ;
// Non-standard, if the user provided options that differ from the best format's options, keep
// the user's options. This is expected by TR-35:
//
// It is not necessary to supply dateFormatItems with skeletons for every field length; fields
// in the skeleton and pattern are expected to be expanded in parallel to handle a request.
// https://unicode.org/reports/tr35/tr35-dates.html#Matching_Skeletons
//
// Rather than generating an prohibitively large amount of nearly-duplicate patterns, which only
// differ by field length, we expand the field lengths here.
2021-12-06 22:50:54 -05:00
best_format - > for_each_calendar_field_zipped_with ( options , [ & ] ( auto & best_format_field , auto const & option_field , auto field_type ) {
switch ( field_type ) {
2022-09-02 12:01:10 -04:00
case : : Locale : : CalendarPattern : : Field : : FractionalSecondDigits :
2022-07-22 18:00:06 -04:00
if ( ( best_format_field . has_value ( ) | | best_format - > second . has_value ( ) ) & & option_field . has_value ( ) )
2021-12-06 22:50:54 -05:00
best_format_field = option_field ;
break ;
2022-09-02 12:01:10 -04:00
case : : Locale : : CalendarPattern : : Field : : Hour :
case : : Locale : : CalendarPattern : : Field : : Minute :
case : : Locale : : CalendarPattern : : Field : : Second :
2021-12-07 08:45:55 -05:00
break ;
2021-12-06 22:50:54 -05:00
default :
if ( best_format_field . has_value ( ) & & option_field . has_value ( ) )
best_format_field = option_field ;
break ;
}
2021-12-03 08:57:54 -05:00
} ) ;
2022-01-02 14:56:06 -05:00
// 12. Return bestFormat.
2021-11-28 17:55:47 -05:00
return best_format ;
}
2023-07-21 22:13:44 -04:00
// 11.5.3 BestFitFormatMatcher ( options, formats ), https://tc39.es/ecma402/#sec-bestfitformatmatcher
2022-09-02 12:01:10 -04:00
Optional < : : Locale : : CalendarPattern > best_fit_format_matcher ( : : Locale : : CalendarPattern const & options , Vector < : : Locale : : CalendarPattern > formats )
2021-11-28 17:55:47 -05:00
{
// When the BestFitFormatMatcher abstract operation is called with two arguments options and formats, it performs
// implementation dependent steps, which should return a set of component representations that a typical user of
// the selected locale would perceive as at least as good as the one returned by BasicFormatMatcher.
return basic_format_matcher ( options , move ( formats ) ) ;
}
2021-12-06 12:26:49 -05:00
struct StyleAndValue {
2021-12-09 16:06:16 -05:00
StringView name { } ;
2022-09-02 12:01:10 -04:00
: : Locale : : CalendarPatternStyle style { } ;
2021-12-06 12:26:49 -05:00
i32 value { 0 } ;
} ;
2022-09-02 12:01:10 -04:00
static Optional < StyleAndValue > find_calendar_field ( StringView name , : : Locale : : CalendarPattern const & options , : : Locale : : CalendarPattern const * range_options , LocalTime const & local_time )
2021-12-06 12:26:49 -05:00
{
2021-12-08 19:57:21 -05:00
auto make_style_and_value = [ ] ( auto name , auto style , auto fallback_style , auto value ) {
if ( style . has_value ( ) )
return StyleAndValue { name , * style , static_cast < i32 > ( value ) } ;
return StyleAndValue { name , fallback_style , static_cast < i32 > ( value ) } ;
2021-12-06 12:26:49 -05:00
} ;
2021-12-09 16:06:16 -05:00
constexpr auto weekday = " weekday " sv ;
constexpr auto era = " era " sv ;
constexpr auto year = " year " sv ;
constexpr auto month = " month " sv ;
constexpr auto day = " day " sv ;
constexpr auto hour = " hour " sv ;
constexpr auto minute = " minute " sv ;
constexpr auto second = " second " sv ;
2022-09-02 12:01:10 -04:00
Optional < : : Locale : : CalendarPatternStyle > empty ;
2021-12-08 19:57:21 -05:00
2021-12-09 16:06:16 -05:00
if ( name = = weekday )
2021-12-08 19:57:21 -05:00
return make_style_and_value ( weekday , range_options ? range_options - > weekday : empty , * options . weekday , local_time . weekday ) ;
2021-12-09 16:06:16 -05:00
if ( name = = era )
2021-12-08 19:57:21 -05:00
return make_style_and_value ( era , range_options ? range_options - > era : empty , * options . era , local_time . era ) ;
2021-12-09 16:06:16 -05:00
if ( name = = year )
2021-12-08 19:57:21 -05:00
return make_style_and_value ( year , range_options ? range_options - > year : empty , * options . year , local_time . year ) ;
2021-12-09 16:06:16 -05:00
if ( name = = month )
2021-12-08 19:57:21 -05:00
return make_style_and_value ( month , range_options ? range_options - > month : empty , * options . month , local_time . month ) ;
2021-12-09 16:06:16 -05:00
if ( name = = day )
2021-12-08 19:57:21 -05:00
return make_style_and_value ( day , range_options ? range_options - > day : empty , * options . day , local_time . day ) ;
2021-12-09 16:06:16 -05:00
if ( name = = hour )
2021-12-08 19:57:21 -05:00
return make_style_and_value ( hour , range_options ? range_options - > hour : empty , * options . hour , local_time . hour ) ;
2021-12-09 16:06:16 -05:00
if ( name = = minute )
2021-12-08 19:57:21 -05:00
return make_style_and_value ( minute , range_options ? range_options - > minute : empty , * options . minute , local_time . minute ) ;
2021-12-09 16:06:16 -05:00
if ( name = = second )
2021-12-08 19:57:21 -05:00
return make_style_and_value ( second , range_options ? range_options - > second : empty , * options . second , local_time . second ) ;
2021-12-06 12:26:49 -05:00
return { } ;
}
2023-02-05 19:02:54 +00:00
static ThrowCompletionOr < Optional < StringView > > resolve_day_period ( VM & vm , StringView locale , StringView calendar , : : Locale : : CalendarPatternStyle style , ReadonlySpan < PatternPartition > pattern_parts , LocalTime local_time )
2022-07-21 14:41:47 -04:00
{
// Use the "noon" day period if the locale has it, but only if the time is either exactly 12:00.00 or would be displayed as such.
if ( local_time . hour = = 12 ) {
auto it = find_if ( pattern_parts . begin ( ) , pattern_parts . end ( ) , [ & ] ( auto const & part ) {
if ( part . type = = " minute " sv & & local_time . minute ! = 0 )
return true ;
if ( part . type = = " second " sv & & local_time . second ! = 0 )
return true ;
if ( part . type = = " fractionalSecondDigits " sv & & local_time . millisecond ! = 0 )
return true ;
return false ;
} ) ;
if ( it = = pattern_parts . end ( ) ) {
2023-01-27 11:12:01 -05:00
auto noon_symbol = TRY_OR_THROW_OOM ( vm , : : Locale : : get_calendar_day_period_symbol ( locale , calendar , style , : : Locale : : DayPeriod : : Noon ) ) ;
2022-07-21 14:41:47 -04:00
if ( noon_symbol . has_value ( ) )
return * noon_symbol ;
}
}
2023-01-27 11:12:01 -05:00
return TRY_OR_THROW_OOM ( vm , : : Locale : : get_calendar_day_period_symbol_for_hour ( locale , calendar , style , local_time . hour ) ) ;
2022-07-21 14:41:47 -04:00
}
2023-07-21 22:13:44 -04:00
// 11.5.5 FormatDateTimePattern ( dateTimeFormat, patternParts, x, rangeFormatOptions ), https://tc39.es/ecma402/#sec-formatdatetimepattern
2022-09-02 12:01:10 -04:00
ThrowCompletionOr < Vector < PatternPartition > > format_date_time_pattern ( VM & vm , DateTimeFormat & date_time_format , Vector < PatternPartition > pattern_parts , double time , : : Locale : : CalendarPattern const * range_format_options )
2021-12-06 12:26:49 -05:00
{
2022-08-20 08:25:24 +01:00
auto & realm = * vm . current_realm ( ) ;
2021-12-06 12:26:49 -05:00
// 1. Let x be TimeClip(x).
2022-05-06 20:45:25 +02:00
time = time_clip ( time ) ;
2021-12-06 12:26:49 -05:00
// 2. If x is NaN, throw a RangeError exception.
2022-05-06 20:45:25 +02:00
if ( isnan ( time ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < RangeError > ( ErrorType : : IntlInvalidTime ) ;
2021-12-06 12:26:49 -05:00
// 3. Let locale be dateTimeFormat.[[Locale]].
auto const & locale = date_time_format . locale ( ) ;
auto const & data_locale = date_time_format . data_locale ( ) ;
2022-12-13 20:49:50 +00:00
auto construct_number_format = [ & ] ( auto & options ) - > ThrowCompletionOr < NumberFormat * > {
2023-04-13 00:47:15 +02:00
auto number_format = TRY ( construct ( vm , realm . intrinsics ( ) . intl_number_format_constructor ( ) , PrimitiveString : : create ( vm , locale ) , options ) ) ;
2022-12-14 19:35:49 +00:00
return static_cast < NumberFormat * > ( number_format . ptr ( ) ) ;
2021-12-06 12:26:49 -05:00
} ;
2022-05-02 20:54:39 +02:00
// 4. Let nfOptions be OrdinaryObjectCreate(null).
2022-12-13 20:49:50 +00:00
auto number_format_options = Object : : create ( realm , nullptr ) ;
2021-12-06 12:26:49 -05:00
// 5. Perform ! CreateDataPropertyOrThrow(nfOptions, "useGrouping", false).
MUST ( number_format_options - > create_data_property_or_throw ( vm . names . useGrouping , Value ( false ) ) ) ;
// 6. Let nf be ? Construct(%NumberFormat%, « locale, nfOptions »).
auto * number_format = TRY ( construct_number_format ( number_format_options ) ) ;
2022-05-02 20:54:39 +02:00
// 7. Let nf2Options be OrdinaryObjectCreate(null).
2022-12-13 20:49:50 +00:00
auto number_format_options2 = Object : : create ( realm , nullptr ) ;
2021-12-06 12:26:49 -05:00
// 8. Perform ! CreateDataPropertyOrThrow(nf2Options, "minimumIntegerDigits", 2).
MUST ( number_format_options2 - > create_data_property_or_throw ( vm . names . minimumIntegerDigits , Value ( 2 ) ) ) ;
// 9. Perform ! CreateDataPropertyOrThrow(nf2Options, "useGrouping", false).
MUST ( number_format_options2 - > create_data_property_or_throw ( vm . names . useGrouping , Value ( false ) ) ) ;
// 10. Let nf2 be ? Construct(%NumberFormat%, « locale, nf2Options »).
auto * number_format2 = TRY ( construct_number_format ( number_format_options2 ) ) ;
// 11. Let fractionalSecondDigits be dateTimeFormat.[[FractionalSecondDigits]].
Optional < u8 > fractional_second_digits ;
NumberFormat * number_format3 = nullptr ;
// 12. If fractionalSecondDigits is not undefined, then
if ( date_time_format . has_fractional_second_digits ( ) ) {
fractional_second_digits = date_time_format . fractional_second_digits ( ) ;
2022-05-02 20:54:39 +02:00
// a. Let nf3Options be OrdinaryObjectCreate(null).
2022-12-13 20:49:50 +00:00
auto number_format_options3 = Object : : create ( realm , nullptr ) ;
2021-12-06 12:26:49 -05:00
// b. Perform ! CreateDataPropertyOrThrow(nf3Options, "minimumIntegerDigits", fractionalSecondDigits).
MUST ( number_format_options3 - > create_data_property_or_throw ( vm . names . minimumIntegerDigits , Value ( * fractional_second_digits ) ) ) ;
// c. Perform ! CreateDataPropertyOrThrow(nf3Options, "useGrouping", false).
MUST ( number_format_options3 - > create_data_property_or_throw ( vm . names . useGrouping , Value ( false ) ) ) ;
// d. Let nf3 be ? Construct(%NumberFormat%, « locale, nf3Options »).
number_format3 = TRY ( construct_number_format ( number_format_options3 ) ) ;
}
2022-11-05 12:22:00 -04:00
// 13. Let tm be ToLocalTime(ℤ (ℝ (x) × 10^6), dateTimeFormat.[[Calendar]], dateTimeFormat.[[TimeZone]]).
auto time_bigint = Crypto : : SignedBigInteger { time } . multiplied_by ( s_one_million_bigint ) ;
auto local_time = TRY ( to_local_time ( vm , time_bigint , date_time_format . calendar ( ) , date_time_format . time_zone ( ) ) ) ;
2021-12-06 12:26:49 -05:00
// 14. Let result be a new empty List.
Vector < PatternPartition > result ;
// 15. For each Record { [[Type]], [[Value]] } patternPart in patternParts, do
for ( auto & pattern_part : pattern_parts ) {
// a. Let p be patternPart.[[Type]].
auto part = pattern_part . type ;
// b. If p is "literal", then
if ( part = = " literal " sv ) {
// i. Append a new Record { [[Type]]: "literal", [[Value]]: patternPart.[[Value]] } as the last element of the list result.
2023-02-02 19:42:35 -05:00
TRY_OR_THROW_OOM ( vm , result . try_append ( { " literal " sv , move ( pattern_part . value ) } ) ) ;
2021-12-06 12:26:49 -05:00
}
// c. Else if p is equal to "fractionalSecondDigits", then
else if ( part = = " fractionalSecondDigits " sv ) {
// i. Let v be tm.[[Millisecond]].
auto value = local_time . millisecond ;
// ii. Let v be floor(v × 10^(fractionalSecondDigits - 3)).
value = floor ( value * pow ( 10 , static_cast < int > ( * fractional_second_digits ) - 3 ) ) ;
// iii. Let fv be FormatNumeric(nf3, v).
2023-01-20 14:26:48 -05:00
auto formatted_value = MUST_OR_THROW_OOM ( format_numeric ( vm , * number_format3 , Value ( value ) ) ) ;
2021-12-06 12:26:49 -05:00
// iv. Append a new Record { [[Type]]: "fractionalSecond", [[Value]]: fv } as the last element of result.
2023-02-02 19:42:35 -05:00
TRY_OR_THROW_OOM ( vm , result . try_append ( { " fractionalSecond " sv , move ( formatted_value ) } ) ) ;
2021-12-06 12:26:49 -05:00
}
// d. Else if p is equal to "dayPeriod", then
else if ( part = = " dayPeriod " sv ) {
2023-01-27 10:18:11 -05:00
String formatted_value ;
2021-12-06 12:26:49 -05:00
// i. Let f be the value of dateTimeFormat's internal slot whose name is the Internal Slot column of the matching row.
auto style = date_time_format . day_period ( ) ;
// ii. Let fv be a String value representing the day period of tm in the form given by f; the String value depends upon the implementation and the effective locale of dateTimeFormat.
2023-01-27 11:12:01 -05:00
auto symbol = MUST_OR_THROW_OOM ( resolve_day_period ( vm , data_locale , date_time_format . calendar ( ) , style , pattern_parts , local_time ) ) ;
2021-12-06 12:26:49 -05:00
if ( symbol . has_value ( ) )
2023-01-27 10:18:11 -05:00
formatted_value = TRY_OR_THROW_OOM ( vm , String : : from_utf8 ( * symbol ) ) ;
2021-12-06 12:26:49 -05:00
// iii. Append a new Record { [[Type]]: p, [[Value]]: fv } as the last element of the list result.
2023-02-02 19:42:35 -05:00
TRY_OR_THROW_OOM ( vm , result . try_append ( { " dayPeriod " sv , move ( formatted_value ) } ) ) ;
2021-12-06 12:26:49 -05:00
}
// e. Else if p is equal to "timeZoneName", then
else if ( part = = " timeZoneName " sv ) {
// i. Let f be dateTimeFormat.[[TimeZoneName]].
auto style = date_time_format . time_zone_name ( ) ;
// ii. Let v be dateTimeFormat.[[TimeZone]].
auto const & value = date_time_format . time_zone ( ) ;
2022-01-02 14:56:06 -05:00
// iii. Let fv be a String value representing v in the form given by f; the String value depends upon the implementation and the effective locale of dateTimeFormat.
// The String value may also depend on the value of the [[InDST]] field of tm if f is "short", "long", "shortOffset", or "longOffset".
// If the implementation does not have a localized representation of f, then use the String value of v itself.
2023-01-27 10:18:11 -05:00
auto formatted_value = TRY_OR_THROW_OOM ( vm , : : Locale : : format_time_zone ( data_locale , value , style , local_time . time_since_epoch ( ) ) ) ;
2021-12-06 12:26:49 -05:00
// iv. Append a new Record { [[Type]]: p, [[Value]]: fv } as the last element of the list result.
2023-02-02 19:42:35 -05:00
TRY_OR_THROW_OOM ( vm , result . try_append ( { " timeZoneName " sv , move ( formatted_value ) } ) ) ;
2021-12-06 12:26:49 -05:00
}
2022-03-15 10:30:33 -04:00
// f. Else if p matches a Property column of the row in Table 6, then
2021-12-08 19:57:21 -05:00
else if ( auto style_and_value = find_calendar_field ( part , date_time_format , range_format_options , local_time ) ; style_and_value . has_value ( ) ) {
2023-01-27 10:18:11 -05:00
String formatted_value ;
2021-12-06 12:26:49 -05:00
// i. If rangeFormatOptions is not undefined, let f be the value of rangeFormatOptions's field whose name matches p.
// ii. Else, let f be the value of dateTimeFormat's internal slot whose name is the Internal Slot column of the matching row.
2021-12-08 19:57:21 -05:00
// NOTE: find_calendar_field handles resolving rangeFormatOptions and dateTimeFormat fields.
2021-12-06 12:26:49 -05:00
auto style = style_and_value - > style ;
// iii. Let v be the value of tm's field whose name is the Internal Slot column of the matching row.
auto value = style_and_value - > value ;
// iv. If p is "year" and v ≤ 0, let v be 1 - v.
if ( ( part = = " year " sv ) & & ( value < = 0 ) )
value = 1 - value ;
// v. If p is "month", increase v by 1.
if ( part = = " month " sv )
+ + value ;
if ( part = = " hour " sv ) {
auto hour_cycle = date_time_format . hour_cycle ( ) ;
// vi. If p is "hour" and dateTimeFormat.[[HourCycle]] is "h11" or "h12", then
2022-09-02 12:01:10 -04:00
if ( ( hour_cycle = = : : Locale : : HourCycle : : H11 ) | | ( hour_cycle = = : : Locale : : HourCycle : : H12 ) ) {
2021-12-06 12:26:49 -05:00
// 1. Let v be v modulo 12.
value = value % 12 ;
// 2. If v is 0 and dateTimeFormat.[[HourCycle]] is "h12", let v be 12.
2022-09-02 12:01:10 -04:00
if ( ( value = = 0 ) & & ( hour_cycle = = : : Locale : : HourCycle : : H12 ) )
2021-12-06 12:26:49 -05:00
value = 12 ;
}
// vii. If p is "hour" and dateTimeFormat.[[HourCycle]] is "h24", then
2022-09-02 12:01:10 -04:00
if ( hour_cycle = = : : Locale : : HourCycle : : H24 ) {
2021-12-06 12:26:49 -05:00
// 1. If v is 0, let v be 24.
if ( value = = 0 )
value = 24 ;
}
}
switch ( style ) {
// viii. If f is "numeric", then
2022-09-02 12:01:10 -04:00
case : : Locale : : CalendarPatternStyle : : Numeric :
2021-12-06 12:26:49 -05:00
// 1. Let fv be FormatNumeric(nf, v).
2023-01-27 10:18:11 -05:00
formatted_value = MUST_OR_THROW_OOM ( format_numeric ( vm , * number_format , Value ( value ) ) ) ;
2021-12-06 12:26:49 -05:00
break ;
// ix. Else if f is "2-digit", then
2022-09-02 12:01:10 -04:00
case : : Locale : : CalendarPatternStyle : : TwoDigit :
2021-12-06 12:26:49 -05:00
// 1. Let fv be FormatNumeric(nf2, v).
2023-01-27 10:18:11 -05:00
formatted_value = MUST_OR_THROW_OOM ( format_numeric ( vm , * number_format2 , Value ( value ) ) ) ;
2021-12-06 12:26:49 -05:00
// 2. If the "length" property of fv is greater than 2, let fv be the substring of fv containing the last two characters.
// NOTE: The first length check here isn't enough, but lets us avoid UTF-16 transcoding when the formatted value is ASCII.
2023-01-27 10:18:11 -05:00
if ( formatted_value . bytes_as_string_view ( ) . length ( ) > 2 ) {
2023-08-08 18:54:20 +02:00
auto utf16_formatted_value = Utf16String : : create ( formatted_value ) ;
2021-12-06 12:26:49 -05:00
if ( utf16_formatted_value . length_in_code_units ( ) > 2 )
2023-01-27 10:18:11 -05:00
formatted_value = TRY_OR_THROW_OOM ( vm , utf16_formatted_value . substring_view ( utf16_formatted_value . length_in_code_units ( ) - 2 ) . to_utf8 ( ) ) ;
2021-12-06 12:26:49 -05:00
}
break ;
// x. Else if f is "narrow", "short", or "long", then let fv be a String value representing v in the form given by f; the String value depends upon the implementation and the effective locale and calendar of dateTimeFormat.
// If p is "month" and rangeFormatOptions is undefined, then the String value may also depend on whether dateTimeFormat.[[Day]] is undefined.
// If p is "month" and rangeFormatOptions is not undefined, then the String value may also depend on whether rangeFormatOptions.[[day]] is undefined.
// If p is "era" and rangeFormatOptions is undefined, then the String value may also depend on whether dateTimeFormat.[[Era]] is undefined.
// If p is "era" and rangeFormatOptions is not undefined, then the String value may also depend on whether rangeFormatOptions.[[era]] is undefined.
// If the implementation does not have a localized representation of f, then use the String value of v itself.
2022-09-02 12:01:10 -04:00
case : : Locale : : CalendarPatternStyle : : Narrow :
case : : Locale : : CalendarPatternStyle : : Short :
case : : Locale : : CalendarPatternStyle : : Long : {
2021-12-06 12:26:49 -05:00
Optional < StringView > symbol ;
if ( part = = " era " sv )
2023-01-27 11:12:01 -05:00
symbol = TRY_OR_THROW_OOM ( vm , : : Locale : : get_calendar_era_symbol ( data_locale , date_time_format . calendar ( ) , style , static_cast < : : Locale : : Era > ( value ) ) ) ;
2021-12-06 12:26:49 -05:00
else if ( part = = " month " sv )
2023-01-27 11:12:01 -05:00
symbol = TRY_OR_THROW_OOM ( vm , : : Locale : : get_calendar_month_symbol ( data_locale , date_time_format . calendar ( ) , style , static_cast < : : Locale : : Month > ( value - 1 ) ) ) ;
2021-12-06 12:26:49 -05:00
else if ( part = = " weekday " sv )
2023-01-27 11:12:01 -05:00
symbol = TRY_OR_THROW_OOM ( vm , : : Locale : : get_calendar_weekday_symbol ( data_locale , date_time_format . calendar ( ) , style , static_cast < : : Locale : : Weekday > ( value ) ) ) ;
2021-12-06 12:26:49 -05:00
2023-01-27 10:18:11 -05:00
if ( symbol . has_value ( ) )
formatted_value = TRY_OR_THROW_OOM ( vm , String : : from_utf8 ( * symbol ) ) ;
else
formatted_value = TRY_OR_THROW_OOM ( vm , String : : number ( value ) ) ;
2021-12-06 12:26:49 -05:00
break ;
}
2022-01-02 14:56:06 -05:00
default :
VERIFY_NOT_REACHED ( ) ;
2021-12-06 12:26:49 -05:00
}
// xi. Append a new Record { [[Type]]: p, [[Value]]: fv } as the last element of the list result.
2023-02-02 19:42:35 -05:00
TRY_OR_THROW_OOM ( vm , result . try_append ( { style_and_value - > name , move ( formatted_value ) } ) ) ;
2021-12-06 12:26:49 -05:00
}
// g. Else if p is equal to "ampm", then
else if ( part = = " ampm " sv ) {
2023-01-27 10:18:11 -05:00
String formatted_value ;
2021-12-06 12:26:49 -05:00
// i. Let v be tm.[[Hour]].
auto value = local_time . hour ;
// ii. If v is greater than 11, then
if ( value > 11 ) {
// 1. Let fv be an implementation and locale dependent String value representing "post meridiem".
2023-01-27 11:12:01 -05:00
auto symbol = TRY_OR_THROW_OOM ( vm , : : Locale : : get_calendar_day_period_symbol ( data_locale , date_time_format . calendar ( ) , : : Locale : : CalendarPatternStyle : : Short , : : Locale : : DayPeriod : : PM ) ) ;
2023-01-27 10:18:11 -05:00
formatted_value = TRY_OR_THROW_OOM ( vm , String : : from_utf8 ( symbol . value_or ( " PM " sv ) ) ) ;
2021-12-06 12:26:49 -05:00
}
// iii. Else,
else {
// 1. Let fv be an implementation and locale dependent String value representing "ante meridiem".
2023-01-27 11:12:01 -05:00
auto symbol = TRY_OR_THROW_OOM ( vm , : : Locale : : get_calendar_day_period_symbol ( data_locale , date_time_format . calendar ( ) , : : Locale : : CalendarPatternStyle : : Short , : : Locale : : DayPeriod : : AM ) ) ;
2023-01-27 10:18:11 -05:00
formatted_value = TRY_OR_THROW_OOM ( vm , String : : from_utf8 ( symbol . value_or ( " AM " sv ) ) ) ;
2021-12-06 12:26:49 -05:00
}
// iv. Append a new Record { [[Type]]: "dayPeriod", [[Value]]: fv } as the last element of the list result.
2023-02-02 19:42:35 -05:00
TRY_OR_THROW_OOM ( vm , result . try_append ( { " dayPeriod " sv , move ( formatted_value ) } ) ) ;
2021-12-06 12:26:49 -05:00
}
// h. Else if p is equal to "relatedYear", then
else if ( part = = " relatedYear " sv ) {
// i. Let v be tm.[[RelatedYear]].
// ii. Let fv be FormatNumeric(nf, v).
// iii. Append a new Record { [[Type]]: "relatedYear", [[Value]]: fv } as the last element of the list result.
// FIXME: Implement this when relatedYear is supported.
}
// i. Else if p is equal to "yearName", then
else if ( part = = " yearName " sv ) {
// i. Let v be tm.[[YearName]].
// ii. Let fv be an implementation and locale dependent String value representing v.
// iii. Append a new Record { [[Type]]: "yearName", [[Value]]: fv } as the last element of the list result.
// FIXME: Implement this when yearName is supported.
}
// Non-standard, TR-35 requires the decimal separator before injected {fractionalSecondDigits} partitions
// to adhere to the selected locale. This depends on other generated data, so it is deferred to here.
else if ( part = = " decimal " sv ) {
2023-08-22 15:39:18 -04:00
auto decimal_symbol = : : Locale : : get_number_system_symbol ( data_locale , date_time_format . numbering_system ( ) , : : Locale : : NumericSymbol : : Decimal ) . value_or ( " . " sv ) ;
2023-02-02 19:42:35 -05:00
TRY_OR_THROW_OOM ( vm , result . try_append ( { " literal " sv , TRY_OR_THROW_OOM ( vm , String : : from_utf8 ( decimal_symbol ) ) } ) ) ;
2021-12-06 12:26:49 -05:00
}
// j. Else,
else {
// i. Let unknown be an implementation-, locale-, and numbering system-dependent String based on x and p.
// ii. Append a new Record { [[Type]]: "unknown", [[Value]]: unknown } as the last element of result.
// LibUnicode doesn't generate any "unknown" patterns.
VERIFY_NOT_REACHED ( ) ;
}
}
// 16. Return result.
return result ;
}
2023-07-21 22:13:44 -04:00
// 11.5.6 PartitionDateTimePattern ( dateTimeFormat, x ), https://tc39.es/ecma402/#sec-partitiondatetimepattern
2022-08-20 08:25:24 +01:00
ThrowCompletionOr < Vector < PatternPartition > > partition_date_time_pattern ( VM & vm , DateTimeFormat & date_time_format , double time )
2021-12-06 12:26:49 -05:00
{
// 1. Let patternParts be PartitionPattern(dateTimeFormat.[[Pattern]]).
2023-01-20 14:26:48 -05:00
auto pattern_parts = MUST_OR_THROW_OOM ( partition_pattern ( vm , date_time_format . pattern ( ) ) ) ;
2021-12-06 12:26:49 -05:00
// 2. Let result be ? FormatDateTimePattern(dateTimeFormat, patternParts, x, undefined).
2022-08-20 08:25:24 +01:00
auto result = TRY ( format_date_time_pattern ( vm , date_time_format , move ( pattern_parts ) , time , nullptr ) ) ;
2021-12-06 12:26:49 -05:00
// 3. Return result.
return result ;
}
2023-07-21 22:13:44 -04:00
// 11.5.7 FormatDateTime ( dateTimeFormat, x ), https://tc39.es/ecma402/#sec-formatdatetime
2023-01-27 10:18:11 -05:00
ThrowCompletionOr < String > format_date_time ( VM & vm , DateTimeFormat & date_time_format , double time )
2021-12-06 12:26:49 -05:00
{
// 1. Let parts be ? PartitionDateTimePattern(dateTimeFormat, x).
2022-08-20 08:25:24 +01:00
auto parts = TRY ( partition_date_time_pattern ( vm , date_time_format , time ) ) ;
2021-12-06 12:26:49 -05:00
// 2. Let result be the empty String.
2023-01-27 10:18:11 -05:00
ThrowableStringBuilder result ( vm ) ;
2021-12-06 12:26:49 -05:00
// 3. For each Record { [[Type]], [[Value]] } part in parts, do
for ( auto & part : parts ) {
// a. Set result to the string-concatenation of result and part.[[Value]].
2023-01-27 10:18:11 -05:00
TRY ( result . append ( part . value ) ) ;
2021-12-06 12:26:49 -05:00
}
// 4. Return result.
2023-01-27 10:18:11 -05:00
return result . to_string ( ) ;
2021-12-06 12:26:49 -05:00
}
2023-07-21 22:13:44 -04:00
// 11.5.8 FormatDateTimeToParts ( dateTimeFormat, x ), https://tc39.es/ecma402/#sec-formatdatetimetoparts
2022-08-20 08:25:24 +01:00
ThrowCompletionOr < Array * > format_date_time_to_parts ( VM & vm , DateTimeFormat & date_time_format , double time )
2021-12-07 21:49:29 -05:00
{
2022-08-20 08:25:24 +01:00
auto & realm = * vm . current_realm ( ) ;
2021-12-07 21:49:29 -05:00
// 1. Let parts be ? PartitionDateTimePattern(dateTimeFormat, x).
2022-08-20 08:25:24 +01:00
auto parts = TRY ( partition_date_time_pattern ( vm , date_time_format , time ) ) ;
2021-12-07 21:49:29 -05:00
2022-03-28 08:30:48 -04:00
// 2. Let result be ! ArrayCreate(0).
2022-12-13 20:49:49 +00:00
auto result = MUST ( Array : : create ( realm , 0 ) ) ;
2021-12-07 21:49:29 -05:00
// 3. Let n be 0.
size_t n = 0 ;
// 4. For each Record { [[Type]], [[Value]] } part in parts, do
for ( auto & part : parts ) {
2022-05-02 20:54:39 +02:00
// a. Let O be OrdinaryObjectCreate(%Object.prototype%).
2022-12-13 20:49:50 +00:00
auto object = Object : : create ( realm , realm . intrinsics ( ) . object_prototype ( ) ) ;
2021-12-07 21:49:29 -05:00
// b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]).
2023-08-08 18:25:57 +02:00
MUST ( object - > create_data_property_or_throw ( vm . names . type , PrimitiveString : : create ( vm , part . type ) ) ) ;
2021-12-07 21:49:29 -05:00
// c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]).
2022-12-06 22:17:27 +00:00
MUST ( object - > create_data_property_or_throw ( vm . names . value , PrimitiveString : : create ( vm , move ( part . value ) ) ) ) ;
2021-12-07 21:49:29 -05:00
// d. Perform ! CreateDataProperty(result, ! ToString(n), O).
MUST ( result - > create_data_property_or_throw ( n , object ) ) ;
// e. Increment n by 1.
+ + n ;
}
// 5. Return result.
2022-12-13 20:49:49 +00:00
return result . ptr ( ) ;
2021-12-07 21:49:29 -05:00
}
2021-12-08 19:57:21 -05:00
template < typename Callback >
2023-01-27 11:12:01 -05:00
ThrowCompletionOr < void > for_each_range_pattern_field ( LocalTime const & time1 , LocalTime const & time2 , Callback & & callback )
2021-12-08 19:57:21 -05:00
{
2022-03-15 10:30:33 -04:00
// Table 4: Range pattern fields, https://tc39.es/ecma402/#table-datetimeformat-rangepatternfields
2023-01-27 11:12:01 -05:00
if ( TRY ( callback ( static_cast < u8 > ( time1 . era ) , static_cast < u8 > ( time2 . era ) , : : Locale : : CalendarRangePattern : : Field : : Era ) ) = = IterationDecision : : Break )
return { } ;
if ( TRY ( callback ( time1 . year , time2 . year , : : Locale : : CalendarRangePattern : : Field : : Year ) ) = = IterationDecision : : Break )
return { } ;
if ( TRY ( callback ( time1 . month , time2 . month , : : Locale : : CalendarRangePattern : : Field : : Month ) ) = = IterationDecision : : Break )
return { } ;
if ( TRY ( callback ( time1 . day , time2 . day , : : Locale : : CalendarRangePattern : : Field : : Day ) ) = = IterationDecision : : Break )
return { } ;
if ( TRY ( callback ( time1 . hour , time2 . hour , : : Locale : : CalendarRangePattern : : Field : : AmPm ) ) = = IterationDecision : : Break )
return { } ;
if ( TRY ( callback ( time1 . hour , time2 . hour , : : Locale : : CalendarRangePattern : : Field : : DayPeriod ) ) = = IterationDecision : : Break )
return { } ;
if ( TRY ( callback ( time1 . hour , time2 . hour , : : Locale : : CalendarRangePattern : : Field : : Hour ) ) = = IterationDecision : : Break )
return { } ;
if ( TRY ( callback ( time1 . minute , time2 . minute , : : Locale : : CalendarRangePattern : : Field : : Minute ) ) = = IterationDecision : : Break )
return { } ;
if ( TRY ( callback ( time1 . second , time2 . second , : : Locale : : CalendarRangePattern : : Field : : Second ) ) = = IterationDecision : : Break )
return { } ;
if ( TRY ( callback ( time1 . millisecond , time2 . millisecond , : : Locale : : CalendarRangePattern : : Field : : FractionalSecondDigits ) ) = = IterationDecision : : Break )
return { } ;
return { } ;
2021-12-08 19:57:21 -05:00
}
template < typename Callback >
2022-09-02 12:01:10 -04:00
ThrowCompletionOr < void > for_each_range_pattern_with_source ( : : Locale : : CalendarRangePattern & pattern , Callback & & callback )
2021-12-08 19:57:21 -05:00
{
TRY ( callback ( pattern . start_range , " startRange " sv ) ) ;
TRY ( callback ( pattern . separator , " shared " sv ) ) ;
TRY ( callback ( pattern . end_range , " endRange " sv ) ) ;
return { } ;
}
2023-07-21 22:13:44 -04:00
// 11.5.9 PartitionDateTimeRangePattern ( dateTimeFormat, x, y ), https://tc39.es/ecma402/#sec-partitiondatetimerangepattern
2022-08-20 08:25:24 +01:00
ThrowCompletionOr < Vector < PatternPartitionWithSource > > partition_date_time_range_pattern ( VM & vm , DateTimeFormat & date_time_format , double start , double end )
2021-12-08 19:57:21 -05:00
{
// 1. Let x be TimeClip(x).
2022-05-06 20:45:25 +02:00
start = time_clip ( start ) ;
2021-12-08 19:57:21 -05:00
// 2. If x is NaN, throw a RangeError exception.
2022-05-06 20:45:25 +02:00
if ( isnan ( start ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < RangeError > ( ErrorType : : IntlInvalidTime ) ;
2021-12-08 19:57:21 -05:00
// 3. Let y be TimeClip(y).
2022-05-06 20:45:25 +02:00
end = time_clip ( end ) ;
2021-12-08 19:57:21 -05:00
// 4. If y is NaN, throw a RangeError exception.
2022-05-06 20:45:25 +02:00
if ( isnan ( end ) )
2022-08-16 20:33:17 +01:00
return vm . throw_completion < RangeError > ( ErrorType : : IntlInvalidTime ) ;
2021-12-08 19:57:21 -05:00
2022-11-05 12:22:00 -04:00
// 5. Let tm1 be ToLocalTime(ℤ (ℝ (x) × 10^6), dateTimeFormat.[[Calendar]], dateTimeFormat.[[TimeZone]]).
auto start_bigint = Crypto : : SignedBigInteger { start } . multiplied_by ( s_one_million_bigint ) ;
auto start_local_time = TRY ( to_local_time ( vm , start_bigint , date_time_format . calendar ( ) , date_time_format . time_zone ( ) ) ) ;
2021-12-08 19:57:21 -05:00
2022-11-05 12:22:00 -04:00
// 6. Let tm2 be ToLocalTime(ℤ (ℝ (y) × 10^6), dateTimeFormat.[[Calendar]], dateTimeFormat.[[TimeZone]]).
auto end_bigint = Crypto : : SignedBigInteger { end } . multiplied_by ( s_one_million_bigint ) ;
auto end_local_time = TRY ( to_local_time ( vm , end_bigint , date_time_format . calendar ( ) , date_time_format . time_zone ( ) ) ) ;
2021-12-08 19:57:21 -05:00
2022-07-26 06:38:35 -04:00
// 7. Let rangePatterns be dateTimeFormat.[[RangePatterns]].
2021-12-08 19:57:21 -05:00
auto range_patterns = date_time_format . range_patterns ( ) ;
2022-07-26 06:38:35 -04:00
// 8. Let rangePattern be undefined.
2022-09-02 12:01:10 -04:00
Optional < : : Locale : : CalendarRangePattern > range_pattern ;
2021-12-08 19:57:21 -05:00
2022-07-26 06:38:35 -04:00
// 9. Let dateFieldsPracticallyEqual be true.
2021-12-08 19:57:21 -05:00
bool date_fields_practically_equal = true ;
2022-07-26 06:38:35 -04:00
// 10. Let patternContainsLargerDateField be false.
2021-12-08 19:57:21 -05:00
bool pattern_contains_larger_date_field = false ;
2022-07-26 06:38:35 -04:00
// 11. While dateFieldsPracticallyEqual is true and patternContainsLargerDateField is false, repeat for each row of Table 4 in order, except the header row:
2023-01-27 11:12:01 -05:00
TRY ( for_each_range_pattern_field ( start_local_time , end_local_time , [ & ] ( auto start_value , auto end_value , auto field_name ) - > ThrowCompletionOr < IterationDecision > {
2021-12-08 19:57:21 -05:00
// a. Let fieldName be the name given in the Range Pattern Field column of the row.
// b. If rangePatterns has a field [[<fieldName>]], let rp be rangePatterns.[[<fieldName>]]; else let rp be undefined.
2022-09-02 12:01:10 -04:00
Optional < : : Locale : : CalendarRangePattern > pattern ;
2021-12-08 19:57:21 -05:00
for ( auto const & range : range_patterns ) {
if ( range . field = = field_name ) {
pattern = range ;
break ;
}
}
// c. If rangePattern is not undefined and rp is undefined, then
if ( range_pattern . has_value ( ) & & ! pattern . has_value ( ) ) {
// i. Set patternContainsLargerDateField to true.
pattern_contains_larger_date_field = true ;
}
// d. Else,
else {
// i. Let rangePattern be rp.
range_pattern = pattern ;
switch ( field_name ) {
// ii. If fieldName is equal to [[AmPm]], then
2022-09-02 12:01:10 -04:00
case : : Locale : : CalendarRangePattern : : Field : : AmPm : {
2021-12-08 19:57:21 -05:00
// 1. Let v1 be tm1.[[Hour]].
// 2. Let v2 be tm2.[[Hour]].
// 3. If v1 is greater than 11 and v2 less or equal than 11, or v1 is less or equal than 11 and v2 is greater than 11, then
if ( ( start_value > 11 & & end_value < = 11 ) | | ( start_value < = 11 & & end_value > 11 ) ) {
// a. Set dateFieldsPracticallyEqual to false.
date_fields_practically_equal = false ;
}
break ;
}
// iii. Else if fieldName is equal to [[DayPeriod]], then
2022-09-02 12:01:10 -04:00
case : : Locale : : CalendarRangePattern : : Field : : DayPeriod : {
2021-12-08 19:57:21 -05:00
// 1. Let v1 be a String value representing the day period of tm1; the String value depends upon the implementation and the effective locale of dateTimeFormat.
2023-01-27 11:12:01 -05:00
auto start_period = TRY_OR_THROW_OOM ( vm , : : Locale : : get_calendar_day_period_symbol_for_hour ( date_time_format . data_locale ( ) , date_time_format . calendar ( ) , : : Locale : : CalendarPatternStyle : : Short , start_value ) ) ;
2021-12-08 19:57:21 -05:00
// 2. Let v2 be a String value representing the day period of tm2; the String value depends upon the implementation and the effective locale of dateTimeFormat.
2023-01-27 11:12:01 -05:00
auto end_period = TRY_OR_THROW_OOM ( vm , : : Locale : : get_calendar_day_period_symbol_for_hour ( date_time_format . data_locale ( ) , date_time_format . calendar ( ) , : : Locale : : CalendarPatternStyle : : Short , end_value ) ) ;
2021-12-08 19:57:21 -05:00
// 3. If v1 is not equal to v2, then
if ( start_period ! = end_period ) {
// a. Set dateFieldsPracticallyEqual to false.
date_fields_practically_equal = false ;
}
break ;
}
// iv. Else if fieldName is equal to [[FractionalSecondDigits]], then
2022-09-02 12:01:10 -04:00
case : : Locale : : CalendarRangePattern : : Field : : FractionalSecondDigits : {
2021-12-08 19:57:21 -05:00
// 1. Let fractionalSecondDigits be dateTimeFormat.[[FractionalSecondDigits]].
Optional < u8 > fractional_second_digits ;
if ( date_time_format . has_fractional_second_digits ( ) )
fractional_second_digits = date_time_format . fractional_second_digits ( ) ;
// 2. If fractionalSecondDigits is undefined, then
if ( ! fractional_second_digits . has_value ( ) ) {
// a. Set fractionalSecondDigits to 3.
fractional_second_digits = 3 ;
}
// 3. Let v1 be tm1.[[Millisecond]].
// 4. Let v2 be tm2.[[Millisecond]].
// 5. Let v1 be floor(v1 × 10( fractionalSecondDigits - 3 )).
start_value = floor ( start_value * pow ( 10 , static_cast < int > ( * fractional_second_digits ) - 3 ) ) ;
// 6. Let v2 be floor(v2 × 10( fractionalSecondDigits - 3 )).
end_value = floor ( end_value * pow ( 10 , static_cast < int > ( * fractional_second_digits ) - 3 ) ) ;
// 7. If v1 is not equal to v2, then
if ( start_value ! = end_value ) {
// a. Set dateFieldsPracticallyEqual to false.
date_fields_practically_equal = false ;
}
break ;
}
// v. Else,
default : {
// 1. Let v1 be tm1.[[<fieldName>]].
// 2. Let v2 be tm2.[[<fieldName>]].
// 3. If v1 is not equal to v2, then
if ( start_value ! = end_value ) {
// a. Set dateFieldsPracticallyEqual to false.
date_fields_practically_equal = false ;
}
break ;
}
}
}
if ( date_fields_practically_equal & & ! pattern_contains_larger_date_field )
return IterationDecision : : Continue ;
return IterationDecision : : Break ;
2023-01-27 11:12:01 -05:00
} ) ) ;
2021-12-08 19:57:21 -05:00
2022-07-26 06:38:35 -04:00
// 12. If dateFieldsPracticallyEqual is true, then
2021-12-08 19:57:21 -05:00
if ( date_fields_practically_equal ) {
// a. Let pattern be dateTimeFormat.[[Pattern]].
auto const & pattern = date_time_format . pattern ( ) ;
// b. Let patternParts be PartitionPattern(pattern).
2023-01-20 14:26:48 -05:00
auto pattern_parts = MUST_OR_THROW_OOM ( partition_pattern ( vm , pattern ) ) ;
2021-12-08 19:57:21 -05:00
// c. Let result be ? FormatDateTimePattern(dateTimeFormat, patternParts, x, undefined).
2022-08-20 08:25:24 +01:00
auto raw_result = TRY ( format_date_time_pattern ( vm , date_time_format , move ( pattern_parts ) , start , nullptr ) ) ;
2023-02-02 19:40:02 -05:00
auto result = MUST_OR_THROW_OOM ( PatternPartitionWithSource : : create_from_parent_list ( vm , move ( raw_result ) ) ) ;
2021-12-08 19:57:21 -05:00
// d. For each Record { [[Type]], [[Value]] } r in result, do
for ( auto & part : result ) {
// i. Set r.[[Source]] to "shared".
part . source = " shared " sv ;
}
// e. Return result.
return result ;
}
2022-07-26 06:38:35 -04:00
// 13. Let result be a new empty List.
2021-12-08 19:57:21 -05:00
Vector < PatternPartitionWithSource > result ;
2022-07-26 06:38:35 -04:00
// 14. If rangePattern is undefined, then
2021-12-08 19:57:21 -05:00
if ( ! range_pattern . has_value ( ) ) {
// a. Let rangePattern be rangePatterns.[[Default]].
2023-01-27 10:18:11 -05:00
range_pattern = TRY_OR_THROW_OOM ( vm , : : Locale : : get_calendar_default_range_format ( date_time_format . data_locale ( ) , date_time_format . calendar ( ) ) ) ;
2021-12-08 19:57:21 -05:00
// Non-standard, range_pattern will be empty if Unicode data generation is disabled.
if ( ! range_pattern . has_value ( ) )
return result ;
// Non-standard, LibUnicode leaves the CLDR's {0} and {1} partitions in the default patterns
// to be replaced at runtime with the DateTimeFormat object's pattern.
auto const & pattern = date_time_format . pattern ( ) ;
if ( range_pattern - > start_range . contains ( " {0} " sv ) ) {
2023-01-27 10:18:11 -05:00
range_pattern - > start_range = TRY_OR_THROW_OOM ( vm , range_pattern - > start_range . replace ( " {0} " sv , pattern , ReplaceMode : : FirstOnly ) ) ;
range_pattern - > end_range = TRY_OR_THROW_OOM ( vm , range_pattern - > end_range . replace ( " {1} " sv , pattern , ReplaceMode : : FirstOnly ) ) ;
2021-12-08 19:57:21 -05:00
} else {
2023-01-27 10:18:11 -05:00
range_pattern - > start_range = TRY_OR_THROW_OOM ( vm , range_pattern - > start_range . replace ( " {1} " sv , pattern , ReplaceMode : : FirstOnly ) ) ;
range_pattern - > end_range = TRY_OR_THROW_OOM ( vm , range_pattern - > end_range . replace ( " {0} " sv , pattern , ReplaceMode : : FirstOnly ) ) ;
2021-12-08 19:57:21 -05:00
}
// FIXME: The above is not sufficient. For example, if the start date is days before the end date, and only the timeStyle
// option is provided, the resulting range will not include the differing dates. We will likely need to implement
// step 3 here: https://unicode.org/reports/tr35/tr35-dates.html#intervalFormats
}
2022-07-26 06:38:35 -04:00
// 15. For each Record { [[Pattern]], [[Source]] } rangePatternPart in rangePattern.[[PatternParts]], do
2021-12-08 19:57:21 -05:00
TRY ( for_each_range_pattern_with_source ( * range_pattern , [ & ] ( auto const & pattern , auto source ) - > ThrowCompletionOr < void > {
// a. Let pattern be rangePatternPart.[[Pattern]].
// b. Let source be rangePatternPart.[[Source]].
// c. If source is "startRange" or "shared", then
// i. Let z be x.
// d. Else,
// i. Let z be y.
auto time = ( ( source = = " startRange " ) | | ( source = = " shared " ) ) ? start : end ;
// e. Let patternParts be PartitionPattern(pattern).
2023-01-20 14:26:48 -05:00
auto pattern_parts = MUST_OR_THROW_OOM ( partition_pattern ( vm , pattern ) ) ;
2021-12-08 19:57:21 -05:00
// f. Let partResult be ? FormatDateTimePattern(dateTimeFormat, patternParts, z, rangePattern).
2022-08-20 08:25:24 +01:00
auto raw_part_result = TRY ( format_date_time_pattern ( vm , date_time_format , move ( pattern_parts ) , time , & range_pattern . value ( ) ) ) ;
2023-02-02 19:40:02 -05:00
auto part_result = MUST_OR_THROW_OOM ( PatternPartitionWithSource : : create_from_parent_list ( vm , move ( raw_part_result ) ) ) ;
2021-12-08 19:57:21 -05:00
// g. For each Record { [[Type]], [[Value]] } r in partResult, do
for ( auto & part : part_result ) {
// i. Set r.[[Source]] to source.
part . source = source ;
}
// h. Add all elements in partResult to result in order.
2023-02-02 19:42:35 -05:00
TRY_OR_THROW_OOM ( vm , result . try_extend ( move ( part_result ) ) ) ;
2021-12-08 19:57:21 -05:00
return { } ;
} ) ) ;
2022-07-26 06:38:35 -04:00
// 16. Return result.
2021-12-08 19:57:21 -05:00
return result ;
}
2023-07-21 22:13:44 -04:00
// 11.5.10 FormatDateTimeRange ( dateTimeFormat, x, y ), https://tc39.es/ecma402/#sec-formatdatetimerange
2023-01-27 10:18:11 -05:00
ThrowCompletionOr < String > format_date_time_range ( VM & vm , DateTimeFormat & date_time_format , double start , double end )
2021-12-08 19:57:21 -05:00
{
// 1. Let parts be ? PartitionDateTimeRangePattern(dateTimeFormat, x, y).
2022-08-20 08:25:24 +01:00
auto parts = TRY ( partition_date_time_range_pattern ( vm , date_time_format , start , end ) ) ;
2021-12-08 19:57:21 -05:00
// 2. Let result be the empty String.
2023-01-27 10:18:11 -05:00
ThrowableStringBuilder result ( vm ) ;
2021-12-08 19:57:21 -05:00
// 3. For each Record { [[Type]], [[Value]], [[Source]] } part in parts, do
for ( auto & part : parts ) {
// a. Set result to the string-concatenation of result and part.[[Value]].
2023-01-27 10:18:11 -05:00
TRY ( result . append ( part . value ) ) ;
2021-12-08 19:57:21 -05:00
}
// 4. Return result.
2023-01-27 10:18:11 -05:00
return result . to_string ( ) ;
2021-12-08 19:57:21 -05:00
}
2023-07-21 22:13:44 -04:00
// 11.5.11 FormatDateTimeRangeToParts ( dateTimeFormat, x, y ), https://tc39.es/ecma402/#sec-formatdatetimerangetoparts
2022-08-20 08:25:24 +01:00
ThrowCompletionOr < Array * > format_date_time_range_to_parts ( VM & vm , DateTimeFormat & date_time_format , double start , double end )
2021-12-09 13:35:59 -05:00
{
2022-08-20 08:25:24 +01:00
auto & realm = * vm . current_realm ( ) ;
2021-12-09 13:35:59 -05:00
// 1. Let parts be ? PartitionDateTimeRangePattern(dateTimeFormat, x, y).
2022-08-20 08:25:24 +01:00
auto parts = TRY ( partition_date_time_range_pattern ( vm , date_time_format , start , end ) ) ;
2021-12-09 13:35:59 -05:00
2022-03-28 08:30:48 -04:00
// 2. Let result be ! ArrayCreate(0).
2022-12-13 20:49:49 +00:00
auto result = MUST ( Array : : create ( realm , 0 ) ) ;
2021-12-09 13:35:59 -05:00
// 3. Let n be 0.
size_t n = 0 ;
// 4. For each Record { [[Type]], [[Value]], [[Source]] } part in parts, do
for ( auto & part : parts ) {
2022-05-02 20:54:39 +02:00
// a. Let O be OrdinaryObjectCreate(%ObjectPrototype%).
2022-12-13 20:49:50 +00:00
auto object = Object : : create ( realm , realm . intrinsics ( ) . object_prototype ( ) ) ;
2021-12-09 13:35:59 -05:00
// b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]).
2023-08-08 18:25:57 +02:00
MUST ( object - > create_data_property_or_throw ( vm . names . type , PrimitiveString : : create ( vm , part . type ) ) ) ;
2021-12-09 13:35:59 -05:00
// c. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]).
2022-12-06 22:17:27 +00:00
MUST ( object - > create_data_property_or_throw ( vm . names . value , PrimitiveString : : create ( vm , move ( part . value ) ) ) ) ;
2021-12-09 13:35:59 -05:00
// d. Perform ! CreateDataPropertyOrThrow(O, "source", part.[[Source]]).
2023-08-08 18:25:57 +02:00
MUST ( object - > create_data_property_or_throw ( vm . names . source , PrimitiveString : : create ( vm , part . source ) ) ) ;
2021-12-09 13:35:59 -05:00
// e. Perform ! CreateDataProperty(result, ! ToString(n), O).
MUST ( result - > create_data_property_or_throw ( n , object ) ) ;
// f. Increment n by 1.
+ + n ;
}
// 5. Return result.
2022-12-13 20:49:49 +00:00
return result . ptr ( ) ;
2021-12-09 13:35:59 -05:00
}
2023-07-21 22:13:44 -04:00
// 11.5.12 ToLocalTime ( epochNs, calendar, timeZone ), https://tc39.es/ecma402/#sec-tolocaltime
2022-11-05 12:22:00 -04:00
ThrowCompletionOr < LocalTime > to_local_time ( VM & vm , Crypto : : SignedBigInteger const & epoch_ns , StringView calendar , StringView time_zone )
2021-12-06 12:26:49 -05:00
{
2022-11-05 12:22:00 -04:00
// 1. Let offsetNs be GetNamedTimeZoneOffsetNanoseconds(timeZone, epochNs).
auto offset_ns = get_named_time_zone_offset_nanoseconds ( time_zone , epoch_ns ) ;
2021-12-06 12:26:49 -05:00
2022-11-05 12:22:00 -04:00
// NOTE: Unlike the spec, we still perform the below computations with BigInts until we are ready
// to divide the number by 10^6. The spec expects an MV here. If we try to use i64, we will
// overflow; if we try to use a double, we lose quite a bit of accuracy.
2021-12-06 12:26:49 -05:00
2022-11-05 12:22:00 -04:00
// 2. Let tz be ℝ (epochNs) + offsetNs.
auto zoned_time_ns = epoch_ns . plus ( Crypto : : SignedBigInteger { offset_ns } ) ;
// 3. If calendar is "gregory", then
if ( calendar = = " gregory " sv ) {
auto zoned_time_ms = zoned_time_ns . divided_by ( s_one_million_bigint ) . quotient ;
auto zoned_time = floor ( zoned_time_ms . to_double ( Crypto : : UnsignedBigInteger : : RoundingMode : : ECMAScriptNumberValueFor ) ) ;
2021-12-06 12:26:49 -05:00
auto year = year_from_time ( zoned_time ) ;
2022-11-05 12:22:00 -04:00
// a. Return a record with fields calculated from tz according to Table 8.
2021-12-06 12:26:49 -05:00
return LocalTime {
2022-11-05 12:22:00 -04:00
// WeekDay(𝔽 (floor(tz / 10^6)))
2021-12-06 12:26:49 -05:00
. weekday = week_day ( zoned_time ) ,
2023-02-01 21:03:59 +00:00
// Let year be YearFromTime(𝔽 (floor(tz / 10^6))). If year < 1𝔽 , return "BC", else return "AD".
. era = year < 1 ? : : Locale : : Era : : BC : : : Locale : : Era : : AD ,
2022-11-05 12:22:00 -04:00
// YearFromTime(𝔽 (floor(tz / 10^6)))
2021-12-06 12:26:49 -05:00
. year = year ,
// undefined.
. related_year = js_undefined ( ) ,
// undefined.
. year_name = js_undefined ( ) ,
2022-11-05 12:22:00 -04:00
// MonthFromTime(𝔽 (floor(tz / 10^6)))
2021-12-06 12:26:49 -05:00
. month = month_from_time ( zoned_time ) ,
2022-11-05 12:22:00 -04:00
// DateFromTime(𝔽 (floor(tz / 10^6)))
2021-12-06 12:26:49 -05:00
. day = date_from_time ( zoned_time ) ,
2022-11-05 12:22:00 -04:00
// HourFromTime(𝔽 (floor(tz / 10^6)))
2021-12-06 12:26:49 -05:00
. hour = hour_from_time ( zoned_time ) ,
2022-11-05 12:22:00 -04:00
// MinFromTime(𝔽 (floor(tz / 10^6)))
2021-12-06 12:26:49 -05:00
. minute = min_from_time ( zoned_time ) ,
2022-11-05 12:22:00 -04:00
// SecFromTime(𝔽 (floor(tz / 10^6)))
2021-12-06 12:26:49 -05:00
. second = sec_from_time ( zoned_time ) ,
2022-11-05 12:22:00 -04:00
// msFromTime(𝔽 (floor(tz / 10^6)))
2021-12-06 12:26:49 -05:00
. millisecond = ms_from_time ( zoned_time ) ,
} ;
}
2022-11-05 12:22:00 -04:00
// 4. Else,
// a. Return a record with the fields of Column 1 of Table 8 calculated from tz for the given calendar. The calculations should use best available information about the specified calendar.
2021-12-06 12:26:49 -05:00
// FIXME: Implement this when non-Gregorian calendars are supported by LibUnicode.
2022-08-20 08:25:24 +01:00
return vm . throw_completion < InternalError > ( ErrorType : : NotImplemented , " Non-Gregorian calendars " sv ) ;
2021-12-06 12:26:49 -05:00
}
2021-11-18 10:30:31 -05:00
}