phacility/phabricator · error · Exception

RRULE BYDAY value "%s" is invalid: rule part must be in the

Error message

RRULE BYDAY value "%s" is invalid: rule part must be in the expected form (like "MO", "-3TH", or "+2SU").

What it means

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.

Source

Thrown at src/applications/calendar/parser/data/PhutilCalendarRecurrenceRule.php:470

  public function setByHour(array $by_hour) {
    $this->assertByRange('BYHOUR', $by_hour, 0, 23);
    $this->byHour = array_fuse($by_hour);
    return $this;
  }

  public function getByHour() {
    return $this->byHour;
  }

  public function setByDay(array $by_day) {
    $constants = self::getAllWeekdayConstants();
    $constants = implode('|', $constants);

    $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,

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Split comma lists before calling: array_map('trim', explode(',', $byday)).
  2. Use bare two-letter uppercase tokens, or ordinal forms like -1SU / +2MO with the ordinal between 1 and 53.
  3. Uppercase and pre-validate tokens with the production regex: /^(?:[+-]?[1-9]\d?)?(SU|MO|TU|WE|TH|FR|SA)$/

Example fix

// before
$rule->setByDay(array('MO,FR', 'WED'));
// after
$rule->setByDay(array('MO', 'FR'));
Defensive patterns

Strategy: type-guard

Validate before calling

// Split and normalize a raw BYDAY list before calling setByDay():
$tokens = array_map('trim', explode(',', (string)$byday_raw));
$pattern = '/^(?:[+-]?[1-9]\d?)?(SU|MO|TU|WE|TH|FR|SA)$/';
$by_day = array();
foreach ($tokens as $t) {
  $t = strtoupper($t);
  if (preg_match($pattern, $t, $m) && (!isset($m[1][0]) || abs((int)$m[1]) <= 53)) {
    $by_day[] = $t;
  }
}
if (!$by_day) {
  // reject the BYDAY part; do not call setByDay()
}

Type guard

function isByDayToken($token) {
  return (bool) preg_match(
    '/^(?:[+-]?[1-9]\d?)?(SU|MO|TU|WE|TH|FR|SA)$/',
    strtoupper(trim((string)$token))
  );
}

Prevention

When it happens

Trigger: 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'.

Common situations: Producers emitting full weekday names; forgetting to explode comma-separated BYDAY lists; edge-case exports with 0 or >53 ordinals; case normalization skipped.

Related errors


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