instructure/canvas-lms · error · RruleValidationError
Invalid BYMONTHDAY
Error message
Invalid BYMONTHDAY '%{bymonthday}' What it means
After the single-day check, parse_bymonthday converts BYMONTHDAY to an integer and requires it to fall between 1 and the number of days in the given BYMONTH (DAYS_IN_MONTH lookup, leap years not considered here). Values like 0, negative numbers, non-numeric strings (to_i => 0), or 30 in February raise RruleValidationError.
Solutions
- Use BYMONTHDAY between 1 and the number of days in the chosen BYMONTH
- For February, cap at 29 (Canvas does not special-case leap years here)
- Model 'last day of month' semantics differently (e.g. BYDAY=-1SU-style or fixed day 28) since negative monthdays are invalid in this validation
Example fix
// before "FREQ=YEARLY;INTERVAL=1;BYMONTH=2;BYMONTHDAY=30" // after "FREQ=YEARLY;INTERVAL=1;BYMONTH=2;BYMONTHDAY=28"
Defensive patterns
Strategy: validation
Validate before calling
days = { 1=>31, 2=>29, 3=>31, 4=>30, 5=>31, 6=>30, 7=>31, 8=>31, 9=>30, 10=>31, 11=>30, 12=>31 }
raise ArgumentError, "BYMONTHDAY out of range" unless bymonthday.to_i.between?(1, days[month]) Try / catch
begin
RruleHelper.rrule_to_natural_language(rropts)
rescue RruleValidationError => e
render json: { errors: [e.message] }, status: :bad_request
end Prevention
- Cap February at 29 (leap-year edge cases are not validated)
- Do not use negative BYMONTHDAY values; Canvas rejects them
- Validate day against the selected month in the UI before submitting
When it happens
Trigger: Calling rrule_to_natural_language with a yearly RRULE where BYMONTHDAY is 0, -1, "x", 31 with BYMONTH=4 (April has 30), or 30 with BYMONTH=2.
Common situations: Clients modeling 'last day of month' with negative values (-1), which RFC-5545 allows but Canvas rejects; picking day 29/30/31 for February; typos like day 0 from off-by-one loops.
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/cd8e04506a4193b2.
Report an issue: GitHub.
Appendix: source
Thrown at app/helpers/rrule_helper.rb:143
day_of_week: DAYS_OF_WEEK[match[2]]
}
end
end
def parse_bymonth(bymonth)
month = bymonth.to_i
raise RruleValidationError, I18n.t("Invalid BYMONTH '%{bymonth}'", bymonth:) unless month.between?(1, 12)
month
end
def parse_bymonthday(bymonthday, month)
raise RruleValidationError, I18n.t("Unsupported BYMONTHDAY, only a single day is permitted.") unless bymonthday.split(",").length == 1
monthday = bymonthday.to_i
# not validating if we're in a leap year
raise RruleValidationError, I18n.t("Invalid BYMONTHDAY '%{bymonthday}'", bymonthday:) unless monthday.between?(1, DAYS_IN_MONTH[month])
monthday
end
def format_date(date_str)
date = date_str.split("T")[0]
year = date[0, 4].to_i
month = date[4, 2].to_i
day = date[6, 2].to_i
I18n.l(Date.new(year, month, day), format: :medium)
end
def format_month_day(month, day)
# 2024 is a leap year, and can handle formatting 2/29
I18n.l(Date.new(2024, month, day), format: :short)
end
View on GitHub (pinned to 1c9f0bb801)