1use chrono::TimeZone;
4use ora_common::{
5 schedule::{MissedTasksPolicy, ScheduleDefinition, SchedulePolicy},
6 UnixNanos,
7};
8
9#[must_use]
11pub fn next_schedule_task(
12 schedule: &ScheduleDefinition,
13 last_task: Option<UnixNanos>,
14 time: UnixNanos,
15) -> Option<UnixNanos> {
16 match last_task {
17 Some(last) => match schedule.missed_tasks {
18 MissedTasksPolicy::Burst => next_from(schedule, last),
19 MissedTasksPolicy::Skip => {
20 let mut next = next_from(schedule, last)?;
21
22 while next < time {
23 next = next_from(schedule, next)?;
24 }
25
26 Some(next)
27 }
28 },
29 None => {
30 if schedule.immediate {
31 Some(time)
32 } else {
33 next_from(schedule, time)
34 }
35 }
36 }
37}
38
39#[allow(clippy::cast_possible_wrap)]
42fn next_from(schedule: &ScheduleDefinition, time: UnixNanos) -> Option<UnixNanos> {
43 match &schedule.policy {
44 SchedulePolicy::Repeat { interval_ns, .. } => Some(UnixNanos(time.0 + interval_ns)),
45 SchedulePolicy::Cron { expr, .. } => {
46 let c: cron::Schedule = match expr.parse() {
47 Ok(c) => c,
48 Err(error) => {
49 tracing::error!(%error, "invalid cron expression");
50 return None;
51 }
52 };
53
54 c.after(&chrono::Utc.timestamp_nanos(time.0 as _))
55 .next()
56 .and_then(|t| {
57 Some(UnixNanos(
58 t.timestamp_nanos_opt()
59 .unwrap_or_default()
60 .try_into()
61 .ok()?,
62 ))
63 })
64 }
65 }
66}