Skip to content

Commit 1d3a87b

Browse files
jungshikCommit Bot
authored and
Commit Bot
committed
Reland "Implement a new spec for timezone offset calculation"
This is a reland of dbdede0 after a webkit layout test (geolocation-api/timestamp.html) was fixed by https://chromium-review.googlesource.com/c/chromium/src/+/994343 . Original change's description: > Implement a new spec for timezone offset calculation > > tc39/ecma262#778 was recently merged > to Ecma 262. > > It changes the way to convert between "local time" and UTC in such > a way that it'd work for all timezones whether or not there has > been any change in the timezone offset of the standard time. For > instance, Europe/Moscow and some parts of US state of Indiana have > changed the standard (non-DST) timezone offset a few times. The > previous spec assumes that the the standard timezone offset is > constant, but the new spec take into account the offset change > history. > > In addition, it specifies a new way to calculate the timezone > offset during a timezone transition (either in and > out of DST or timezone offset shift). > > During a negative transition (e.g. fall backward / getting > out of DST), repeated times are to be interpreted as if the > offset before the transition is in effect. > > During a positive transition (e.g. spring forward / getting > into DST), skipped times are to be treated similarly. That > is, they are to be interpreted as if the offset before the > transition is in effect. > > With icu-timezone-data, v8 is compliant to the new spec for the > past and the future as well as now whether or not the standard > timezone offset of a given timezone has changed over time > (e.g. Europe/Moscow, Pacific/Apia). With icu-timezone-data, > Australia/Lord_Howe (30 minute DST change) also works per spec. > > Without icu-timezone-data, it works only for timezones of which > the standard timezone offset is the same as the current offset > (e.g. most North American timezones other than parts of Indiana) > and of which the DST shift is an hour. For instance, it doesn't work > for Europe/Moscow in 2010 when the standard timezone offset was > +4h because the current (2018) standard timezone offset is +3h. Neither > does it for Lord Howe in Australia with the DST shift of 0.5 hr. > > This CL used to require one of the two ICU CLs below, but not > any more. > > https://chromium-review.googlesource.com/c/chromium/deps/icu/+/572652 > https://chromium-review.googlesource.com/851265 (a proposed CL to the > upstream ICU). > > Bug: v8:3547,chromium:417640,v8:5714 > Cq-Include-Trybots: luci.v8.try:v8_linux_noi18n_rel_ng > Change-Id: Ib162295da5bee31b2390bd0918157014aebd3e33 > Reviewed-on: https://chromium-review.googlesource.com/572148 > Commit-Queue: Jungshik Shin <[email protected]> > Reviewed-by: Daniel Ehrenberg <[email protected]> > Reviewed-by: Michael Lippautz <[email protected]> > Cr-Commit-Position: refs/heads/master@{#52332} Bug: v8:3547, chromium:417640, v8:5714 Change-Id: I47536c111143f75e3cfeecf5d9761c43a98a10f5 Cq-Include-Trybots: luci.v8.try:v8_linux_noi18n_rel_ng;master.tryserver.blink:linux_trusty_blink_rel Reviewed-on: https://chromium-review.googlesource.com/995971 Commit-Queue: Jungshik Shin <[email protected]> Reviewed-by: Clemens Hammacher <[email protected]> Cr-Commit-Position: refs/heads/master@{#52372}
1 parent 539a244 commit 1d3a87b

21 files changed

+435
-88
lines changed

src/base/platform/platform-aix.cc

