///|
fn recurrence_values(values : Array[Int32]?, fallback : Int32) -> Array[Int32] {
  match values {
    Some(values) => values
    None => [fallback]
  }
}

///|
fn recurrence_contains(values : Array[Int32]?, value : Int32) -> Bool {
  match values {
    Some(values) => values.contains(value)
    None => true
  }
}

///|
fn DateRule::matches_date(
  self : DateRule,
  value : DateTime,
  anchor : DateTime,
  period_year : Int32,
) -> Bool {
  if !recurrence_contains(self.bymonth, value.month) ||
    !recurrence_contains(self.byweekday, value.weekday()) ||
    !self.matches_ordinal_weekday(value, period_year) ||
    !self.matches_year_fields(value, period_year) {
    return false
  }
  if self.bymonthday is Some(days) &&
    !days.contains(value.day) &&
    !days.contains(value.day - days_in_month(value.year, value.month) - 1) {
    return false
  }
  // dateutil only supplies these defaults in the absence of a day selector.
  if self.bymonthday is None &&
    self.byweekday is None &&
    self.bynweekday is None &&
    self.byyearday is None &&
    self.byweekno is None &&
    self.byeaster is None {
    match self.frequency {
      Yearly =>
        value.day == anchor.day &&
        (self.bymonth is Some(_) || value.month == anchor.month)
      Monthly => value.day == anchor.day
      Weekly => value.weekday() == anchor.weekday()
      _ => true
    }
  } else {
    true
  }
}

///|
// Keep a whole period until BYSETPOS has been applied. Filtering to the view
// first would incorrectly turn a partial month into a complete month.
fn recurrence_days(
  rule : DateRule,
  anchor : DateTime,
  first_day : Int32,
  end_day : Int32,
) -> Array[Int32] {
  let anchor_day = days_from_civil(anchor.year, anchor.month, anchor.day)
  let (period_year, _, _) = civil_from_days(Int32::max(first_day, anchor_day))
  // The first weekly period starts at DTSTART, while later periods start at
  // WKST. Other frequencies apply BYSETPOS to their complete calendar period.
  let start = if rule.frequency is Weekly {
    Int32::max(first_day, anchor_day)
  } else {
    first_day
  }
  let result : Array[Int32] = []
  for day in Int32::max(start, -719162).. Array[Int32] {
  let indices : Array[Int32] = []
  for position in positions {
    let index = if position > 0 { position - 1 } else { length + position }
    if index >= 0 && index < length && !indices.contains(index) {
      indices.push(index)
    }
  }
  indices.sort()
  indices
}

///|
// For daily and coarser frequencies, expand the clock once and share it across
// all calendar periods. Never allocate an entire year's date/time product.
fn recurrence_times(rule : DateRule, anchor : DateTime) -> Array[Int32] {
  let times : Array[Int32] = []
  for hour in recurrence_values(rule.byhour, anchor.hour) {
    for minute in recurrence_values(rule.byminute, anchor.minute) {
      for second in recurrence_values(rule.bysecond, anchor.second) {
        times.push(hour * 3600 + minute * 60 + second)
      }
    }
  }
  times
}