1use std::time::Duration;
2
3use time::DateTime;
4
5use crate::error::SchedulerError;
6
7pub enum Scheduler {
8 Cron(Box<cron::Schedule>),
13
14 Interval {
17 interval_duration: Duration,
18 execute_at_startup: bool,
19 },
20
21 Multi(Vec<Scheduler>),
23
24 Never,
26}
27
28impl Scheduler {
29 pub fn from(schedule: &[&dyn TryToScheduler]) -> Result<Scheduler, SchedulerError> {
30 schedule.to_scheduler()
31 }
32
33 pub fn next(&mut self, after: &DateTime) -> Option<DateTime> {
35 match *self {
36 Scheduler::Cron(ref cs) => cs.after(after).next(),
37
38 Scheduler::Interval {
39 ref interval_duration,
40 ref mut execute_at_startup,
41 } => {
42 if *execute_at_startup {
43 *execute_at_startup = false;
44 Some(*after)
45 } else {
46 after.checked_add(*interval_duration).ok()
47 }
48 }
49
50 Scheduler::Multi(ref mut schedulers) => {
51 let mut result = None;
52 for scheduler in schedulers {
53 if let Some(local_next) = scheduler.next(after) {
54 result = match result {
55 Some(current_next) => {
56 if local_next < current_next {
57 Some(local_next)
58 } else {
59 Some(current_next)
60 }
61 }
62 None => Some(local_next),
63 }
64 }
65 }
66 result
67 }
68
69 Scheduler::Never => None,
70 }
71 }
72}
73
74pub trait TryToScheduler {
75 fn to_scheduler(&self) -> Result<Scheduler, SchedulerError>;
76}
77
78impl TryToScheduler for Vec<String> {
79 fn to_scheduler(&self) -> Result<Scheduler, SchedulerError> {
80 let refs: Vec<&str> = self.iter().map(|s| s.as_ref()).collect();
81 refs.to_scheduler()
82 }
83}
84
85impl TryToScheduler for Vec<&str> {
86 fn to_scheduler(&self) -> Result<Scheduler, SchedulerError> {
87 match self.len() {
88 0 => Ok(Scheduler::Never),
89 1 => self[0].to_scheduler(),
90 _ => {
91 let mut result = vec![];
92 for scheduler in self {
93 result.push(scheduler.to_scheduler()?);
94 }
95 Ok(Scheduler::Multi(result))
96 }
97 }
98 }
99}
100
101impl TryToScheduler for Vec<&dyn TryToScheduler> {
102 fn to_scheduler(&self) -> Result<Scheduler, SchedulerError> {
103 (&self[..]).to_scheduler()
104 }
105}
106
107impl TryToScheduler for &[&dyn TryToScheduler] {
108 fn to_scheduler(&self) -> Result<Scheduler, SchedulerError> {
109 match self.len() {
110 0 => Ok(Scheduler::Never),
111 1 => self[0].to_scheduler(),
112 _ => {
113 let mut result = vec![];
114 for scheduler in *self {
115 result.push(scheduler.to_scheduler()?);
116 }
117 Ok(Scheduler::Multi(result))
118 }
119 }
120 }
121}
122
123impl<'a> TryToScheduler for &'a str {
124 fn to_scheduler(&self) -> Result<Scheduler, SchedulerError> {
125 Ok(Scheduler::Cron(Box::new(self.parse().map_err(|err| {
126 SchedulerError::ScheduleDefinitionError {
127 message: format!("Cannot create schedule for [{self}]. Err: {err:?}"),
128 }
129 })?)))
130 }
131}
132
133impl TryToScheduler for String {
134 fn to_scheduler(&self) -> Result<Scheduler, SchedulerError> {
135 self.as_str().to_scheduler()
136 }
137}
138
139impl TryToScheduler for Duration {
140 fn to_scheduler(&self) -> Result<Scheduler, SchedulerError> {
141 Ok(Scheduler::Interval {
142 interval_duration: *self,
143 execute_at_startup: false,
144 })
145 }
146}
147
148impl TryToScheduler for (Duration, bool) {
149 fn to_scheduler(&self) -> Result<Scheduler, SchedulerError> {
150 Ok(Scheduler::Interval {
151 interval_duration: self.0,
152 execute_at_startup: self.1,
153 })
154 }
155}
156
157impl From<Vec<Scheduler>> for Scheduler {
158 fn from(val: Vec<Scheduler>) -> Self {
159 Scheduler::Multi(val)
160 }
161}
162
163#[cfg(test)]
164pub mod test {
165
166 use time::TimeZone;
167
168 use super::*;
169
170 #[test]
171 fn never_should_not_schedule() {
172 let mut schedule = Scheduler::Never;
173 assert_eq!(None, schedule.next(&DateTime::now_in(TimeZone::UTC)))
174 }
175
176 #[test]
177 fn interval_should_schedule_plus_duration() {
178 let now = DateTime::now_in(TimeZone::UTC);
179 let secs = 10;
180 let mut schedule = Duration::new(secs, 0).to_scheduler().unwrap();
181
182 let next = schedule.next(&now).unwrap();
183
184 assert!(next.unix() >= now.unix() + (secs as i64));
185 }
186
187 #[test]
188 fn interval_should_schedule_at_startup() {
189 let now = DateTime::now_in(TimeZone::UTC);
190 let secs = 10;
191 let mut schedule = (Duration::new(secs, 0), true).to_scheduler().unwrap();
192
193 let first = schedule.next(&now).unwrap();
194 assert_eq!(now.unix(), first.unix());
195
196 let next = schedule.next(&now).unwrap();
197 assert!(next.unix() >= now.unix() + (secs as i64));
198 }
199
200 #[test]
201 fn should_build_an_interval_schedule_from_duration() {
202 let schedule = Duration::new(1, 1).to_scheduler().unwrap();
203 match schedule {
204 Scheduler::Interval {
205 ..
206 } => (),
207 _ => panic!(),
208 }
209 }
210
211 #[test]
212 fn should_build_a_periodic_schedule_from_str() {
213 let schedule = "* * * * * *".to_scheduler().unwrap();
214 match schedule {
215 Scheduler::Cron(_) => (),
216 _ => panic!(),
217 }
218 }
219
220 #[test]
221 fn should_build_a_multi_scheduler_from_empty_array() {
222 let schedule = Scheduler::from(&[]).unwrap();
223 match schedule {
224 Scheduler::Never => (),
225 _ => panic!(),
226 }
227 }
228
229 #[test]
230 fn should_build_a_multi_scheduler_from_single_entry_array() {
231 let schedule = Scheduler::from(&[&vec!["* * * * * *"]]).unwrap();
232 match schedule {
233 Scheduler::Cron(_) => (),
234 _ => panic!(),
235 }
236 }
237
238 #[test]
239 fn should_build_a_multi_scheduler_from_array() {
240 let schedule = Scheduler::from(&[&"* * * * * *", &Duration::from_secs(9)]).unwrap();
241 match schedule {
242 Scheduler::Multi(inner) => {
243 match inner[0] {
244 Scheduler::Cron(_) => (),
245 _ => panic!(),
246 };
247 match inner[1] {
248 Scheduler::Interval {
249 ..
250 } => (),
251 _ => panic!(),
252 };
253 }
254 _ => panic!(),
255 }
256 }
257
258 #[test]
259 fn cron_should_be_time_zone_aware_with_utc() {
260 let mut schedule = "* 11 10 * * *".to_scheduler().unwrap();
261 let date = DateTime::from_parts(2010, 1, 1, 10, 10, 0, 0, TimeZone::UTC).unwrap();
262
263 let expected_utc = DateTime::from_parts(2010, 1, 1, 10, 11, 0, 0, TimeZone::UTC).unwrap();
264
265 let next = schedule.next(&date).unwrap();
266
267 assert_eq!(next, expected_utc);
268 }
269
270 #[test]
283 fn multi_should_return_first_possible_next_execution() {
284 let mut schedule = Scheduler::from(&[&"* 10 10 * * *", &"* 20 20 * * *"]).unwrap();
285
286 {
287 let date = DateTime::from_parts(2010, 1, 1, 10, 8, 0, 0, TimeZone::UTC).unwrap();
288 let expected = DateTime::from_parts(2010, 1, 1, 10, 10, 0, 0, TimeZone::UTC).unwrap();
289
290 let next = schedule.next(&date).unwrap();
291 assert_eq!(next, expected);
292 }
293
294 {
295 let date = DateTime::from_parts(2010, 1, 1, 11, 8, 0, 0, TimeZone::UTC).unwrap();
296 let expected = DateTime::from_parts(2010, 1, 1, 20, 20, 0, 0, TimeZone::UTC).unwrap();
297
298 let next = schedule.next(&date).unwrap();
299 assert_eq!(next, expected);
300 }
301
302 {
303 let date = DateTime::from_parts(2010, 1, 1, 22, 8, 0, 0, TimeZone::UTC).unwrap();
304 let expected = DateTime::from_parts(2010, 1, 2, 10, 10, 0, 0, TimeZone::UTC).unwrap();
305
306 let next = schedule.next(&date).unwrap();
307 assert_eq!(next, expected);
308 }
309 }
310}