{"record":{"id":"48227bc3c386c174","repo":"instructure/canvas-lms","slug":"invalid-byday-byday","errorCode":null,"errorMessage":"Invalid BYDAY '%{byday}'","messagePattern":"Invalid BYDAY '%(.+?)'","errorType":"validation","errorClass":"RruleValidationError","httpStatus":null,"severity":"error","filePath":"app/helpers/rrule_helper.rb","lineNumber":121,"sourceCode":"\n  def byday_to_days(byday)\n    byday.split(/\\s*,\\s*/).map { |d| DAYS_OF_WEEK[d] }.join(\", \")\n  end\n\n  def bymonth_to_month(bymonth)\n    MONTHS[bymonth]\n  end\n\n  # days is array of string digits\n  #  e.g. [\"1\",\"15\"]\n  def join_month_dys(days)\n    days.join(\",\")\n  end\n\n  def parse_byday(byday)\n    byday.split(\",\").map do |d|\n      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","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/helpers/rrule_helper.rb#L103-L139","documentation":"parse_byday splits a BYDAY value on commas and matches each item against /\\A([-+]?\\d+)?([A-Z]{2})\\z/ — an optional signed occurrence number followed by exactly two uppercase letters for the weekday (MO, TU, WE, TH, FR, SA, SU). Any item failing this regex raises RruleValidationError with \"Invalid BYDAY\".","triggerScenarios":"Calling rrule_to_natural_language with a monthly/yearly RRULE whose BYDAY contains items like \"MON\", \"mo\" (lowercase), \"1stMO\", \"MO,WEDNESDAY\", or any malformed token that does not match the pattern.","commonSituations":"Clients use full weekday names instead of two-letter codes; lowercase weekday codes from hand-built RRULEs; occurrence ordinals formatted like '-1st' or 'week=2'; case-mangling from other tooling lowercasing the rule string.","solutions":["Use two-letter uppercase weekday codes: BYDAY=MO,TU,WE,TH,FR,SA,SU","For nth-weekday recurrence use a signed integer prefix, e.g. BYDAY=2MO or BYDAY=-1FR (last Friday)","Downcase/normalize nothing — keep the RRULE token in the exact RFC-5545 BYDAY form before calling RruleHelper"],"exampleFix":"// before\n\"FREQ=MONTHLY;INTERVAL=1;BYDAY=MONDAY\"\n// after\n\"FREQ=MONTHLY;INTERVAL=1;BYDAY=2MO\"","handlingStrategy":"validation","validationCode":"raise ArgumentError, \"invalid BYDAY\" unless byday.split(\",\").all? { |d| d.match?(/\\A[-+]?\\d+(MO|TU|WE|TH|FR|SA|SU)\\z|\\A(MO|TU|WE|TH|FR|SA|SU)\\z/) }","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":["Use RFC-5545 two-letter uppercase weekday codes","Use signed integer prefixes (2MO, -1FR) for nth-weekday","Do not lowercase or expand weekday tokens when generating rules"],"tags":["rrule","validation","byday","calendar"],"backgroundTag":"invalid-argument-format","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"}