phacility/phabricator · error · Exception

RRULE INTERVAL "%s" is invalid: interval must be an integer.

Error message

RRULE INTERVAL "%s" is invalid: interval must be an integer.

What it means

setInterval() requires a genuine PHP int (checked with is_int()). Numeric strings like '2' and floats like 2.0 throw even though they look numeric. The dictionary path (newFromDictionary) casts to int during normalization, so this error indicates direct setter use with an uncast value.

Source

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

    }

    $this->frequency = $frequency;
    $this->frequencyScale = $map[$frequency];

    return $this;
  }

  public function getFrequency() {
    return $this->frequency;
  }

  public function getFrequencyScale() {
    return $this->frequencyScale;
  }

  public function setInterval($interval) {
    if (!is_int($interval)) {
      throw new Exception(
        pht(
          'RRULE INTERVAL "%s" is invalid: interval must be an integer.',
          $interval));
    }

    if ($interval < 1) {
      throw new Exception(
        pht(
          'RRULE INTERVAL "%s" is invalid: interval must be 1 or more.',
          $interval));
    }

    $this->interval = $interval;
    return $this;
  }

  public function getInterval() {
    return $this->interval;

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Cast before calling: setInterval((int)$interval).
  2. Prefer newFromDictionary() for external data - it performs the cast for you.
  3. Validate the range too: a cast '2' passes, but 0 or negative then throws the follow-up 'must be 1 or more' error.

Example fix

// before
$rule->setInterval($raw['interval']); // '2' from JSON
// after
$rule->setInterval((int)$raw['interval']);
Defensive patterns

Strategy: type-guard

Validate before calling

$interval = idx($dict, 'INTERVAL', 1);
if (!is_int($interval)) {
  $dict['INTERVAL'] = (int)$interval; // explicit cast before it reaches setInterval()
}
// Prefer newFromDictionary($dict): its normalization performs this cast for you.

Type guard

function isIntegerInterval($interval) {
  return is_int($interval);
}

Prevention

When it happens

Trigger: setInterval('2'); setInterval(2.0); values fetched from JSON, databases, or request params, which arrive as strings.

Common situations: Round-tripping rules through JSON/storage and rehydrating without casting; mixed type flows where the same value sometimes comes from the parser (int) and sometimes from raw input (string).

Related errors


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