Commit b16aa83f authored by Frank Tang's avatar Frank Tang Committed by V8 LUCI CQ

[Temporal] Fix DST transition

Refactor the interface between intl and Temporal and pass
the nanosecond in BigInt to intl. Approximate the nanoseconds
to the correct close by millisecond depending on the usage
before calling ICU API and convert the result millisecond into
BigInt in nanosecond before return from intl.

Remove Maybe for function always complete.

Bug: v8:11544
Change-Id: Icc471b80312c513c9415b690804aa624df4a387d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3897165Reviewed-by: 's avatarAdam Klein <adamk@chromium.org>
Commit-Queue: Frank Tang <ftang@chromium.org>
Cr-Commit-Position: refs/heads/main@{#83310}
parent d0944e3e
......@@ -2962,10 +2962,54 @@ const icu::BasicTimeZone* CreateBasicTimeZoneFromIndex(
Intl::TimeZoneIdFromIndex(time_zone_index).c_str(), -1, US_INV)));
}
// ICU only support TimeZone information in millisecond but Temporal require
// nanosecond. For most of the case, we find a approximate millisecond by
// floor to the millisecond just past the nanosecond_epoch. For negative epoch
// value, the BigInt Divide will floor closer to zero so we need to minus 1 if
// the remainder is not zero. For the case of finding previous transition, we
// need to ceil to the millisecond in the near future of the nanosecond_epoch.
enum class Direction { kPast, kFuture };
int64_t ApproximateMillisecondEpoch(Isolate* isolate,
Handle<BigInt> nanosecond_epoch,
Direction direction = Direction::kPast) {
Handle<BigInt> one_million = BigInt::FromUint64(isolate, 1000000);
int64_t ms = BigInt::Divide(isolate, nanosecond_epoch, one_million)
.ToHandleChecked()
->AsInt64();
Handle<BigInt> remainder =
BigInt::Remainder(isolate, nanosecond_epoch, one_million)
.ToHandleChecked();
// If the nanosecond_epoch is not on the exact millisecond
if (remainder->ToBoolean()) {
if (direction == Direction::kPast) {
if (remainder->IsNegative()) {
// If the remaninder is negative, we know we have an negative epoch
// We need to decrease one millisecond.
// Move to the previous millisecond
ms -= 1;
}
} else {
if (!remainder->IsNegative()) {
// Move to the future millisecond
ms += 1;
}
}
}
return ms;
}
// Helper function to convert the milliseconds in int64_t
// to a BigInt in nanoseconds.
Handle<BigInt> MillisecondToNanosecond(Isolate* isolate, int64_t ms) {
return BigInt::Multiply(isolate, BigInt::FromInt64(isolate, ms),
BigInt::FromUint64(isolate, 1000000))
.ToHandleChecked();
}
} // namespace
Maybe<int64_t> Intl::GetTimeZoneOffsetTransitionMilliseconds(
Isolate* isolate, int32_t time_zone_index, int64_t time_ms,
Handle<Object> Intl::GetTimeZoneOffsetTransitionNanoseconds(
Isolate* isolate, int32_t time_zone_index, Handle<BigInt> nanosecond_epoch,
Intl::Transition transition) {
std::unique_ptr<const icu::BasicTimeZone> basic_time_zone(
CreateBasicTimeZoneFromIndex(time_zone_index));
......@@ -2974,56 +3018,77 @@ Maybe<int64_t> Intl::GetTimeZoneOffsetTransitionMilliseconds(
UBool has_transition;
switch (transition) {
case Intl::Transition::kNext:
has_transition =
basic_time_zone->getNextTransition(time_ms, false, icu_transition);
has_transition = basic_time_zone->getNextTransition(
ApproximateMillisecondEpoch(isolate, nanosecond_epoch), false,
icu_transition);
break;
case Intl::Transition::kPrevious:
has_transition = basic_time_zone->getPreviousTransition(time_ms, false,
icu_transition);
has_transition = basic_time_zone->getPreviousTransition(
ApproximateMillisecondEpoch(isolate, nanosecond_epoch,
Direction::kFuture),
false, icu_transition);
break;
}
if (!has_transition) {
return Nothing<int64_t>();
return isolate->factory()->null_value();
}
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) {
// #sec-temporal-getianatimezonenexttransition and
// #sec-temporal-getianatimezoneprevioustransition states:
// "The operation returns null if no such transition exists for which t ≤
// ℤ(nsMaxInstant)." and "The operation returns null if no such transition
// exists for which t ≥ ℤ(nsMinInstant)."
//
// nsMinInstant = -nsMaxInstant = -8.64 × 10^21 => msMinInstant = -8.64 x
// 10^15
constexpr int64_t kMsMinInstant = -8.64e15;
// nsMaxInstant = 10^8 × nsPerDay = 8.64 × 10^21 => msMaxInstant = 8.64 x
// 10^15
constexpr int64_t kMsMaxInstant = 8.64e15;
int64_t time_ms = static_cast<int64_t>(icu_transition.getTime());
if (time_ms < kMsMinInstant || time_ms > kMsMaxInstant) {
return isolate->factory()->null_value();
}
return MillisecondToNanosecond(isolate, time_ms);
}
std::vector<Handle<BigInt>> Intl::GetTimeZonePossibleOffsetNanoseconds(
Isolate* isolate, int32_t time_zone_index,
Handle<BigInt> nanosecond_epoch) {
std::unique_ptr<const icu::BasicTimeZone> basic_time_zone(
CreateBasicTimeZoneFromIndex(time_zone_index));
int64_t time_ms = ApproximateMillisecondEpoch(isolate, nanosecond_epoch);
int32_t raw_offset;
int32_t dst_offset;
UErrorCode status = U_ZERO_ERROR;
basic_time_zone->getOffsetFromLocal(time_in_millisecond, UCAL_TZ_LOCAL_FORMER,
basic_time_zone->getOffsetFromLocal(time_ms, 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
// offset for time_ms interpretted as before a time zone
// transition
int32_t offset_former = raw_offset + dst_offset;
int64_t offset_former = raw_offset + dst_offset;
basic_time_zone->getOffsetFromLocal(time_in_millisecond, UCAL_TZ_LOCAL_LATTER,
basic_time_zone->getOffsetFromLocal(time_ms, 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
// offset for time_ms interpretted as after a time zone
// transition
int32_t offset_latter = raw_offset + dst_offset;
int64_t offset_latter = raw_offset + dst_offset;
std::vector<int64_t> result;
std::vector<Handle<BigInt>> 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);
result.push_back(MillisecondToNanosecond(isolate, 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);
result.push_back(MillisecondToNanosecond(isolate, offset_former));
result.push_back(MillisecondToNanosecond(isolate, 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
......@@ -3033,17 +3098,19 @@ std::vector<int64_t> Intl::GetTimeZonePossibleOffsetMilliseconds(
return result;
}
Maybe<int64_t> Intl::GetTimeZoneOffsetMilliseconds(
Isolate* isolate, int32_t time_zone_index, int64_t time_in_millisecond) {
int64_t Intl::GetTimeZoneOffsetNanoseconds(Isolate* isolate,
int32_t time_zone_index,
Handle<BigInt> nanosecond_epoch) {
std::unique_ptr<const icu::BasicTimeZone> basic_time_zone(
CreateBasicTimeZoneFromIndex(time_zone_index));
int64_t time_ms = ApproximateMillisecondEpoch(isolate, nanosecond_epoch);
int32_t raw_offset;
int32_t dst_offset;
UErrorCode status = U_ZERO_ERROR;
basic_time_zone->getOffset(time_in_millisecond, false, raw_offset, dst_offset,
status);
basic_time_zone->getOffset(time_ms, false, raw_offset, dst_offset, status);
DCHECK(U_SUCCESS(status));
return Just(static_cast<int64_t>(raw_offset + dst_offset));
// Turn ms into ns
return static_cast<int64_t>(raw_offset + dst_offset) * 1000000;
}
} // namespace internal
......
......@@ -365,30 +365,35 @@ class Intl {
// 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);
// Return the epoch of transition in BigInt or null if there are no
// transition.
static Handle<Object> GetTimeZoneOffsetTransitionNanoseconds(
Isolate* isolate, int32_t time_zone_index,
Handle<BigInt> nanosecond_epoch, Transition transition);
// Return the Time Zone offset, in the unit of nanosecond by int64_t, during
// the time of the nanosecond_epoch.
static int64_t GetTimeZoneOffsetNanoseconds(Isolate* isolate,
int32_t time_zone_index,
Handle<BigInt> nanosecond_epoch);
// 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
// 1. While nanosecond_epoch 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
// 3. when nanosecond_epoch 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);
// The unit of the return values in BigInt is nanosecond.
static std::vector<Handle<BigInt>> GetTimeZonePossibleOffsetNanoseconds(
Isolate* isolate, int32_t time_zone_index,
Handle<BigInt> nanosecond_epoch);
static Handle<String> DefaultTimeZone(Isolate* isolate);
V8_WARN_UNUSED_RESULT static MaybeHandle<String> CanonicalizeTimeZoneName(
Isolate* isolate, Handle<String> identifier);
......
......@@ -1279,7 +1279,7 @@ DateTimeRecordCommon GetISOPartsFromEpoch(Isolate* isolate,
// 1. Assert: ! IsValidEpochNanoseconds(ℤ(epochNanoseconds)) is true.
DCHECK(IsValidEpochNanoseconds(isolate, epoch_nanoseconds));
// 2. Let remainderNs be epochNanoseconds modulo 10^6.
Handle<BigInt> million = BigInt::FromInt64(isolate, 1000000);
Handle<BigInt> million = BigInt::FromUint64(isolate, 1000000);
Handle<BigInt> remainder_ns =
BigInt::Remainder(isolate, epoch_nanoseconds, million).ToHandleChecked();
// Need to do some remainder magic to negative remainder.
......@@ -10894,92 +10894,56 @@ 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) {
Handle<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);
// If there are no transition in this timezone, return null.
if (maybe_transition.IsNothing()) {
return isolate->factory()->null_value();
}
// #sec-temporal-getianatimezonenexttransition and
// #sec-temporal-getianatimezoneprevioustransition states:
// "The operation returns null if no such transition exists for which t ≤
// ℤ(nsMaxInstant)." and "The operation returns null if no such transition
// exists for which t ≥ ℤ(nsMinInstant)."
//
// nsMinInstant = -nsMaxInstant = -8.64 × 10^21 => msMinInstant = -8.64 x
// 10^15
constexpr int64_t kMsMinInstant = -8.64e15;
// nsMaxInstant = 10^8 × nsPerDay = 8.64 × 10^21 => msMaxInstant = 8.64 x
// 10^15
constexpr int64_t kMsMaxInstant = 8.64e15;
int64_t ms = maybe_transition.FromJust();
if (ms < kMsMinInstant || ms > kMsMaxInstant) {
return isolate->factory()->null_value();
}
// Convert the transition from milliseconds to nanoseconds.
return BigInt::Multiply(isolate, BigInt::FromInt64(isolate, ms), one_million);
return Intl::GetTimeZoneOffsetTransitionNanoseconds(isolate, time_zone_index,
nanoseconds, transition);
}
// #sec-temporal-getianatimezonenexttransition
MaybeHandle<Object> GetIANATimeZoneNextTransition(Isolate* isolate,
Handle<BigInt> nanoseconds,
int32_t time_zone_index) {
Handle<Object> GetIANATimeZoneNextTransition(Isolate* isolate,
Handle<BigInt> nanoseconds,
int32_t time_zone_index) {
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) {
Handle<Object> GetIANATimeZonePreviousTransition(Isolate* isolate,
Handle<BigInt> nanoseconds,
int32_t time_zone_index) {
return GetIANATimeZoneTransition(isolate, nanoseconds, time_zone_index,
Intl::Transition::kPrevious);
}
MaybeHandle<Object> GetIANATimeZoneOffsetNanoseconds(Isolate* isolate,
Handle<BigInt> nanoseconds,
int32_t time_zone_index) {
Handle<Object> GetIANATimeZoneOffsetNanoseconds(Isolate* isolate,
Handle<BigInt> nanoseconds,
int32_t time_zone_index) {
if (time_zone_index == JSTemporalTimeZone::kUTCTimeZoneIndex) {
return handle(Smi::zero(), isolate);
}
return isolate->factory()->NewNumberFromInt64(
1000000 * Intl::GetTimeZoneOffsetMilliseconds(
isolate, time_zone_index,
BigInt::Divide(isolate, nanoseconds,
BigInt::FromUint64(isolate, 1000000))
.ToHandleChecked()
->AsInt64())
.ToChecked());
Intl::GetTimeZoneOffsetNanoseconds(isolate, time_zone_index,
nanoseconds));
}
#else // V8_INTL_SUPPORT
// #sec-temporal-getianatimezonenexttransition
MaybeHandle<Object> GetIANATimeZoneNextTransition(Isolate* isolate,
Handle<BigInt>, int32_t) {
Handle<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) {
Handle<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) {
Handle<Object> GetIANATimeZoneOffsetNanoseconds(Isolate* isolate,
Handle<BigInt>,
int32_t time_zone_index) {
DCHECK_EQ(time_zone_index, JSTemporalTimeZone::kUTCTimeZoneIndex);
return handle(Smi::zero(), isolate);
}
......@@ -11016,7 +10980,7 @@ MaybeHandle<JSTemporalPlainDateTime> JSTemporalTimeZone::GetPlainDateTimeFor(
// template for shared code of Temporal.TimeZone.prototype.getNextTransition and
// Temporal.TimeZone.prototype.getPreviousTransition
template <MaybeHandle<Object> (*iana_func)(Isolate*, Handle<BigInt>, int32_t)>
template <Handle<Object> (*iana_func)(Isolate*, Handle<BigInt>, int32_t)>
MaybeHandle<Object> GetTransition(Isolate* isolate,
Handle<JSTemporalTimeZone> time_zone,
Handle<Object> starting_point_obj,
......@@ -11037,12 +11001,9 @@ MaybeHandle<Object> GetTransition(Isolate* isolate,
// 5. Let transition be ?
// GetIANATimeZoneNextTransition(startingPoint.[[Nanoseconds]],
// timeZone.[[Identifier]]).
Handle<Object> transition_obj;
ASSIGN_RETURN_ON_EXCEPTION(
isolate, transition_obj,
Handle<Object> transition_obj =
iana_func(isolate, handle(starting_point->nanoseconds(), isolate),
time_zone->time_zone_index()),
Object);
time_zone->time_zone_index());
// 6. If transition is null, return null.
if (transition_obj->IsNull()) {
return isolate->factory()->null_value();
......@@ -11107,24 +11068,16 @@ MaybeHandle<JSArray> GetIANATimeZoneEpochValueAsArrayOfInstant(
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());
std::vector<Handle<BigInt>> possible_offset =
Intl::GetTimeZonePossibleOffsetNanoseconds(isolate, time_zone_index,
nanoseconds_in_local_time);
int32_t array_length =
static_cast<int32_t>(possible_offset_in_milliseconds.size());
int32_t array_length = static_cast<int32_t>(possible_offset.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<BigInt> epoch_nanoseconds =
BigInt::Subtract(isolate, nanoseconds_in_local_time,
BigInt::FromInt64(isolate, offset_in_nanoseconds))
BigInt::Subtract(isolate, nanoseconds_in_local_time, possible_offset[i])
.ToHandleChecked();
// a. If ! IsValidEpochNanoseconds(epochNanoseconds) is false, throw a
// RangeError exception.
......
......@@ -527,10 +527,6 @@
'intl402/Temporal/Calendar/prototype/yearMonthFromFields/order-of-operations': [FAIL],
'built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-with-fractional-days-different-sign': [FAIL],
'built-ins/Temporal/Duration/prototype/total/relativeto-zoneddatetime-with-fractional-days': [FAIL],
'intl402/Temporal/TimeZone/prototype/getNextTransition/subtract-second-and-nanosecond-from-last-transition': [FAIL],
'intl402/Temporal/TimeZone/prototype/getPreviousTransition/nanoseconds-subtracted-or-added-at-dst-transition': [FAIL],
'intl402/Temporal/TimeZone/prototype/getOffsetNanosecondsFor/nanoseconds-subtracted-or-added-at-dst-transition': [FAIL],
'staging/Temporal/Duration/old/limits': [FAIL],
'staging/Temporal/Duration/old/round': [FAIL],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment