instructure/canvas-lms · error · RruleValidationError

Invalid BYDAY

Error message

Invalid BYDAY '%{byday}'

What it means

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".

Solutions

  1. Use two-letter uppercase weekday codes: BYDAY=MO,TU,WE,TH,FR,SA,SU
  2. For nth-weekday recurrence use a signed integer prefix, e.g. BYDAY=2MO or BYDAY=-1FR (last Friday)
  3. Downcase/normalize nothing — keep the RRULE token in the exact RFC-5545 BYDAY form before calling RruleHelper

Example fix

// before
"FREQ=MONTHLY;INTERVAL=1;BYDAY=MONDAY"
// after
"FREQ=MONTHLY;INTERVAL=1;BYDAY=2MO"
Defensive patterns

Strategy: validation

Validate before calling

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/) }

Try / catch

begin
  RruleHelper.rrule_to_natural_language(rropts)
rescue RruleValidationError => e
  render json: { errors: [e.message] }, status: :bad_request
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at app/helpers/rrule_helper.rb:121

  def byday_to_days(byday)
    byday.split(/\s*,\s*/).map { |d| DAYS_OF_WEEK[d] }.join(", ")
  end

  def bymonth_to_month(bymonth)
    MONTHS[bymonth]
  end

  # days is array of string digits
  #  e.g. ["1","15"]
  def join_month_dys(days)
    days.join(",")
  end

  def parse_byday(byday)
    byday.split(",").map do |d|
      match = /\A([-+]?\d+)?([A-Z]{2})\z/.match(d)
      raise RruleValidationError, I18n.t("Invalid BYDAY '%{byday}'", byday:) unless match

      {
        occurrence: match[1].to_i,
        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

View on GitHub (pinned to 1c9f0bb801)