phacility/phabricator · error · Exception

Unexpected value "%s" in "%s" RULE property: expected an int

Error message

Unexpected value "%s" in "%s" RULE property: expected an integer.

What it means

While normalizing an RRULE dictionary, the scalar integer properties COUNT and INTERVAL must match /^\d+$/ (a non-negative integer string). Negative numbers, decimals, or non-numeric values throw before the cast to int. Note the message text says 'RULE property' (the RRULE spelling used elsewhere is inconsistent).

Source

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

        'BYHOUR',
        'BYMONTH',
        'BYMONTHDAY',
        'BYYEARDAY',
        'BYWEEKNO',
        'BYSETPOS',
      ));

    $int_values = array_fuse(
      array(
        'COUNT',
        'INTERVAL',
      ));

    foreach ($dict as $key => $value) {
      if (isset($int_values[$key])) {
        // None of these values may be negative.
        if (!preg_match('/^\d+\z/', $value)) {
          throw new Exception(
            pht(
              'Unexpected value "%s" in "%s" RULE property: expected an '.
              'integer.',
              $value,
              $key));
        }
        $dict[$key] = (int)$value;
      }

      if (isset($int_lists[$key])) {
        foreach ($value as $k => $v) {
          if (!preg_match('/^-?\d+\z/', $v)) {
            throw new Exception(
              pht(
                'Unexpected value "%s" in "%s" RRULE property: expected '.
                'only integers.',
                $v,
                $key));

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Coerce values before building the dict: $dict['COUNT'] = (string)(int)$count with $count >= 0.
  2. Reject or sanitize external ICS values that do not match ^\d+$.
  3. Remember COUNT=0 passes this check but later throws in setCount(); keep COUNT >= 1.

Example fix

// before
$dict['INTERVAL'] = '2x';
// after
$dict['INTERVAL'] = (string)(int)$interval;
Defensive patterns

Strategy: type-guard

Validate before calling

// Coerce scalar integer fields before building the dict:
foreach (array('COUNT', 'INTERVAL') as $k) {
  if (isset($dict[$k]) && preg_match('/^\d+$/', (string)$dict[$k])) {
    $dict[$k] = (string)(int)$dict[$k];
  } else {
    unset($dict[$k]); // drop invalid values instead of throwing later
  }
}

Type guard

function isNonNegativeIntegerString($value) {
  return is_string($value) && preg_match('/^\d+$/', $value)
    || is_int($value) && $value >= 0;
}

Try / catch

try {
  $rule = PhutilCalendarRecurrenceRule::newFromDictionary($dict);
} catch (Exception $ex) {
  phlog($ex->getMessage()); // identifies the offending property and value
  continue;
}

Prevention

When it happens

Trigger: Dict with 'COUNT' => '-5', 'INTERVAL' => '1.5', 'COUNT' => 'weekly', or values that arrived as free text from an ICS producer. Plain PHP ints are fine because preg_match stringifies them.

Common situations: Hand-built dicts from user input; ICS files with malformed RRULE parts; JSON round-trips that mangle numbers into strings like '1.0'.

Related errors


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