{"record":{"id":"ce492ed5e08c5f97","repo":"phacility/phabricator","slug":"rrule-interval-s-is-invalid-interval-must-be-a","errorCode":null,"errorMessage":"RRULE INTERVAL \"%s\" is invalid: interval must be an integer.","messagePattern":"RRULE INTERVAL \"(.+?)\" is invalid: interval must be an integer\\.","errorType":"validation","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/calendar/parser/data/PhutilCalendarRecurrenceRule.php","lineNumber":411,"sourceCode":"    }\n\n    $this->frequency = $frequency;\n    $this->frequencyScale = $map[$frequency];\n\n    return $this;\n  }\n\n  public function getFrequency() {\n    return $this->frequency;\n  }\n\n  public function getFrequencyScale() {\n    return $this->frequencyScale;\n  }\n\n  public function setInterval($interval) {\n    if (!is_int($interval)) {\n      throw new Exception(\n        pht(\n          'RRULE INTERVAL \"%s\" is invalid: interval must be an integer.',\n          $interval));\n    }\n\n    if ($interval < 1) {\n      throw new Exception(\n        pht(\n          'RRULE INTERVAL \"%s\" is invalid: interval must be 1 or more.',\n          $interval));\n    }\n\n    $this->interval = $interval;\n    return $this;\n  }\n\n  public function getInterval() {\n    return $this->interval;","sourceCodeStart":393,"sourceCodeEnd":429,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/calendar/parser/data/PhutilCalendarRecurrenceRule.php#L393-L429","documentation":"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.","triggerScenarios":"setInterval('2'); setInterval(2.0); values fetched from JSON, databases, or request params, which arrive as strings.","commonSituations":"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).","solutions":["Cast before calling: setInterval((int)$interval).","Prefer newFromDictionary() for external data - it performs the cast for you.","Validate the range too: a cast '2' passes, but 0 or negative then throws the follow-up 'must be 1 or more' error."],"exampleFix":"// before\n$rule->setInterval($raw['interval']); // '2' from JSON\n// after\n$rule->setInterval((int)$raw['interval']);","handlingStrategy":"type-guard","validationCode":"$interval = idx($dict, 'INTERVAL', 1);\nif (!is_int($interval)) {\n  $dict['INTERVAL'] = (int)$interval; // explicit cast before it reaches setInterval()\n}\n// Prefer newFromDictionary($dict): its normalization performs this cast for you.","typeGuard":"function isIntegerInterval($interval) {\n  return is_int($interval);\n}","tryCatchPattern":null,"preventionTips":["Cast values from JSON/storage/request input to int before calling setters.","Route external data through newFromDictionary() rather than individual setters.","Watch out for floats: is_int(2.0) is false; use (int) first."],"tags":["phutil","calendar","rrule","interval","type-error"],"backgroundTag":"type-mismatch","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}