instructure/canvas-lms · warning · RruleValidationError

Invalid FREQ

Error message

Invalid FREQ '%{freq}'

What it means

rrule_to_natural_language maps an RRULE's FREQ (DAILY/WEEKLY/MONTHLY/YEARLY). An unrecognized FREQ raises RruleValidationError "Invalid FREQ '%{freq}'"; the surrounding rescue logs it and returns nil, so calendar events silently lose their natural-language description.

Solutions

  1. Reject or normalize sub-daily FREQ values at ICS import time.
  2. Catch RruleValidationError upstream and render a generic 'repeats on a custom schedule' string instead of nil.
  3. Fix the stored rrule to a supported FREQ (DAILY/WEEKLY/MONTHLY/YEARLY).
  4. Add validation on CalendarEvent save to whitelist supported FREQ values.

Example fix

// before
freq = rropts['FREQ'] # 'HOURLY'
// after
unless %w[DAILY WEEKLY MONTHLY YEARLY].include?(rropts['FREQ'])
  rropts['FREQ'] = 'DAILY' # or reject the event at import
end
Defensive patterns

Strategy: validation

Validate before calling

return nil unless %w[DAILY WEEKLY MONTHLY YEARLY].include?(rropts['FREQ'])

Try / catch

desc = begin
  rrule_to_natural_language(rrule)
rescue RruleValidationError
  I18n.t('repeats on a custom schedule')
end

Prevention

When it happens

Trigger: Rendering a calendar_event_json for an event whose rrule field has FREQ values like SECONDLY, MINUTELY, HOURLY, or a malformed/empty FREQ (e.g. from an imported .ics with sub-daily recurrence).

Common situations: Importing external ICS feeds using SECONDLY/HOURLY recurrences; hand-edited rrule strings; data migrations from other calendar systems.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at app/helpers/rrule_helper.rb:44

# rubocop:disable Style/IfInsideElse
module RruleHelper
  RECURRING_EVENT_LIMIT = 400

  def rrule_to_natural_language(rrule)
    rropts = rrule_parse(rrule)
    rrule_validate_common_opts(rropts)
    case rropts["FREQ"]
    when "DAILY"
      parse_daily(rropts)
    when "WEEKLY"
      parse_weekly(rropts)
    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")

View on GitHub (pinned to 1c9f0bb801)