Commit b213f239 authored by littledan's avatar littledan Committed by Commit bot

[date] Add ICU backend for timezone info behind a flag

This patch implements a timezone backend which is based on ICU, rather
than operating system calls. It can be turned on by passing the
--icu-timezone-data flag. The goal here is to take advantage of ICU's
data, which is more complete than the data that some system calls expose.
For example, without any special code, this patch fixes the time zone
of Lord Howe Island to have a correct 30 minute DST offset, rather than
60 minutes as the OS backends assume it to have.

Unfortunately, the parenthized timezone name in Date.prototype.toString()
differs across platforms. This patch chooses the long timezone name,
which matches Windows behavior and might be the most intelligible, but
the web compatibility impact is unclear.

BUG=v8:6031,v8:2137,v8:6076

Review-Url: https://codereview.chromium.org/2724373002
Cr-Commit-Position: refs/heads/master@{#44562}
parent 9ff68b0c
...@@ -7,6 +7,10 @@ ...@@ -7,6 +7,10 @@
#include "src/objects.h" #include "src/objects.h"
#include "src/objects-inl.h" #include "src/objects-inl.h"
#ifdef V8_I18N_SUPPORT
#include "src/i18n.h"
#endif
namespace v8 { namespace v8 {
namespace internal { namespace internal {
...@@ -21,6 +25,18 @@ static const int kYearsOffset = 400000; ...@@ -21,6 +25,18 @@ static const int kYearsOffset = 400000;
static const char kDaysInMonths[] = static const char kDaysInMonths[] =
{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
DateCache::DateCache()
: stamp_(0),
tz_cache_(
#ifdef V8_I18N_SUPPORT
FLAG_icu_timezone_data ? new ICUTimezoneCache()
: base::OS::CreateTimezoneCache()
#else
base::OS::CreateTimezoneCache()
#endif
) {
ResetDateCache();
}
void DateCache::ResetDateCache() { void DateCache::ResetDateCache() {
static const int kMaxStamp = Smi::kMaxValue; static const int kMaxStamp = Smi::kMaxValue;
......
...@@ -39,9 +39,7 @@ class DateCache { ...@@ -39,9 +39,7 @@ class DateCache {
// It is an invariant of DateCache that cache stamp is non-negative. // It is an invariant of DateCache that cache stamp is non-negative.
static const int kInvalidStamp = -1; static const int kInvalidStamp = -1;
DateCache() : stamp_(0), tz_cache_(base::OS::CreateTimezoneCache()) { DateCache();
ResetDateCache();
}
virtual ~DateCache() { virtual ~DateCache() {
delete tz_cache_; delete tz_cache_;
......
...@@ -254,6 +254,11 @@ HARMONY_STAGED(FLAG_STAGED_FEATURES) ...@@ -254,6 +254,11 @@ HARMONY_STAGED(FLAG_STAGED_FEATURES)
HARMONY_SHIPPING(FLAG_SHIPPING_FEATURES) HARMONY_SHIPPING(FLAG_SHIPPING_FEATURES)
#undef FLAG_SHIPPING_FEATURES #undef FLAG_SHIPPING_FEATURES
#ifdef V8_I18N_SUPPORT
DEFINE_BOOL(icu_timezone_data, false,
"get information about timezones from ICU")
#endif
#ifdef V8_ENABLE_FUTURE #ifdef V8_ENABLE_FUTURE
#define FUTURE_BOOL true #define FUTURE_BOOL true
#else #else
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "src/objects-inl.h" #include "src/objects-inl.h"
#include "src/string-case.h" #include "src/string-case.h"
#include "unicode/brkiter.h" #include "unicode/brkiter.h"
#include "unicode/bytestream.h"
#include "unicode/calendar.h" #include "unicode/calendar.h"
#include "unicode/coll.h" #include "unicode/coll.h"
#include "unicode/curramt.h" #include "unicode/curramt.h"
...@@ -1212,5 +1213,57 @@ MUST_USE_RESULT Object* ConvertCase(Handle<String> s, bool is_upper, ...@@ -1212,5 +1213,57 @@ MUST_USE_RESULT Object* ConvertCase(Handle<String> s, bool is_upper,
return is_upper ? ConvertToUpper(s, isolate) : ConvertToLower(s, isolate); return is_upper ? ConvertToUpper(s, isolate) : ConvertToLower(s, isolate);
} }
ICUTimezoneCache::ICUTimezoneCache() : timezone_(nullptr) { Clear(); }
ICUTimezoneCache::~ICUTimezoneCache() { Clear(); }
const char* ICUTimezoneCache::LocalTimezone(double time_ms) {
bool is_dst = DaylightSavingsOffset(time_ms) != 0;
char* name = is_dst ? dst_timezone_name_ : timezone_name_;
if (name[0] == '\0') {
icu::UnicodeString result;
GetTimeZone()->getDisplayName(is_dst, icu::TimeZone::LONG, result);
result += '\0';
icu::CheckedArrayByteSink byte_sink(name, kMaxTimezoneChars);
result.toUTF8(byte_sink);
CHECK(!byte_sink.Overflowed());
}
return const_cast<const char*>(name);
}
icu::TimeZone* ICUTimezoneCache::GetTimeZone() {
if (timezone_ == nullptr) {
timezone_ = icu::TimeZone::createDefault();
}
return timezone_;
}
bool ICUTimezoneCache::GetOffsets(double time_ms, int32_t* raw_offset,
int32_t* dst_offset) {
UErrorCode status = U_ZERO_ERROR;
GetTimeZone()->getOffset(time_ms, false, *raw_offset, *dst_offset, status);
return U_SUCCESS(status);
}
double ICUTimezoneCache::DaylightSavingsOffset(double time_ms) {
int32_t raw_offset, dst_offset;
if (!GetOffsets(time_ms, &raw_offset, &dst_offset)) return 0;
return dst_offset;
}
double ICUTimezoneCache::LocalTimeOffset() {
int32_t raw_offset, dst_offset;
if (!GetOffsets(icu::Calendar::getNow(), &raw_offset, &dst_offset)) return 0;
return raw_offset;
}
void ICUTimezoneCache::Clear() {
delete timezone_;
timezone_ = nullptr;
timezone_name_[0] = '\0';
dst_timezone_name_[0] = '\0';
}
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#ifndef V8_I18N_H_ #ifndef V8_I18N_H_
#define V8_I18N_H_ #define V8_I18N_H_
#include "src/base/timezone-cache.h"
#include "src/objects.h" #include "src/objects.h"
#include "unicode/uversion.h" #include "unicode/uversion.h"
...@@ -14,6 +15,7 @@ class BreakIterator; ...@@ -14,6 +15,7 @@ class BreakIterator;
class Collator; class Collator;
class DecimalFormat; class DecimalFormat;
class SimpleDateFormat; class SimpleDateFormat;
class TimeZone;
} }
namespace v8 { namespace v8 {
...@@ -138,6 +140,34 @@ MUST_USE_RESULT Object* ConvertToUpper(Handle<String> s, Isolate* isolate); ...@@ -138,6 +140,34 @@ MUST_USE_RESULT Object* ConvertToUpper(Handle<String> s, Isolate* isolate);
MUST_USE_RESULT Object* ConvertCase(Handle<String> s, bool is_upper, MUST_USE_RESULT Object* ConvertCase(Handle<String> s, bool is_upper,
Isolate* isolate); Isolate* isolate);
// ICUTimezoneCache calls out to ICU for TimezoneCache
// functionality in a straightforward way.
class ICUTimezoneCache : public base::TimezoneCache {
public:
ICUTimezoneCache();
~ICUTimezoneCache() override;
const char* LocalTimezone(double time_ms) override;
double DaylightSavingsOffset(double time_ms) override;
double LocalTimeOffset() override;
void Clear() override;
private:
icu::TimeZone* GetTimeZone();
bool GetOffsets(double time_ms, int32_t* raw_offset, int32_t* dst_offset);
icu::TimeZone* timezone_;
static const int32_t kMaxTimezoneChars = 100;
char timezone_name_[kMaxTimezoneChars];
char dst_timezone_name_[kMaxTimezoneChars];
};
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
......
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --icu-timezone-data
// Environment Variables: TZ=Australia/Lord_Howe LC_ALL=en
assertEquals(
"Mon Jan 01 1990 11:00:00 GMT+1100 (Lord Howe Daylight Time)",
new Date("1990-01-01").toString());
assertEquals(
"Fri Jun 01 1990 10:30:00 GMT+1030 (Lord Howe Standard Time)",
new Date("1990-06-01").toString());
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --icu-timezone-data
// Environment Variables: TZ=Europe/Madrid LC_ALL=de
assertEquals(
"Sun Dec 31 1989 00:00:00 GMT+0100 (Mitteleuropäische Normalzeit)",
new Date(1990, 0, 0).toString());
assertEquals(
"Sat Jun 30 1990 00:00:00 GMT+0200 (Mitteleuropäische Sommerzeit)",
new Date(1990, 6, 0).toString());
...@@ -543,6 +543,10 @@ ...@@ -543,6 +543,10 @@
# BUG(v8:4495). # BUG(v8:4495).
'es6/collections': [PASS, ['arch == ia32', FAST_VARIANTS]], 'es6/collections': [PASS, ['arch == ia32', FAST_VARIANTS]],
# Setting the timezone and locale with environment variables unavailable
'icu-date-to-string': [SKIP],
'icu-date-lord-howe': [SKIP],
}], # 'system == windows' }], # 'system == windows'
############################################################################## ##############################################################################
......
...@@ -33,6 +33,7 @@ from testrunner.objects import testcase ...@@ -33,6 +33,7 @@ from testrunner.objects import testcase
FLAGS_PATTERN = re.compile(r"//\s+Flags:(.*)") FLAGS_PATTERN = re.compile(r"//\s+Flags:(.*)")
FILES_PATTERN = re.compile(r"//\s+Files:(.*)") FILES_PATTERN = re.compile(r"//\s+Files:(.*)")
ENV_PATTERN = re.compile(r"//\s+Environment Variables:(.*)")
SELF_SCRIPT_PATTERN = re.compile(r"//\s+Env: TEST_FILE_NAME") SELF_SCRIPT_PATTERN = re.compile(r"//\s+Env: TEST_FILE_NAME")
MODULE_PATTERN = re.compile(r"^// MODULE$", flags=re.MULTILINE) MODULE_PATTERN = re.compile(r"^// MODULE$", flags=re.MULTILINE)
NO_HARNESS_PATTERN = re.compile(r"^// NO HARNESS$", flags=re.MULTILINE) NO_HARNESS_PATTERN = re.compile(r"^// NO HARNESS$", flags=re.MULTILINE)
...@@ -94,6 +95,12 @@ class MjsunitTestSuite(testsuite.TestSuite): ...@@ -94,6 +95,12 @@ class MjsunitTestSuite(testsuite.TestSuite):
flags.append("--isolate") flags.append("--isolate")
flags += files flags += files
env_match = ENV_PATTERN.search(source)
if env_match:
for env_pair in env_match.group(1).strip().split():
var, value = env_pair.split('=')
testcase.env[var] = value
return testcase.flags + flags return testcase.flags + flags
def GetSourceForTest(self, testcase): def GetSourceForTest(self, testcase):
......
...@@ -50,7 +50,7 @@ def Win32SetErrorMode(mode): ...@@ -50,7 +50,7 @@ def Win32SetErrorMode(mode):
return prev_error_mode return prev_error_mode
def RunProcess(verbose, timeout, args, **rest): def RunProcess(verbose, timeout, args, additional_env, **rest):
if verbose: print "#", " ".join(args) if verbose: print "#", " ".join(args)
popen_args = args popen_args = args
prev_error_mode = SEM_INVALID_VALUE prev_error_mode = SEM_INVALID_VALUE
...@@ -64,6 +64,7 @@ def RunProcess(verbose, timeout, args, **rest): ...@@ -64,6 +64,7 @@ def RunProcess(verbose, timeout, args, **rest):
Win32SetErrorMode(error_mode | prev_error_mode) Win32SetErrorMode(error_mode | prev_error_mode)
env = os.environ.copy() env = os.environ.copy()
env.update(additional_env)
# GTest shard information is read by the V8 tests runner. Make sure it # GTest shard information is read by the V8 tests runner. Make sure it
# doesn't leak into the execution of gtests we're wrapping. Those might # doesn't leak into the execution of gtests we're wrapping. Those might
# otherwise apply a second level of sharding and as a result skip tests. # otherwise apply a second level of sharding and as a result skip tests.
...@@ -126,6 +127,6 @@ def RunProcess(verbose, timeout, args, **rest): ...@@ -126,6 +127,6 @@ def RunProcess(verbose, timeout, args, **rest):
) )
def Execute(args, verbose=False, timeout=None): def Execute(args, verbose=False, timeout=None, env=None):
args = [ c for c in args if c != "" ] args = [ c for c in args if c != "" ]
return RunProcess(verbose, timeout, args=args) return RunProcess(verbose, timeout, args, env or {})
...@@ -49,11 +49,12 @@ TEST_DIR = os.path.join(BASE_DIR, "test") ...@@ -49,11 +49,12 @@ TEST_DIR = os.path.join(BASE_DIR, "test")
class Instructions(object): class Instructions(object):
def __init__(self, command, test_id, timeout, verbose): def __init__(self, command, test_id, timeout, verbose, env):
self.command = command self.command = command
self.id = test_id self.id = test_id
self.timeout = timeout self.timeout = timeout
self.verbose = verbose self.verbose = verbose
self.env = env
# Structure that keeps global information per worker process. # Structure that keeps global information per worker process.
...@@ -111,7 +112,7 @@ def _GetInstructions(test, context): ...@@ -111,7 +112,7 @@ def _GetInstructions(test, context):
# the like. # the like.
if statusfile.IsSlow(test.outcomes or [statusfile.PASS]): if statusfile.IsSlow(test.outcomes or [statusfile.PASS]):
timeout *= 2 timeout *= 2
return Instructions(command, test.id, timeout, context.verbose) return Instructions(command, test.id, timeout, context.verbose, test.env)
class Job(object): class Job(object):
...@@ -178,7 +179,8 @@ class TestJob(Job): ...@@ -178,7 +179,8 @@ class TestJob(Job):
return SetupProblem(e, self.test) return SetupProblem(e, self.test)
start_time = time.time() start_time = time.time()
output = commands.Execute(instr.command, instr.verbose, instr.timeout) output = commands.Execute(instr.command, instr.verbose, instr.timeout,
instr.env)
self._rename_coverage_data(output, process_context.context) self._rename_coverage_data(output, process_context.context)
return (instr.id, output, time.time() - start_time) return (instr.id, output, time.time() - start_time)
......
...@@ -41,11 +41,13 @@ class TestCase(object): ...@@ -41,11 +41,13 @@ class TestCase(object):
self.id = None # int, used to map result back to TestCase instance self.id = None # int, used to map result back to TestCase instance
self.duration = None # assigned during execution self.duration = None # assigned during execution
self.run = 1 # The nth time this test is executed. self.run = 1 # The nth time this test is executed.
self.env = {}
def CopyAddingFlags(self, variant, flags): def CopyAddingFlags(self, variant, flags):
copy = TestCase(self.suite, self.path, variant, self.flags + flags, copy = TestCase(self.suite, self.path, variant, self.flags + flags,
self.override_shell) self.override_shell)
copy.outcomes = self.outcomes copy.outcomes = self.outcomes
copy.env = self.env
return copy return copy
def PackTask(self): def PackTask(self):
...@@ -56,7 +58,7 @@ class TestCase(object): ...@@ -56,7 +58,7 @@ class TestCase(object):
assert self.id is not None assert self.id is not None
return [self.suitename(), self.path, self.variant, self.flags, return [self.suitename(), self.path, self.variant, self.flags,
self.override_shell, list(self.outcomes or []), self.override_shell, list(self.outcomes or []),
self.id] self.id, self.env]
@staticmethod @staticmethod
def UnpackTask(task): def UnpackTask(task):
...@@ -66,6 +68,7 @@ class TestCase(object): ...@@ -66,6 +68,7 @@ class TestCase(object):
test.outcomes = frozenset(task[5]) test.outcomes = frozenset(task[5])
test.id = task[6] test.id = task[6]
test.run = 1 test.run = 1
test.env = task[7]
return test return test
def SetSuiteObject(self, suites): def SetSuiteObject(self, suites):
......
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