+5-5
Original file line numberDiff line numberDiff line change
@@ -39,21 +39,21 @@ namespace base {
3939
class AIXTimezoneCache : public PosixTimezoneCache {
4040
const char* LocalTimezone(double time) override;
4141

42-
double LocalTimeOffset() override;
42+
double LocalTimeOffset(double time_ms, bool is_utc) override;
4343

4444
~AIXTimezoneCache() override {}
4545
};
4646

47-
const char* AIXTimezoneCache::LocalTimezone(double time) {
48-
if (std::isnan(time)) return "";
49-
time_t tv = static_cast<time_t>(floor(time / msPerSecond));
47+
const char* AIXTimezoneCache::LocalTimezone(double time_ms) {
48+
if (std::isnan(time_ms)) return "";
49+
time_t tv = static_cast<time_t>(floor(time_ms / msPerSecond));
5050
struct tm tm;
5151
struct tm* t = localtime_r(&tv, &tm);
5252
if (nullptr == t) return "";
5353
return tzname[0]; // The location of the timezone string on AIX.
5454
}
5555

56-
double AIXTimezoneCache::LocalTimeOffset() {
56+
double AIXTimezoneCache::LocalTimeOffset(double time_ms, bool is_utc) {
5757
// On AIX, struct tm does not contain a tm_gmtoff field.
5858
time_t utc = time(nullptr);
5959
DCHECK_NE(utc, -1);

src/base/platform/platform-cygwin.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ uint8_t* RandomizedVirtualAlloc(size_t size, DWORD flags, DWORD protect,
6666
class CygwinTimezoneCache : public PosixTimezoneCache {
6767
const char* LocalTimezone(double time) override;
6868

69-
double LocalTimeOffset() override;
69+
double LocalTimeOffset(double time_ms, bool is_utc) override;
7070

7171
~CygwinTimezoneCache() override {}
7272
};
@@ -80,7 +80,7 @@ const char* CygwinTimezoneCache::LocalTimezone(double time) {
8080
return tzname[0]; // The location of the timezone string on Cygwin.
8181
}
8282

83-
double CygwinTimezoneCache::LocalTimeOffset() {
83+
double LocalTimeOffset(double time_ms, bool is_utc) {
8484
// On Cygwin, struct tm does not contain a tm_gmtoff field.
8585
time_t utc = time(nullptr);
8686
DCHECK_NE(utc, -1);

src/base/platform/platform-posix-time.cc

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ const char* PosixDefaultTimezoneCache::LocalTimezone(double time) {
1818
return t->tm_zone;
1919
}
2020

21-
double PosixDefaultTimezoneCache::LocalTimeOffset() {
21+
double PosixDefaultTimezoneCache::LocalTimeOffset(double time_ms, bool is_utc) {
22+
// Preserve the old behavior for non-ICU implementation by ignoring both
23+
// time_ms and is_utc.
2224
time_t tv = time(nullptr);
2325
struct tm tm;
2426
struct tm* t = localtime_r(&tv, &tm);

src/base/platform/platform-posix-time.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace base {
1313
class PosixDefaultTimezoneCache : public PosixTimezoneCache {
1414
public:
1515
const char* LocalTimezone(double time_ms) override;
16-
double LocalTimeOffset() override;
16+
double LocalTimeOffset(double time_ms, bool is_utc) override;
1717

1818
~PosixDefaultTimezoneCache() override {}
1919
};

src/base/platform/platform-solaris.cc

+2-3
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ namespace base {
3737
class SolarisTimezoneCache : public PosixTimezoneCache {
3838
const char* LocalTimezone(double time) override;
3939

40-
double LocalTimeOffset() override;
41-
40+
double LocalTimeOffset(double time, bool is_utc) override;
4241
~SolarisTimezoneCache() override {}
4342
};
4443

@@ -51,7 +50,7 @@ const char* SolarisTimezoneCache::LocalTimezone(double time) {
5150
return tzname[0]; // The location of the timezone string on Solaris.
5251
}
5352

54-
double SolarisTimezoneCache::LocalTimeOffset() {
53+
double SolarisTimezoneCache::LocalTimeOffset(double time, bool is_utc) {
5554
tzset();
5655
return -static_cast<double>(timezone * msPerSecond);
5756
}

src/base/platform/platform-win32.cc

+4-2
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ class WindowsTimezoneCache : public TimezoneCache {
115115

116116
const char* LocalTimezone(double time) override;
117117

118-
double LocalTimeOffset() override;
118+
double LocalTimeOffset(double time, bool is_utc) override;
119119

120120
double DaylightSavingsOffset(double time) override;
121121

@@ -466,7 +466,9 @@ const char* WindowsTimezoneCache::LocalTimezone(double time) {
466466

467467
// Returns the local time offset in milliseconds east of UTC without
468468
// taking daylight savings time into account.
469-
double WindowsTimezoneCache::LocalTimeOffset() {
469+
double WindowsTimezoneCache::LocalTimeOffset(double time_ms, bool is_utc) {
470+
// Ignore is_utc and time_ms for now. That way, the behavior wouldn't
471+
// change with icu_timezone_data disabled.
470472
// Use current time, rounded to the millisecond.
471473
Win32Time t(OS::TimeCurrentMillis());
472474
// Time::LocalOffset inlcudes any daylight savings offset, so subtract it.

src/base/timezone-cache.h

+1-3
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@ class TimezoneCache {
2020
// ES #sec-local-time-zone-adjustment
2121
// Local Time Zone Adjustment
2222
//
23-
// TODO(littledan): Make more accurate with another parameter along the
24-
// lines of this spec change:
2523
// https://github.com/tc39/ecma262/pull/778
26-
virtual double LocalTimeOffset() = 0;
24+
virtual double LocalTimeOffset(double time_ms, bool is_utc) = 0;
2725

2826
// Called when the local timezone changes
2927
virtual void Clear() = 0;

src/date.cc

+71-1
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,14 @@ void DateCache::ResetDateCache() {
5252
dst_usage_counter_ = 0;
5353
before_ = &dst_[0];
5454
after_ = &dst_[1];
55-
local_offset_ms_ = kInvalidLocalOffsetInMs;
5655
ymd_valid_ = false;
56+
#ifdef V8_INTL_SUPPORT
57+
if (!FLAG_icu_timezone_data) {
58+
#endif
59+
local_offset_ms_ = kInvalidLocalOffsetInMs;
60+
#ifdef V8_INTL_SUPPORT
61+
}
62+
#endif
5763
tz_cache_->Clear();
5864
tz_name_ = nullptr;
5965
dst_tz_name_ = nullptr;
@@ -206,6 +212,70 @@ void DateCache::BreakDownTime(int64_t time_ms, int* year, int* month, int* day,
206212
*ms = time_in_day_ms % 1000;
207213
}
208214

215+
// Implements LocalTimeZonedjustment(t, isUTC)
216+
// ECMA 262 - ES#sec-local-time-zone-adjustment
217+
int DateCache::GetLocalOffsetFromOS(int64_t time_ms, bool is_utc) {
218+
double offset;
219+
#ifdef V8_INTL_SUPPORT
220+
if (FLAG_icu_timezone_data) {
221+
offset = tz_cache_->LocalTimeOffset(static_cast<double>(time_ms), is_utc);
222+
} else {
223+
#endif
224+
// When ICU timezone data is not used, we need to compute the timezone
225+
// offset for a given local time.
226+
//
227+
// The following shows that using DST for (t - LocalTZA - hour) produces
228+
// correct conversion where LocalTZA is the timezone offset in winter (no
229+
// DST) and the timezone offset is assumed to have no historical change.
230+
// Note that it does not work for the past and the future if LocalTZA (no
231+
// DST) is different from the current LocalTZA (no DST). For instance,
232+
// this will break for Europe/Moscow in 2012 ~ 2013 because LocalTZA was
233+
// 4h instead of the current 3h (as of 2018).
234+
//
235+
// Consider transition to DST at local time L1.
236+
// Let L0 = L1 - hour, L2 = L1 + hour,
237+
// U1 = UTC time that corresponds to L1,
238+
// U0 = U1 - hour.
239+
// Transitioning to DST moves local clock one hour forward L1 => L2, so
240+
// U0 = UTC time that corresponds to L0 = L0 - LocalTZA,
241+
// U1 = UTC time that corresponds to L1 = L1 - LocalTZA,
242+
// U1 = UTC time that corresponds to L2 = L2 - LocalTZA - hour.
243+
// Note that DST(U0 - hour) = 0, DST(U0) = 0, DST(U1) = 1.
244+
// U0 = L0 - LocalTZA - DST(L0 - LocalTZA - hour),
245+
// U1 = L1 - LocalTZA - DST(L1 - LocalTZA - hour),
246+
// U1 = L2 - LocalTZA - DST(L2 - LocalTZA - hour).
247+
//
248+
// Consider transition from DST at local time L1.
249+
// Let L0 = L1 - hour,
250+
// U1 = UTC time that corresponds to L1,
251+
// U0 = U1 - hour, U2 = U1 + hour.
252+
// Transitioning from DST moves local clock one hour back L1 => L0, so
253+
// U0 = UTC time that corresponds to L0 (before transition)
254+
// = L0 - LocalTZA - hour.
255+
// U1 = UTC time that corresponds to L0 (after transition)
256+
// = L0 - LocalTZA = L1 - LocalTZA - hour
257+
// U2 = UTC time that corresponds to L1 = L1 - LocalTZA.
258+
// Note that DST(U0) = 1, DST(U1) = 0, DST(U2) = 0.
259+
// U0 = L0 - LocalTZA - DST(L0 - LocalTZA - hour) = L0 - LocalTZA - DST(U0).
260+
// U2 = L1 - LocalTZA - DST(L1 - LocalTZA - hour) = L1 - LocalTZA - DST(U1).
261+
// It is impossible to get U1 from local time.
262+
if (local_offset_ms_ == kInvalidLocalOffsetInMs) {
263+
// This gets the constant LocalTZA (arguments are ignored).
264+
local_offset_ms_ =
265+
tz_cache_->LocalTimeOffset(static_cast<double>(time_ms), is_utc);
266+
}
267+
offset = local_offset_ms_;
268+
if (!is_utc) {
269+
const int kMsPerHour = 3600 * 1000;
270+
time_ms -= (offset + kMsPerHour);
271+
}
272+
offset += DaylightSavingsOffsetInMs(time_ms);
273+
#ifdef V8_INTL_SUPPORT
274+
}
275+
#endif
276+
DCHECK_LT(offset, kInvalidLocalOffsetInMs);
277+
return static_cast<int>(offset);
278+
}
209279

210280
void DateCache::ExtendTheAfterSegment(int time_sec, int offset_ms) {
211281
if (after_->offset_ms == offset_ms &&

src/date.h

+10-55
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,9 @@ class DateCache {
7575
return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
7676
}
7777

78-
79-
// ECMA 262 - 15.9.1.7.
80-
int LocalOffsetInMs() {
81-
if (local_offset_ms_ == kInvalidLocalOffsetInMs) {
82-
local_offset_ms_ = GetLocalOffsetFromOS();
83-
}
84-
return local_offset_ms_;
78+
// ECMA 262 - ES#sec-local-time-zone-adjustment
79+
int LocalOffsetInMs(int64_t time, bool is_utc) {
80+
return GetLocalOffsetFromOS(time, is_utc);
8581
}
8682

8783

@@ -103,53 +99,16 @@ class DateCache {
10399
return static_cast<int>((time_ms - local_ms) / kMsPerMin);
104100
}
105101

106-
// ECMA 262 - 15.9.1.9
107-
// LocalTime(t) = t + LocalTZA + DaylightSavingTA(t)
102+
// ECMA 262 - ES#sec-localtime-t
103+
// LocalTime(t) = t + LocalTZA(t, true)
108104
int64_t ToLocal(int64_t time_ms) {
109-
return time_ms + LocalOffsetInMs() + DaylightSavingsOffsetInMs(time_ms);
105+
return time_ms + LocalOffsetInMs(time_ms, true);
110106
}
111107

112-
// ECMA 262 - 15.9.1.9
113-
// UTC(t) = t - LocalTZA - DaylightSavingTA(t - LocalTZA)
108+
// ECMA 262 - ES#sec-utc-t
109+
// UTC(t) = t - LocalTZA(t, false)
114110
int64_t ToUTC(int64_t time_ms) {
115-
// We need to compute UTC time that corresponds to the given local time.
116-
// Literally following spec here leads to incorrect time computation at
117-
// the points were we transition to and from DST.
118-
//
119-
// The following shows that using DST for (t - LocalTZA - hour) produces
120-
// correct conversion.
121-
//
122-
// Consider transition to DST at local time L1.
123-
// Let L0 = L1 - hour, L2 = L1 + hour,
124-
// U1 = UTC time that corresponds to L1,
125-
// U0 = U1 - hour.
126-
// Transitioning to DST moves local clock one hour forward L1 => L2, so
127-
// U0 = UTC time that corresponds to L0 = L0 - LocalTZA,
128-
// U1 = UTC time that corresponds to L1 = L1 - LocalTZA,
129-
// U1 = UTC time that corresponds to L2 = L2 - LocalTZA - hour.
130-
// Note that DST(U0 - hour) = 0, DST(U0) = 0, DST(U1) = 1.
131-
// U0 = L0 - LocalTZA - DST(L0 - LocalTZA - hour),
132-
// U1 = L1 - LocalTZA - DST(L1 - LocalTZA - hour),
133-
// U1 = L2 - LocalTZA - DST(L2 - LocalTZA - hour).
134-
//
135-
// Consider transition from DST at local time L1.
136-
// Let L0 = L1 - hour,
137-
// U1 = UTC time that corresponds to L1,
138-
// U0 = U1 - hour, U2 = U1 + hour.
139-
// Transitioning from DST moves local clock one hour back L1 => L0, so
140-
// U0 = UTC time that corresponds to L0 (before transition)
141-
// = L0 - LocalTZA - hour.
142-
// U1 = UTC time that corresponds to L0 (after transition)
143-
// = L0 - LocalTZA = L1 - LocalTZA - hour
144-
// U2 = UTC time that corresponds to L1 = L1 - LocalTZA.
145-
// Note that DST(U0) = 1, DST(U1) = 0, DST(U2) = 0.
146-
// U0 = L0 - LocalTZA - DST(L0 - LocalTZA - hour) = L0 - LocalTZA - DST(U0).
147-
// U2 = L1 - LocalTZA - DST(L1 - LocalTZA - hour) = L1 - LocalTZA - DST(U1).
148-
// It is impossible to get U1 from local time.
149-
150-
const int kMsPerHour = 3600 * 1000;
151-
time_ms -= LocalOffsetInMs();
152-
return time_ms - DaylightSavingsOffsetInMs(time_ms - kMsPerHour);
111+
return time_ms - LocalOffsetInMs(time_ms, false);
153112
}
154113

155114

@@ -208,11 +167,7 @@ class DateCache {
208167
return static_cast<int>(tz_cache_->DaylightSavingsOffset(time_ms));
209168
}
210169

211-
virtual int GetLocalOffsetFromOS() {
212-
double offset = tz_cache_->LocalTimeOffset();
213-
DCHECK_LT(offset, kInvalidLocalOffsetInMs);
214-
return static_cast<int>(offset);
215-
}
170+
virtual int GetLocalOffsetFromOS(int64_t time_ms, bool is_utc);
216171

217172
private:
218173
// The implementation relies on the fact that no time zones have

src/intl.cc

+26-7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "src/isolate.h"
1515
#include "src/objects-inl.h"
1616
#include "src/string-case.h"
17+
#include "unicode/basictz.h"
1718
#include "unicode/calendar.h"
1819
#include "unicode/gregocal.h"
1920
#include "unicode/timezone.h"
@@ -373,23 +374,41 @@ icu::TimeZone* ICUTimezoneCache::GetTimeZone() {
373374
return timezone_;
374375
}
375376

376-
bool ICUTimezoneCache::GetOffsets(double time_ms, int32_t* raw_offset,
377-
int32_t* dst_offset) {
377+
bool ICUTimezoneCache::GetOffsets(double time_ms, bool is_utc,
378+
int32_t* raw_offset, int32_t* dst_offset) {
378379
UErrorCode status = U_ZERO_ERROR;
379-
GetTimeZone()->getOffset(time_ms, false, *raw_offset, *dst_offset, status);
380+
// TODO(jshin): ICU TimeZone class handles skipped time differently from
381+
// Ecma 262 (https://github.com/tc39/ecma262/pull/778) and icu::TimeZone
382+
// class does not expose the necessary API. Fixing
383+
// http://bugs.icu-project.org/trac/ticket/13268 would make it easy to
384+
// implement the proposed spec change. A proposed fix for ICU is
385+
// https://chromium-review.googlesource.com/851265 .
386+
// In the meantime, use an internal (still public) API of icu::BasicTimeZone.
387+
// Once it's accepted by the upstream, get rid of cast. Note that casting
388+
// TimeZone to BasicTimeZone is safe because we know that icu::TimeZone used
389+
// here is a BasicTimeZone.
390+
if (is_utc) {
391+
GetTimeZone()->getOffset(time_ms, false, *raw_offset, *dst_offset, status);
392+
} else {
393+
static_cast<const icu::BasicTimeZone*>(GetTimeZone())
394+
->getOffsetFromLocal(time_ms, icu::BasicTimeZone::kFormer,
395+
icu::BasicTimeZone::kFormer, *raw_offset,
396+
*dst_offset, status);
397+
}
398+
380399
return U_SUCCESS(status);
381400
}
382401

383402
double ICUTimezoneCache::DaylightSavingsOffset(double time_ms) {
384403
int32_t raw_offset, dst_offset;
385-
if (!GetOffsets(time_ms, &raw_offset, &dst_offset)) return 0;
404+
if (!GetOffsets(time_ms, true, &raw_offset, &dst_offset)) return 0;
386405
return dst_offset;
387406
}
388407

389-
double ICUTimezoneCache::LocalTimeOffset() {
408+
double ICUTimezoneCache::LocalTimeOffset(double time_ms, bool is_utc) {
390409
int32_t raw_offset, dst_offset;
391-
if (!GetOffsets(icu::Calendar::getNow(), &raw_offset, &dst_offset)) return 0;
392-
return raw_offset;
410+
if (!GetOffsets(time_ms, is_utc, &raw_offset, &dst_offset)) return 0;
411+
return raw_offset + dst_offset;
393412
}
394413

395414
void ICUTimezoneCache::Clear() {

src/intl.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,15 @@ class ICUTimezoneCache : public base::TimezoneCache {
4848

4949
double DaylightSavingsOffset(double time_ms) override;
5050

51-
double LocalTimeOffset() override;
51+
double LocalTimeOffset(double time_ms, bool is_utc) override;
5252

5353
void Clear() override;
5454

5555
private:
5656
icu::TimeZone* GetTimeZone();
5757

58-
bool GetOffsets(double time_ms, int32_t* raw_offset, int32_t* dst_offset);
58+
bool GetOffsets(double time_ms, bool is_utc, int32_t* raw_offset,
59+
int32_t* dst_offset);
5960

6061
icu::TimeZone* timezone_;
6162

test/cctest/test-date.cc

+3-5
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,8 @@ class DateCacheMock: public DateCache {
5353
return rule == nullptr ? 0 : rule->offset_sec * 1000;
5454
}
5555

56-
57-
virtual int GetLocalOffsetFromOS() {
58-
return local_offset_;
56+
virtual int GetLocalOffsetFromOS(int64_t time_sec, bool is_utc) {
57+
return local_offset_ + GetDaylightSavingsOffsetFromOS(time_sec);
5958
}
6059

6160
private:
@@ -113,8 +112,7 @@ static void CheckDST(int64_t time) {
113112
Isolate* isolate = CcTest::i_isolate();
114113
DateCache* date_cache = isolate->date_cache();
115114
int64_t actual = date_cache->ToLocal(time);
116-
int64_t expected = time + date_cache->GetLocalOffsetFromOS() +
117-
date_cache->GetDaylightSavingsOffsetFromOS(time / 1000);
115+
int64_t expected = time + date_cache->GetLocalOffsetFromOS(time, true);
118116
CHECK_EQ(actual, expected);
119117
}
120118

0 commit comments

Comments
 (0)