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

  1. Reduce the BYDAY ordinal to a value between -53 and 53 (e.g. '54MO' -> '53MO')
  2. If the intent is simply 'every Monday', drop the ordinal entirely and use the plain two-letter day code ('MO')
  3. 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

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


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/29656076326c257b. Report an issue: GitHub.