{"record":{"id":"f121fdd0b3d1fcaf","repo":"phacility/phabricator","slug":"rrule-byday-value-s-is-invalid-rule-part-must","errorCode":null,"errorMessage":"RRULE BYDAY value \"%s\" is invalid: rule part must be in the expected form (like \"MO\", \"-3TH\", or \"+2SU\").","messagePattern":"RRULE BYDAY value \"(.+?)\" is invalid: rule part must be in the expected form \\(like \"MO\", \"-3TH\", or \"\\+2SU\"\\)\\.","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/calendar/parser/data/PhutilCalendarRecurrenceRule.php","lineNumber":470,"sourceCode":"  public function setByHour(array $by_hour) {\n    $this->assertByRange('BYHOUR', $by_hour, 0, 23);\n    $this->byHour = array_fuse($by_hour);\n    return $this;\n  }\n\n  public function getByHour() {\n    return $this->byHour;\n  }\n\n  public function setByDay(array $by_day) {\n    $constants = self::getAllWeekdayConstants();\n    $constants = implode('|', $constants);\n\n    $pattern = '/^(?:[+-]?([1-9]\\d?))?('.$constants.')\\z/';\n    foreach ($by_day as $key => $value) {\n      $matches = null;\n      if (!preg_match($pattern, $value, $matches)) {\n        throw new Exception(\n          pht(\n            'RRULE BYDAY value \"%s\" is invalid: rule part must be in the '.\n            'expected form (like \"MO\", \"-3TH\", or \"+2SU\").',\n            $value));\n      }\n\n      // The maximum allowed value is 53, which corresponds to \"the 53rd\n      // Monday every year\" or similar when evaluated against a YEARLY rule.\n\n      $maximum = 53;\n      $magnitude = (int)$matches[1];\n      if ($magnitude > $maximum) {\n        throw new Exception(\n          pht(\n            'RRULE BYDAY value \"%s\" has an offset with magnitude \"%s\", but '.\n            'the maximum permitted value is \"%s\".',\n            $value,\n            $magnitude,","sourceCodeStart":452,"sourceCodeEnd":488,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/calendar/parser/data/PhutilCalendarRecurrenceRule.php#L452-L488","documentation":"setByDay() validates each token against an optional ordinal (1-99, optionally signed) followed by a two-letter weekday constant - forms like 'MO', '-3TH', '+2SU'. Anything else (full names, 'MON', '+0MO', ordinal 0, unsplit comma lists) fails the regex and throws. Ordinals that pass but exceed 53 fail a separate magnitude check immediately after.","triggerScenarios":"setByDay(array('MON')) ('MON' not matched); '+0FR' or '0MO' (ordinal must start with 1-9); passing the raw string 'MO,WE' instead of an array; lowercase 'mo'.","commonSituations":"Producers emitting full weekday names; forgetting to explode comma-separated BYDAY lists; edge-case exports with 0 or >53 ordinals; case normalization skipped.","solutions":["Split comma lists before calling: array_map('trim', explode(',', $byday)).","Use bare two-letter uppercase tokens, or ordinal forms like -1SU / +2MO with the ordinal between 1 and 53.","Uppercase and pre-validate tokens with the production regex: /^(?:[+-]?[1-9]\\d?)?(SU|MO|TU|WE|TH|FR|SA)$/"],"exampleFix":"// before\n$rule->setByDay(array('MO,FR', 'WED'));\n// after\n$rule->setByDay(array('MO', 'FR'));","handlingStrategy":"type-guard","validationCode":"// Split and normalize a raw BYDAY list before calling setByDay():\n$tokens = array_map('trim', explode(',', (string)$byday_raw));\n$pattern = '/^(?:[+-]?[1-9]\\d?)?(SU|MO|TU|WE|TH|FR|SA)$/';\n$by_day = array();\nforeach ($tokens as $t) {\n  $t = strtoupper($t);\n  if (preg_match($pattern, $t, $m) && (!isset($m[1][0]) || abs((int)$m[1]) <= 53)) {\n    $by_day[] = $t;\n  }\n}\nif (!$by_day) {\n  // reject the BYDAY part; do not call setByDay()\n}","typeGuard":"function isByDayToken($token) {\n  return (bool) preg_match(\n    '/^(?:[+-]?[1-9]\\d?)?(SU|MO|TU|WE|TH|FR|SA)$/',\n    strtoupper(trim((string)$token))\n  );\n}","tryCatchPattern":null,"preventionTips":["Explode comma-separated BYDAY strings before passing them as arrays.","Uppercase and trim every token; reject full weekday names after normalization.","Keep ordinals within 1..53 - the regex alone allows up to 99 but the setter rejects more than 53."],"tags":["phutil","calendar","rrule","byday","format-validation"],"backgroundTag":"invalid-weekday-value","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}