DateTime parse(
String formattedString
)

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);
  }
}