[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 <ftang@chromium.org>
Reviewed-by: Adam Klein <adamk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#81983}
This commit is contained in:
Frank Tang 2022-07-26 15:10:10 -07:00 committed by V8 LUCI CQ
parent a3e4099df1
commit a06680e714
4 changed files with 260 additions and 48 deletions

View File

@ -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<icu::StringEnumeration> enumeration(
icu::TimeZone::createEnumeration());
int32_t curr = 0;
@ -2941,5 +2944,113 @@ Handle<String> Intl::SourceString(Isolate* isolate, FormatRangeSource source) {
}
}
Handle<String> Intl::DefaultTimeZone(Isolate* isolate) {
icu::UnicodeString id;
{
std::unique_ptr<icu::TimeZone> 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<const icu::BasicTimeZone*>(
icu::TimeZone::createTimeZone(icu::UnicodeString(
Intl::TimeZoneIdFromIndex(time_zone_index).c_str(), -1, US_INV)));
}
} // namespace
Maybe<int64_t> Intl::GetTimeZoneOffsetTransitionMilliseconds(
Isolate* isolate, int32_t time_zone_index, int64_t time_ms,
Intl::Transition transition) {
std::unique_ptr<const icu::BasicTimeZone> 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<int64_t>();
}
return Just(static_cast<int64_t>(icu_transition.getTime()));
}
std::vector<int64_t> Intl::GetTimeZonePossibleOffsetMilliseconds(
Isolate* isolate, int32_t time_zone_index, int64_t time_in_millisecond) {
std::unique_ptr<const icu::BasicTimeZone> 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<int64_t> 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<int64_t> Intl::GetTimeZoneOffsetMilliseconds(
Isolate* isolate, int32_t time_zone_index, int64_t time_in_millisecond) {
std::unique_ptr<const icu::BasicTimeZone> 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<int64_t>(raw_offset + dst_offset));
}
} // namespace internal
} // namespace v8

View File

@ -361,6 +361,35 @@ class Intl {
// TimeZone name.
static int32_t GetTimeZoneIndex(Isolate* isolate, Handle<String> identifier);
enum class Transition { kNext, kPrevious };
// Functions to support Temporal
V8_WARN_UNUSED_RESULT static Maybe<int64_t>
GetTimeZoneOffsetTransitionMilliseconds(Isolate* isolate,
int32_t time_zone_index,
int64_t time_ms,
Transition transition);
static Handle<String> DefaultTimeZone(Isolate* isolate);
V8_WARN_UNUSED_RESULT static Maybe<int64_t> GetTimeZoneOffsetMilliseconds(
Isolate* isolate, int32_t time_zone_index, int64_t millisecond);
// This function may return the result, the std::vector<int64_t> 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<int64_t> GetTimeZonePossibleOffsetMilliseconds(
Isolate* isolate, int32_t time_zone_index, int64_t time_ms);
V8_WARN_UNUSED_RESULT static MaybeHandle<String> CanonicalizeTimeZoneName(
Isolate* isolate, Handle<String> identifier);

View File

@ -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<String> DefaultTimeZone(Isolate* isolate) {
TEMPORAL_ENTER_FUNC();
return Intl::DefaultTimeZone(isolate);
}
#else // V8_INTL_SUPPORT
Handle<String> 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<JSTemporalInstant> JSTemporalTimeZone::GetInstantFor(
namespace {
#ifdef V8_INTL_SUPPORT
MaybeHandle<Object> GetIANATimeZoneTransition(Isolate* isolate,
Handle<BigInt> nanoseconds,
int32_t time_zone_index,
Intl::Transition transition) {
if (time_zone_index == JSTemporalTimeZone::kUTCTimeZoneIndex) {
return isolate->factory()->null_value();
}
Handle<BigInt> one_million = BigInt::FromUint64(isolate, 1000000);
Maybe<int64_t> maybe_transition =
Intl::GetTimeZoneOffsetTransitionMilliseconds(
isolate, time_zone_index,
BigInt::Divide(isolate, nanoseconds, one_million)
.ToHandleChecked()
->AsInt64(),
transition);
MAYBE_RETURN(maybe_transition, Handle<Object>());
// 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<Object> GetIANATimeZoneNextTransition(Isolate* isolate,
Handle<BigInt> 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<Object> GetIANATimeZonePreviousTransition(
Isolate* isolate, Handle<BigInt> 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<Object> GetIANATimeZoneOffsetNanoseconds(Isolate* isolate,
Handle<BigInt> 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<Object> GetIANATimeZoneNextTransition(Isolate* isolate,
Handle<BigInt>, int32_t) {
return isolate->factory()->null_value();
}
// #sec-temporal-getianatimezoneprevioustransition
MaybeHandle<Object> GetIANATimeZonePreviousTransition(Isolate* isolate,
Handle<BigInt>, int32_t) {
return isolate->factory()->null_value();
}
MaybeHandle<Object> GetIANATimeZoneOffsetNanoseconds(Isolate* isolate,
Handle<BigInt>,
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<Object> JSTemporalTimeZone::GetPreviousTransition(
// #sec-temporal.timezone.prototype.getpossibleinstantsfor
// #sec-temporal-getianatimezoneepochvalue
MaybeHandle<JSArray> GetIANATimeZoneEpochValueAsArrayOfInstantForUTC(
Isolate* isolate, const DateTimeRecordCommon& date_time) {
Factory* factory = isolate->factory();
// 6. Let possibleInstants be a new empty List.
Handle<BigInt> epoch_nanoseconds = GetEpochFromISOParts(isolate, date_time);
Handle<FixedArray> fixed_array = factory->NewFixedArray(1);
// 7. For each value epochNanoseconds in possibleEpochNanoseconds, do
// a. Let instant be ! CreateTemporalInstant(epochNanoseconds).
Handle<JSTemporalInstant> 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<JSArray> 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<BigInt> epoch_nanoseconds = GetEpochFromISOParts(isolate, date_time);
Handle<FixedArray> 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<BigInt> nanoseconds_in_local_time =
GetEpochFromISOParts(isolate, date_time);
std::vector<int64_t> 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<int32_t>(possible_offset_in_milliseconds.size());
Handle<FixedArray> 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<JSTemporalInstant> 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<JSArray> 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<JSArray> JSTemporalTimeZone::GetPossibleInstantsFor(
Isolate* isolate, Handle<JSTemporalTimeZone> time_zone,

View File

@ -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],