blob: 97c95ae7a2e6e46481d95515ee78ec3f00fb6429 [file] [log] [blame]
// Copyright (c) 2011, 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.
part of dart.core;
/**
* An instant in time, such as July 20, 1969, 8:18pm GMT.
*
* Create a DateTime object by using one of the constructors
* or by parsing a correctly formatted string,
* which complies with a subset of ISO 8601.
* Note that hours are specified between 0 and 23,
* as in a 24-hour clock.
* For example:
*
* DateTime now = new DateTime.now();
* DateTime berlinWallFell = new DateTime(1989, 11, 9);
* DateTime moonLanding = DateTime.parse("1969-07-20 20:18:00"); // 8:18pm
*
* A DateTime object is anchored either in the UTC time zone
* or in the local time zone of the current computer
* when the object is created.
*
* Once created, neither the value nor the time zone
* of a DateTime object may be changed.
*
* You can use properties to get
* the individual units of a DateTime object.
*
* assert(berlinWallFell.month == 11);
* assert(moonLanding.hour == 20);
*
* For convenience and readability,
* the DateTime class provides a constant for each day and month
* name—for example, [AUGUST] and [FRIDAY].
* You can use these constants to improve code readability:
*
* DateTime berlinWallFell = new DateTime(1989, DateTime.NOVEMBER, 9);
* assert(berlinWallFell.weekday == DateTime.THURSDAY);
*
* Day and month values begin at 1, and the week starts on Monday.
* That is, the constants [JANUARY] and [MONDAY] are both 1.
*
* ## Working with UTC and local time
*
* A DateTime object is in the local time zone
* unless explicitly created in the UTC time zone.
*
* DateTime dDay = new DateTime.utc(1944, 6, 6);
*
* Use [isUtc] to determine whether a DateTime object is based in UTC.
* Use the methods [toLocal] and [toUtc]
* to get the equivalent date/time value specified in the other time zone.
* Use [timeZoneName] to get an abbreviated name of the time zone
* for the DateTime object.
* To find the difference
* between UTC and the time zone of a DateTime object
* call [timeZoneOffset].
*
* ## Comparing DateTime objects
*
* The DateTime class contains several handy methods,
* such as [isAfter], [isBefore], and [isAtSameMomentAs],
* for comparing DateTime objects.
*
* assert(berlinWallFell.isAfter(moonLanding) == true);
* assert(berlinWallFell.isBefore(moonLanding) == false);
*
* ## Using DateTime with Duration
*
* Use the [add] and [subtract] methods with a [Duration] object
* to create a new DateTime object based on another.
* For example, to find the date that is sixty days after today, write:
*
* DateTime today = new DateTime.now();
* DateTime sixtyDaysFromNow = today.add(new Duration(days: 60));
*
* To find out how much time is between two DateTime objects use
* [difference], which returns a [Duration] object:
*
* Duration difference = berlinWallFell.difference(moonLanding)
* assert(difference.inDays == 7416);
*
* The difference between two dates in different time zones
* is just the number of nanoseconds between the two points in time.
* It doesn't take calendar days into account.
* That means that the difference between two midnights in local time may be
* less than 24 hours times the number of days between them,
* if there is a daylight saving change in between.
* If the difference above is calculated using Australian local time, the
* difference is 7415 days and 23 hours, which is only 7415 whole days as
* reported by `inDays`.
*
* ## Other resources
*
* See [Duration] to represent a span of time.
* See [Stopwatch] to measure timespans.
*
* The DateTime class does not provide internationalization.
* To internationalize your code, use
* the [intl](http://pub.dartlang.org/packages/intl) package.
*
*/
class DateTime implements Comparable<DateTime> {
// Weekday constants that are returned by [weekday] method:
static const int MONDAY = 1;
static const int TUESDAY = 2;
static const int WEDNESDAY = 3;
static const int THURSDAY = 4;
static const int FRIDAY = 5;
static const int SATURDAY = 6;
static const int SUNDAY = 7;
static const int DAYS_PER_WEEK = 7;
// Month constants that are returned by the [month] getter.
static const int JANUARY = 1;
static const int FEBRUARY = 2;
static const int MARCH = 3;
static const int APRIL = 4;
static const int MAY = 5;
static const int JUNE = 6;
static const int JULY = 7;
static const int AUGUST = 8;
static const int SEPTEMBER = 9;
static const int OCTOBER = 10;
static const int NOVEMBER = 11;
static const int DECEMBER = 12;
static const int MONTHS_PER_YEAR = 12;
/**
* The value of this DateTime.
*
* The content of this field is implementation dependent. On JavaScript it is
* equal to [millisecondsSinceEpoch]. On the VM it is equal to
* [microsecondsSinceEpoch].
*/
final int _value;
/**
* True if this [DateTime] is set to UTC time.
*
* DateTime dDay = new DateTime.utc(1944, 6, 6);
* assert(dDay.isUtc);
*
*/
final bool isUtc;
/**
* Constructs a [DateTime] instance specified in the local time zone.
*
* For example,
* to create a new DateTime object representing April 29, 2014, 6:04am:
*
* DateTime annularEclipse = new DateTime(2014, DateTime.APRIL, 29, 6, 4);
*/
DateTime(int year,
[int month = 1,
int day = 1,
int hour = 0,
int minute = 0,
int second = 0,
int millisecond = 0,
int microsecond = 0])
: this._internal(year, month, day, hour, minute, second, millisecond,
microsecond, false);
/**
* Constructs a [DateTime] instance specified in the UTC time zone.
*
* DateTime dDay = new DateTime.utc(1944, DateTime.JUNE, 6);
*/
DateTime.utc(int year,
[int month = 1,
int day = 1,
int hour = 0,
int minute = 0,
int second = 0,
int millisecond = 0,
int microsecond = 0])
: this._internal(year, month, day, hour, minute, second, millisecond,
microsecond, true);
/**
* Constructs a [DateTime] instance with current date and time in the
* local time zone.
*
* DateTime thisInstant = new DateTime.now();
*
*/
DateTime.now() : this._now();
/**
* Constructs a new [DateTime] instance based on [formattedString].
*
* Throws a [FormatException] if the input cannot be parsed.
*
* The function parses a subset of ISO 8601
* which includes the subset accepted by RFC 3339.
*
* The accepted inputs are currently:
*
* * A date: A signed four-to-six digit year, two digit month and
* two digit day, optionally separated by `-` characters.
* Examples: "19700101", "-0004-12-24", "81030-04-01".
* * An optional time part, separated from the date by either `T` or a space.
* The time part is a two digit hour,
* then optionally a two digit minutes value,
* then optionally a two digit seconds value, and
* then optionally a '.' followed by a one-to-six digit second fraction.
* The minutes and seconds may be separated from the previous parts by a
* ':'.
* Examples: "12", "12:30:24.124", "123010.50".
* * An optional time-zone offset part,
* possibly separated from the previous by a space.
* The time zone is either 'z' or 'Z', or it is a signed two digit hour
* part and an optional two digit minute part. The sign must be either
* "+" or "-", and can not be omitted.
* The minutes may be separated from the hours by a ':'.
* Examples: "Z", "-10", "01:30", "1130".
*
* This includes the output of both [toString] and [toIso8601String], which
* will be parsed back into a `DateTime` object with the same time as the
* original.
*
* The result is always in either local time or UTC.
* If a time zone offset other than UTC is specified,
* the time is converted to the equivalent UTC time.
*
* Examples of accepted strings:
*
* * `"2012-02-27 13:27:00"`
* * `"2012-02-27 13:27:00.123456z"`
* * `"20120227 13:27:00"`
* * `"20120227T132700"`
* * `"20120227"`
* * `"+20120227"`
* * `"2012-02-27T14Z"`
* * `"2012-02-27T14+00:00"`
* * `"-123450101 00:00:00 Z"`: in the year -12345.
* * `"2002-02-27T14:00:00-0500"`: Same as `"2002-02-27T19:00:00Z"`
*/
// TODO(lrn): restrict incorrect values like 2003-02-29T50:70:80.
// Or not, that may be a breaking change.
static DateTime parse(String formattedString) {
/*
* date ::= yeardate time_opt timezone_opt
* yeardate ::= year colon_opt month colon_opt day
* year ::= sign_opt digit{4,6}
* colon_opt :: <empty> | ':'
* sign ::= '+' | '-'
* sign_opt ::= <empty> | sign
* month ::= digit{2}
* day ::= digit{2}
* time_opt ::= <empty> | (' ' | 'T') hour minutes_opt
* minutes_opt ::= <empty> | colon_opt digit{2} seconds_opt
* seconds_opt ::= <empty> | colon_opt digit{2} millis_opt
* micros_opt ::= <empty> | '.' digit{1,6}
* timezone_opt ::= <empty> | space_opt timezone
* space_opt :: ' ' | <empty>
* timezone ::= 'z' | 'Z' | sign digit{2} timezonemins_opt
* timezonemins_opt ::= <empty> | colon_opt digit{2}
*/
final RegExp re = new RegExp(r'^([+-]?\d{4,6})-?(\d\d)-?(\d\d)' // Day part.
r'(?:[ T](\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d{1,6}))?)?)?' // Time part.
r'( ?[zZ]| ?([-+])(\d\d)(?::?(\d\d))?)?)?$'); // Timezone part.
Match match = re.firstMatch(formattedString);
if (match != null) {
int parseIntOrZero(String matched) {
if (matched == null) return 0;
return int.parse(matched);
}
// Parses fractional second digits of '.(\d{1,6})' into the combined
// microseconds.
int parseMilliAndMicroseconds(String matched) {
if (matched == null) return 0;
int length = matched.length;
assert(length >= 1);
assert(length <= 6);
int result = 0;
for (int i = 0; i < 6; i++) {
result *= 10;
if (i < matched.length) {
result += matched.codeUnitAt(i) ^ 0x30;
}
}
return result;
}
int years = int.parse(match[1]);
int month = int.parse(match[2]);
int day = int.parse(match[3]);
int hour = parseIntOrZero(match[4]);
int minute = parseIntOrZero(match[5]);
int second = parseIntOrZero(match[6]);
bool addOneMillisecond = false;
int milliAndMicroseconds = parseMilliAndMicroseconds(match[7]);
int millisecond =
milliAndMicroseconds ~/ Duration.MICROSECONDS_PER_MILLISECOND;
int microsecond =
milliAndMicroseconds.remainder(Duration.MICROSECONDS_PER_MILLISECOND);
bool isUtc = false;
if (match[8] != null) {
// timezone part
isUtc = true;
if (match[9] != null) {
// timezone other than 'Z' and 'z'.
int sign = (match[9] == '-') ? -1 : 1;
int hourDifference = int.parse(match[10]);
int minuteDifference = parseIntOrZero(match[11]);
minuteDifference += 60 * hourDifference;
minute -= sign * minuteDifference;
}
}
int value = _brokenDownDateToValue(years, month, day, hour, minute,
second, millisecond, microsecond, isUtc);
if (value == null) {
throw new FormatException("Time out of range", formattedString);
}
return new DateTime._withValue(value, isUtc: isUtc);
} else {
throw new FormatException("Invalid date format", formattedString);
}
}
static const int _MAX_MILLISECONDS_SINCE_EPOCH = 8640000000000000;
/**
* Constructs a new [DateTime] instance
* with the given [millisecondsSinceEpoch].
*
* If [isUtc] is false then the date is in the local time zone.
*
* The constructed [DateTime] represents
* 1970-01-01T00:00:00Z + [millisecondsSinceEpoch] ms in the given
* time zone (local or UTC).
*/
DateTime.fromMillisecondsSinceEpoch(int millisecondsSinceEpoch,
{bool isUtc: false})
: this._withValue(
millisecondsSinceEpoch * Duration.MICROSECONDS_PER_MILLISECOND,
isUtc: isUtc);
/**
* Constructs a new [DateTime] instance
* with the given [microsecondsSinceEpoch].
*
* If [isUtc] is false then the date is in the local time zone.
*
* The constructed [DateTime] represents
* 1970-01-01T00:00:00Z + [microsecondsSinceEpoch] us in the given
* time zone (local or UTC).
*/
DateTime.fromMicrosecondsSinceEpoch(int microsecondsSinceEpoch,
{bool isUtc: false})
: this._withValue(microsecondsSinceEpoch, isUtc: isUtc);
/**
* Constructs a new [DateTime] instance with the given value.
*
* If [isUtc] is false then the date is in the local time zone.
*/
DateTime._withValue(this._value, {this.isUtc}) {
if (millisecondsSinceEpoch.abs() > _MAX_MILLISECONDS_SINCE_EPOCH ||
(millisecondsSinceEpoch.abs() == _MAX_MILLISECONDS_SINCE_EPOCH &&
microsecond != 0)) {
throw new ArgumentError("$millisecondsSinceEpoch");
}
if (isUtc == null) throw new ArgumentError(isUtc);
}
/**
* Returns true if [other] is a [DateTime] at the same moment and in the
* same time zone (UTC or local).
*
* DateTime dDayUtc = new DateTime.utc(1944, DateTime.JUNE, 6);
* DateTime dDayLocal = new DateTime(1944, DateTime.JUNE, 6);
*
* assert(dDayUtc.isAtSameMomentAs(dDayLocal) == false);
*
* See [isAtSameMomentAs] for a comparison that adjusts for time zone.
*/
bool operator ==(other) {
return other is DateTime && (_value == other._value && isUtc == other.isUtc);
}
/**
* Returns true if [this] occurs before [other].
*
* The comparison is independent
* of whether the time is in UTC or in the local time zone.
*
* DateTime berlinWallFell = new DateTime(1989, 11, 9);
* DateTime moonLanding = DateTime.parse("1969-07-20 20:18:00");
*
* assert(berlinWallFell.isBefore(moonLanding) == false);
*
*/
bool isBefore(DateTime other) {
return _value < other._value;
}
/**
* Returns true if [this] occurs after [other].
*
* The comparison is independent
* of whether the time is in UTC or in the local time zone.
*
* DateTime berlinWallFell = new DateTime(1989, 11, 9);
* DateTime moonLanding = DateTime.parse("1969-07-20 20:18:00");
*
* assert(berlinWallFell.isAfter(moonLanding) == true);
*
*/
bool isAfter(DateTime other) {
return _value > other._value;
}
/**
* Returns true if [this] occurs at the same moment as [other].
*
* The comparison is independent of whether the time is in UTC or in the local
* time zone.
*
* DateTime berlinWallFell = new DateTime(1989, 11, 9);
* DateTime moonLanding = DateTime.parse("1969-07-20 20:18:00");
*
* assert(berlinWallFell.isAtSameMomentAs(moonLanding) == false);
*/
bool isAtSameMomentAs(DateTime other) {
return _value == other._value;
}
/**
* Compares this DateTime object to [other],
* returning zero if the values are equal.
*
* This function returns a negative integer
* if this DateTime is smaller (earlier) than [other],
* or a positive integer if it is greater (later).
*/
int compareTo(DateTime other) => _value.compareTo(other._value);
int get hashCode => (_value ^ (_value >> 30)) & 0x3FFFFFFF;
/**
* Returns this DateTime value in the local time zone.
*
* Returns [this] if it is already in the local time zone.
* Otherwise this method is equivalent to:
*
* new DateTime.fromMicrosecondsSinceEpoch(microsecondsSinceEpoch,
* isUtc: false)
*/
DateTime toLocal() {
if (isUtc) {
return new DateTime._withValue(_value, isUtc: false);
}
return this;
}
/**
* Returns this DateTime value in the UTC time zone.
*
* Returns [this] if it is already in UTC.
* Otherwise this method is equivalent to:
*
* new DateTime.fromMicrosecondsSinceEpoch(microsecondsSinceEpoch,
* isUtc: true)
*/
DateTime toUtc() {
if (isUtc) return this;
return new DateTime._withValue(_value, isUtc: true);
}
static String _fourDigits(int n) {
int absN = n.abs();
String sign = n < 0 ? "-" : "";
if (absN >= 1000) return "$n";
if (absN >= 100) return "${sign}0$absN";
if (absN >= 10) return "${sign}00$absN";
return "${sign}000$absN";
}
static String _sixDigits(int n) {
assert(n < -9999 || n > 9999);
int absN = n.abs();
String sign = n < 0 ? "-" : "+";
if (absN >= 100000) return "$sign$absN";
return "${sign}0$absN";
}
static String _threeDigits(int n) {
if (n >= 100) return "${n}";
if (n >= 10) return "0${n}";
return "00${n}";
}
static String _twoDigits(int n) {
if (n >= 10) return "${n}";
return "0${n}";
}
/**
* Returns a human-readable string for this instance.
*
* The returned string is constructed for the time zone of this instance.
* The `toString()` method provides a simply formatted string.
* It does not support internationalized strings.
* Use the [intl](http://pub.dartlang.org/packages/intl) package
* at the pub shared packages repo.
*
* The resulting string can be parsed back using [parse].
*/
String toString() {
String y = _fourDigits(year);
String m = _twoDigits(month);
String d = _twoDigits(day);
String h = _twoDigits(hour);
String min = _twoDigits(minute);
String sec = _twoDigits(second);
String ms = _threeDigits(millisecond);
String us = microsecond == 0 ? "" : _threeDigits(microsecond);
if (isUtc) {
return "$y-$m-$d $h:$min:$sec.$ms${us}Z";
} else {
return "$y-$m-$d $h:$min:$sec.$ms$us";
}
}
/**
* Returns an ISO-8601 full-precision extended format representation.
*
* The format is `yyyy-MM-ddTHH:mm:ss.mmmuuuZ` for UTC time, and
* `yyyy-MM-ddTHH:mm:ss.mmmuuu` (no trailing "Z") for local/non-UTC time,
* where:
*
* * `yyyy` is a, possibly negative, four digit representation of the year,
* if the year is in the range -9999 to 9999,
* otherwise it is a signed six digit representation of the year.
* * `MM` is the month in the range 01 to 12,
* * `dd` is the day of the month in the range 01 to 31,
* * `HH` are hours in the range 00 to 23,
* * `mm` are minutes in the range 00 to 59,
* * `ss` are seconds in the range 00 to 59 (no leap seconds),
* * `mmm` are milliseconds in the range 000 to 999, and
* * `uuu` are microseconds in the range 001 to 999. If [microsecond] equals
* 0, then this part is omitted.
*
* The resulting string can be parsed back using [parse].
*/
String toIso8601String() {
String y =
(year >= -9999 && year <= 9999) ? _fourDigits(year) : _sixDigits(year);
String m = _twoDigits(month);
String d = _twoDigits(day);
String h = _twoDigits(hour);
String min = _twoDigits(minute);
String sec = _twoDigits(second);
String ms = _threeDigits(millisecond);
String us = microsecond == 0 ? "" : _threeDigits(microsecond);
if (isUtc) {
return "$y-$m-${d}T$h:$min:$sec.$ms${us}Z";
} else {
return "$y-$m-${d}T$h:$min:$sec.$ms$us";
}
}
/**
* Returns a new [DateTime] instance with [duration] added to [this].
*
* DateTime today = new DateTime.now();
* DateTime fiftyDaysFromNow = today.add(new Duration(days: 50));
*
* Notice that the duration being added is actually 50 * 24 * 60 * 60
* seconds. If the resulting `DateTime` has a different daylight saving offset
* than `this`, then the result won't have the same time-of-day as `this`, and
* may not even hit the calendar date 50 days later.
*
* Be careful when working with dates in local time.
*/
DateTime add(Duration duration) {
return new DateTime._withValue(_value + duration.inMicroseconds,
isUtc: isUtc);
}
/**
* Returns a new [DateTime] instance with [duration] subtracted from [this].
*
* DateTime today = new DateTime.now();
* DateTime fiftyDaysAgo = today.subtract(new Duration(days: 50));
*
* Notice that the duration being subtracted is actually 50 * 24 * 60 * 60
* seconds. If the resulting `DateTime` has a different daylight saving offset
* than `this`, then the result won't have the same time-of-day as `this`, and
* may not even hit the calendar date 50 days earlier.
*
* Be careful when working with dates in local time.
*/
DateTime subtract(Duration duration) {
return new DateTime._withValue(_value - duration.inMicroseconds,
isUtc: isUtc);
}
/**
* Returns a [Duration] with the difference between [this] and [other].
*
* DateTime berlinWallFell = new DateTime.utc(1989, DateTime.NOVEMBER, 9);
* DateTime dDay = new DateTime.utc(1944, DateTime.JUNE, 6);
*
* Duration difference = berlinWallFell.difference(dDay);
* assert(difference.inDays == 16592);
*
* The difference is measured in seconds and fractions of seconds.
* The difference above counts the number of fractional seconds between
* midnight at the beginning of those dates.
* If the dates above had been in local time, not UTC, then the difference
* between two midnights may not be a multiple of 24 hours due to daylight
* saving differences.
*
* For example, in Australia, similar code using local time instead of UTC:
*
* DateTime berlinWallFell = new DateTime(1989, DateTime.NOVEMBER, 9);
* DateTime dDay = new DateTime(1944, DateTime.JUNE, 6);
* Duration difference = berlinWallFell.difference(dDay);
* assert(difference.inDays == 16592);
*
* will fail because the difference is actually 16591 days and 23 hours, and
* [Duration.inDays] only returns the number of whole days.
*/
Duration difference(DateTime other) {
return new Duration(microseconds: _value - other._value);
}
DateTime._internal(int year, int month, int day, int hour, int minute,
int second, int millisecond, int microsecond, bool isUtc)
: this.isUtc = isUtc,
this._value = _brokenDownDateToValue(year, month, day, hour, minute,
second, millisecond, microsecond, isUtc) {
if (_value == null) throw new ArgumentError();
if (isUtc == null) throw new ArgumentError();
}
DateTime._now()
: isUtc = false,
_value = _getCurrentMicros() {}
/// Returns the time as value (millisecond or microsecond since epoch), or
/// null if the values are out of range.
static int _brokenDownDateToValue(int year, int month, int day, int hour,
int minute, int second, int millisecond, int microsecond, bool isUtc) {
// Simplify calculations by working with zero-based month.
--month;
// Deal with under and overflow.
if (month >= 12) {
year += month ~/ 12;
month = month % 12;
} else if (month < 0) {
int realMonth = month % 12;
year += (month - realMonth) ~/ 12;
month = realMonth;
}
// 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(year) ? 1 : 0][month];
days += _dayFromYear(year);
int microsecondsSinceEpoch = days * Duration.MICROSECONDS_PER_DAY +
hour * Duration.MICROSECONDS_PER_HOUR +
minute * Duration.MICROSECONDS_PER_MINUTE +
second * Duration.MICROSECONDS_PER_SECOND +
millisecond * Duration.MICROSECONDS_PER_MILLISECOND +
microsecond;
// 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 (microsecondsSinceEpoch.abs() >
_MAX_MILLISECONDS_SINCE_EPOCH * 1000 + Duration.MICROSECONDS_PER_DAY) {
return null;
}
if (!isUtc) {
// 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) {
return null;
}
return microsecondsSinceEpoch;
}
/**
* The number of milliseconds since
* the "Unix epoch" 1970-01-01T00:00:00Z (UTC).
*
* This value is independent of the time zone.
*
* This value is at most
* 8,640,000,000,000,000ms (100,000,000 days) from the Unix epoch.
* In other words: `millisecondsSinceEpoch.abs() <= 8640000000000000`.
*/
int get millisecondsSinceEpoch =>
_value ~/ Duration.MICROSECONDS_PER_MILLISECOND;
/**
* The number of microseconds since
* the "Unix epoch" 1970-01-01T00:00:00Z (UTC).
*
* This value is independent of the time zone.
*
* This value is at most
* 8,640,000,000,000,000,000us (100,000,000 days) from the Unix epoch.
* In other words: `microsecondsSinceEpoch.abs() <= 8640000000000000000`.
*
* Note that this value does not fit into 53 bits (the size of a IEEE double).
* A JavaScript number is not able to hold this value.
*/
int get microsecondsSinceEpoch => _value;
/**
* The time zone name.
*
* This value is provided by the operating system and may be an
* abbreviation or a full name.
*
* In the browser or on Unix-like systems commonly returns abbreviations,
* such as "CET" or "CEST". On Windows returns the full name, for example
* "Pacific Standard Time".
*/
String get timeZoneName {
if (isUtc) return "UTC";
return _timeZoneName(microsecondsSinceEpoch);
}
/**
* The time zone offset, which
* is the difference between local time and UTC.
*
* The offset is positive for time zones east of UTC.
*
* Note, that JavaScript, Python and C return the difference between UTC and
* local time. Java, C# and Ruby return the difference between local time and
* UTC.
*/
Duration get timeZoneOffset {
if (isUtc) return new Duration();
int offsetInSeconds = _timeZoneOffsetInSeconds(microsecondsSinceEpoch);
return new Duration(seconds: offsetInSeconds);
}
/**
* The year.
*
* DateTime moonLanding = DateTime.parse("1969-07-20 20:18:00");
* assert(moonLanding.year == 1969);
*/
int get year => _parts[_YEAR_INDEX];
/**
* The month [1..12].
*
* DateTime moonLanding = DateTime.parse("1969-07-20 20:18:00");
* assert(moonLanding.month == 7);
* assert(moonLanding.month == DateTime.JULY);
*/
int get month => _parts[_MONTH_INDEX];
/**
* The day of the month [1..31].
*
* DateTime moonLanding = DateTime.parse("1969-07-20 20:18:00");
* assert(moonLanding.day == 20);
*/
int get day => _parts[_DAY_INDEX];
/**
* The hour of the day, expressed as in a 24-hour clock [0..23].
*
* DateTime moonLanding = DateTime.parse("1969-07-20 20:18:00");
* assert(moonLanding.hour == 20);
*/
int get hour => _parts[_HOUR_INDEX];
/**
* The minute [0...59].
*
* DateTime moonLanding = DateTime.parse("1969-07-20 20:18:00");
* assert(moonLanding.minute == 18);
*/
int get minute => _parts[_MINUTE_INDEX];
/**
* The second [0...59].
*
* DateTime moonLanding = DateTime.parse("1969-07-20 20:18:00");
* assert(moonLanding.second == 0);
*/
int get second => _parts[_SECOND_INDEX];
/**
* The millisecond [0...999].
*
* DateTime moonLanding = DateTime.parse("1969-07-20 20:18:00");
* assert(moonLanding.millisecond == 0);
*/
int get millisecond => _parts[_MILLISECOND_INDEX];
/**
* The microsecond [0...999].
*
* DateTime moonLanding = DateTime.parse("1969-07-20 20:18:00");
* assert(moonLanding.microsecond == 0);
*/
int get microsecond => _parts[_MICROSECOND_INDEX];
/**
* The day of the week [MONDAY]..[SUNDAY].
*
* In accordance with ISO 8601
* a week starts with Monday, which has the value 1.
*
* DateTime moonLanding = DateTime.parse("1969-07-20 20:18:00");
* assert(moonLanding.weekday == 7);
* assert(moonLanding.weekday == DateTime.SUNDAY);
*
*/
int get weekday => _parts[_WEEKDAY_INDEX];
static int _getCurrentMicros() native "DateTime_currentTimeMicros";
static String _timeZoneNameForClampedSeconds(int secondsSinceEpoch)
native "DateTime_timeZoneName";
static int _timeZoneOffsetInSecondsForClampedSeconds(int secondsSinceEpoch)
native "DateTime_timeZoneOffsetInSeconds";
static int _localTimeZoneAdjustmentInSeconds()
native "DateTime_localTimeZoneAdjustmentInSeconds";
static const _MICROSECOND_INDEX = 0;
static const _MILLISECOND_INDEX = 1;
static const _SECOND_INDEX = 2;
static const _MINUTE_INDEX = 3;
static const _HOUR_INDEX = 4;
static const _DAY_INDEX = 5;
static String _timeZoneName(int microsecondsSinceEpoch) {
int equivalentSeconds = _equivalentSeconds(microsecondsSinceEpoch);
return _timeZoneNameForClampedSeconds(equivalentSeconds);
}
static const _MONTH_INDEX = 7;
static const _YEAR_INDEX = 8;
List<int> __parts;
/** The first list contains the days until each month in non-leap years. The
* second list contains the days in leap years. */
static const 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]
];
static List<int> _computeUpperPart(int localMicros) {
const int DAYS_IN_4_YEARS = 4 * 365 + 1;
const int DAYS_IN_100_YEARS = 25 * DAYS_IN_4_YEARS - 1;
const int DAYS_IN_400_YEARS = 4 * DAYS_IN_100_YEARS + 1;
const int DAYS_1970_TO_2000 = 30 * 365 + 7;
const int DAYS_OFFSET =
1000 * DAYS_IN_400_YEARS + 5 * DAYS_IN_400_YEARS - DAYS_1970_TO_2000;
const int YEARS_OFFSET = 400000;
int resultYear = 0;
int resultMonth = 0;
int resultDay = 0;
// Always round down.
final int daysSince1970 =
_flooredDivision(localMicros, Duration.MICROSECONDS_PER_DAY);
int days = daysSince1970;
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;
int resultMicrosecond = localMicros % Duration.MICROSECONDS_PER_MILLISECOND;
int resultMillisecond =
_flooredDivision(localMicros, Duration.MICROSECONDS_PER_MILLISECOND) %
Duration.MILLISECONDS_PER_SECOND;
int resultSecond =
_flooredDivision(localMicros, Duration.MICROSECONDS_PER_SECOND) %
Duration.SECONDS_PER_MINUTE;
int resultMinute =
_flooredDivision(localMicros, Duration.MICROSECONDS_PER_MINUTE);
resultMinute %= Duration.MINUTES_PER_HOUR;
int resultHour =
_flooredDivision(localMicros, Duration.MICROSECONDS_PER_HOUR);
resultHour %= Duration.HOURS_PER_DAY;
// In accordance with ISO 8601 a week
// starts with Monday. Monday has the value 1 up to Sunday with 7.
// 1970-1-1 was a Thursday.
int resultWeekday = ((daysSince1970 + DateTime.THURSDAY - DateTime.MONDAY) %
DateTime.DAYS_PER_WEEK) +
DateTime.MONDAY;
List<int> list = new List<int>(_YEAR_INDEX + 1);
list[_MICROSECOND_INDEX] = resultMicrosecond;
list[_MILLISECOND_INDEX] = resultMillisecond;
list[_SECOND_INDEX] = resultSecond;
list[_MINUTE_INDEX] = resultMinute;
list[_HOUR_INDEX] = resultHour;
list[_DAY_INDEX] = resultDay;
list[_WEEKDAY_INDEX] = resultWeekday;
list[_MONTH_INDEX] = resultMonth;
list[_YEAR_INDEX] = resultYear;
return list;
}
List<int> get _parts {
if (__parts == null) {
__parts = _computeUpperPart(_localDateInUtcMicros);
}
return __parts;
}
/**
* Returns the amount of microseconds in UTC that represent the same values
* as [this].
*
* Say `t` is the result of this function, then
* * `this.year == new DateTime.fromMicrosecondsSinceEpoch(t, true).year`,
* * `this.month == new DateTime.fromMicrosecondsSinceEpoch(t, true).month`,
* * `this.day == new DateTime.fromMicrosecondsSinceEpoch(t, true).day`,
* * `this.hour == new DateTime.fromMicrosecondsSinceEpoch(t, true).hour`,
* * ...
*
* 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 _localDateInUtcMicros {
int micros = _value;
if (isUtc) return micros;
int offset =
_timeZoneOffsetInSeconds(micros) * Duration.MICROSECONDS_PER_SECOND;
return micros + offset;
}
static int _flooredDivision(int a, int b) {
return (a - (a < 0 ? b - 1 : 0)) ~/ b;
}
static int _dayFromYear(int year) {
return 365 * (year - 1970) +
_flooredDivision(year - 1969, 4) -
_flooredDivision(year - 1901, 100) +
_flooredDivision(year - 1601, 400);
}
static bool _isLeapYear(int y) {
// (y % 16 == 0) matches multiples of 400, and is faster than % 400.
return (y % 4 == 0) && ((y % 16 == 0) || (y % 100 != 0));
}
static int _weekDay(int y) {
// 1/1/1970 was a Thursday.
return (_dayFromYear(y) + 4) % 7;
}
/**
* 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 int _equivalentYear(int year) {
// 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
// 1/1/1968 was a Monday). With leap-years it jumps over one week day
// (e.g. 1/1/1957 was a Tuesday).
// After 12 years the weekdays have advanced by 12 days + 3 leap days =
// 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 = (_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;
}
/**
* 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) {
const int DAYS_IN_4_YEARS = 4 * 365 + 1;
const int DAYS_IN_100_YEARS = 25 * DAYS_IN_4_YEARS - 1;
const 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;
}
int micros = secondsSinceEpoch * Duration.MICROSECONDS_PER_SECOND;
return _computeUpperPart(micros)[_YEAR_INDEX];
}
/**
* 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 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 = 0x7FFFFFFF;
int secondsSinceEpoch = _flooredDivision(
microsecondsSinceEpoch, Duration.MICROSECONDS_PER_SECOND);
if (secondsSinceEpoch.abs() > 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 microsecondsSinceEpoch) {
int equivalentSeconds = _equivalentSeconds(microsecondsSinceEpoch);
return _timeZoneOffsetInSecondsForClampedSeconds(equivalentSeconds);
}
static const _WEEKDAY_INDEX = 6;
}