Index: runtime/lib/date.dart |
diff --git a/runtime/lib/date.dart b/runtime/lib/date.dart |
index d9c91ac578965435d96faee83f2d1421d5099063..9f28de137009f370b7d80bd129845a7f86c590bd 100644 |
--- a/runtime/lib/date.dart |
+++ b/runtime/lib/date.dart |
@@ -5,7 +5,7 @@ |
// VM implementation of DateImplementation. |
class DateImplementation implements Date { |
- static final int _SECONDS_YEAR_2035 = 2051222400; |
+ static final int _MAX_VALUE = 8640000000000000; |
DateImplementation(int years, |
[int month = 1, |
@@ -80,11 +80,14 @@ class DateImplementation implements Date { |
} |
DateImplementation.fromEpoch(int this.value, [bool isUtc = false]) |
- : _isUtc = isUtc; |
+ : _isUtc = isUtc { |
+ if (value.abs() > _MAX_VALUE) throw new IllegalArgumentException(value); |
+ } |
bool operator ==(Object other) { |
if (other is !DateImplementation) return false; |
- return value == other.value; |
+ DateImplementation otherDate = other; |
+ return value == otherDate.value; |
} |
bool operator <(Date other) => value < other.value; |
@@ -110,79 +113,56 @@ class DateImplementation implements Date { |
String get timeZoneName() { |
if (isUtc()) return "UTC"; |
- return _timeZoneName(_equivalentSeconds(_secondsSinceEpoch)); |
+ return _timeZoneName(value); |
} |
Duration get timeZoneOffset() { |
if (isUtc()) return new Duration(0); |
- int offsetInSeconds = |
- _timeZoneOffsetInSeconds(_equivalentSeconds(_secondsSinceEpoch)); |
+ int offsetInSeconds = _timeZoneOffsetInSeconds(value); |
return new Duration(seconds: offsetInSeconds); |
} |
int get year() { |
- int secondsSinceEpoch = _secondsSinceEpoch; |
- // According to V8 some library calls have troubles with negative values. |
- // Therefore clamp to 0 - year 2035 (which is less than the size of 32bit). |
- if (secondsSinceEpoch >= 0 && secondsSinceEpoch < _SECONDS_YEAR_2035) { |
- return _getYear(secondsSinceEpoch, isUtc()); |
- } |
- |
- // Approximate the result. We don't take timeZone into account. |
- int approximateYear = _yearsFromSecondsSinceEpoch(secondsSinceEpoch); |
- int equivalentYear = _equivalentYear(approximateYear); |
- int y = _getYear(_equivalentSeconds(_secondsSinceEpoch), isUtc()); |
- return approximateYear + (y - equivalentYear); |
+ return _decomposeIntoYearMonthDay(_localDateInUtcValue)[0]; |
} |
int get month() { |
- return _getMonth(_equivalentSeconds(_secondsSinceEpoch), isUtc()); |
+ return _decomposeIntoYearMonthDay(_localDateInUtcValue)[1]; |
} |
int get day() { |
- return _getDay(_equivalentSeconds(_secondsSinceEpoch), isUtc()); |
+ return _decomposeIntoYearMonthDay(_localDateInUtcValue)[2]; |
} |
int get hours() { |
- return _getHours(_equivalentSeconds(_secondsSinceEpoch), isUtc()); |
+ int valueInHours = _flooredDivision(_localDateInUtcValue, |
+ Duration.MILLISECONDS_PER_HOUR); |
+ return valueInHours % Duration.HOURS_PER_DAY; |
} |
int get minutes() { |
- return _getMinutes(_equivalentSeconds(_secondsSinceEpoch), isUtc()); |
+ int valueInMinutes = _flooredDivision(_localDateInUtcValue, |
+ Duration.MILLISECONDS_PER_MINUTE); |
+ return valueInMinutes % Duration.MINUTES_PER_HOUR; |
} |
int get seconds() { |
- return _getSeconds(_equivalentSeconds(_secondsSinceEpoch), isUtc()); |
+ // Seconds are unaffected by the timezone the user is in. So we can |
+ // directly use the value and not the [_localDateInUtcValue]. |
+ int valueInSeconds = |
+ _flooredDivision(value, Duration.MILLISECONDS_PER_SECOND); |
+ return valueInSeconds % Duration.SECONDS_PER_MINUTE; |
} |
int get milliseconds() { |
+ // Milliseconds are unaffected by the timezone the user is in. So we can |
+ // directly use the value and not the [_localDateInUtcValue]. |
return value % Duration.MILLISECONDS_PER_SECOND; |
} |
- int get _secondsSinceEpoch() { |
- // Always round down. |
- if (value < 0) { |
- return (value + 1) ~/ Duration.MILLISECONDS_PER_SECOND - 1; |
- } else { |
- return value ~/ Duration.MILLISECONDS_PER_SECOND; |
- } |
- } |
- |
int get weekday() { |
- final Date unixTimeStart = new Date(1970, 1, 1, 0, 0, 0, 0, isUtc()); |
- int msSince1970 = this.difference(unixTimeStart).inMilliseconds; |
- // Adjust the milliseconds to avoid problems with summer-time. |
- if (hours < 2) { |
- msSince1970 += 2 * Duration.MILLISECONDS_PER_HOUR; |
- } |
- // Compute the floor of msSince1970 / Duration.MS_PER_DAY. |
- int daysSince1970; |
- if (msSince1970 >= 0) { |
- daysSince1970 = msSince1970 ~/ Duration.MILLISECONDS_PER_DAY; |
- } else { |
- daysSince1970 = (msSince1970 - Duration.MILLISECONDS_PER_DAY + 1) ~/ |
- Duration.MILLISECONDS_PER_DAY; |
- } |
+ int daysSince1970 = |
+ _flooredDivision(_localDateInUtcValue, Duration.MILLISECONDS_PER_DAY); |
// 1970-1-1 was a Thursday. |
return ((daysSince1970 + Date.THU) % Date.DAYS_IN_WEEK); |
} |
@@ -222,27 +202,34 @@ class DateImplementation implements Date { |
} |
} |
- // Adds the [duration] to this Date instance. |
+ /** Returns a new [Date] with the [duration] added to [this]. */ |
Date add(Duration duration) { |
return new DateImplementation.fromEpoch(value + duration.inMilliseconds, |
isUtc()); |
} |
- // Subtracts the [duration] from this Date instance. |
+ /** Returns a new [Date] with the [duration] subtracted from [this]. */ |
Date subtract(Duration duration) { |
return new DateImplementation.fromEpoch(value - duration.inMilliseconds, |
isUtc()); |
} |
- // Returns a [Duration] with the difference of [this] and [other]. |
+ /** Returns a [Duration] with the difference of [this] and [other]. */ |
Duration difference(Date other) { |
return new DurationImplementation(milliseconds: value - other.value); |
} |
- // Returns the UTC year for the corresponding [secondsSinceEpoch]. |
- // It is relatively fast for values in the range 0 to year 2098. |
+ /** The first list contains the days until each month in non-leap years. The |
+ * second list contains the days in leap years. */ |
+ static final List<List<int>> _DAYS_UNTIL_MONTH = |
+ const [const [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334], |
+ const [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335]]; |
+ |
+ // Returns the UTC year, month and day for the corresponding |
+ // [millisecondsSinceEpoch]. |
// Code is adapted from V8. |
- static int _yearsFromSecondsSinceEpoch(int secondsSinceEpoch) { |
+ static List<int> _decomposeIntoYearMonthDay(int millisecondsSinceEpoch) { |
+ // TODO(floitsch): cache result. |
final int DAYS_IN_4_YEARS = 4 * 365 + 1; |
final int DAYS_IN_100_YEARS = 25 * DAYS_IN_4_YEARS - 1; |
final int DAYS_IN_400_YEARS = 4 * DAYS_IN_100_YEARS + 1; |
@@ -250,76 +237,143 @@ class DateImplementation implements Date { |
final int DAYS_OFFSET = 1000 * DAYS_IN_400_YEARS + 5 * DAYS_IN_400_YEARS - |
DAYS_1970_TO_2000; |
final int YEARS_OFFSET = 400000; |
- final int DAYS_YEAR_2098 = DAYS_IN_100_YEARS + 6 * DAYS_IN_4_YEARS; |
- int days = secondsSinceEpoch ~/ Duration.SECONDS_PER_DAY; |
- if (days > 0 && days < DAYS_YEAR_2098) { |
- // According to V8 this fast case works for dates from 1970 to 2099. |
- return 1970 + (4 * days + 2) ~/ DAYS_IN_4_YEARS; |
- } else { |
- days += DAYS_OFFSET; |
- int result = 400 * (days ~/ DAYS_IN_400_YEARS) - YEARS_OFFSET; |
- days = days.remainder(DAYS_IN_400_YEARS); |
- days--; |
- int yd1 = days ~/ DAYS_IN_100_YEARS; |
- days = days.remainder(DAYS_IN_100_YEARS); |
- result += 100 * yd1; |
- days++; |
- int yd2 = days ~/ DAYS_IN_4_YEARS; |
- days = days.remainder(DAYS_IN_4_YEARS); |
- result += 4 * yd2; |
- days--; |
- int yd3 = days ~/ 365; |
- days = days.remainder(365); |
- result += yd3; |
- return result; |
+ int resultYear = 0; |
+ int resultMonth = 0; |
+ int resultDay = 0; |
+ |
+ // Always round down. |
+ int days = _flooredDivision(millisecondsSinceEpoch, |
+ Duration.MILLISECONDS_PER_DAY); |
+ days += DAYS_OFFSET; |
+ resultYear = 400 * (days ~/ DAYS_IN_400_YEARS) - YEARS_OFFSET; |
+ days = days.remainder(DAYS_IN_400_YEARS); |
+ days--; |
+ int yd1 = days ~/ DAYS_IN_100_YEARS; |
+ days = days.remainder(DAYS_IN_100_YEARS); |
+ resultYear += 100 * yd1; |
+ days++; |
+ int yd2 = days ~/ DAYS_IN_4_YEARS; |
+ days = days.remainder(DAYS_IN_4_YEARS); |
+ resultYear += 4 * yd2; |
+ days--; |
+ int yd3 = days ~/ 365; |
+ days = days.remainder(365); |
+ resultYear += yd3; |
+ |
+ bool isLeap = (yd1 == 0 || yd2 != 0) && yd3 == 0; |
+ if (isLeap) days++; |
+ |
+ List<int> daysUntilMonth = _DAYS_UNTIL_MONTH[isLeap ? 1 : 0]; |
+ for (resultMonth = 12; |
+ daysUntilMonth[resultMonth - 1] > days; |
+ resultMonth--) { |
+ // Do nothing. |
} |
+ resultDay = days - daysUntilMonth[resultMonth - 1] + 1; |
+ return <int>[resultYear, resultMonth, resultDay]; |
} |
- // Given [secondsSinceEpoch] returns seconds such that they are at the same |
- // time in an equivalent year (see [_equivalentYear]). |
- // Leap seconds are ignored. |
- static int _equivalentSeconds(int secondsSinceEpoch) { |
- if (secondsSinceEpoch >= 0 && secondsSinceEpoch < _SECONDS_YEAR_2035) { |
- return secondsSinceEpoch; |
- } |
- int year = _yearsFromSecondsSinceEpoch(secondsSinceEpoch); |
- int days = _dayFromYear(year); |
- int equivalentYear = _equivalentYear(year); |
- int equivalentDays = _dayFromYear(equivalentYear); |
- int diffDays = equivalentDays - days; |
- return secondsSinceEpoch + diffDays * Duration.SECONDS_PER_DAY; |
+ /** |
+ * Returns the amount of milliseconds in UTC that represent the same values as |
+ * [this]. |
+ * |
+ * Say [:t:] is the result of this function, then |
+ * * [:this.year == new Date.fromEpoch(t, isUtc: true).year:], |
+ * * [:this.month == new Date.fromEpoch(t, isUtc: true).month:], |
+ * * [:this.day == new Date.fromEpoch(t, isUtc: true).day:], |
+ * * [:this.hours == new Date.fromEpoch(t, isUtc: true).hours:], |
+ * * ... |
+ * |
+ * Daylight savings is computed as if the date was computed in [1970..2037]. |
+ * If [this] lies outside this range then it is a year with similar properties |
+ * (leap year, weekdays) is used instead. |
+ */ |
+ int get _localDateInUtcValue() { |
+ if (isUtc()) return value; |
+ int offset = |
+ _timeZoneOffsetInSeconds(value) * Duration.MILLISECONDS_PER_SECOND; |
+ return value + offset; |
+ } |
+ |
+ static int _flooredDivision(int a, int b) { |
+ return (a - (a < 0 ? b - 1 : 0)) ~/ b; |
} |
// Returns the days since 1970 for the start of the given [year]. |
// [year] may be before epoch. |
static int _dayFromYear(int year) { |
- int flooredDivision(int a, int b) { |
- return (a - (a < 0 ? b - 1 : 0)) ~/ b; |
- } |
- |
return 365 * (year - 1970) |
- + flooredDivision(year - 1969, 4) |
- - flooredDivision(year - 1901, 100) |
- + flooredDivision(year - 1601, 400); |
+ + _flooredDivision(year - 1969, 4) |
+ - _flooredDivision(year - 1901, 100) |
+ + _flooredDivision(year - 1601, 400); |
} |
- // Returns a year in the range 2008-2035 matching |
- // - leap year, and |
- // - week day of first day. |
- // Leap seconds are ignored. |
- // Adapted from V8's date implementation. See ECMA 262 - 15.9.1.9. |
- static _equivalentYear(int year) { |
- // Returns 1 if in leap year. 0 otherwise. |
- bool inLeapYear(year) { |
- return (year.remainder(4) == 0) && |
- ((year.remainder(100) != 0) || (year.remainder(400) == 0)); |
+ static bool _isLeapYear(y) { |
+ return (y.remainder(4) == 0) && |
+ ((y.remainder(100) != 0) || (y.remainder(400) == 0)); |
+ } |
+ |
+ static _brokenDownDateToMillisecondsSinceEpoch( |
+ int years, int month, int day, |
+ int hours, int minutes, int seconds, int milliseconds, |
+ bool isUtc) { |
+ if ((month < 1) || (month > 12)) return null; |
+ if ((day < 1) || (day > 31)) return null; |
+ // Leap seconds can lead to hours == 24. |
+ if ((hours < 0) || (hours > 24)) return null; |
+ if ((hours == 24) && ((minutes != 0) || (seconds != 0))) return null; |
+ if ((minutes < 0) || (minutes > 59)) return null; |
+ if ((seconds < 0) || (seconds > 59)) return null; |
+ if ((milliseconds < 0) || (milliseconds > 999)) return null; |
+ |
+ // First compute the seconds in UTC, independent of the [isUtc] flag. If |
+ // necessary we will add the time-zone offset later on. |
+ int days = day - 1; |
+ days += _DAYS_UNTIL_MONTH[_isLeapYear(years) ? 1 : 0][month - 1]; |
+ days += _dayFromYear(years); |
+ int millisecondsSinceEpoch = days * Duration.MILLISECONDS_PER_DAY + |
+ hours * Duration.MILLISECONDS_PER_HOUR + |
+ minutes * Duration.MILLISECONDS_PER_MINUTE+ |
+ seconds * Duration.MILLISECONDS_PER_SECOND + |
+ milliseconds; |
+ |
+ // Since [_timeZoneOffsetInSeconds] will crash if the input is far out of |
+ // the valid range we do a preliminary test that weeds out values that can |
+ // not become valid even with timezone adjustments. |
+ // The timezone adjustment is always less than a day, so adding a security |
+ // margin of one day should be enough. |
+ if (millisecondsSinceEpoch.abs() > |
+ (_MAX_VALUE + Duration.MILLISECONDS_PER_DAY)) { |
+ return null; |
} |
+ if (!isUtc) { |
+ // Note that we need to remove the local timezone adjustement before |
+ // asking for the correct zone offset. |
+ int adjustment = _localTimeZoneAdjustmentInSeconds() * |
+ Duration.MILLISECONDS_PER_SECOND; |
+ int zoneOffset = |
+ _timeZoneOffsetInSeconds(millisecondsSinceEpoch - adjustment); |
+ millisecondsSinceEpoch -= zoneOffset * Duration.MILLISECONDS_PER_SECOND; |
+ } |
+ if (millisecondsSinceEpoch.abs() > _MAX_VALUE) return null; |
+ return millisecondsSinceEpoch; |
+ } |
+ |
+ /** |
+ * Returns a year in the range 2008-2035 matching |
+ * * leap year, and |
+ * * week day of first day. |
+ * |
+ * Leap seconds are ignored. |
+ * Adapted from V8's date implementation. See ECMA 262 - 15.9.1.9. |
+ */ |
+ static _equivalentYear(int year) { |
// Returns the week day (in range 0 - 6). |
- int weekDay(year) { |
+ int weekDay(y) { |
// 1/1/1970 was a Thursday. |
- return (_dayFromYear(year) + 4) % 7; |
+ return (_dayFromYear(y) + 4) % 7; |
} |
// 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). |
@@ -330,82 +384,82 @@ class DateImplementation implements Date { |
// 15 days. 15 % 7 = 1. So after 12 years the week day has always |
// (now independently of leap-years) advanced by one. |
// weekDay * 12 gives thus a year starting with the wanted weekDay. |
- int recentYear = (inLeapYear(year) ? 1956 : 1967) + (weekDay(year) * 12); |
+ int recentYear = (_isLeapYear(year) ? 1956 : 1967) + (weekDay(year) * 12); |
// Close to the year 2008 the calendar cycles every 4 * 7 years (4 for the |
// leap years, 7 for the weekdays). |
// Find the year in the range 2008..2037 that is equivalent mod 28. |
return 2008 + (recentYear - 2008) % 28; |
} |
- static _brokenDownDateToMillisecondsSinceEpoch( |
- int years, int month, int day, |
- int hours, int minutes, int seconds, int milliseconds, |
- bool isUtc) { |
- if ((month < 1) || (month > 12)) return null; |
- if ((day < 1) || (day > 31)) return null; |
- // Leap seconds can lead to hours == 24. |
- if ((hours < 0) || (hours > 24)) return null; |
- if ((hours == 24) && ((minutes != 0) || (seconds != 0))) return null; |
- if ((minutes < 0) || (minutes > 59)) return null; |
- if ((seconds < 0) || (seconds > 59)) return null; |
- if ((milliseconds < 0) || (milliseconds > 999)) return null; |
+ /** |
+ * Returns the UTC year for the corresponding [secondsSinceEpoch]. |
+ * It is relatively fast for values in the range 0 to year 2098. |
+ * |
+ * Code is adapted from V8. |
+ */ |
+ static int _yearsFromSecondsSinceEpoch(int secondsSinceEpoch) { |
+ final int DAYS_IN_4_YEARS = 4 * 365 + 1; |
+ final int DAYS_IN_100_YEARS = 25 * DAYS_IN_4_YEARS - 1; |
+ final int DAYS_YEAR_2098 = DAYS_IN_100_YEARS + 6 * DAYS_IN_4_YEARS; |
- int equivalentYear; |
- int offsetInSeconds; |
- // According to V8 some library calls have troubles with negative values. |
- // Therefore clamp to 1970 - year 2035 (which is less than the size of |
- // 32bit). |
- // We exclude the year 1970 when the time is not UTC, since the epoch |
- // value could then be negative. |
- if (years < (isUtc ? 1970 : 1971) || years > 2035) { |
- equivalentYear = _equivalentYear(years); |
- int offsetInDays = (_dayFromYear(years) - _dayFromYear(equivalentYear)); |
- // Leap seconds are ignored. |
- offsetInSeconds = offsetInDays * Duration.SECONDS_PER_DAY; |
- } else { |
- equivalentYear = years; |
- offsetInSeconds = 0; |
+ int days = secondsSinceEpoch ~/ Duration.SECONDS_PER_DAY; |
+ if (days > 0 && days < DAYS_YEAR_2098) { |
+ // According to V8 this fast case works for dates from 1970 to 2099. |
+ return 1970 + (4 * days + 2) ~/ DAYS_IN_4_YEARS; |
} |
- int secondsSinceEpoch = _brokenDownDateToSecondsSinceEpoch( |
- equivalentYear, month, day, hours, minutes, seconds, isUtc); |
- int adjustedSeconds = secondsSinceEpoch + offsetInSeconds; |
- return adjustedSeconds * Duration.MILLISECONDS_PER_SECOND + milliseconds; |
+ int ms = secondsSinceEpoch * Duration.MILLISECONDS_PER_SECOND; |
+ return _decomposeIntoYearMonthDay(ms)[0]; |
+ } |
+ |
+ /** |
+ * 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]. |
+ * |
+ * * 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. |
+ */ |
+ static int _equivalentSeconds(int millisecondsSinceEpoch) { |
+ final int CUT_OFF_SECONDS = 2100000000; |
+ |
+ int secondsSinceEpoch = _flooredDivision(millisecondsSinceEpoch, |
+ Duration.MILLISECONDS_PER_SECOND); |
+ |
+ if (secondsSinceEpoch < 0 || secondsSinceEpoch >= CUT_OFF_SECONDS) { |
+ int year = _yearsFromSecondsSinceEpoch(secondsSinceEpoch); |
+ int days = _dayFromYear(year); |
+ int equivalentYear = _equivalentYear(year); |
+ int equivalentDays = _dayFromYear(equivalentYear); |
+ int diffDays = equivalentDays - days; |
+ secondsSinceEpoch += diffDays * Duration.SECONDS_PER_DAY; |
+ } |
+ return secondsSinceEpoch; |
+ } |
+ |
+ static int _timeZoneOffsetInSeconds(int millisecondsSinceEpoch) { |
+ int equivalentSeconds = _equivalentSeconds(millisecondsSinceEpoch); |
+ return _timeZoneOffsetInSecondsForClampedSeconds(equivalentSeconds); |
+ } |
+ |
+ static String _timeZoneName(int millisecondsSinceEpoch) { |
+ int equivalentSeconds = _equivalentSeconds(millisecondsSinceEpoch); |
+ return _timeZoneNameForClampedSeconds(equivalentSeconds); |
} |
final bool _isUtc; |
final int value; |
- |
// Natives |
- static _brokenDownDateToSecondsSinceEpoch( |
- int years, int month, int day, int hours, int minutes, int seconds, |
- bool isUtc) native "DateNatives_brokenDownToSecondsSinceEpoch"; |
- |
static int _getCurrentMs() native "DateNatives_currentTimeMillis"; |
- static String _timeZoneName(int secondsSinceEpoch) |
+ static String _timeZoneNameForClampedSeconds(int secondsSinceEpoch) |
native "DateNatives_timeZoneName"; |
- static int _timeZoneOffsetInSeconds(int secondsSinceEpoch) |
+ static int _timeZoneOffsetInSecondsForClampedSeconds(int secondsSinceEpoch) |
native "DateNatives_timeZoneOffsetInSeconds"; |
- // TODO(floitsch): it would be more efficient if we didn't call the native |
- // function for every member, but cached the broken-down date. |
- static int _getYear(int secondsSinceEpoch, bool isUtc) |
- native "DateNatives_getYear"; |
- |
- static int _getMonth(int secondsSinceEpoch, bool isUtc) |
- native "DateNatives_getMonth"; |
- |
- static int _getDay(int secondsSinceEpoch, bool isUtc) |
- native "DateNatives_getDay"; |
- |
- static int _getHours(int secondsSinceEpoch, bool isUtc) |
- native "DateNatives_getHours"; |
- |
- static int _getMinutes(int secondsSinceEpoch, bool isUtc) |
- native "DateNatives_getMinutes"; |
- |
- static int _getSeconds(int secondsSinceEpoch, bool isUtc) |
- native "DateNatives_getSeconds"; |
+ static int _localTimeZoneAdjustmentInSeconds() |
+ native "DateNatives_localTimeZoneAdjustmentInSeconds"; |
} |