{"record":{"id":"81d9f0ab36f21764","repo":"instructure/canvas-lms","slug":"unsupported-bymonthday-only-a-single-day-is-permitted","errorCode":null,"errorMessage":"Unsupported BYMONTHDAY, only a single day is permitted.","messagePattern":"Unsupported BYMONTHDAY, only a single day is permitted\\.","errorType":"validation","errorClass":"RruleValidationError","httpStatus":null,"severity":"error","filePath":"app/helpers/rrule_helper.rb","lineNumber":138,"sourceCode":"      match = /\\A([-+]?\\d+)?([A-Z]{2})\\z/.match(d)\n      raise RruleValidationError, I18n.t(\"Invalid BYDAY '%{byday}'\", byday:) unless match\n\n      {\n        occurrence: match[1].to_i,\n        day_of_week: DAYS_OF_WEEK[match[2]]\n      }\n    end\n  end\n\n  def parse_bymonth(bymonth)\n    month = bymonth.to_i\n    raise RruleValidationError, I18n.t(\"Invalid BYMONTH '%{bymonth}'\", bymonth:) unless month.between?(1, 12)\n\n    month\n  end\n\n  def parse_bymonthday(bymonthday, month)\n    raise RruleValidationError, I18n.t(\"Unsupported BYMONTHDAY, only a single day is permitted.\") unless bymonthday.split(\",\").length == 1\n\n    monthday = bymonthday.to_i\n\n    # not validating if we're in a leap year\n    raise RruleValidationError, I18n.t(\"Invalid BYMONTHDAY '%{bymonthday}'\", bymonthday:) unless monthday.between?(1, DAYS_IN_MONTH[month])\n\n    monthday\n  end\n\n  def format_date(date_str)\n    date = date_str.split(\"T\")[0]\n    year = date[0, 4].to_i\n    month = date[4, 2].to_i\n    day = date[6, 2].to_i\n\n    I18n.l(Date.new(year, month, day), format: :medium)\n  end\n","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/helpers/rrule_helper.rb#L120-L156","documentation":"Raised by the generic validation guard in parse_bymonthday when parsing a yearly RRULE's BYMONTHDAY part: Canvas only supports a single month day (e.g. '15'), so the input at fault is a comma-separated BYMONTHDAY list (e.g. '1,15') supplied by the calendar/RRULE payload.","triggerScenarios":"Calling rrule_to_natural_language with a yearly RRULE whose BYMONTHDAY contains a comma-separated list, e.g. BYMONTHDAY=1,15 — any value where split(\",\").length != 1.","commonSituations":"Clients copying RFC-5545 examples that legally allow lists (BYMONTHDAY=1,15) and expecting Canvas to support them; UIs allowing multi-select of days; importing ICS files with multi-day yearly rules.","solutions":["Use a single BYMONTHDAY value, e.g. BYMONTHDAY=15","Model multiple days as multiple separate recurring calendar events (one per day)","Reject or split multi-day rules at the API boundary with a clear client-facing message"],"exampleFix":"// before\n\"FREQ=YEARLY;INTERVAL=1;BYMONTHDAY=1,15\"\n// after\n\"FREQ=YEARLY;INTERVAL=1;BYMONTHDAY=15\"","handlingStrategy":"validation","validationCode":"raise ArgumentError, \"only a single BYMONTHDAY is supported\" unless bymonthday.split(\",\").length == 1","typeGuard":null,"tryCatchPattern":"begin\n  RruleHelper.rrule_to_natural_language(rropts)\nrescue RruleValidationError => e\n  render json: { errors: [e.message] }, status: :bad_request\nend","preventionTips":["Model multi-day yearly recurrences as separate events","Restrict yearly recurrence pickers to a single day","Handle the error explicitly and inform users of the platform limitation"],"tags":["rrule","validation","bymonthday","unsupported-feature"],"backgroundTag":"unsupported-operation","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"}