instructure/canvas-lms · warning · RruleValidationError

INTERVAL must be > 0

Error message

INTERVAL must be > 0

What it means

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.

Solutions

  1. Correct the stored rrule INTERVAL to >= 1.
  2. Sanitize at write/import time: reject or clamp INTERVAL < 1 before persisting.
  3. Validate the rrule string with a regex/model validation on CalendarEvent create/update.
  4. Rescue RruleValidationError upstream and fall back to a generic recurrence label.

Example fix

// before
rrule = 'RRULE:FREQ=WEEKLY;INTERVAL=0;COUNT=4'
// after
rrule = 'RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=4' # clamp: [interval, 1].max
Defensive patterns

Strategy: validation

Validate before calling

interval = rropts['INTERVAL'].to_i
raise ArgumentError, 'INTERVAL must be > 0' unless interval > 0

Try / catch

begin
  desc = rrule_to_natural_language(rrule)
rescue RruleValidationError => e
  logger.warn("rrule skipped: #{e.message}")
  desc = nil
end

Prevention

When it happens

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

Common situations: Bad hand-edited rrule values; importers writing RFC-invalid INTERVALs (RFC requires >= 1); data corruption/migration bugs zeroing the interval.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/713c42be7b5fe757. Report an issue: GitHub.

Appendix: source

Thrown at app/helpers/rrule_helper.rb:57

    when "MONTHLY"
      parse_monthly(rropts)
    when "YEARLY"
      parse_yearly(rropts)
    else
      raise RruleValidationError, I18n.t("Invalid FREQ '%{freq}'", freq: rropts["FREQ"])
    end
  rescue => e
    logger.error "RRULE to natural language failure: #{e}"
    nil
  end

  def rrule_parse(rrule)
    Hash[*rrule.sub(/^RRULE:/, "").split(/[;=]/)]
  end

  def rrule_validate_common_opts(rropts)
    raise RruleValidationError, I18n.t("Missing INTERVAL") unless rropts.key?("INTERVAL")
    raise RruleValidationError, I18n.t("INTERVAL must be > 0") unless rropts["INTERVAL"].to_i > 0

    # We do not support never ending series because each event in the series
    # must get created in the db to support the paginated calendar_events api
    raise RruleValidationError, I18n.t("Missing COUNT or UNTIL") unless rropts.key?("COUNT") || rropts.key?("UNTIL")

    if rropts.key?("COUNT")
      raise RruleValidationError, I18n.t("COUNT must be > 0") unless rropts["COUNT"].to_i > 0
      raise RruleValidationError, I18n.t("COUNT must be <= %{limit}", limit: RruleHelper::RECURRING_EVENT_LIMIT) unless rropts["COUNT"].to_i <= RruleHelper::RECURRING_EVENT_LIMIT
    else
      begin
        format_date(rropts["UNTIL"])
      rescue
        raise RruleValidationError, I18n.t("Invalid UNTIL '%{until_date}'", until_date: rropts["UNTIL"])
      end
    end
  end

  private

View on GitHub (pinned to 1c9f0bb801)