///|
/// Upper bound on the day-level search in `next_after`. Ten years is far
/// beyond the longest real gap between occurrences (a February the 29th
/// schedule waits at most eight years), so hitting the bound means the
/// schedule can never fire.
const MAX_SEARCH_DAYS : Int = 3660
///|
/// The calendar day immediately after the given one.
fn next_day(year : Int, month : Int, day : Int) -> (Int, Int, Int) {
if day < days_in_month(year, month) {
(year, month, day + 1)
} else if month < 12 {
(year, month + 1, 1)
} else {
(year + 1, 1, 1)
}
}
///|
/// The minute immediately after `time`, rolling over hours, days, months
/// and years as needed. Internal only: the result may leave the validated
/// year range at the far end of the calendar.
fn next_minute(time : UtcDateTime) -> UtcDateTime {
if time.minute < 59 {
return { ..time, minute: time.minute + 1 }
}
if time.hour < 23 {
return { ..time, hour: time.hour + 1, minute: 0 }
}
let (year, month, day) = next_day(time.year, time.month, time.day)
{ year, month, day, hour: 0, minute: 0 }
}
///|
/// True when the schedule can fire on the given calendar date.
fn Cron::date_matches(self : Cron, year : Int, month : Int, day : Int) -> Bool {
self.month.matches(month, lower=1) &&
self.calendar_matches(day, weekday_of(year, month, day))
}
///|
/// The first minute strictly after `from` that satisfies the schedule, or
/// `None` when no occurrence exists within the ten-year search horizon
/// (for example a schedule pinned to day 30 of February).
pub fn Cron::next_after(self : Cron, from : UtcDateTime) -> UtcDateTime? {
let start = next_minute(from)
let mut year = start.year
let mut month = start.month
let mut day = start.day
let mut first_hour = start.hour
let mut first_minute = start.minute
for _ in 0.. 9999 {
return None
}
if self.date_matches(year, month, day) {
for hour in first_hour..<24 {
if self.hour.matches(hour) {
let minute_start = if hour == first_hour { first_minute } else { 0 }
for minute in minute_start..<60 {
if self.minute.matches(minute) {
return Some({ year, month, day, hour, minute })
}
}
}
}
}
let following = next_day(year, month, day)
year = following.0
month = following.1
day = following.2
first_hour = 0
first_minute = 0
}
None
}
///|
/// Up to `limit` occurrences strictly after `from`, in chronological
/// order. Fewer are returned when the schedule stops producing matches
/// inside the search horizon.
pub fn Cron::next_occurrences(
self : Cron,
from : UtcDateTime,
limit : Int,
) -> Array[UtcDateTime] {
let occurrences : Array[UtcDateTime] = []
let mut cursor = from
for _ in 0.. {
occurrences.push(occurrence)
cursor = occurrence
}
None => break
}
}
occurrences
}
///|
/// Check a schedule directly against a validated date-time.
pub fn Cron::matches_at(self : Cron, at : UtcDateTime) -> Bool {
self.matches(at.to_utc_time())
}