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 @@
#include "src/objects.h"
#include "src/objects-inl.h"
#ifdef V8_I18N_SUPPORT
#include "src/i18n.h"
#endif
namespace v8 {
namespace internal {
......@@ -21,6 +25,18 @@ static const int kYearsOffset = 400000;
static const char kDaysInMonths[] =
{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() {
static const int kMaxStamp = Smi::kMaxValue;
......
......@@ -39,9 +39,7 @@ class DateCache {
// It is an invariant of DateCache that cache stamp is non-negative.
static const int kInvalidStamp = -1;
DateCache() : stamp_(0), tz_cache_(base::OS::CreateTimezoneCache()) {
ResetDateCache();
}
DateCache();
virtual ~DateCache() {
delete tz_cache_;
......
......@@ -254,6 +254,11 @@ HARMONY_STAGED(FLAG_STAGED_FEATURES)
HARMONY_SHIPPING(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
#define FUTURE_BOOL true
#else
......
......@@ -13,6 +13,7 @@
#include "src/objects-inl.h"
#include "src/string-case.h"
#include "unicode/brkiter.h"
#include "unicode/bytestream.h"
#include "unicode/calendar.h"
#include "unicode/coll.h"
#include "unicode/curramt.h"
......@@ -1212,5 +1213,57 @@ MUST_USE_RESULT Object* ConvertCase(Handle<String> s, bool is_upper,
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 v8
......@@ -6,6 +6,7 @@
#ifndef V8_I18N_H_
#define V8_I18N_H_
#include "src/base/timezone-cache.h"
#include "src/objects.h"
#include "unicode/uversion.h"
......@@ -14,6 +15,7 @@ class BreakIterator;
class Collator;
class DecimalFormat;
class SimpleDateFormat;
class TimeZone;
}
namespace v8 {
......@@ -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,
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 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 @@
# BUG(v8:4495).
'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'
##############################################################################
......
......@@ -33,6 +33,7 @@ from testrunner.objects import testcase
FLAGS_PATTERN = re.compile(r"//\s+Flags:(.*)")
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")
MODULE_PATTERN = re.compile(r"^// MODULE$", flags=re.MULTILINE)
NO_HARNESS_PATTERN = re.compile(r"^// NO HARNESS$", flags=re.MULTILINE)
......@@ -94,6 +95,12 @@ class MjsunitTestSuite(testsuite.TestSuite):
flags.append("--isolate")
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
def GetSourceForTest(self, testcase):
......
......@@ -50,7 +50,7 @@ def Win32SetErrorMode(mode):
return prev_error_mode
def RunProcess(verbose, timeout, args, **rest):
def RunProcess(verbose, timeout, args, additional_env, **rest):
if verbose: print "#", " ".join(args)
popen_args = args
prev_error_mode = SEM_INVALID_VALUE
......@@ -64,6 +64,7 @@ def RunProcess(verbose, timeout, args, **rest):
Win32SetErrorMode(error_mode | prev_error_mode)
env = os.environ.copy()
env.update(additional_env)
# 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
# otherwise apply a second level of sharding and as a result skip tests.
......@@ -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 != "" ]
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")
class Instructions(object):
def __init__(self, command, test_id, timeout, verbose):
def __init__(self, command, test_id, timeout, verbose, env):
self.command = command
self.id = test_id
self.timeout = timeout
self.verbose = verbose
self.env = env
# Structure that keeps global information per worker process.
......@@ -111,7 +112,7 @@ def _GetInstructions(test, context):
# the like.
if statusfile.IsSlow(test.outcomes or [statusfile.PASS]):
timeout *= 2
return Instructions(command, test.id, timeout, context.verbose)
return Instructions(command, test.id, timeout, context.verbose, test.env)
class Job(object):
......@@ -178,7 +179,8 @@ class TestJob(Job):
return SetupProblem(e, self.test)
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)
return (instr.id, output, time.time() - start_time)
......
......@@ -41,11 +41,13 @@ class TestCase(object):
self.id = None # int, used to map result back to TestCase instance
self.duration = None # assigned during execution
self.run = 1 # The nth time this test is executed.
self.env = {}
def CopyAddingFlags(self, variant, flags):
copy = TestCase(self.suite, self.path, variant, self.flags + flags,
self.override_shell)
copy.outcomes = self.outcomes
copy.env = self.env
return copy
def PackTask(self):
......@@ -56,7 +58,7 @@ class TestCase(object):
assert self.id is not None
return [self.suitename(), self.path, self.variant, self.flags,
self.override_shell, list(self.outcomes or []),
self.id]
self.id, self.env]
@staticmethod
def UnpackTask(task):
......@@ -66,6 +68,7 @@ class TestCase(object):
test.outcomes = frozenset(task[5])
test.id = task[6]
test.run = 1
test.env = task[7]
return test
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