phacility/phabricator · error · Exception

Expected ISO8601 datetime in the format "19990105T112233Z",

Error message

Expected ISO8601 datetime in the format "19990105T112233Z", found "%s".

What it means

PhutilCalendarAbsoluteDateTime::newFromISO8601() accepts only the basic ISO 8601 form YYYYMMDD, optionally followed by THHMMSS and an optional trailing Z. Any deviation - hyphens, colons, timezone offsets like +0100, missing digits, or an empty string - fails the regex and throws.

Source

Thrown at src/applications/calendar/parser/data/PhutilCalendarAbsoluteDateTime.php:26

  private $day;
  private $hour = 0;
  private $minute = 0;
  private $second = 0;
  private $timezone;

  public static function newFromISO8601($value, $timezone = 'UTC') {
    $pattern =
      '/^'.
      '(?P<y>\d{4})(?P<m>\d{2})(?P<d>\d{2})'.
      '(?:'.
        'T(?P<h>\d{2})(?P<i>\d{2})(?P<s>\d{2})(?<z>Z)?'.
      ')?'.
      '\z/';

    $matches = null;
    $ok = preg_match($pattern, $value, $matches);
    if (!$ok) {
      throw new Exception(
        pht(
          'Expected ISO8601 datetime in the format "19990105T112233Z", '.
          'found "%s".',
          $value));
    }

    if (isset($matches['z'])) {
      if ($timezone != 'UTC') {
        throw new Exception(
          pht(
            'ISO8601 date ends in "Z" indicating UTC, but a timezone other '.
            'than UTC ("%s") was specified.',
            $timezone));
      }
    }

    $datetime = id(new self())
      ->setYear((int)$matches['y'])

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Normalize to basic format before parsing: strip everything except digits and a trailing Z (e.g. preg_replace('/[^0-9TZ]/', '', $value)).
  2. Generate correct values directly: gmdate('Ymd\\THis\\Z', $timestamp).
  3. If the input carries an offset, convert it to a UTC timestamp first (new DateTime($input)->getTimestamp()) and format as basic UTC.
  4. Trim whitespace: $value = trim($value) before calling.

Example fix

// before
$dt = PhutilCalendarAbsoluteDateTime::newFromISO8601('2024-01-05T11:22:33Z');
// after
$dt = PhutilCalendarAbsoluteDateTime::newFromISO8601('20240105T112233Z');
Defensive patterns

Strategy: type-guard

Validate before calling

$value = trim($value);
if (!preg_match('/^\d{4}\d{2}\d{2}(T\d{2}\d{2}\d{2}Z?)?$/', $value)) {
  // normalize extended format to basic before calling the parser
  $dt = new DateTime($value);
  $value = $dt->format('Ymd\\THis');
}
$abs = PhutilCalendarAbsoluteDateTime::newFromISO8601($value);

Type guard

function isBasicIso8601DateTime($value) {
  return (bool) preg_match('/^\d{4}\d{2}\d{2}(T\d{2}\d{2}\d{2}Z?)?$/', trim((string)$value));
}

Try / catch

try {
  $dt = PhutilCalendarAbsoluteDateTime::newFromISO8601($value, $tz);
} catch (Exception $ex) {
  // skip/log the bad property; keep parsing the rest of the ICS object
  phlog('Bad ISO8601 datetime: '.$value);
  continue;
}

Prevention

When it happens

Trigger: newFromISO8601('2024-01-05'), newFromISO8601('20240105T11:22:33'), newFromISO8601('20240105T112233+0100'), newFromISO8601(''). Extended-format values from PHP's DateTime::format('c') or from ICS files with relaxed producers.

Common situations: Passing output of date('c') or DATE_ATOM; user-typed dates; third-party ICS/calendar feeds emitting extended ISO format; whitespace padding around the value.

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/2516107701e372e8. Report an issue: GitHub.