Fix core lib DateTime in the VM (fixes #19923).

Symptom of the problem:
Set your Linux workstation (or Mac or MIPS board) to the Europe/London timezone
and the corelib/date_time test will fail, claiming that 1/1/1970 was a Wednesday
(it was actually a Thursday, trust me, I was already born).

Problem:
The implementation of DateTime in the VM relies on Unix time_t, the number of
seconds since the Epoch (1/1/1970 UTC). When asked for the weekday of a given
time, our implementation limits itself to a 32-bit positive range of time_t.
If the time falls outside of this range, the implementation picks an equivalent
time in the valid range with the same weekday, also in leap year or not, etc...
The issue is that DateTime is using the underlying OS in an inconsistent manner.
Let's take the example above: 1/1/1970 in the Europe/London timezone.
First, the number of seconds since the Epoch in UTC is calculated, here 0.
Then, the timezone offset at the given time is calculated using the underlying
OS. In this case, an historical deviation is taken into account. Indeed, London
stayed on British Summer Time between 27 October 1968 and 31 October 1971. See
https://en.wikipedia.org/wiki/British_Summer_Time#Periods_of_deviation for
details.
Our resulting time is therefore negative (one hour difference with UTC).
When asked about the weekday of this time, the implementation notices that the
time is not in the positive range and picks an "equivalent" time in the future.
It then asks the underlying OS about the timezone offset for this time, which
is 0 (usually no daylight saving time in January in London). Unfortunately,
this time is not really equivalent, because it ignores the original historical
deviation. The result is wrongly equivalent to 12/31/1969 23:00 in London, i.e.
a Wednesday, and not a Thursday as expected.

Solution:
We should use the underlying OS in a consistent way, by simply allowing the
value of time_t passed to the underlying OS to be negative, which is legal.

R=floitsch@google.com, rmacnak@google.com

Review URL: https://codereview.chromium.org/1845483002 .
This commit is contained in:
Regis Crelier 2016-03-30 10:09:58 -07:00
parent dbb4272fc0
commit 48dc790398
3 changed files with 31 additions and 13 deletions

View file

@ -1,5 +1,10 @@
## 1.16.0
### Core library changes
* `dart:core`
* Fixed DateTime implementation in the VM (issue #19923), but no API change.
### Analyzer
* Static checking of `for in` statements. These will now produce static

View file

@ -12,13 +12,13 @@
namespace dart {
static int32_t kMaxAllowedSeconds = 2100000000;
static int64_t kMaxAllowedSeconds = kMaxInt32;
DEFINE_NATIVE_ENTRY(DateTime_timeZoneName, 1) {
GET_NON_NULL_NATIVE_ARGUMENT(
Integer, dart_seconds, arguments->NativeArgAt(0));
int64_t seconds = dart_seconds.AsInt64Value();
if (seconds < 0 || seconds > kMaxAllowedSeconds) {
if (llabs(seconds) > kMaxAllowedSeconds) {
Exceptions::ThrowArgumentError(dart_seconds);
}
const char* name = OS::GetTimeZoneName(seconds);
@ -30,7 +30,7 @@ DEFINE_NATIVE_ENTRY(DateTime_timeZoneOffsetInSeconds, 1) {
GET_NON_NULL_NATIVE_ARGUMENT(
Integer, dart_seconds, arguments->NativeArgAt(0));
int64_t seconds = dart_seconds.AsInt64Value();
if (seconds < 0 || seconds > kMaxAllowedSeconds) {
if (llabs(seconds) > kMaxAllowedSeconds) {
Exceptions::ThrowArgumentError(dart_seconds);
}
int offset = OS::GetTimeZoneOffsetInSeconds(seconds);

View file

@ -284,14 +284,24 @@ patch class DateTime {
}
if (!isUtc) {
// Note that we need to remove the local timezone adjustement before
// Note that we need to remove the local timezone adjustment before
// asking for the correct zone offset.
int adjustment = _localTimeZoneAdjustmentInSeconds() *
Duration.MICROSECONDS_PER_SECOND;
// The adjustment is independent of the actual date and of the daylight
// saving time. It is positive east of the Prime Meridian and negative
// west of it, e.g. -28800 sec for America/Los_Angeles timezone.
int zoneOffset =
_timeZoneOffsetInSeconds(microsecondsSinceEpoch - adjustment);
// The zoneOffset depends on the actual date and reflects any daylight
// saving time and/or historical deviation relative to UTC time.
// It is positive east of the Prime Meridian and negative west of it,
// e.g. -25200 sec for America/Los_Angeles timezone during DST.
microsecondsSinceEpoch -= zoneOffset * Duration.MICROSECONDS_PER_SECOND;
// The resulting microsecondsSinceEpoch value is therefore the calculated
// UTC value decreased by a (positive if east of GMT) timezone adjustment
// and decreased by typically one hour if DST is in effect.
}
if (microsecondsSinceEpoch.abs() >
_MAX_MILLISECONDS_SINCE_EPOCH * Duration.MICROSECONDS_PER_MILLISECOND) {
@ -314,7 +324,8 @@ patch class DateTime {
* Adapted from V8's date implementation. See ECMA 262 - 15.9.1.9.
*/
static int _equivalentYear(int year) {
// Returns the week day (in range 0 - 6).
// Returns year y so that _weekDay(y) == _weekDay(year).
// _weekDay returns the week day (in range 0 - 6).
// 1/1/1956 was a Sunday (i.e. weekday 0). 1956 was a leap-year.
// 1/1/1967 was a Sunday (i.e. weekday 0).
// Without leap years a subsequent year has a week day + 1 (for example
@ -352,22 +363,24 @@ patch class DateTime {
}
/**
* Returns a date in seconds that is equivalent to the current date. An
* equivalent date has the same fields (`month`, `day`, etc.) as the
* [this], but the `year` is in the range [1970..2037].
* Returns a date in seconds that is equivalent to the given
* date in microseconds [microsecondsSinceEpoch]. An equivalent
* date has the same fields (`month`, `day`, etc.) as the given
* date, but the `year` is in the range [1901..2038].
*
* * The time since the beginning of the year is the same.
* * If [this] is in a leap year then the returned seconds are in a leap
* year, too.
* * The week day of [this] is the same as the one for the returned date.
* * If the given date is in a leap year then the returned
* seconds are in a leap year, too.
* * The week day of given date is the same as the one for the
* returned date.
*/
static int _equivalentSeconds(int microsecondsSinceEpoch) {
const int CUT_OFF_SECONDS = 2100000000;
const int CUT_OFF_SECONDS = 0x7FFFFFFF;
int secondsSinceEpoch = _flooredDivision(microsecondsSinceEpoch,
Duration.MICROSECONDS_PER_SECOND);
if (secondsSinceEpoch < 0 || secondsSinceEpoch >= CUT_OFF_SECONDS) {
if (secondsSinceEpoch.abs() > CUT_OFF_SECONDS) {
int year = _yearsFromSecondsSinceEpoch(secondsSinceEpoch);
int days = _dayFromYear(year);
int equivalentYear = _equivalentYear(year);