Fix datetime for daylight-saving transitions.

Change-Id: I0ec35937a76ada32991b79bcdac57912731db489
Reviewed-on: https://dart-review.googlesource.com/14380
Reviewed-by: Florian Loitsch <floitsch@google.com>
This commit is contained in:
Florian Loitsch 2017-10-17 08:26:21 +00:00
parent 9a0e962e67
commit 88f58757b1
3 changed files with 153 additions and 2 deletions

View file

@ -297,16 +297,28 @@ class DateTime {
}
if (!isUtc) {
// Note that we need to remove the local timezone adjustment before
// asking for the correct zone offset.
// Note that we can't literally follow the ECMAScript spec (which this
// code is based on), because it leads to incorrect computations at
// the DST transition points.
//
// See V8's comment here:
// https://github.com/v8/v8/blob/089dd7d2447d6eaf57c8ba6d8f37957f3a269777/src/date.h#L118
// 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.
// We remove one hour to ensure that we have the correct offset at
// DST transitioning points. This is a temporary solution and only
// correct in timezones that shift for exactly one hour.
adjustment += Duration.MICROSECONDS_PER_HOUR;
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,

View file

@ -419,6 +419,7 @@ iterable_to_set_test: RuntimeError # is-checks do not implement strong mode type
regexp/no-extensions_test: RuntimeError
regexp/lookahead_test: RuntimeError
regexp/overflow_test: RuntimeError
date_time11_test: RuntimeError, OK # Bug in Safari.
[ $runtime == safari || $runtime == safarimobilesim ]
string_trimlr_test/02: RuntimeError # Uses Unicode 6.2.0 or earlier.

View file

@ -0,0 +1,138 @@
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "package:expect/expect.dart";
// Make sure that date-times close to daylight savings work correctly.
// See http://dartbug.com/30550
/// A list of (decomposed) date-times where daylight saving changes
/// happen.
///
/// This list covers multiple timezones to increase test coverage on
/// different machines.
final daylightSavingChanges = [
// TZ environment, y, m, d, h, change.
["Europe/Paris", 2017, 03, 26, 02, 60],
["Europe/Paris", 2017, 10, 29, 03, -60],
["Antarctica/Troll", 2017, 03, 19, 01, 120],
["Antarctica/Troll", 2017, 10, 29, 03, -120],
["Australia/Canberra", 2017, 04, 02, 03, -60],
["Australia/Canberra", 2017, 10, 01, 02, 60],
["Australia/Lord_Howe", 2017, 04, 02, 02, -30],
["Australia/Lord_Howe", 2017, 10, 01, 02, 30],
["Atlantic/Bermuda", 2017, 03, 12, 02, 60], // US and Canada.
["Atlantic/Bermuda", 2017, 11, 05, 02, -60],
["America/Campo_Grande", 2017, 02, 19, 00, -60], // Brazil
["America/Campo_Grande", 2017, 10, 15, 00, 60],
["America/Santiago", 2017, 05, 14, 00, -60],
["America/Santiago", 2017, 08, 13, 00, 60],
["Chile/EasterIsland", 2017, 05, 13, 22, -60],
["Chile/EasterIsland", 2017, 08, 12, 22, 60],
["Pacific/Fiji", 2017, 01, 15, 03, -60],
["Pacific/Fiji", 2017, 11, 05, 02, 60],
["America/Scoresbysund", 2017, 03, 26, 00, 60], // Ittoqqortoormiit.
["America/Scoresbysund", 2017, 10, 29, 01, -60],
["Asia/Tehran", 2017, 03, 22, 00, 60],
["Asia/Tehran", 2017, 09, 22, 00, -60],
["Israel", 2017, 03, 24, 02, 60],
["Israel", 2017, 10, 29, 02, -60],
["Asia/Amman", 2017, 03, 31, 00, 60],
["Asia/Amman", 2017, 10, 27, 01, -60],
["Mexico/General", 2017, 04, 02, 02, 60],
["Mexico/General", 2017, 10, 29, 02, -60],
];
void runTests() {
// Makes sure we don't go into the wrong direction during a
// daylight-savings change (as happened in #30550).
for (var test in daylightSavingChanges) {
for (int i = 0; i < 2; i++) {
int year = test[1];
int month = test[2];
int day = test[3];
int hour = test[4];
int minute = i == 0 ? 0 : test[5];
// Rather adjust the hours than keeping the minutes.
hour += minute ~/ 60;
minute = minute.remainder(60);
if (hour < 0) {
hour += 24;
day--;
}
{
// Check that microseconds are taken into account.
var dtMillisecond = new DateTime(year, month, day, hour, minute, 0, 1);
var dtSecond = new DateTime(year, month, day, hour, minute, 1);
Expect.equals(const Duration(milliseconds: 999),
dtSecond.difference(dtMillisecond));
dtMillisecond = new DateTime(year, month, day, hour, minute, 0, -1);
dtSecond = new DateTime(year, month, day, hour, minute, -1);
Expect.equals(const Duration(milliseconds: 999),
dtMillisecond.difference(dtSecond));
}
var dt1 = new DateTime(year, month, day, hour);
var dt2 = new DateTime(year, month, day, hour, 1);
// Earlier:
int earlierDay = day;
int earlierHour = hour - 1;
if (earlierHour < 0) {
earlierHour = 23;
earlierDay--;
}
var dt3 = new DateTime(year, month, earlierDay, earlierHour, 59);
var diff1 = dt2.difference(dt1).inMinutes;
var diff2 = dt1.difference(dt3).inMinutes;
if (diff1 == 1 && diff2 == 1 && dt1.hour == hour && dt1.minute == 0) {
// Regular date-time.
continue;
}
// At most one is at a distance of more than a minute.
Expect.isTrue(diff1 == 1 || diff2 == 1);
if (diff2 < 0) {
// This happens, when we ask for invalid times.
// Suppose daylight-saving is at 2:00 and switches to 3:00. If we
// ask for 2:59, we get 3:59 (59 minutes after 2:00).
Expect.isFalse(dt3.day == earlierDay && dt3.hour == earlierHour);
// If that's the case, then removing one minute from dt1 should
// not yield a date-time with the earlier values, and it should
// be far away from dt3.
var dt4 = dt1.add(const Duration(minutes: -1));
Expect.isFalse(dt4.day == earlierDay && dt4.hour == earlierHour);
Expect.isTrue(dt4.isBefore(dt1));
Expect.isTrue(dt4.day < earlierDay ||
(dt4.day == earlierDay && dt4.hour < earlierHour));
continue;
}
// They must be in the right order.
Expect.isTrue(dt1.isBefore(dt2));
Expect.isTrue(dt3.isBefore(dt1));
}
}
}
void main(List<String> args) {
// The following code constructs a String with all timezones that are
// relevant for this test.
// This can be helpful for running tests in multiple timezones.
// Typically, one would write something like:
// for tz in <contents_of_string>; do TZ=$tz tools/test.py ...; done
var result = new StringBuffer();
for (int i = 0; i < daylightSavingChanges.length; i += 2) {
if (i != 0) result.write(" ");
result.write(daylightSavingChanges[i][0]);
}
runTests();
}