import automata/cron/normalize.{type CronPlan, type IntSet, AnyValue, Values}
import automata/internal/calendar
import automata/schedule/ast.{type DateTime}
import gleam/list
import gleam/option.{type Option, None, Some}
pub fn matches(plan plan: CronPlan, at at: DateTime) -> Bool {
let minute_match = int_set_contains(plan.minute, at.time.minute)
let hour_match = int_set_contains(plan.hour, at.time.hour)
let month_match = int_set_contains(plan.month, at.date.month)
let day_of_month_match = int_set_contains(plan.day_of_month, at.date.day)
let day_of_week_match =
int_set_contains(plan.day_of_week, calendar.day_of_week_number(at))
minute_match
&& hour_match
&& month_match
&& matches_day(plan, day_of_month_match, day_of_week_match)
&& at.time.second == 0
}
pub fn int_set_contains(int_set: IntSet, value: Int) -> Bool {
case int_set {
AnyValue(min:, max:) -> value >= min && value <= max
Values(values) -> list.any(values, fn(item) { item == value })
}
}
pub fn first_value(int_set: IntSet) -> Int {
case int_set {
AnyValue(min:, max: _) -> min
Values([first, ..]) -> first
Values([]) -> 0
}
}
pub fn next_value_at_or_after(int_set: IntSet, current: Int) -> Option(Int) {
case int_set {
AnyValue(min:, max:) ->
case current < min {
True -> Some(min)
False ->
case current > max {
True -> None
False -> Some(current)
}
}
Values(values) -> next_list_value(values, current)
}
}
fn next_list_value(values: List(Int), current: Int) -> Option(Int) {
case values {
[] -> None
[value, ..rest] ->
case value >= current {
True -> Some(value)
False -> next_list_value(rest, current)
}
}
}
fn matches_day(
plan: CronPlan,
day_of_month_match: Bool,
day_of_week_match: Bool,
) -> Bool {
let dom_any = is_any(plan.day_of_month)
let dow_any = is_any(plan.day_of_week)
case dom_any, dow_any {
True, True -> True
True, False -> day_of_week_match
False, True -> day_of_month_match
False, False -> day_of_month_match || day_of_week_match
}
}
fn is_any(int_set: IntSet) -> Bool {
case int_set {
AnyValue(_, _) -> True
Values(_) -> False
}
}