instructure/canvas-lms · error · RruleValidationError
A yearly RRULE must include BYDAY or BYMONTHDAY
Error message
A yearly RRULE must include BYDAY or BYMONTHDAY
What it means
parse_yearly requires a yearly RRULE to disambiguate its recurrence: either BYDAY (nth weekday of a month) or BYMONTHDAY (specific day of month). A bare FREQ=YEARLY rule with neither clause raises RruleValidationError because Canvas cannot generate the occurrences.
Solutions
- Add BYMONTHDAY for same-date-each-year recurrence, e.g. BYMONTH=3;BYMONTHDAY=15
- Add BYDAY for nth-weekday recurrence, e.g. BYMONTH=11;BYDAY=4TH (fourth Thursday)
- Ensure the RRULE serializer always emits a day clause for FREQ=YEARLY rules
Example fix
// before "FREQ=YEARLY;INTERVAL=1;COUNT=5" // after "FREQ=YEARLY;INTERVAL=1;COUNT=5;BYMONTH=3;BYMONTHDAY=15"
Defensive patterns
Strategy: validation
Validate before calling
if rropts["FREQ"] == "YEARLY"
raise ArgumentError, "yearly RRULE needs BYDAY or BYMONTHDAY" unless rropts.key?("BYDAY") || rropts.key?("BYMONTHDAY")
end Try / catch
begin
RruleHelper.rrule_to_natural_language(rropts)
rescue RruleValidationError => e
render json: { errors: [e.message] }, status: :bad_request
end Prevention
- Always pair FREQ=YEARLY with BYMONTH+BYMONTHDAY or BYDAY
- Emit an explicit day clause from your RRULE serializer for yearly rules
- Catch RruleValidationError in yearly flows and surface a client-facing message
When it happens
Trigger: Calling rrule_to_natural_language with rropts like {"FREQ"=>"YEARLY", "INTERVAL"=>"1", "COUNT"=>"5"} — no "BYDAY" and no "BYMONTHDAY" key present.
Common situations: Clients assume FREQ=YEARLY alone means 'same date every year' and omit the day clause; hand-built RRULEs dropping optional-looking parts; imports of simple yearly rules from other calendar systems.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/ea3a51d8c4d110cd.
Report an issue: GitHub.
Appendix: source
Thrown at app/helpers/rrule_helper.rb:397
else
I18n.t({
one: "Monthly until %{until}",
other: "Every %{count} months until %{until}"
},
{
count: interval,
until: format_date(until_date)
})
end
end
def parse_yearly(rropts)
if rropts["BYDAY"]
parse_yearly_byday(rropts)
elsif rropts["BYMONTHDAY"]
parse_yearly_bymonthday(rropts)
else
raise RruleValidationError, I18n.t("A yearly RRULE must include BYDAY or BYMONTHDAY")
end
end
def parse_yearly_byday(rropts)
times = rropts["COUNT"]
interval = rropts["INTERVAL"].to_i
until_date = rropts["UNTIL"]
month = bymonth_to_month(parse_bymonth(rropts["BYMONTH"]))
by_days = parse_byday(rropts["BYDAY"])
days_of_week = by_days.pluck(:day_of_week).join(", ")
occurrence = by_days.first[:occurrence]
if times
if [0, 1].include?(occurrence)
I18n.t({
one: "Annually on the first %{days} of %{month}, %{times} times",
other: "Every %{count} years on the first %{days} of %{month}, %{times} times"
},View on GitHub (pinned to 1c9f0bb801)