///|
/// Time matching against cron expressions.
/// Determines whether a given DateTime satisfies a parsed cron expression.
///|
/// Check if a DateTime matches all fields of a cron expression.
pub fn matches_time(fields : Array[CronField], dt : DateTime) -> Bool {
if fields.length() != 5 && fields.length() != 6 {
return false
}
let offset = if fields.length() == 6 { 1 } else { 0 }
let second_matches = if offset == 1 {
match_field(fields, 0, dt.second)
} else {
dt.second == 0
}
second_matches &&
match_field(fields, offset, dt.minute) &&
match_field(fields, offset + 1, dt.hour) &&
matches_day_constraint(fields, dt) &&
match_field(fields, offset + 3, dt.month)
}
///|
/// Check if a value matches a specific field.
fn match_field(fields : Array[CronField], index : Int, value : Int) -> Bool {
if index >= fields.length() {
return false
}
let field = fields[index]
for i = 0; i < field.values.length(); i = i + 1 {
if field.values[i] == value {
return true
}
}
false
}
///|
/// Check day-of-month field.
fn match_day_of_month(
fields : Array[CronField],
index : Int,
dt : DateTime,
) -> Bool {
if index >= fields.length() {
return false
}
let field = fields[index]
for i = 0; i < field.values.length(); i = i + 1 {
if field.values[i] == dt.day {
return true
}
}
false
}
///|
/// Check day-of-week field. Handles the mapping where both 0 and 7 represent Sunday.
fn match_day_of_week(
fields : Array[CronField],
index : Int,
dt : DateTime,
) -> Bool {
if index >= fields.length() {
return false
}
let field = fields[index]
let dow = day_of_week(dt.year, dt.month, dt.day)
for i = 0; i < field.values.length(); i = i + 1 {
let v = field.values[i]
if v == dow || (v == 7 && dow == 0) {
return true
}
}
false
}
///|
/// Check if any day-of-week or day-of-month would match.
/// When both day-of-month and day-of-week are not restricted to all values,
/// cron semantics say a match on EITHER is sufficient (OR logic).
pub fn matches_day_constraint(fields : Array[CronField], dt : DateTime) -> Bool {
if fields.length() != 5 && fields.length() != 6 {
return false
}
let offset = if fields.length() == 6 { 1 } else { 0 }
let dom_index = offset + 2
let dow_index = offset + 4
let dom_field = fields[dom_index]
let dow_field = fields[dow_index]
let dom_restricted = !is_star_field(dom_field)
let dow_restricted = !is_star_field(dow_field)
if dom_restricted && dow_restricted {
// OR logic: match on either day-of-month or day-of-week
match_day_of_month(fields, dom_index, dt) ||
match_day_of_week(fields, dow_index, dt)
} else if dom_restricted {
match_day_of_month(fields, dom_index, dt)
} else if dow_restricted {
match_day_of_week(fields, dow_index, dt)
} else {
true
}
}
///|
/// Check if a field represents the "all values" wildcard.
fn is_star_field(field : CronField) -> Bool {
let (min, max) = field_range(field.field_type)
if field.values.length() != max - min + 1 {
return false
}
let mut expected = min
for i = 0; i < field.values.length(); i = i + 1 {
if field.values[i] != expected {
return false
}
expected = expected + 1
}
true
}