1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use crate::error::SchedulerError;
use chrono::prelude::*;
use cron;
use std::convert::TryFrom;
use std::time::Duration;
pub enum Scheduler {
Cron(cron::Schedule),
Interval(Duration),
Never,
}
impl Scheduler {
pub fn next<T: TimeZone>(&self, after: &DateTime<T>) -> Option<DateTime<T>> {
match *self {
Scheduler::Cron(ref cs) => cs.after(&after).next(),
Scheduler::Interval(ref duration) => {
let ch_duration = match time::Duration::from_std(*duration) {
Ok(value) => value,
Err(_) => {
return None;
}
};
let date = after.with_timezone(&Utc) + ch_duration;
Some(date.with_timezone(&after.timezone()))
}
Scheduler::Never => None,
}
}
}
impl<'a> TryFrom<&'a str> for Scheduler {
type Error = SchedulerError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(Scheduler::Cron(value.parse().map_err(|err| {
SchedulerError::ScheduleDefinitionError {
message: format!("Cannot create schedule for [{}]. Err: {}", value, err),
}
})?))
}
}
impl TryFrom<Duration> for Scheduler {
type Error = SchedulerError;
fn try_from(value: Duration) -> Result<Self, Self::Error> {
Ok(Scheduler::Interval(value))
}
}
#[cfg(test)]
pub mod test {
use super::*;
use std::convert::TryInto;
#[test]
fn never_should_not_schedule() {
let schedule = Scheduler::Never;
assert_eq!(None, schedule.next(&Utc::now()))
}
#[test]
fn interval_should_schedule_plus_duration() {
let now = Utc::now();
let secs = 10;
let schedule: Scheduler = Duration::new(secs, 0).try_into().unwrap();
let next = schedule.next(&now).unwrap();
assert!(next.timestamp() >= now.timestamp() + (secs as i64));
}
#[test]
fn should_build_an_interval_schedule_from_duration() {
let schedule: Scheduler = Duration::new(1, 1).try_into().unwrap();
match schedule {
Scheduler::Interval(_) => assert!(true),
_ => assert!(false),
}
}
#[test]
fn should_build_a_periodic_schedule_from_str() {
let schedule: Scheduler = "* * * * * *".try_into().unwrap();
match schedule {
Scheduler::Cron(_) => assert!(true),
_ => assert!(false),
}
}
#[test]
fn cron_should_be_time_zone_aware_with_utc() {
let schedule: Scheduler = "* 11 10 * * *".try_into().unwrap();
let date = Utc.ymd(2010, 1, 1).and_hms(10, 10, 0);
let expected_utc = Utc.ymd(2010, 1, 1).and_hms(10, 11, 0);
let next = schedule.next(&date).unwrap();
assert_eq!(next, expected_utc);
}
#[test]
fn cron_should_be_time_zone_aware_with_custom_time_zone() {
let schedule: Scheduler = "* 11 10 * * *".try_into().unwrap();
let date = FixedOffset::east(3600).ymd(2010, 1, 1).and_hms(10, 10, 0);
let expected_utc = Utc.ymd(2010, 1, 1).and_hms(9, 11, 0);
let next = schedule.next(&date).unwrap();
assert_eq!(next.with_timezone(&Utc), expected_utc);
}
}