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 minuts 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 minutes may be separted 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"
Source
/**
* 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 minuts 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 minutes may be separted 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
* millis_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)' // The day part.
r'(?:[ T](\d\d)(?::?(\d\d)(?::?(\d\d)(.\d{1,6})?)?)?' // The time part
r'( ?[zZ]| ?([-+])(\d\d)(?::?(\d\d))?)?)?$'); // The timezone part
Match match = re.firstMatch(formattedString);
if (match != null) {
int parseIntOrZero(String matched) {
if (matched == null) return 0;
return int.parse(matched);
}
double parseDoubleOrZero(String matched) {
if (matched == null) return 0.0;
return double.parse(matched);
}
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 millisecond = (parseDoubleOrZero(match[7]) * 1000).round();
if (millisecond == 1000) {
addOneMillisecond = true;
millisecond = 999;
}
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 millisecondsSinceEpoch = _brokenDownDateToMillisecondsSinceEpoch(
years, month, day, hour, minute, second, millisecond, isUtc);
if (millisecondsSinceEpoch == null) {
throw new FormatException("Time out of range", formattedString);
}
if (addOneMillisecond) millisecondsSinceEpoch++;
return new DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch,
isUtc: isUtc);
} else {
throw new FormatException("Invalid date format", formattedString);
}
}