{"record":{"id":"29656076326c257b","repo":"phacility/phabricator","slug":"rrule-byday-value-s-has-an-offset-with-magnitud","errorCode":null,"errorMessage":"RRULE BYDAY value \"%s\" has an offset with magnitude \"%s\", but the maximum permitted value is \"%s\".","messagePattern":"RRULE BYDAY value \"(.+?)\" has an offset with magnitude \"(.+?)\", but the maximum permitted value is \"(.+?)\"\\.","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/calendar/parser/data/PhutilCalendarRecurrenceRule.php","lineNumber":483,"sourceCode":"\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,\n            $maximum));\n      }\n\n      // Normalize \"+3FR\" into \"3FR\".\n      $by_day[$key] = ltrim($value, '+');\n    }\n\n    $this->byDay = array_fuse($by_day);\n    return $this;\n  }\n\n  public function getByDay() {\n    return $this->byDay;","sourceCodeStart":465,"sourceCodeEnd":501,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/calendar/parser/data/PhutilCalendarRecurrenceRule.php#L465-L501","documentation":"PhutilCalendarRecurrenceRule parses each BYDAY entry (like 'MO', '-3TH', '+2SU') and rejects any whose numeric ordinal prefix has a magnitude above 53. The cap exists because a year contains at most 53 occurrences of any given weekday, so a larger offset (e.g. '54MO') can never match an occurrence and the rule would be degenerate. This is a parse-time RFC5545 sanity check, thrown as soon as the RRULE is read.","triggerScenarios":"Constructing or importing an RRULE that contains a BYDAY value with an oversized ordinal, e.g. 'FREQ=YEARLY;BYDAY=54MO', 'FREQ=MONTHLY;BYDAY=-60SA', or programmatically calling setByDay(array('+99FR')) on a rule object.","commonSituations":"Hand-edited .ics files, calendar imports from third-party tools that emit unbounded ordinals, or user-typed recurrence strings in an import UI where the ordinal was never clamped.","solutions":["Reduce the BYDAY ordinal to a value between -53 and 53 (e.g. '54MO' -> '53MO')","If the intent is simply 'every Monday', drop the ordinal entirely and use the plain two-letter day code ('MO')","Validate RRULE strings with a BYDAY regex/offset check before passing them to PhutilCalendarRecurrenceRule::newFromRRule or the import pipeline"],"exampleFix":"// before\n$rrule = 'FREQ=YEARLY;BYDAY=54MO';\n\n// after\n$rrule = 'FREQ=YEARLY;BYDAY=MO';\n// or, for a bounded ordinal:\n$rrule = 'FREQ=YEARLY;BYDAY=53MO';","handlingStrategy":"validation","validationCode":"function isByDayValueValid($value) {\n  if (!preg_match('/^([+-]\\d{1,3})?(MO|TU|WE|TH|FR|SA|SU)$/', $value, $m)) {\n    return false;\n  }\n  if (isset($m[1]) && abs((int)$m[1]) > 53) {\n    return false;\n  }\n  return true;\n}\n\n$parts = explode(';', $rrule);\nforeach ($parts as $part) {\n  if (strncmp($part, 'BYDAY=', 6) === 0) {\n    foreach (explode(',', substr($part, 6)) as $day) {\n      if (!isByDayValueValid($day)) {\n        throw new Exception('Invalid BYDAY entry: '.$day);\n      }\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  $rule = PhutilCalendarRecurrenceRule::newFromRRule($rrule);\n} catch (Exception $ex) {\n  // Surface a user-actionable message with the raw RRULE for triage.\n  throw new Exception(\n    pht('Unsupported recurrence rule: %s (%s)', $ex->getMessage(), $rrule));\n}","preventionTips":["Validate RRULE strings with a dedicated linter before persisting them on events","Clamp or reject BYDAY ordinals above 53 at the input boundary (form/import UI)","Keep a unit test matrix of valid/invalid BYDAY values ('MO', '-3TH', '+2SU', '54MO') so regressions surface early"],"tags":["rrule","icalendar","rfc5545","calendar","byday"],"backgroundTag":"rrule-validation-failed","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}