diff options
Diffstat (limited to 'chromium/v8/src/objects/js-date-time-format.cc')
-rw-r--r-- | chromium/v8/src/objects/js-date-time-format.cc | 1084 |
1 files changed, 968 insertions, 116 deletions
diff --git a/chromium/v8/src/objects/js-date-time-format.cc b/chromium/v8/src/objects/js-date-time-format.cc index 955370b7ba8..82d6d067e2b 100644 --- a/chromium/v8/src/objects/js-date-time-format.cc +++ b/chromium/v8/src/objects/js-date-time-format.cc @@ -20,6 +20,7 @@ #include "src/heap/factory.h" #include "src/objects/intl-objects.h" #include "src/objects/js-date-time-format-inl.h" +#include "src/objects/js-temporal-objects-inl.h" #include "src/objects/managed-inl.h" #include "src/objects/option-utils.h" #include "unicode/calendar.h" @@ -494,37 +495,6 @@ int FractionalSecondDigitsFromPattern(const std::string& pattern) { } // namespace -Handle<Object> JSDateTimeFormat::TimeZoneId(Isolate* isolate, - const icu::TimeZone& tz) { - Factory* factory = isolate->factory(); - icu::UnicodeString time_zone; - tz.getID(time_zone); - UErrorCode status = U_ZERO_ERROR; - icu::UnicodeString canonical_time_zone; - icu::TimeZone::getCanonicalID(time_zone, canonical_time_zone, status); - Handle<Object> timezone_value; - if (U_SUCCESS(status)) { - // In CLDR (http://unicode.org/cldr/trac/ticket/9943), Etc/UTC is made - // a separate timezone ID from Etc/GMT even though they're still the same - // timezone. We have Etc/UTC because 'UTC', 'Etc/Universal', - // 'Etc/Zulu' and others are turned to 'Etc/UTC' by ICU. Etc/GMT comes - // from Etc/GMT0, Etc/GMT+0, Etc/GMT-0, Etc/Greenwich. - // ecma402#sec-canonicalizetimezonename step 3 - if (canonical_time_zone == UNICODE_STRING_SIMPLE("Etc/UTC") || - canonical_time_zone == UNICODE_STRING_SIMPLE("Etc/GMT")) { - timezone_value = factory->UTC_string(); - } else { - ASSIGN_RETURN_ON_EXCEPTION_VALUE( - isolate, timezone_value, Intl::ToString(isolate, canonical_time_zone), - Handle<Object>()); - } - } else { - // Somehow on Windows we will reach here. - timezone_value = factory->undefined_value(); - } - return timezone_value; -} - namespace { Handle<String> GetCalendar(Isolate* isolate, const icu::SimpleDateFormat& simple_date_format, @@ -556,8 +526,8 @@ Handle<String> GetCalendar(Isolate* isolate, Handle<Object> GetTimeZone(Isolate* isolate, const icu::SimpleDateFormat& simple_date_format) { - return JSDateTimeFormat::TimeZoneId( - isolate, simple_date_format.getCalendar()->getTimeZone()); + return Intl::TimeZoneId(isolate, + simple_date_format.getCalendar()->getTimeZone()); } } // namespace @@ -739,6 +709,645 @@ MaybeHandle<JSObject> JSDateTimeFormat::ResolvedOptions( namespace { +// #sec-temporal-istemporalobject +bool IsTemporalObject(Handle<Object> value) { + // 1. If Type(value) is not Object, then + if (!value->IsJSReceiver()) { + // a. Return false. + return false; + } + // 2. If value does not have an [[InitializedTemporalDate]], + // [[InitializedTemporalTime]], [[InitializedTemporalDateTime]], + // [[InitializedTemporalZonedDateTime]], [[InitializedTemporalYearMonth]], + // [[InitializedTemporalMonthDay]], or [[InitializedTemporalInstant]] internal + // slot, then + if (!value->IsJSTemporalPlainDate() && !value->IsJSTemporalPlainTime() && + !value->IsJSTemporalPlainDateTime() && + !value->IsJSTemporalZonedDateTime() && + !value->IsJSTemporalPlainYearMonth() && + !value->IsJSTemporalPlainMonthDay() && !value->IsJSTemporalInstant()) { + // a. Return false. + return false; + } + // 3. Return true. + return true; +} + +// #sec-temporal-sametemporaltype +bool SameTemporalType(Handle<Object> x, Handle<Object> y) { + // 1. If either of ! IsTemporalObject(x) or ! IsTemporalObject(y) is false, + // return false. + if (!IsTemporalObject(x)) return false; + if (!IsTemporalObject(y)) return false; + // 2. If x has an [[InitializedTemporalDate]] internal slot and y does not, + // return false. + if (x->IsJSTemporalPlainDate() && !y->IsJSTemporalPlainDate()) return false; + // 3. If x has an [[InitializedTemporalTime]] internal slot and y does not, + // return false. + if (x->IsJSTemporalPlainTime() && !y->IsJSTemporalPlainTime()) return false; + // 4. If x has an [[InitializedTemporalDateTime]] internal slot and y does + // not, return false. + if (x->IsJSTemporalPlainDateTime() && !y->IsJSTemporalPlainDateTime()) { + return false; + } + // 5. If x has an [[InitializedTemporalZonedDateTime]] internal slot and y + // does not, return false. + if (x->IsJSTemporalZonedDateTime() && !y->IsJSTemporalZonedDateTime()) { + return false; + } + // 6. If x has an [[InitializedTemporalYearMonth]] internal slot and y does + // not, return false. + if (x->IsJSTemporalPlainYearMonth() && !y->IsJSTemporalPlainYearMonth()) { + return false; + } + // 7. If x has an [[InitializedTemporalMonthDay]] internal slot and y does + // not, return false. + if (x->IsJSTemporalPlainMonthDay() && !y->IsJSTemporalPlainMonthDay()) { + return false; + } + // 8. If x has an [[InitializedTemporalInstant]] internal slot and y does not, + // return false. + if (x->IsJSTemporalInstant() && !y->IsJSTemporalInstant()) return false; + // 9. Return true. + return true; +} + +enum class PatternKind { + kDate, + kPlainDate, + kPlainDateTime, + kPlainTime, + kPlainYearMonth, + kPlainMonthDay, + kZonedDateTime, + kInstant, +}; +struct DateTimeValueRecord { + double epoch_milliseconds; + PatternKind kind; +}; + +DateTimeValueRecord TemporalInstantToRecord(Isolate* isolate, + Handle<JSTemporalInstant> instant, + PatternKind kind) { + double milliseconds = + BigInt::Divide(isolate, Handle<BigInt>(instant->nanoseconds(), isolate), + BigInt::FromInt64(isolate, 1000000)) + .ToHandleChecked() + ->AsInt64(); + return {milliseconds, kind}; +} + +Maybe<DateTimeValueRecord> TemporalPlainDateTimeToRecord( + Isolate* isolate, const icu::SimpleDateFormat& date_time_format, + PatternKind kind, Handle<JSTemporalPlainDateTime> plain_date_time, + const char* method_name) { + // 8. Let timeZone be ! CreateTemporalTimeZone(dateTimeFormat.[[TimeZone]]). + Handle<Object> time_zone_obj = GetTimeZone(isolate, date_time_format); + // TODO(ftang): we should change the return type of GetTimeZone() to + // Handle<String> by ensure it will not return undefined. + CHECK(time_zone_obj->IsString()); + Handle<JSTemporalTimeZone> time_zone = + temporal::CreateTemporalTimeZone(isolate, + Handle<String>::cast(time_zone_obj)) + .ToHandleChecked(); + // 9. Let instant be ? BuiltinTimeZoneGetInstantFor(timeZone, plainDateTime, + // "compatible"). + Handle<JSTemporalInstant> instant; + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, instant, + temporal::BuiltinTimeZoneGetInstantForCompatible( + isolate, time_zone, plain_date_time, method_name), + Nothing<DateTimeValueRecord>()); + // 10. If pattern is null, throw a TypeError exception. + + // 11. Return the Record { [[pattern]]: pattern.[[pattern]], + // [[rangePatterns]]: pattern.[[rangePatterns]], [[epochNanoseconds]]: + // instant.[[Nanoseconds]] }. + return Just(TemporalInstantToRecord(isolate, instant, kind)); +} + +template <typename T> +Maybe<DateTimeValueRecord> TemporalToRecord( + Isolate* isolate, const icu::SimpleDateFormat& date_time_format, + PatternKind kind, Handle<T> temporal, Handle<JSReceiver> calendar, + const char* method_name) { + // 7. Let plainDateTime be ? CreateTemporalDateTime(temporalDate.[[ISOYear]], + // temporalDate.[[ISOMonth]], temporalDate.[[ISODay]], 12, 0, 0, 0, 0, 0, + // calendarOverride). + Handle<JSTemporalPlainDateTime> plain_date_time; + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, plain_date_time, + temporal::CreateTemporalDateTime( + isolate, + {{temporal->iso_year(), temporal->iso_month(), temporal->iso_day()}, + {12, 0, 0, 0, 0, 0}}, + calendar), + Nothing<DateTimeValueRecord>()); + return TemporalPlainDateTimeToRecord(isolate, date_time_format, kind, + plain_date_time, method_name); +} + +// #sec-temporal-handledatetimevaluetemporaldate +Maybe<DateTimeValueRecord> HandleDateTimeTemporalDate( + Isolate* isolate, const icu::SimpleDateFormat& date_time_format, + Handle<String> date_time_format_calendar, + Handle<JSTemporalPlainDate> temporal_date, const char* method_name) { + // 1. Assert: temporalDate has an [[InitializedTemporalDate]] internal slot. + + // 2. Let pattern be dateTimeFormat.[[TemporalPlainDatePattern]]. + + // 3. Let calendar be ? ToString(temporalDate.[[Calendar]]). + Handle<String> calendar; + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, calendar, + Object::ToString(isolate, handle(temporal_date->calendar(), isolate)), + Nothing<DateTimeValueRecord>()); + + // 4. If calendar is dateTimeFormat.[[Calendar]], then + Handle<JSReceiver> calendar_override; + if (String::Equals(isolate, calendar, date_time_format_calendar)) { + // a. Let calendarOverride be temporalDate.[[Calendar]]. + calendar_override = handle(temporal_date->calendar(), isolate); + // 5. Else if calendar is "iso8601", then + } else if (String::Equals(isolate, calendar, + isolate->factory()->iso8601_string())) { + // a. Let calendarOverride be ? + // GetBuiltinCalendar(dateTimeFormat.[[Calendar]]). + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, calendar_override, + temporal::GetBuiltinCalendar(isolate, date_time_format_calendar), + Nothing<DateTimeValueRecord>()); + // 6. Else, + } else { + // a. Throw a RangeError exception. + THROW_NEW_ERROR_RETURN_VALUE( + isolate, + NewRangeError(MessageTemplate::kInvalid, + isolate->factory()->calendar_string(), calendar), + Nothing<DateTimeValueRecord>()); + } + return TemporalToRecord<JSTemporalPlainDate>( + isolate, date_time_format, PatternKind::kPlainDate, temporal_date, + calendar_override, method_name); +} +// #sec-temporal-handledatetimevaluetemporaldatetime +Maybe<DateTimeValueRecord> HandleDateTimeTemporalDateTime( + Isolate* isolate, const icu::SimpleDateFormat& date_time_format, + Handle<String> date_time_format_calendar, + Handle<JSTemporalPlainDateTime> date_time, const char* method_name) { + // 1. Assert: dateTime has an [[InitializedTemporalDateTime]] internal slot. + // 2. Let pattern be dateTimeFormat.[[TemporalPlainDateTimePattern]]. + // 3. Let calendar be ? ToString(dateTime.[[Calendar]]). + Handle<String> calendar; + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, calendar, + Object::ToString(isolate, handle(date_time->calendar(), isolate)), + Nothing<DateTimeValueRecord>()); + // 4. If calendar is not "iso8601" and not equal to + // dateTimeFormat.[[Calendar]], then + Handle<JSReceiver> calendar_override; + if (!String::Equals(isolate, calendar, + isolate->factory()->iso8601_string()) && + !String::Equals(isolate, calendar, date_time_format_calendar)) { + // a. Throw a RangeError exception. + THROW_NEW_ERROR_RETURN_VALUE( + isolate, + NewRangeError(MessageTemplate::kInvalid, + isolate->factory()->calendar_string(), calendar), + Nothing<DateTimeValueRecord>()); + } + + // 5. Let timeZone be ! CreateTemporalTimeZone(dateTimeFormat.[[TimeZone]]). + // 6. Let instant be ? BuiltinTimeZoneGetInstantFor(timeZone, dateTime, + // "compatible"). + // 7. If pattern is null, throw a TypeError exception. + + // 8. Return the Record { [[pattern]]: pattern.[[pattern]], [[rangePatterns]]: + // pattern.[[rangePatterns]], [[epochNanoseconds]]: instant.[[Nanoseconds]] }. + + return TemporalPlainDateTimeToRecord(isolate, date_time_format, + PatternKind::kPlainDateTime, date_time, + method_name); +} + +// #sec-temporal-handledatetimevaluetemporalzoneddatetime +Maybe<DateTimeValueRecord> HandleDateTimeTemporalZonedDateTime( + Isolate* isolate, const icu::SimpleDateFormat& date_time_format, + Handle<String> date_time_format_calendar, + Handle<JSTemporalZonedDateTime> zoned_date_time, const char* method_name) { + // 1. Assert: zonedDateTime has an [[InitializedTemporalZonedDateTime]] + // internal slot. + // 2. Let pattern be dateTimeFormat.[[TemporalZonedDateTimePattern]]. + + // 3. Let calendar be ? ToString(zonedDateTime.[[Calendar]]). + Handle<String> calendar; + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, calendar, + Object::ToString(isolate, handle(zoned_date_time->calendar(), isolate)), + Nothing<DateTimeValueRecord>()); + // 4. If calendar is not "iso8601" and not equal to + // dateTimeFormat.[[Calendar]], then + Handle<JSReceiver> calendar_override; + if (!String::Equals(isolate, calendar, + isolate->factory()->iso8601_string()) && + !String::Equals(isolate, calendar, date_time_format_calendar)) { + // a. Throw a RangeError exception. + THROW_NEW_ERROR_RETURN_VALUE( + isolate, + NewRangeError(MessageTemplate::kInvalid, + isolate->factory()->calendar_string(), calendar), + Nothing<DateTimeValueRecord>()); + } + // 5. Let timeZone be ? ToString(zonedDateTime.[[TimeZone]]). + Handle<String> time_zone; + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, time_zone, + Object::ToString(isolate, handle(zoned_date_time->time_zone(), isolate)), + Nothing<DateTimeValueRecord>()); + // 6. If dateTimeFormat.[[TimeZone]] is not equal to DefaultTimeZone(), and + // timeZone is not equal to dateTimeFormat.[[TimeZone]], then + Handle<Object> date_time_format_time_zone = + GetTimeZone(isolate, date_time_format); + DCHECK(date_time_format_time_zone->IsString()); + Handle<String> date_time_format_time_zone_string = + Handle<String>::cast(date_time_format_time_zone); + if (!String::Equals(isolate, date_time_format_time_zone_string, + Intl::DefaultTimeZone(isolate)) && + !String::Equals(isolate, time_zone, date_time_format_time_zone_string)) { + // a. Throw a RangeError exception. + THROW_NEW_ERROR_RETURN_VALUE( + isolate, + NewRangeError(MessageTemplate::kInvalid, + isolate->factory()->timeZone_string(), time_zone), + Nothing<DateTimeValueRecord>()); + } + // 7. Let instant be ! CreateTemporalInstant(zonedDateTime.[[Nanoseconds]]). + Handle<JSTemporalInstant> instant = + temporal::CreateTemporalInstant( + isolate, handle(zoned_date_time->nanoseconds(), isolate)) + .ToHandleChecked(); + // 8. If pattern is null, throw a TypeError exception. + + // 9. Return the Record { [[pattern]]: pattern.[[pattern]], [[rangePatterns]]: + // pattern.[[rangePatterns]], [[epochNanoseconds]]: instant.[[Nanoseconds]] }. + return Just( + TemporalInstantToRecord(isolate, instant, PatternKind::kZonedDateTime)); +} + +// #sec-temporal-handledatetimevaluetemporalinstant +Maybe<DateTimeValueRecord> HandleDateTimeTemporalInstant( + Isolate* isolate, const icu::SimpleDateFormat& date_time_format, + Handle<JSTemporalInstant> instant, const char* method_name) { + // 1. Assert: instant has an [[InitializedTemporalInstant]] internal slot. + // 2. Let pattern be dateTimeFormat.[[TemporalInstantPattern]]. + // 3. If pattern is null, throw a TypeError exception. + + // 4. Return the Record { [[pattern]]: pattern.[[pattern]], [[rangePatterns]]: + // pattern.[[rangePatterns]], [[epochNanoseconds]]: instant.[[Nanoseconds]] }. + return Just(TemporalInstantToRecord(isolate, instant, PatternKind::kInstant)); +} + +// #sec-temporal-handledatetimevaluetemporaltime +Maybe<DateTimeValueRecord> HandleDateTimeTemporalTime( + Isolate* isolate, const icu::SimpleDateFormat& date_time_format, + Handle<JSTemporalPlainTime> temporal_time, const char* method_name) { + // 1. Assert: temporalTime has an [[InitializedTemporalTime]] internal slot. + // 2. Let pattern be dateTimeFormat.[[TemporalPlainTimePattern]]. + + // 3. Let isoCalendar be ! GetISO8601Calendar(). + + Handle<JSReceiver> iso_calendar = temporal::GetISO8601Calendar(isolate); + // 4. Let plainDateTime be ? CreateTemporalDateTime(1970, 1, 1, + // temporalTime.[[ISOHour]], temporalTime.[[ISOMinute]], + // temporalTime.[[ISOSecond]], temporalTime.[[ISOMillisecond]], + // temporalTime.[[ISOMicrosecond]], temporalTime.[[ISONanosecond]], + // isoCalendar). + Handle<JSTemporalPlainDateTime> plain_date_time; + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, plain_date_time, + temporal::CreateTemporalDateTime( + isolate, + {{1970, 1, 1}, + {temporal_time->iso_hour(), temporal_time->iso_minute(), + temporal_time->iso_second(), temporal_time->iso_millisecond(), + temporal_time->iso_microsecond(), temporal_time->iso_nanosecond()}}, + iso_calendar), + Nothing<DateTimeValueRecord>()); + return TemporalPlainDateTimeToRecord(isolate, date_time_format, + PatternKind::kPlainTime, plain_date_time, + method_name); +} + +template <typename T> +Maybe<DateTimeValueRecord> HandleDateTimeTemporalYearMonthOrMonthDay( + Isolate* isolate, const icu::SimpleDateFormat& date_time_format, + Handle<String> date_time_format_calendar, PatternKind kind, + Handle<T> temporal, const char* method_name) { + // 3. Let calendar be ? ToString(temporalYearMonth.[[Calendar]]). + Handle<String> calendar; + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, calendar, + Object::ToString(isolate, handle(temporal->calendar(), isolate)), + Nothing<DateTimeValueRecord>()); + // 4. If calendar is not equal to dateTimeFormat.[[Calendar]], then + // https://github.com/tc39/proposal-temporal/issues/2364 + if (!String::Equals(isolate, calendar, date_time_format_calendar)) { + // a. Throw a RangeError exception. + THROW_NEW_ERROR_RETURN_VALUE( + isolate, + NewRangeError(MessageTemplate::kInvalid, + isolate->factory()->calendar_string(), calendar), + Nothing<DateTimeValueRecord>()); + } + + return TemporalToRecord<T>(isolate, date_time_format, kind, temporal, + handle(temporal->calendar(), isolate), + method_name); +} + +// #sec-temporal-handledatetimevaluetemporalyearmonth +Maybe<DateTimeValueRecord> HandleDateTimeTemporalYearMonth( + Isolate* isolate, const icu::SimpleDateFormat& date_time_format, + Handle<String> date_time_format_calendar, + Handle<JSTemporalPlainYearMonth> temporal_year_month, + const char* method_name) { + return HandleDateTimeTemporalYearMonthOrMonthDay<JSTemporalPlainYearMonth>( + isolate, date_time_format, date_time_format_calendar, + PatternKind::kPlainYearMonth, temporal_year_month, method_name); +} + +// #sec-temporal-handledatetimevaluetemporalmonthday +Maybe<DateTimeValueRecord> HandleDateTimeTemporalMonthDay( + Isolate* isolate, const icu::SimpleDateFormat& date_time_format, + Handle<String> date_time_format_calendar, + Handle<JSTemporalPlainMonthDay> temporal_month_day, + const char* method_name) { + return HandleDateTimeTemporalYearMonthOrMonthDay<JSTemporalPlainMonthDay>( + isolate, date_time_format, date_time_format_calendar, + PatternKind::kPlainMonthDay, temporal_month_day, method_name); +} + +// #sec-temporal-handledatetimeothers +Maybe<DateTimeValueRecord> HandleDateTimeOthers( + Isolate* isolate, const icu::SimpleDateFormat& date_time_format, + Handle<Object> x_obj, const char* method_name) { + // 1. Assert: ! IsTemporalObject(x) is false. + DCHECK(!IsTemporalObject(x_obj)); + // 2. Let pattern be dateTimeFormat.[[Pattern]]. + + // 3. Let rangePatterns be dateTimeFormat.[[RangePatterns]]. + + // 4. If x is undefined, then + double x; + if (x_obj->IsUndefined()) { + // a. Set x to ! Call(%Date.now%, undefined). + x = JSDate::CurrentTimeValue(isolate); + // 5. Else, + } else { + // a. Set x to ? ToNumber(x). + ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, x_obj, + Object::ToNumber(isolate, x_obj), + Nothing<DateTimeValueRecord>()); + x = x_obj->Number(); + } + // 6. Set x to TimeClip(x). + x = DateCache::TimeClip(x); + // 7. If x is NaN, throw a RangeError exception. + if (std::isnan(x)) { + THROW_NEW_ERROR_RETURN_VALUE( + isolate, NewRangeError(MessageTemplate::kInvalidTimeValue), + Nothing<DateTimeValueRecord>()); + } + + // 8. Let epochNanoseconds be ℤ(x) × 10^6ℤ. + + // 9. Return the Record { [[pattern]]: pattern, [[rangePatterns]]: + // rangePatterns, [[epochNanoseconds]]: epochNanoseconds }. + return Just(DateTimeValueRecord({x, PatternKind::kDate})); +} + +// #sec-temporal-handledatetimevalue +Maybe<DateTimeValueRecord> HandleDateTimeValue( + Isolate* isolate, const icu::SimpleDateFormat& date_time_format, + Handle<String> date_time_format_calendar, Handle<Object> x, + const char* method_name) { + if (IsTemporalObject(x)) { + // a. If x has an [[InitializedTemporalDate]] internal slot, then + if (x->IsJSTemporalPlainDate()) { + // i. Return ? HandleDateTimeTemporalDate(dateTimeFormat, x). + return HandleDateTimeTemporalDate( + isolate, date_time_format, date_time_format_calendar, + Handle<JSTemporalPlainDate>::cast(x), method_name); + } + // b. If x has an [[InitializedTemporalYearMonth]] internal slot, then + if (x->IsJSTemporalPlainYearMonth()) { + // i. Return ? HandleDateTimeTemporalYearMonth(dateTimeFormat, x). + return HandleDateTimeTemporalYearMonth( + isolate, date_time_format, date_time_format_calendar, + Handle<JSTemporalPlainYearMonth>::cast(x), method_name); + } + // c. If x has an [[InitializedTemporalMonthDay]] internal slot, then + if (x->IsJSTemporalPlainMonthDay()) { + // i. Return ? HandleDateTimeTemporalMonthDay(dateTimeFormat, x). + return HandleDateTimeTemporalMonthDay( + isolate, date_time_format, date_time_format_calendar, + Handle<JSTemporalPlainMonthDay>::cast(x), method_name); + } + // d. If x has an [[InitializedTemporalTime]] internal slot, then + if (x->IsJSTemporalPlainTime()) { + // i. Return ? HandleDateTimeTemporalTime(dateTimeFormat, x). + return HandleDateTimeTemporalTime(isolate, date_time_format, + Handle<JSTemporalPlainTime>::cast(x), + method_name); + } + // e. If x has an [[InitializedTemporalDateTime]] internal slot, then + if (x->IsJSTemporalPlainDateTime()) { + // i. Return ? HandleDateTimeTemporalDateTime(dateTimeFormat, x). + return HandleDateTimeTemporalDateTime( + isolate, date_time_format, date_time_format_calendar, + Handle<JSTemporalPlainDateTime>::cast(x), method_name); + } + // f. If x has an [[InitializedTemporalInstant]] internal slot, then + if (x->IsJSTemporalInstant()) { + // i. Return ? HandleDateTimeTemporalInstant(dateTimeFormat, x). + return HandleDateTimeTemporalInstant(isolate, date_time_format, + Handle<JSTemporalInstant>::cast(x), + method_name); + } + // g. Assert: x has an [[InitializedTemporalZonedDateTime]] internal slot. + DCHECK(x->IsJSTemporalZonedDateTime()); + // h. Return ? HandleDateTimeTemporalZonedDateTime(dateTimeFormat, x). + return HandleDateTimeTemporalZonedDateTime( + isolate, date_time_format, date_time_format_calendar, + Handle<JSTemporalZonedDateTime>::cast(x), method_name); + } + + // 2. Return ? HandleDateTimeOthers(dateTimeFormat, x). + return HandleDateTimeOthers(isolate, date_time_format, x, method_name); +} + +// This helper function handles Supported fields and Default fields in Table 16 +// ( #table-temporal-patterns ). It remove all the fields not stated in keep +// from input, and add the fields in add_default if a skeleton in the same +// category is in the input, with considering the equivalent. +// For example, if input is "yyyyMMhm", keep is {y,M,d} and add_default is +// {y,M,d}, the output will be "yyyyMMd". For example, if input is +// "yyyyMMhmOOOO", keep is {h,m,s,z,O,v} and add_default is {h,m,s}, then the +// output will be "hmOOOOs". The meaning of the skeleton letters is stated in +// UTS35 +// https://www.unicode.org/reports/tr35/tr35-dates.html#table-date-field-symbol-table +icu::UnicodeString KeepSupportedAddDefault( + const icu::UnicodeString& input, const std::set<char16_t>& keep, + const std::set<char16_t>& add_default) { + const std::map<char16_t, char16_t> equivalent({{'L', 'M'}, + {'h', 'j'}, + {'H', 'j'}, + {'k', 'j'}, + {'K', 'j'}, + {'O', 'z'}, + {'v', 'z'}}); + std::set<char16_t> to_be_added(add_default); + icu::UnicodeString result; + for (int32_t i = 0; i < input.length(); i++) { + char16_t ch = input.charAt(i); + if (keep.find(ch) != keep.end()) { + to_be_added.erase(ch); + auto also = equivalent.find(ch); + if (also != equivalent.end()) { + to_be_added.erase(also->second); + } + result.append(ch); + } + } + for (auto it = to_be_added.begin(); it != to_be_added.end(); ++it) { + result.append(*it); + } + return result; +} + +icu::UnicodeString GetSkeletonForPatternKind(const icu::UnicodeString& input, + PatternKind kind) { + // [[weekday]] skeleton could be one or more 'E' or 'c'. + // [[era]] skeleton could be one or more 'G'. + // [[year]] skeleton could be one or more 'y'. + // [[month]] skeleton could be one or more 'M' or 'L'. + // [[day]] skeleton could be one or more 'd'. + // [[hour]] skeleton could be one or more 'h', 'H', 'k', 'K', or 'j'. + // [[minute]] skeleton could be one or more 'm'. + // [[second]] skeleton could be one or more 's'. + // [[dayPeriod]] skeleton could be one or more 'b', 'B' or 'a'. + // [[fractionalSecondDigits]] skeleton could be one or more 'S'. + // [[timeZoneName]] skeleton could be one or more 'z', 'O', or 'v'. + + switch (kind) { + case PatternKind::kDate: + return input; + case PatternKind::kPlainDate: + return KeepSupportedAddDefault( + // Supported fields: [[weekday]], [[era]], [[year]], [[month]], + // [[day]] + input, {'E', 'c', 'G', 'y', 'M', 'L', 'd'}, + // Default fields: [[year]], [[month]], [[day]] + {'y', 'M', 'd'}); + case PatternKind::kPlainYearMonth: + return KeepSupportedAddDefault( + // Supported fields: [[era]], [[year]], [[month]] + input, {'G', 'y', 'M', 'L'}, + // Default fields: [[year]], [[month]] + {'y', 'M'}); + case PatternKind::kPlainMonthDay: + return KeepSupportedAddDefault( + // Supported fields: [[month]] [[day]] + input, {'M', 'L', 'd'}, + // Default fields: [[month]] [[day]] + {'M', 'd'}); + + case PatternKind::kPlainTime: + return KeepSupportedAddDefault( + input, + // Supported fields: [[hour]], [[minute]], [[second]], [[dayPeriod]], + // [[fractionalSecondDigits]] + {'h', 'H', 'k', 'K', 'j', 'm', 's', 'B', 'b', 'a', 'S'}, + // Default fields: [[hour]], [[minute]], + // [[second]] + {'j', 'm', 's'}); + + case PatternKind::kPlainDateTime: + // Row TemporalInstantPattern is the same as TemporalPlainDateTimePattern + // in Table 16: Supported fields for Temporal patterns + // #table-temporal-patterns + V8_FALLTHROUGH; + case PatternKind::kInstant: + return KeepSupportedAddDefault( + input, + // Supported fields: [[weekday]], [[era]], [[year]], [[month]], + // [[day]], [[hour]], [[minute]], [[second]], [[dayPeriod]], + // [[fractionalSecondDigits]] + {'E', 'c', 'G', 'y', 'M', 'L', 'd', 'h', 'H', 'k', 'K', 'j', 'm', 's', + 'B', 'b', 'a', 'S'}, + // Default fields: [[year]], [[month]], [[day]], [[hour]], [[minute]], + // [[second]] + {'y', 'M', 'd', 'j', 'm', 's'}); + + case PatternKind::kZonedDateTime: + return KeepSupportedAddDefault( + // Supported fields: [[weekday]], [[era]], [[year]], [[month]], + // [[day]], [[hour]], [[minute]], [[second]], [[dayPeriod]], + // [[fractionalSecondDigits]], [[timeZoneName]] + input, {'E', 'c', 'G', 'y', 'M', 'L', 'd', 'h', 'H', 'k', 'K', + 'j', 'm', 's', 'B', 'b', 'a', 'S', 'z', 'O', 'v'}, + // Default fields: [[year]], [[month]], [[day]], [[hour]], [[minute]], + // [[second]], [[timeZoneName]] + {'y', 'M', 'd', 'j', 'm', 's', 'z'}); + } +} + +icu::UnicodeString SkeletonFromDateFormat( + const icu::SimpleDateFormat& icu_date_format) { + icu::UnicodeString pattern; + pattern = icu_date_format.toPattern(pattern); + + UErrorCode status = U_ZERO_ERROR; + icu::UnicodeString skeleton = + icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status); + DCHECK(U_SUCCESS(status)); + return skeleton; +} + +std::unique_ptr<icu::SimpleDateFormat> GetSimpleDateTimeForTemporal( + const icu::SimpleDateFormat& date_format, PatternKind kind) { + DCHECK_NE(kind, PatternKind::kDate); + icu::UnicodeString skeleton = + GetSkeletonForPatternKind(SkeletonFromDateFormat(date_format), kind); + UErrorCode status = U_ZERO_ERROR; + std::unique_ptr<icu::SimpleDateFormat> result( + static_cast<icu::SimpleDateFormat*>( + icu::DateFormat::createInstanceForSkeleton( + skeleton, date_format.getSmpFmtLocale(), status))); + DCHECK(result); + DCHECK(U_SUCCESS(status)); + result->setTimeZone(date_format.getTimeZone()); + return result; +} + +icu::UnicodeString CallICUFormat(const icu::SimpleDateFormat& date_format, + PatternKind kind, double time_in_milliseconds, + icu::FieldPositionIterator* fp_iter, + UErrorCode& status) { + icu::UnicodeString result; + // Use the date_format directly for Date value. + if (kind == PatternKind::kDate) { + date_format.format(time_in_milliseconds, result, fp_iter, status); + return result; + } + // For other Temporal objects, lazy generate a SimpleDateFormat for the kind. + std::unique_ptr<icu::SimpleDateFormat> pattern( + GetSimpleDateTimeForTemporal(date_format, kind)); + pattern->format(time_in_milliseconds, result, fp_iter, status); + return result; +} + // ecma402/#sec-formatdatetime // FormatDateTime( dateTimeFormat, x ) MaybeHandle<String> FormatDateTime(Isolate* isolate, @@ -756,15 +1365,51 @@ MaybeHandle<String> FormatDateTime(Isolate* isolate, return Intl::ToString(isolate, result); } +MaybeHandle<String> FormatMillisecondsByKindToString( + Isolate* isolate, const icu::SimpleDateFormat& date_format, + PatternKind kind, double x) { + UErrorCode status = U_ZERO_ERROR; + icu::UnicodeString result = + CallICUFormat(date_format, kind, x, nullptr, status); + DCHECK(U_SUCCESS(status)); + + return Intl::ToString(isolate, result); +} +MaybeHandle<String> FormatDateTimeWithTemporalSupport( + Isolate* isolate, const icu::SimpleDateFormat& date_format, + Handle<String> date_time_format_calendar, Handle<Object> x, + const char* method_name) { + DateTimeValueRecord record; + MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, record, + HandleDateTimeValue(isolate, date_format, date_time_format_calendar, x, + method_name), + Handle<String>()); + return FormatMillisecondsByKindToString(isolate, date_format, record.kind, + record.epoch_milliseconds); +} + +MaybeHandle<String> FormatDateTimeWithTemporalSupport( + Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, + Handle<Object> x, const char* method_name) { + return FormatDateTimeWithTemporalSupport( + isolate, *(date_time_format->icu_simple_date_format().raw()), + JSDateTimeFormat::Calendar(isolate, date_time_format), x, method_name); +} + } // namespace // ecma402/#sec-datetime-format-functions // DateTime Format Functions MaybeHandle<String> JSDateTimeFormat::DateTimeFormat( Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, - Handle<Object> date) { + Handle<Object> date, const char* method_name) { // 2. Assert: Type(dtf) is Object and dtf has an [[InitializedDateTimeFormat]] // internal slot. + if (FLAG_harmony_temporal) { + return FormatDateTimeWithTemporalSupport(isolate, date_time_format, date, + method_name); + } // 3. If date is not provided or is undefined, then double x; @@ -797,6 +1442,7 @@ Isolate::ICUObjectCacheType ConvertToCacheType( return Isolate::ICUObjectCacheType::kDefaultSimpleDateFormat; } } + } // namespace MaybeHandle<String> JSDateTimeFormat::ToLocaleDateTime( @@ -813,7 +1459,6 @@ MaybeHandle<String> JSDateTimeFormat::ToLocaleDateTime( factory->Date_string()), String); } - double const x = Handle<JSDate>::cast(date)->value().Number(); // 2. If x is NaN, return "Invalid Date" if (std::isnan(x)) { @@ -868,6 +1513,26 @@ MaybeHandle<String> JSDateTimeFormat::ToLocaleDateTime( return FormatDateTime(isolate, *format, x); } +MaybeHandle<String> JSDateTimeFormat::TemporalToLocaleString( + Isolate* isolate, Handle<JSReceiver> x, Handle<Object> locales, + Handle<Object> options, const char* method_name) { + // 4. Let dateFormat be ? Construct(%DateTimeFormat%, « locales, options »). + Handle<JSFunction> constructor( + isolate->context().native_context().intl_date_time_format_function(), + isolate); + Handle<Map> map = JSFunction::GetDerivedMap(isolate, constructor, constructor) + .ToHandleChecked(); + Handle<JSDateTimeFormat> date_time_format; + ASSIGN_RETURN_ON_EXCEPTION( + isolate, date_time_format, + JSDateTimeFormat::New(isolate, map, locales, options, method_name), + String); + + // 5. Return FormatDateTime(dateFormat, x). + return FormatDateTimeWithTemporalSupport(isolate, date_time_format, x, + method_name); +} + namespace { Maybe<bool> IsPropertyUndefined(Isolate* isolate, Handle<JSObject> options, @@ -1244,27 +1909,22 @@ std::unique_ptr<icu::SimpleDateFormat> CreateICUDateFormatFromCache( cache.Pointer()->Create(icu_locale, skeleton, generator, hc)); } -icu::UnicodeString SkeletonFromDateFormat( - const icu::SimpleDateFormat& icu_date_format) { - icu::UnicodeString pattern; - pattern = icu_date_format.toPattern(pattern); - - UErrorCode status = U_ZERO_ERROR; - icu::UnicodeString skeleton = - icu::DateTimePatternGenerator::staticGetSkeleton(pattern, status); - DCHECK(U_SUCCESS(status)); - return skeleton; -} - -icu::DateIntervalFormat* LazyCreateDateIntervalFormat( - Isolate* isolate, Handle<JSDateTimeFormat> date_time_format) { +// We treat PatternKind::kDate different than other because most of the +// pre-existing usage are using the formatter with Date() and Temporal is +// new and not yet adopted by the web yet. We try to optimize the performance +// and memory usage for the pre-existing code so we cache for it. +// We may later consider caching Temporal one also if the usage increase. +// Right now we want to avoid making the constructor more expensive and +// increasing overhead in the object. +std::unique_ptr<icu::DateIntervalFormat> LazyCreateDateIntervalFormat( + Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, + PatternKind kind) { Managed<icu::DateIntervalFormat> managed_format = date_time_format->icu_date_interval_format(); - if (managed_format.get()) { - return managed_format.raw(); + if (kind == PatternKind::kDate && managed_format.get()) { + return std::unique_ptr<icu::DateIntervalFormat>( + managed_format.raw()->clone()); } - icu::SimpleDateFormat* icu_simple_date_format = - date_time_format->icu_simple_date_format().raw(); UErrorCode status = U_ZERO_ERROR; icu::Locale loc = *(date_time_format->icu_locale().raw()); @@ -1275,18 +1935,25 @@ icu::DateIntervalFormat* LazyCreateDateIntervalFormat( loc.setUnicodeKeywordValue("hc", hcString, status); } + icu::SimpleDateFormat* icu_simple_date_format = + date_time_format->icu_simple_date_format().raw(); + + icu::UnicodeString skeleton = GetSkeletonForPatternKind( + SkeletonFromDateFormat(*icu_simple_date_format), kind); + std::unique_ptr<icu::DateIntervalFormat> date_interval_format( - icu::DateIntervalFormat::createInstance( - SkeletonFromDateFormat(*icu_simple_date_format), loc, status)); - if (U_FAILURE(status)) { - return nullptr; - } + icu::DateIntervalFormat::createInstance(skeleton, loc, status)); + DCHECK(U_SUCCESS(status)); date_interval_format->setTimeZone(icu_simple_date_format->getTimeZone()); + if (kind != PatternKind::kDate) { + return date_interval_format; + } Handle<Managed<icu::DateIntervalFormat>> managed_interval_format = Managed<icu::DateIntervalFormat>::FromUniquePtr( isolate, 0, std::move(date_interval_format)); date_time_format->set_icu_date_interval_format(*managed_interval_format); - return (*managed_interval_format).raw(); + return std::unique_ptr<icu::DateIntervalFormat>( + (*managed_interval_format).raw()->clone()); } JSDateTimeFormat::HourCycle HourCycleFromPattern( @@ -1726,11 +2393,13 @@ MaybeHandle<JSDateTimeFormat> JSDateTimeFormat::New( if (item.property == "timeZoneName") { // Let _value_ be ? GetNumberOption(options, "fractionalSecondDigits", 1, // 3, *undefined*). The *undefined* is represented by value 0 here. - Maybe<int> maybe_fsd = GetNumberOption( - isolate, options, factory->fractionalSecondDigits_string(), 1, 3, 0); - MAYBE_RETURN(maybe_fsd, MaybeHandle<JSDateTimeFormat>()); + int fsd; + MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, fsd, + GetNumberOption(isolate, options, + factory->fractionalSecondDigits_string(), 1, 3, 0), + Handle<JSDateTimeFormat>()); // Convert fractionalSecondDigits to skeleton. - int fsd = maybe_fsd.FromJust(); for (int i = 0; i < fsd; i++) { skeleton += "S"; } @@ -2000,25 +2669,102 @@ Handle<String> IcuDateFieldIdToDateType(int32_t field_id, Isolate* isolate) { } } -} // namespace +MaybeHandle<JSArray> FieldPositionIteratorToArray( + Isolate* isolate, const icu::UnicodeString& formatted, + icu::FieldPositionIterator fp_iter, bool output_source); -MaybeHandle<JSArray> JSDateTimeFormat::FormatToParts( +MaybeHandle<JSArray> FormatMillisecondsByKindToArray( + Isolate* isolate, const icu::SimpleDateFormat& date_format, + PatternKind kind, double x, bool output_source) { + icu::FieldPositionIterator fp_iter; + UErrorCode status = U_ZERO_ERROR; + icu::UnicodeString formatted = + CallICUFormat(date_format, kind, x, &fp_iter, status); + if (U_FAILURE(status)) { + THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), JSArray); + } + return FieldPositionIteratorToArray(isolate, formatted, fp_iter, + output_source); +} +MaybeHandle<JSArray> FormatMillisecondsByKindToArrayOutputSource( + Isolate* isolate, const icu::SimpleDateFormat& date_format, + PatternKind kind, double x) { + return FormatMillisecondsByKindToArray(isolate, date_format, kind, x, true); +} + +MaybeHandle<JSArray> FormatToPartsWithTemporalSupport( Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, - double date_value, bool output_source) { - Factory* factory = isolate->factory(); + Handle<Object> x, bool output_source, const char* method_name) { icu::SimpleDateFormat* format = date_time_format->icu_simple_date_format().raw(); DCHECK_NOT_NULL(format); + // 1. Let x be ? HandleDateTimeValue(dateTimeFormat, x). + DateTimeValueRecord x_record; + MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, x_record, + HandleDateTimeValue( + isolate, *format, + GetCalendar(isolate, *format, date_time_format->alt_calendar()), x, + method_name), + Handle<JSArray>()); + + return FormatMillisecondsByKindToArray(isolate, *format, x_record.kind, + x_record.epoch_milliseconds, + output_source); +} + +MaybeHandle<JSArray> FormatMillisecondsToArray( + Isolate* isolate, const icu::SimpleDateFormat& format, double value, + bool output_source) { icu::UnicodeString formatted; icu::FieldPositionIterator fp_iter; - icu::FieldPosition fp; UErrorCode status = U_ZERO_ERROR; - format->format(date_value, formatted, &fp_iter, status); + format.format(value, formatted, &fp_iter, status); if (U_FAILURE(status)) { THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), JSArray); } + return FieldPositionIteratorToArray(isolate, formatted, fp_iter, + output_source); +} +MaybeHandle<JSArray> FormatMillisecondsToArrayOutputSource( + Isolate* isolate, const icu::SimpleDateFormat& format, double value) { + return FormatMillisecondsToArray(isolate, format, value, true); +} +} // namespace + +MaybeHandle<JSArray> JSDateTimeFormat::FormatToParts( + Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, + Handle<Object> x, bool output_source, const char* method_name) { + Factory* factory = isolate->factory(); + if (FLAG_harmony_temporal) { + return FormatToPartsWithTemporalSupport(isolate, date_time_format, x, + output_source, method_name); + } + + if (x->IsUndefined(isolate)) { + x = factory->NewNumber(JSDate::CurrentTimeValue(isolate)); + } else { + ASSIGN_RETURN_ON_EXCEPTION(isolate, x, Object::ToNumber(isolate, x), + JSArray); + } + + double date_value = DateCache::TimeClip(x->Number()); + if (std::isnan(date_value)) { + THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kInvalidTimeValue), + JSArray); + } + return FormatMillisecondsToArray( + isolate, *(date_time_format->icu_simple_date_format().raw()), date_value, + output_source); +} +namespace { +MaybeHandle<JSArray> FieldPositionIteratorToArray( + Isolate* isolate, const icu::UnicodeString& formatted, + icu::FieldPositionIterator fp_iter, bool output_source) { + Factory* factory = isolate->factory(); + icu::FieldPosition fp; Handle<JSArray> result = factory->NewJSArray(0); int32_t length = formatted.length(); if (length == 0) return result; @@ -2080,6 +2826,8 @@ MaybeHandle<JSArray> JSDateTimeFormat::FormatToParts( return result; } +} // namespace + const std::set<std::string>& JSDateTimeFormat::GetAvailableLocales() { return Intl::GetAvailableLocalesForDateFormat(); } @@ -2118,29 +2866,32 @@ Maybe<bool> AddPartForFormatRange( return Just(true); } -MaybeHandle<String> FormattedToString(Isolate* isolate, - const icu::FormattedValue& formatted, - bool* outputRange) { +// If this function return a value, it could be a throw of TypeError, or normal +// formatted string. If it return a nullopt the caller should call the fallback +// function. +base::Optional<MaybeHandle<String>> FormattedToString( + Isolate* isolate, const icu::FormattedValue& formatted) { UErrorCode status = U_ZERO_ERROR; icu::UnicodeString result = formatted.toString(status); if (U_FAILURE(status)) { THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), String); } - *outputRange = false; icu::ConstrainedFieldPosition cfpos; while (formatted.nextPosition(cfpos, status)) { if (cfpos.getCategory() == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) { - *outputRange = true; - break; + return Intl::ToString(isolate, result); } } - return Intl::ToString(isolate, result); + return base::nullopt; } // A helper function to convert the FormattedDateInterval to a // MaybeHandle<JSArray> for the implementation of formatRangeToParts. -MaybeHandle<JSArray> FormattedDateIntervalToJSArray( - Isolate* isolate, const icu::FormattedValue& formatted, bool* outputRange) { +// If this function return a value, it could be a throw of TypeError, or normal +// formatted parts in JSArray. If it return a nullopt the caller should call +// the fallback function. +base::Optional<MaybeHandle<JSArray>> FormattedDateIntervalToJSArray( + Isolate* isolate, const icu::FormattedValue& formatted) { UErrorCode status = U_ZERO_ERROR; icu::UnicodeString result = formatted.toString(status); @@ -2150,7 +2901,7 @@ MaybeHandle<JSArray> FormattedDateIntervalToJSArray( int index = 0; int32_t previous_end_pos = 0; Intl::FormatRangeSourceTracker tracker; - *outputRange = false; + bool output_range = false; while (formatted.nextPosition(cfpos, status)) { int32_t category = cfpos.getCategory(); int32_t field = cfpos.getField(); @@ -2159,7 +2910,7 @@ MaybeHandle<JSArray> FormattedDateIntervalToJSArray( if (category == UFIELD_CATEGORY_DATE_INTERVAL_SPAN) { DCHECK_LE(field, 2); - *outputRange = true; + output_range = true; tracker.Add(field, start, limit); } else { DCHECK(category == UFIELD_CATEGORY_DATE); @@ -2193,19 +2944,22 @@ MaybeHandle<JSArray> FormattedDateIntervalToJSArray( } JSObject::ValidateElements(*array); - return array; + if (output_range) return array; + return base::nullopt; } // The shared code between formatRange and formatRangeToParts -template <typename T, - MaybeHandle<T> (*F)(Isolate*, const icu::FormattedValue&, bool*)> -MaybeHandle<T> FormatRangeCommon(Isolate* isolate, - Handle<JSDateTimeFormat> date_time_format, - double x, double y, bool* outputRange) { - // Track newer feature formateRange and formatRangeToParts - isolate->CountUsage(v8::Isolate::UseCounterFeature::kDateTimeFormatRange); - - // #sec-partitiondatetimerangepattern +template <typename T, base::Optional<MaybeHandle<T>> (*Format)( + Isolate*, const icu::FormattedValue&)> +base::Optional<MaybeHandle<T>> CallICUFormatRange( + Isolate* isolate, const icu::DateIntervalFormat* format, + const icu::Calendar* calendar, double x, double y); +// #sec-partitiondatetimerangepattern +template <typename T, base::Optional<MaybeHandle<T>> (*Format)( + Isolate*, const icu::FormattedValue&)> +base::Optional<MaybeHandle<T>> PartitionDateTimeRangePattern( + Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x, + double y, const char* method_name) { // 1. Let x be TimeClip(x). x = DateCache::TimeClip(x); // 2. If x is NaN, throw a RangeError exception. @@ -2221,17 +2975,26 @@ MaybeHandle<T> FormatRangeCommon(Isolate* isolate, T); } - icu::DateIntervalFormat* format = - LazyCreateDateIntervalFormat(isolate, date_time_format); - if (format == nullptr) { + std::unique_ptr<icu::DateIntervalFormat> format(LazyCreateDateIntervalFormat( + isolate, date_time_format, PatternKind::kDate)); + if (format.get() == nullptr) { THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), T); } - UErrorCode status = U_ZERO_ERROR; - icu::SimpleDateFormat* date_format = date_time_format->icu_simple_date_format().raw(); const icu::Calendar* calendar = date_format->getCalendar(); + + return CallICUFormatRange<T, Format>(isolate, format.get(), calendar, x, y); +} + +template <typename T, base::Optional<MaybeHandle<T>> (*Format)( + Isolate*, const icu::FormattedValue&)> +base::Optional<MaybeHandle<T>> CallICUFormatRange( + Isolate* isolate, const icu::DateIntervalFormat* format, + const icu::Calendar* calendar, double x, double y) { + UErrorCode status = U_ZERO_ERROR; + std::unique_ptr<icu::Calendar> c1(calendar->clone()); std::unique_ptr<icu::Calendar> c2(calendar->clone()); c1->setTime(x, status); @@ -2244,35 +3007,124 @@ MaybeHandle<T> FormatRangeCommon(Isolate* isolate, if (U_FAILURE(status)) { THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), T); } - return F(isolate, formatted, outputRange); + return Format(isolate, formatted); +} + +template <typename T, + base::Optional<MaybeHandle<T>> (*Format)(Isolate*, + const icu::FormattedValue&), + MaybeHandle<T> (*Fallback)(Isolate*, const icu::SimpleDateFormat&, + PatternKind, double)> +MaybeHandle<T> FormatRangeCommonWithTemporalSupport( + Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, + Handle<Object> x_obj, Handle<Object> y_obj, const char* method_name) { + // 5. If either of ! IsTemporalObject(x) or ! IsTemporalObject(y) is true, + // then + if (IsTemporalObject(x_obj) || IsTemporalObject(y_obj)) { + // a. If ! SameTemporalType(x, y) is false, throw a TypeError exception. + if (!SameTemporalType(x_obj, y_obj)) { + THROW_NEW_ERROR( + isolate, + NewTypeError(MessageTemplate::kInvalidArgumentForTemporal, y_obj), T); + } + } + // 6. Let x be ? HandleDateTimeValue(dateTimeFormat, x). + icu::SimpleDateFormat* icu_simple_date_format = + date_time_format->icu_simple_date_format().raw(); + Handle<String> date_time_format_calendar = GetCalendar( + isolate, *icu_simple_date_format, date_time_format->alt_calendar()); + DateTimeValueRecord x_record; + MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, x_record, + HandleDateTimeValue(isolate, *icu_simple_date_format, + date_time_format_calendar, x_obj, method_name), + Handle<T>()); + + // 7. Let y be ? HandleDateTimeValue(dateTimeFormat, y). + DateTimeValueRecord y_record; + MAYBE_ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, y_record, + HandleDateTimeValue(isolate, *icu_simple_date_format, + date_time_format_calendar, y_obj, method_name), + Handle<T>()); + + std::unique_ptr<icu::DateIntervalFormat> format( + LazyCreateDateIntervalFormat(isolate, date_time_format, x_record.kind)); + if (format.get() == nullptr) { + THROW_NEW_ERROR(isolate, NewTypeError(MessageTemplate::kIcuError), T); + } + + const icu::Calendar* calendar = + date_time_format->icu_simple_date_format().raw()->getCalendar(); + + base::Optional<MaybeHandle<T>> result = CallICUFormatRange<T, Format>( + isolate, format.get(), calendar, x_record.epoch_milliseconds, + y_record.epoch_milliseconds); + if (result.has_value()) return *result; + return Fallback(isolate, *icu_simple_date_format, x_record.kind, + x_record.epoch_milliseconds); +} + +template <typename T, + base::Optional<MaybeHandle<T>> (*Format)(Isolate*, + const icu::FormattedValue&), + MaybeHandle<T> (*Fallback)(Isolate*, const icu::SimpleDateFormat&, + double)> +MaybeHandle<T> FormatRangeCommon(Isolate* isolate, + Handle<JSDateTimeFormat> date_time_format, + Handle<Object> x_obj, Handle<Object> y_obj, + const char* method_name) { + // 4. Let x be ? ToNumber(startDate). + ASSIGN_RETURN_ON_EXCEPTION(isolate, x_obj, Object::ToNumber(isolate, x_obj), + T); + double x = x_obj->Number(); + // 5. Let y be ? ToNumber(endDate). + ASSIGN_RETURN_ON_EXCEPTION(isolate, y_obj, Object::ToNumber(isolate, y_obj), + T); + double y = y_obj->Number(); + + base::Optional<MaybeHandle<T>> result = + PartitionDateTimeRangePattern<T, Format>(isolate, date_time_format, x, y, + method_name); + if (result.has_value()) return *result; + return Fallback(isolate, *(date_time_format->icu_simple_date_format().raw()), + x); } } // namespace MaybeHandle<String> JSDateTimeFormat::FormatRange( - Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x, - double y) { - bool outputRange = true; - MaybeHandle<String> ret = FormatRangeCommon<String, FormattedToString>( - isolate, date_time_format, x, y, &outputRange); - if (outputRange) { - return ret; - } - return FormatDateTime(isolate, - *(date_time_format->icu_simple_date_format().raw()), x); + Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, + Handle<Object> x, Handle<Object> y, const char* method_name) { + // Track newer feature formateRange and formatRangeToParts + isolate->CountUsage(v8::Isolate::UseCounterFeature::kDateTimeFormatRange); + if (FLAG_harmony_temporal) { + // For Temporal enable support + return FormatRangeCommonWithTemporalSupport< + String, FormattedToString, FormatMillisecondsByKindToString>( + isolate, date_time_format, x, y, method_name); + } + // Pre Temporal implementation + return FormatRangeCommon<String, FormattedToString, FormatDateTime>( + isolate, date_time_format, x, y, method_name); } MaybeHandle<JSArray> JSDateTimeFormat::FormatRangeToParts( - Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, double x, - double y) { - bool outputRange = true; - MaybeHandle<JSArray> ret = - FormatRangeCommon<JSArray, FormattedDateIntervalToJSArray>( - isolate, date_time_format, x, y, &outputRange); - if (outputRange) { - return ret; - } - return JSDateTimeFormat::FormatToParts(isolate, date_time_format, x, true); + Isolate* isolate, Handle<JSDateTimeFormat> date_time_format, + Handle<Object> x, Handle<Object> y, const char* method_name) { + // Track newer feature formateRange and formatRangeToParts + isolate->CountUsage(v8::Isolate::UseCounterFeature::kDateTimeFormatRange); + if (FLAG_harmony_temporal) { + // For Temporal enable support + return FormatRangeCommonWithTemporalSupport< + JSArray, FormattedDateIntervalToJSArray, + FormatMillisecondsByKindToArrayOutputSource>(isolate, date_time_format, + x, y, method_name); + } + // Pre Temporal implementation + return FormatRangeCommon<JSArray, FormattedDateIntervalToJSArray, + FormatMillisecondsToArrayOutputSource>( + isolate, date_time_format, x, y, method_name); } } // namespace internal |