{"record":{"id":"2d310d32a6529fee","repo":"phacility/phabricator","slug":"expected-iso8601-duration-in-the-format-p12dt3h4m","errorCode":null,"errorMessage":"Expected ISO8601 duration in the format \"P12DT3H4M5S\", found \"%s\".","messagePattern":"Expected ISO8601 duration in the format \"P12DT3H4M5S\", found \"(.+?)\"\\.","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/calendar/parser/data/PhutilCalendarDuration.php","lineNumber":75,"sourceCode":"\n  public static function newFromISO8601($value) {\n    $pattern =\n      '/^'.\n      '(?P<sign>[+-])?'.\n      'P'.\n      '(?:'.\n        '(?P<W>\\d+)W'.\n        '|'.\n        '(?:(?:(?P<D>\\d+)D)?'.\n          '(?:T(?:(?P<H>\\d+)H)?(?:(?P<M>\\d+)M)?(?:(?P<S>\\d+)S)?)?'.\n        ')'.\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 duration in the format \"P12DT3H4M5S\", found '.\n          '\"%s\".',\n          $value));\n    }\n\n    $is_negative = (idx($matches, 'sign') == '-');\n\n    return id(new self())\n      ->setIsNegative($is_negative)\n      ->setWeeks((int)idx($matches, 'W', 0))\n      ->setDays((int)idx($matches, 'D', 0))\n      ->setHours((int)idx($matches, 'H', 0))\n      ->setMinutes((int)idx($matches, 'M', 0))\n      ->setSeconds((int)idx($matches, 'S', 0));\n  }\n\n  public function toISO8601() {","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/calendar/parser/data/PhutilCalendarDuration.php#L57-L93","documentation":"PhutilCalendarDuration's ISO 8601 duration parser accepts an optional sign, a literal 'P', then either a weeks component (nW) or days and/or time components (nD, T nH nM nS). Values missing the P, missing every component, or using unsupported units (Y years, M months) fail the regex and throw.","triggerScenarios":"newFromISO8601('3H') (missing P), 'PT' or 'P' (no components), 'P1Y6M' (years/months unsupported), 'P1.5H' (fraction), 'P1D2H' (T missing before time parts).","commonSituations":"ICS DURATION values from producers that emit years/months; human-written shorthand like '30m'; forgetting the T separator between date and time parts.","solutions":["Ensure the value has the leading P (with optional +/- sign) and at least one component: 'PT30M', 'P1D', 'P2W', '-P1D'.","Convert unsupported units before parsing: years/months to days/weeks (e.g. P1Y -> P52W or P365D per your business rule).","Insert the required T before hour/minute/second parts when a date part is also present: 'P1DT2H'.","Validate with the production regex before feeding external data in."],"exampleFix":"// before\n$dur = PhutilCalendarDuration::newFromISO8601('30M');\n// after\n$dur = PhutilCalendarDuration::newFromISO8601('PT30M');","handlingStrategy":"type-guard","validationCode":"$value = strtoupper(trim($value));\nif (!preg_match('/^[+-]?P(\\d+W|(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+S)?)?)$/', $value)\n    || preg_match('/^[+-]?P[^WDT]/', $value)) {\n  // rewrite unsupported units (Y/M) to days/weeks per your rules first\n  $value = preg_replace('/(\\d+)Y/', '${1}365D', $value);\n  $value = preg_replace('/(\\d+)M(?!\\d*S)/', '${1}30D', $value);\n}","typeGuard":"function isIso8601Duration($value) {\n  return (bool) preg_match(\n    '/^[+-]?P(\\d+W|\\d+D(T(\\d+H)?(\\d+M)?(\\d+S)?)?|T(\\d+H)?(\\d+M)?(\\d+S)?)$/',\n    strtoupper(trim((string)$value))\n  );\n}","tryCatchPattern":"try {\n  $dur = PhutilCalendarDuration::newFromISO8601($value);\n} catch (Exception $ex) {\n  phlog('Bad ISO8601 duration: '.$value);\n  continue; // skip this property, keep importing\n}","preventionTips":["Always emit the leading P and the T before time components.","Reject or convert Y/M units before feeding external durations in.","Unit-test your duration writer against the parser's accepted forms (P2W, P1DT2H, PT30M, -P1D)."],"tags":["phutil","calendar","iso8601","duration","parse-error"],"backgroundTag":"duration-parse-failed","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}