From a06680e71490fea2bf01d92f736c94fe6f4217a3 Mon Sep 17 00:00:00 2001 From: Frank Tang Date: Tue, 26 Jul 2022 15:10:10 -0700 Subject: [PATCH] [Temporal][Intl] Implement non UTC timezone in intl Call Intl function which call ICU TimeZone for the calculation of timezone other than UTC Bug: v8:11544 Change-Id: Idc355aaeccc0bed026a7117bb366ee914fa29733 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3783074 Commit-Queue: Frank Tang Reviewed-by: Adam Klein Cr-Commit-Position: refs/heads/main@{#81983} --- src/objects/intl-objects.cc | 113 ++++++++++++++++++++- src/objects/intl-objects.h | 29 ++++++ src/objects/js-temporal-objects.cc | 155 ++++++++++++++++++++++------- test/test262/test262.status | 11 -- 4 files changed, 260 insertions(+), 48 deletions(-) diff --git a/src/objects/intl-objects.cc b/src/objects/intl-objects.cc index 25cc4fdd04..cb03384719 100644 --- a/src/objects/intl-objects.cc +++ b/src/objects/intl-objects.cc @@ -25,6 +25,7 @@ #include "src/objects/js-locale-inl.h" #include "src/objects/js-locale.h" #include "src/objects/js-number-format-inl.h" +#include "src/objects/js-temporal-objects.h" #include "src/objects/managed-inl.h" #include "src/objects/objects-inl.h" #include "src/objects/option-utils.h" @@ -2854,7 +2855,9 @@ bool Intl::IsValidTimeZoneName(const icu::TimeZone& tz) { // Function to support Temporal std::string Intl::TimeZoneIdFromIndex(int32_t index) { - if (index == 0) return "UTC"; + if (index == JSTemporalTimeZone::kUTCTimeZoneIndex) { + return "UTC"; + } std::unique_ptr enumeration( icu::TimeZone::createEnumeration()); int32_t curr = 0; @@ -2941,5 +2944,113 @@ Handle Intl::SourceString(Isolate* isolate, FormatRangeSource source) { } } +Handle Intl::DefaultTimeZone(Isolate* isolate) { + icu::UnicodeString id; + { + std::unique_ptr tz(icu::TimeZone::createDefault()); + tz->getID(id); + } + UErrorCode status = U_ZERO_ERROR; + icu::UnicodeString canonical; + icu::TimeZone::getCanonicalID(id, canonical, status); + DCHECK(U_SUCCESS(status)); + return Intl::ToString(isolate, canonical).ToHandleChecked(); +} + +namespace { + +const icu::BasicTimeZone* CreateBasicTimeZoneFromIndex( + int32_t time_zone_index) { + DCHECK_NE(time_zone_index, 0); + return static_cast( + icu::TimeZone::createTimeZone(icu::UnicodeString( + Intl::TimeZoneIdFromIndex(time_zone_index).c_str(), -1, US_INV))); +} + +} // namespace + +Maybe Intl::GetTimeZoneOffsetTransitionMilliseconds( + Isolate* isolate, int32_t time_zone_index, int64_t time_ms, + Intl::Transition transition) { + std::unique_ptr basic_time_zone( + CreateBasicTimeZoneFromIndex(time_zone_index)); + + icu::TimeZoneTransition icu_transition; + UBool has_transition; + switch (transition) { + case Intl::Transition::kNext: + has_transition = + basic_time_zone->getNextTransition(time_ms, false, icu_transition); + break; + case Intl::Transition::kPrevious: + has_transition = basic_time_zone->getPreviousTransition(time_ms, false, + icu_transition); + break; + } + + if (!has_transition) { + return Nothing(); + } + return Just(static_cast(icu_transition.getTime())); +} + +std::vector Intl::GetTimeZonePossibleOffsetMilliseconds( + Isolate* isolate, int32_t time_zone_index, int64_t time_in_millisecond) { + std::unique_ptr basic_time_zone( + CreateBasicTimeZoneFromIndex(time_zone_index)); + int32_t raw_offset; + int32_t dst_offset; + UErrorCode status = U_ZERO_ERROR; + basic_time_zone->getOffsetFromLocal(time_in_millisecond, UCAL_TZ_LOCAL_FORMER, + UCAL_TZ_LOCAL_FORMER, raw_offset, + dst_offset, status); + DCHECK(U_SUCCESS(status)); + // offset for time_in_milliseconds interpretted as before a time zone + // transition + int32_t offset_former = raw_offset + dst_offset; + + basic_time_zone->getOffsetFromLocal(time_in_millisecond, UCAL_TZ_LOCAL_LATTER, + UCAL_TZ_LOCAL_LATTER, raw_offset, + dst_offset, status); + DCHECK(U_SUCCESS(status)); + // offset for time_in_milliseconds interpretted as after a time zone + // transition + int32_t offset_latter = raw_offset + dst_offset; + + std::vector result; + if (offset_former == offset_latter) { + // For most of the time, when either interpretation are the same, we are not + // in a moment of offset transition based on rule changing: Just return that + // value. + result.push_back(offset_former); + } else if (offset_former > offset_latter) { + // When the input represents a local time repeating multiple times at a + // negative time zone transition (e.g. when the daylight saving time ends + // or the time zone offset is decreased due to a time zone rule change). + result.push_back(offset_former); + result.push_back(offset_latter); + } else { + // If the offset after the transition is greater than the offset before the + // transition, that mean it is in the moment the time "skip" an hour, or two + // (or six in a Time Zone in south pole) in that case there are no possible + // Time Zone offset for that moment and nothing will be added to the result. + } + return result; +} + +Maybe Intl::GetTimeZoneOffsetMilliseconds( + Isolate* isolate, int32_t time_zone_index, int64_t time_in_millisecond) { + std::unique_ptr basic_time_zone( + CreateBasicTimeZoneFromIndex(time_zone_index)); + int32_t raw_offset; + int32_t dst_offset; + UErrorCode status = U_ZERO_ERROR; + basic_time_zone->getOffsetFromLocal(time_in_millisecond, UCAL_TZ_LOCAL_FORMER, + UCAL_TZ_LOCAL_FORMER, raw_offset, + dst_offset, status); + DCHECK(U_SUCCESS(status)); + return Just(static_cast(raw_offset + dst_offset)); +} + } // namespace internal } // namespace v8 diff --git a/src/objects/intl-objects.h b/src/objects/intl-objects.h index 4fbfe349da..0ac4b14096 100644 --- a/src/objects/intl-objects.h +++ b/src/objects/intl-objects.h @@ -361,6 +361,35 @@ class Intl { // TimeZone name. static int32_t GetTimeZoneIndex(Isolate* isolate, Handle identifier); + enum class Transition { kNext, kPrevious }; + + // Functions to support Temporal + + V8_WARN_UNUSED_RESULT static Maybe + GetTimeZoneOffsetTransitionMilliseconds(Isolate* isolate, + int32_t time_zone_index, + int64_t time_ms, + Transition transition); + + static Handle DefaultTimeZone(Isolate* isolate); + + V8_WARN_UNUSED_RESULT static Maybe GetTimeZoneOffsetMilliseconds( + Isolate* isolate, int32_t time_zone_index, int64_t millisecond); + + // This function may return the result, the std::vector in one of + // the following three condictions: + // 1. While time_in_millisecond fall into the daylight saving time change + // moment that skipped one (or two or even six, in some Time Zone) hours + // later in local time: + // [], + // 2. In other moment not during daylight saving time change: + // [offset_former], and + // 3. when time_in_millisecond fall into they daylight saving time change hour + // which the clock time roll back one (or two or six, in some Time Zone) hour: + // [offset_former, offset_later] + static std::vector GetTimeZonePossibleOffsetMilliseconds( + Isolate* isolate, int32_t time_zone_index, int64_t time_ms); + V8_WARN_UNUSED_RESULT static MaybeHandle CanonicalizeTimeZoneName( Isolate* isolate, Handle identifier); diff --git a/src/objects/js-temporal-objects.cc b/src/objects/js-temporal-objects.cc index 74eab15939..41f2384caa 100644 --- a/src/objects/js-temporal-objects.cc +++ b/src/objects/js-temporal-objects.cc @@ -505,12 +505,17 @@ inline double modulo(double a, int32_t b) { return a - std::floor(a / b) * b; } isolate->factory()->NewStringFromStaticChars(TEMPORAL_DEBUG_INFO)) // #sec-defaulttimezone +#ifdef V8_INTL_SUPPORT +Handle DefaultTimeZone(Isolate* isolate) { + TEMPORAL_ENTER_FUNC(); + return Intl::DefaultTimeZone(isolate); +} +#else // V8_INTL_SUPPORT Handle DefaultTimeZone(Isolate* isolate) { TEMPORAL_ENTER_FUNC(); - // For now, always return "UTC" - // TODO(ftang) implement behavior specified in #sup-defaulttimezone return isolate->factory()->UTC_string(); } +#endif // V8_INTL_SUPPORT // #sec-temporal-isodatetimewithinlimits bool ISODateTimeWithinLimits(Isolate* isolate, @@ -10743,45 +10748,82 @@ MaybeHandle JSTemporalTimeZone::GetInstantFor( namespace { +#ifdef V8_INTL_SUPPORT +MaybeHandle GetIANATimeZoneTransition(Isolate* isolate, + Handle nanoseconds, + int32_t time_zone_index, + Intl::Transition transition) { + if (time_zone_index == JSTemporalTimeZone::kUTCTimeZoneIndex) { + return isolate->factory()->null_value(); + } + + Handle one_million = BigInt::FromUint64(isolate, 1000000); + Maybe maybe_transition = + Intl::GetTimeZoneOffsetTransitionMilliseconds( + isolate, time_zone_index, + BigInt::Divide(isolate, nanoseconds, one_million) + .ToHandleChecked() + ->AsInt64(), + transition); + MAYBE_RETURN(maybe_transition, Handle()); + // If there are no transition in this timezone, return null. + if (maybe_transition.IsNothing()) { + return isolate->factory()->null_value(); + } + + // Convert the transition from milliseconds to nanoseconds. + return BigInt::Multiply( + isolate, BigInt::FromInt64(isolate, maybe_transition.FromJust()), + one_million); +} // #sec-temporal-getianatimezonenexttransition MaybeHandle GetIANATimeZoneNextTransition(Isolate* isolate, Handle nanoseconds, int32_t time_zone_index) { -#ifdef V8_INTL_SUPPORT - // TODO(ftang) Add support for non UTC timezone - DCHECK_EQ(time_zone_index, JSTemporalTimeZone::kUTCTimeZoneIndex); - return isolate->factory()->null_value(); -#else // V8_INTL_SUPPORT - return isolate->factory()->null_value(); -#endif // V8_INTL_SUPPORT + return GetIANATimeZoneTransition(isolate, nanoseconds, time_zone_index, + Intl::Transition::kNext); } - // #sec-temporal-getianatimezoneprevioustransition MaybeHandle GetIANATimeZonePreviousTransition( Isolate* isolate, Handle nanoseconds, int32_t time_zone_index) { -#ifdef V8_INTL_SUPPORT - // TODO(ftang) Add support for non UTC timezone - DCHECK_EQ(time_zone_index, JSTemporalTimeZone::kUTCTimeZoneIndex); - return isolate->factory()->null_value(); -#else // V8_INTL_SUPPORT - return isolate->factory()->null_value(); -#endif // V8_INTL_SUPPORT + return GetIANATimeZoneTransition(isolate, nanoseconds, time_zone_index, + Intl::Transition::kPrevious); } MaybeHandle GetIANATimeZoneOffsetNanoseconds(Isolate* isolate, Handle nanoseconds, int32_t time_zone_index) { -#ifdef V8_INTL_SUPPORT - // TODO(ftang) Handle offset for other timezone if (time_zone_index == JSTemporalTimeZone::kUTCTimeZoneIndex) { return handle(Smi::zero(), isolate); } - UNREACHABLE(); + + return isolate->factory()->NewNumberFromInt64( + 1000000 * Intl::GetTimeZoneOffsetMilliseconds( + isolate, time_zone_index, + BigInt::Divide(isolate, nanoseconds, + BigInt::FromUint64(isolate, 1000000)) + .ToHandleChecked() + ->AsInt64()) + .ToChecked()); +} #else // V8_INTL_SUPPORT +// #sec-temporal-getianatimezonenexttransition +MaybeHandle GetIANATimeZoneNextTransition(Isolate* isolate, + Handle, int32_t) { + return isolate->factory()->null_value(); +} +// #sec-temporal-getianatimezoneprevioustransition +MaybeHandle GetIANATimeZonePreviousTransition(Isolate* isolate, + Handle, int32_t) { + return isolate->factory()->null_value(); +} +MaybeHandle GetIANATimeZoneOffsetNanoseconds(Isolate* isolate, + Handle, + int32_t time_zone_index) { DCHECK_EQ(time_zone_index, JSTemporalTimeZone::kUTCTimeZoneIndex); return handle(Smi::zero(), isolate); -#endif // V8_INTL_SUPPORT } +#endif // V8_INTL_SUPPORT } // namespace @@ -10870,35 +10912,76 @@ MaybeHandle JSTemporalTimeZone::GetPreviousTransition( // #sec-temporal.timezone.prototype.getpossibleinstantsfor // #sec-temporal-getianatimezoneepochvalue +MaybeHandle GetIANATimeZoneEpochValueAsArrayOfInstantForUTC( + Isolate* isolate, const DateTimeRecordCommon& date_time) { + Factory* factory = isolate->factory(); + // 6. Let possibleInstants be a new empty List. + Handle epoch_nanoseconds = GetEpochFromISOParts(isolate, date_time); + Handle fixed_array = factory->NewFixedArray(1); + // 7. For each value epochNanoseconds in possibleEpochNanoseconds, do + // a. Let instant be ! CreateTemporalInstant(epochNanoseconds). + Handle instant = + temporal::CreateTemporalInstant(isolate, epoch_nanoseconds) + .ToHandleChecked(); + // b. Append instant to possibleInstants. + fixed_array->set(0, *instant); + // 8. Return ! CreateArrayFromList(possibleInstants). + return factory->NewJSArrayWithElements(fixed_array); +} + +#ifdef V8_INTL_SUPPORT MaybeHandle GetIANATimeZoneEpochValueAsArrayOfInstant( Isolate* isolate, int32_t time_zone_index, const DateTimeRecordCommon& date_time) { Factory* factory = isolate->factory(); - // 6. Let possibleInstants be a new empty List. - Handle epoch_nanoseconds = GetEpochFromISOParts(isolate, date_time); - Handle fixed_array; if (time_zone_index == JSTemporalTimeZone::kUTCTimeZoneIndex) { - fixed_array = factory->NewFixedArray(1); - // 7. For each value epochNanoseconds in possibleEpochNanoseconds, do - // a. Let instant be ! CreateTemporalInstant(epochNanoseconds). + return GetIANATimeZoneEpochValueAsArrayOfInstantForUTC(isolate, date_time); + } + + // For TimeZone other than UTC, call ICU indirectly from Intl + Handle nanoseconds_in_local_time = + GetEpochFromISOParts(isolate, date_time); + + std::vector possible_offset_in_milliseconds = + Intl::GetTimeZonePossibleOffsetMilliseconds( + isolate, time_zone_index, + BigInt::Divide(isolate, nanoseconds_in_local_time, + BigInt::FromUint64(isolate, 1000000)) + .ToHandleChecked() + ->AsInt64()); + + int32_t array_length = + static_cast(possible_offset_in_milliseconds.size()); + Handle fixed_array = factory->NewFixedArray(array_length); + + for (int32_t i = 0; i < array_length; i++) { + int64_t offset_in_nanoseconds = + possible_offset_in_milliseconds[i] * 1000000; Handle instant = - temporal::CreateTemporalInstant(isolate, epoch_nanoseconds) + temporal::CreateTemporalInstant( + isolate, + BigInt::Subtract(isolate, nanoseconds_in_local_time, + BigInt::FromInt64(isolate, offset_in_nanoseconds)) + .ToHandleChecked()) .ToHandleChecked(); // b. Append instant to possibleInstants. - fixed_array->set(0, *instant); - } else { -#ifdef V8_INTL_SUPPORT - // TODO(ftang) put in 402 version of implementation calling intl classes. - UNIMPLEMENTED(); -#else - UNREACHABLE(); -#endif // V8_INTL_SUPPORT + fixed_array->set(i, *(instant)); } // 8. Return ! CreateArrayFromList(possibleInstants). return factory->NewJSArrayWithElements(fixed_array); } +#else // V8_INTL_SUPPORT + +MaybeHandle GetIANATimeZoneEpochValueAsArrayOfInstant( + Isolate* isolate, int32_t time_zone_index, + const DateTimeRecordCommon& date_time) { + DCHECK_EQ(time_zone_index, JSTemporalTimeZone::kUTCTimeZoneIndex); + return GetIANATimeZoneEpochValueAsArrayOfInstantForUTC(isolate, date_time); +} +#endif // V8_INTL_SUPPORT + // #sec-temporal.timezone.prototype.getpossibleinstantsfor MaybeHandle JSTemporalTimeZone::GetPossibleInstantsFor( Isolate* isolate, Handle time_zone, diff --git a/test/test262/test262.status b/test/test262/test262.status index 562660fe61..464453a14c 100644 --- a/test/test262/test262.status +++ b/test/test262/test262.status @@ -606,7 +606,6 @@ 'intl402/Temporal/Instant/prototype/toLocaleString/locales-undefined': [FAIL], 'intl402/Temporal/Instant/prototype/toLocaleString/options-conflict': [FAIL], 'intl402/Temporal/Instant/prototype/toLocaleString/options-undefined': [FAIL], - 'intl402/Temporal/Now/plainDateTimeISO/timezone-string-datetime': [FAIL], 'intl402/Temporal/PlainDate/prototype/toLocaleString/locales-undefined': [FAIL], 'intl402/Temporal/PlainDate/prototype/toLocaleString/options-conflict': [FAIL], 'intl402/Temporal/PlainDate/prototype/toLocaleString/options-undefined': [FAIL], @@ -632,9 +631,6 @@ 'intl402/Temporal/PlainYearMonth/prototype/toLocaleString/options-undefined': [FAIL], 'intl402/Temporal/PlainYearMonth/prototype/toLocaleString/resolved-time-zone': [FAIL], 'intl402/Temporal/PlainYearMonth/prototype/toLocaleString/timezone-getoffsetnanosecondsfor-not-callable': [FAIL], - 'intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/instant-string': [FAIL], - 'intl402/Temporal/TimeZone/prototype/getOffsetStringFor/instant-string': [FAIL], - 'intl402/Temporal/TimeZone/prototype/getPlainDateTimeFor/instant-string': [FAIL], 'intl402/Temporal/ZonedDateTime/prototype/since/infinity-throws-rangeerror': [FAIL], 'intl402/Temporal/ZonedDateTime/prototype/toLocaleString/locales-undefined': [FAIL], 'intl402/Temporal/ZonedDateTime/prototype/toLocaleString/options-conflict': [FAIL], @@ -644,12 +640,6 @@ 'intl402/Temporal/Duration/prototype/round/relativeto-string-datetime': [FAIL], 'intl402/Temporal/Duration/prototype/subtract/relativeto-string-datetime': [FAIL], 'intl402/Temporal/Duration/prototype/total/relativeto-string-datetime': [FAIL], - 'intl402/Temporal/Instant/prototype/toString/timezone-string-datetime': [FAIL], - 'intl402/Temporal/PlainDate/prototype/toZonedDateTime/timezone-string-datetime': [FAIL], - 'intl402/Temporal/PlainDateTime/prototype/toZonedDateTime/timezone-string-datetime': [FAIL], - 'intl402/Temporal/PlainTime/prototype/toZonedDateTime/timezone-string-datetime': [FAIL], - 'intl402/Temporal/ZonedDateTime/from/timezone-string-datetime': [FAIL], - 'intl402/Temporal/ZonedDateTime/prototype/equals/timezone-string-datetime': [FAIL], 'intl402/Temporal/ZonedDateTime/prototype/since/timezone-string-datetime': [FAIL], 'intl402/Temporal/ZonedDateTime/prototype/until/timezone-string-datetime': [FAIL], 'intl402/Temporal/PlainYearMonth/from/argument-object': [FAIL], @@ -734,7 +724,6 @@ 'built-ins/Temporal/ZonedDateTime/prototype/toString/rounding-direction': [FAIL], 'built-ins/Temporal/ZonedDateTime/prototype/toString/roundingmode-ceil': [FAIL], 'built-ins/Temporal/ZonedDateTime/prototype/until/options-wrong-type': [FAIL], - 'intl402/Temporal/Instant/prototype/toString/timezone-offset': [FAIL], 'intl402/Temporal/PlainDateTime/prototype/withPlainDate/argument-string-calendar': [FAIL], 'built-ins/Temporal/Calendar/from/calendar-number': [FAIL],