phacility/phabricator · error · Exception
RRULE BYDAY value "%s" has an offset with magnitude "%s", bu
Error message
RRULE BYDAY value "%s" has an offset with magnitude "%s", but the maximum permitted value is "%s".
What it means
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.
Source
Thrown at src/applications/calendar/parser/data/PhutilCalendarRecurrenceRule.php:483
$pattern = '/^(?:[+-]?([1-9]\d?))?('.$constants.')\z/';
foreach ($by_day as $key => $value) {
$matches = null;
if (!preg_match($pattern, $value, $matches)) {
throw new Exception(
pht(
'RRULE BYDAY value "%s" is invalid: rule part must be in the '.
'expected form (like "MO", "-3TH", or "+2SU").',
$value));
}
// The maximum allowed value is 53, which corresponds to "the 53rd
// Monday every year" or similar when evaluated against a YEARLY rule.
$maximum = 53;
$magnitude = (int)$matches[1];
if ($magnitude > $maximum) {
throw new Exception(
pht(
'RRULE BYDAY value "%s" has an offset with magnitude "%s", but '.
'the maximum permitted value is "%s".',
$value,
$magnitude,
$maximum));
}
// Normalize "+3FR" into "3FR".
$by_day[$key] = ltrim($value, '+');
}
$this->byDay = array_fuse($by_day);
return $this;
}
public function getByDay() {
return $this->byDay;View on GitHub (pinned to 5720a38cfe)
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
Example fix
// before $rrule = 'FREQ=YEARLY;BYDAY=54MO'; // after $rrule = 'FREQ=YEARLY;BYDAY=MO'; // or, for a bounded ordinal: $rrule = 'FREQ=YEARLY;BYDAY=53MO';
Defensive patterns
Strategy: validation
Validate before calling
function isByDayValueValid($value) {
if (!preg_match('/^([+-]\d{1,3})?(MO|TU|WE|TH|FR|SA|SU)$/', $value, $m)) {
return false;
}
if (isset($m[1]) && abs((int)$m[1]) > 53) {
return false;
}
return true;
}
$parts = explode(';', $rrule);
foreach ($parts as $part) {
if (strncmp($part, 'BYDAY=', 6) === 0) {
foreach (explode(',', substr($part, 6)) as $day) {
if (!isByDayValueValid($day)) {
throw new Exception('Invalid BYDAY entry: '.$day);
}
}
}
} Try / catch
try {
$rule = PhutilCalendarRecurrenceRule::newFromRRule($rrule);
} catch (Exception $ex) {
// Surface a user-actionable message with the raw RRULE for triage.
throw new Exception(
pht('Unsupported recurrence rule: %s (%s)', $ex->getMessage(), $rrule));
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- RRULE specifies BYMONTHDAY with FREQ set to WEEKLY, which vi
- RRULE specifies BYYEARDAY with FREQ of DAILY, WEEKLY or MONT
- Value "%s" in RRULE "%s" parameter is invalid: it must be be
- Value "%s" in RRULE "%s" parameter is invalid: it must not b
- RRULE BYDAY value "%s" is invalid: rule part must be in the
AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21).
Data as JSON: /api/errors/29656076326c257b.
Report an issue: GitHub.