{"record":{"id":"713c42be7b5fe757","repo":"instructure/canvas-lms","slug":"interval-must-be-0","errorCode":null,"errorMessage":"INTERVAL must be > 0","messagePattern":"INTERVAL must be > 0","errorType":"validation","errorClass":"RruleValidationError","httpStatus":null,"severity":"warning","filePath":"app/helpers/rrule_helper.rb","lineNumber":57,"sourceCode":"    when \"MONTHLY\"\n      parse_monthly(rropts)\n    when \"YEARLY\"\n      parse_yearly(rropts)\n    else\n      raise RruleValidationError, I18n.t(\"Invalid FREQ '%{freq}'\", freq: rropts[\"FREQ\"])\n    end\n  rescue => e\n    logger.error \"RRULE to natural language failure: #{e}\"\n    nil\n  end\n\n  def rrule_parse(rrule)\n    Hash[*rrule.sub(/^RRULE:/, \"\").split(/[;=]/)]\n  end\n\n  def rrule_validate_common_opts(rropts)\n    raise RruleValidationError, I18n.t(\"Missing INTERVAL\") unless rropts.key?(\"INTERVAL\")\n    raise RruleValidationError, I18n.t(\"INTERVAL must be > 0\") unless rropts[\"INTERVAL\"].to_i > 0\n\n    # We do not support never ending series because each event in the series\n    # must get created in the db to support the paginated calendar_events api\n    raise RruleValidationError, I18n.t(\"Missing COUNT or UNTIL\") unless rropts.key?(\"COUNT\") || rropts.key?(\"UNTIL\")\n\n    if rropts.key?(\"COUNT\")\n      raise RruleValidationError, I18n.t(\"COUNT must be > 0\") unless rropts[\"COUNT\"].to_i > 0\n      raise RruleValidationError, I18n.t(\"COUNT must be <= %{limit}\", limit: RruleHelper::RECURRING_EVENT_LIMIT) unless rropts[\"COUNT\"].to_i <= RruleHelper::RECURRING_EVENT_LIMIT\n    else\n      begin\n        format_date(rropts[\"UNTIL\"])\n      rescue\n        raise RruleValidationError, I18n.t(\"Invalid UNTIL '%{until_date}'\", until_date: rropts[\"UNTIL\"])\n      end\n    end\n  end\n\n  private","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/helpers/rrule_helper.rb#L39-L75","documentation":"After confirming INTERVAL exists, rrule_validate_common_opts requires it to coerce to a positive integer; INTERVAL=0 or non-numeric raises RruleValidationError 'INTERVAL must be > 0'. Like the other RRULE validation errors, rrule_to_natural_language rescues it, logs, and returns nil.","triggerScenarios":"calendar_event_json rendering for an event with rrule like 'RRULE:FREQ=DAILY;INTERVAL=0;COUNT=5' or a non-numeric INTERVAL (e.g. 'INTERVAL=abc' from corrupted import data).","commonSituations":"Bad hand-edited rrule values; importers writing RFC-invalid INTERVALs (RFC requires >= 1); data corruption/migration bugs zeroing the interval.","solutions":["Correct the stored rrule INTERVAL to >= 1.","Sanitize at write/import time: reject or clamp INTERVAL < 1 before persisting.","Validate the rrule string with a regex/model validation on CalendarEvent create/update.","Rescue RruleValidationError upstream and fall back to a generic recurrence label."],"exampleFix":"// before\nrrule = 'RRULE:FREQ=WEEKLY;INTERVAL=0;COUNT=4'\n// after\nrrule = 'RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=4' # clamp: [interval, 1].max","handlingStrategy":"validation","validationCode":"interval = rropts['INTERVAL'].to_i\nraise ArgumentError, 'INTERVAL must be > 0' unless interval > 0","typeGuard":null,"tryCatchPattern":"begin\n  desc = rrule_to_natural_language(rrule)\nrescue RruleValidationError => e\n  logger.warn(\"rrule skipped: #{e.message}\")\n  desc = nil\nend","preventionTips":["Clamp INTERVAL to >= 1 whenever writing RRULE strings","Validate RRULE syntax on CalendarEvent save","Sanitize INTERVAL on import from external ICS feeds"],"tags":["rrule","calendar","value-out-of-range"],"backgroundTag":"value-out-of-range","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}