{"record":{"id":"6d481329c660d0f2","repo":"phacility/phabricator","slug":"iso8601-date-ends-in-z-indicating-utc-but-a-tim","errorCode":null,"errorMessage":"ISO8601 date ends in \"Z\" indicating UTC, but a timezone other than UTC (\"%s\") was specified.","messagePattern":"ISO8601 date ends in \"Z\" indicating UTC, but a timezone other than UTC \\(\"(.+?)\"\\) was specified\\.","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/calendar/parser/data/PhutilCalendarAbsoluteDateTime.php","lineNumber":35,"sourceCode":"      '(?P<y>\\d{4})(?P<m>\\d{2})(?P<d>\\d{2})'.\n      '(?:'.\n        'T(?P<h>\\d{2})(?P<i>\\d{2})(?P<s>\\d{2})(?<z>Z)?'.\n      ')?'.\n      '\\z/';\n\n    $matches = null;\n    $ok = preg_match($pattern, $value, $matches);\n    if (!$ok) {\n      throw new Exception(\n        pht(\n          'Expected ISO8601 datetime in the format \"19990105T112233Z\", '.\n          'found \"%s\".',\n          $value));\n    }\n\n    if (isset($matches['z'])) {\n      if ($timezone != 'UTC') {\n        throw new Exception(\n          pht(\n            'ISO8601 date ends in \"Z\" indicating UTC, but a timezone other '.\n            'than UTC (\"%s\") was specified.',\n            $timezone));\n      }\n    }\n\n    $datetime = id(new self())\n      ->setYear((int)$matches['y'])\n      ->setMonth((int)$matches['m'])\n      ->setDay((int)$matches['d'])\n      ->setTimezone($timezone);\n\n    if (isset($matches['h'])) {\n      $datetime\n        ->setHour((int)$matches['h'])\n        ->setMinute((int)$matches['i'])\n        ->setSecond((int)$matches['s']);","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/calendar/parser/data/PhutilCalendarAbsoluteDateTime.php#L17-L53","documentation":"PhutilCalendarAbsoluteDateTime::newFromISO8601() throws when the parsed value ends in 'Z' (which asserts UTC) but a $timezone argument other than 'UTC' was also supplied. The two are contradictory, so the constructor refuses rather than silently choosing one.","triggerScenarios":"newFromISO8601('19990105T112233Z', 'America/New_York'). Common when a helper always passes a default timezone while values arrive with Z suffixes.","commonSituations":"Reusable wrappers hardcoding a local timezone; ICS data mixing UTC (Z) values with a per-calendar timezone default; refactors that added a timezone parameter to existing call sites.","solutions":["Pass 'UTC' (or omit the parameter, UTC is the default) whenever the value ends in Z.","If you need local time, keep timezone 'UTC' here and convert afterwards with setTimezone()/toLocalDateTime-style APIs.","Strip the trailing Z and pre-convert the instant into the target timezone if you must pass a non-UTC zone."],"exampleFix":"// before\n$dt = PhutilCalendarAbsoluteDateTime::newFromISO8601('19990105T112233Z', 'America/New_York');\n// after\n$dt = PhutilCalendarAbsoluteDateTime::newFromISO8601('19990105T112233Z', 'UTC');","handlingStrategy":"validation","validationCode":"// Reconcile the timezone with the value before parsing:\nif (strtoupper(substr($value, -1)) === 'Z') {\n  $timezone = 'UTC';\n}\n$dt = PhutilCalendarAbsoluteDateTime::newFromISO8601($value, $timezone);","typeGuard":"function timezoneMatchesValue($value, $timezone) {\n  if (strtoupper(substr(trim($value), -1)) === 'Z') {\n    return strcasecmp($timezone, 'UTC') === 0;\n  }\n  return true;\n}","tryCatchPattern":"try {\n  $dt = PhutilCalendarAbsoluteDateTime::newFromISO8601($value, $timezone);\n} catch (Exception $ex) {\n  // Z + non-UTC conflict: retry in UTC, then convert\n  $dt = PhutilCalendarAbsoluteDateTime::newFromISO8601($value, 'UTC');\n}","preventionTips":["Default to UTC and only pass another timezone for values without a Z suffix.","Convert after construction (setTimezone) instead of mixing zone hints at parse time."],"tags":["phutil","calendar","iso8601","timezone","conflict"],"backgroundTag":"timezone-conflict","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}