rcron/lib.rs
1//! # rcron
2//!
3//! a simple cron-like job scheduling library for Rust.
4//!
5//! ## Usage
6//!
7//! Be sure to add the rcron crate to your `Cargo.toml`:
8//!
9//! ```toml
10//! [dependencies]
11//! rcron = "~1.2"
12//! ```
13//!
14//! Creating a schedule for a job is done using the `FromStr` impl for the
15//! `Schedule` type of the [cron](https://github.com/zslayton/cron) library.
16//!
17//! The scheduling format is as follows:
18//!
19//! ```text
20//! sec min hour day of month month day of week year
21//! * * * * * * *
22//! ```
23//!
24//! Note that the year may be omitted.
25//!
26//! Comma separated values such as `5,8,10` represent more than one time
27//! value. So for example, a schedule of `0 2,14,26 * * * *` would execute
28//! on the 2nd, 14th, and 26th minute of every hour.
29//!
30//! Ranges can be specified with a dash. A schedule of `0 0 * 5-10 * *`
31//! would execute once per hour but only on day 5 through 10 of the month.
32//!
33//! Day of the week can be specified as an abbreviation or the full name.
34//! A schedule of `0 0 6 * * Sun,Sat` would execute at 6am on Sunday and
35//! Saturday.
36//!
37//! A simple usage example:
38//!
39//! ```rust
40//! extern crate rcron;
41//! use rcron::{JobScheduler, Job};
42//! use std::time::Duration;
43//!
44//! fn main() {
45//! let mut sched = JobScheduler::new();
46//!
47//! sched.add(Job::new("1/10 * * * * *".parse().unwrap(), || {
48//! println!("exec task every 10 seconds!");
49//! }));
50//!
51//! sched.add(Job::new("1/5 * * * * *".parse().unwrap(), || {
52//! println!("exec task every 5 seconds!");
53//! }));
54//!
55//! loop {
56//! sched.tick();
57//!
58//! std::thread::sleep(Duration::from_millis(500));
59//! }
60//! }
61//! ```
62
63use chrono::{DateTime, Duration, Local};
64pub use cron::Schedule;
65pub use uuid::Uuid;
66
67/// A scheduled `Job`.
68pub struct Job<'a> {
69 schedule: Schedule,
70 run: Box<dyn (FnMut() -> ()) + 'a>,
71 last_tick: Option<DateTime<Local>>,
72 limit_missed_runs: usize,
73 job_id: Uuid,
74}
75
76impl<'a> Job<'a> {
77 /// Create a new job.
78 ///
79 /// ```rust,ignore
80 /// // Run at second 0 of the 15th minute of the 6th, 8th, and 10th hour
81 /// // of any day in March and June that is a Friday of the year 2017.
82 /// let s: Schedule = "0 15 6,8,10 * Mar,Jun Fri 2017".into().unwrap();
83 /// Job::new(s, || println!("I have a complex schedule...") );
84 /// ```
85 pub fn new<T>(schedule: Schedule, run: T) -> Job<'a>
86 where
87 T: 'a,
88 T: FnMut() -> (),
89 {
90 Job {
91 schedule,
92 run: Box::new(run),
93 last_tick: None,
94 limit_missed_runs: 1,
95 job_id: Uuid::new_v4(),
96 }
97 }
98
99 fn tick(&mut self) {
100 let now = Local::now();
101 if self.last_tick.is_none() {
102 self.last_tick = Some(now);
103 return;
104 }
105
106 if self.limit_missed_runs > 0 {
107 self.schedule
108 .after(&self.last_tick.unwrap())
109 .take(self.limit_missed_runs)
110 .take_while(|&event| event <= now)
111 .for_each(|_| (self.run)());
112 } else {
113 self.schedule
114 .after(&self.last_tick.unwrap())
115 .take_while(|&event| event <= now)
116 .for_each(|_| (self.run)());
117 }
118
119 self.last_tick = Some(now);
120 }
121
122 /// Set the limit for missed jobs in the case of delayed runs. Setting to 0 means unlimited.
123 ///
124 /// ```rust,ignore
125 /// let mut job = Job::new("0/1 * * * * *".parse().unwrap(), || {
126 /// println!("I get executed every 1 seconds!");
127 /// });
128 /// job.limit_missed_runs(99);
129 /// ```
130 pub fn limit_missed_runs(&mut self, limit: usize) {
131 self.limit_missed_runs = limit;
132 }
133
134 /// Set last tick to force re-running of missed runs.
135 ///
136 /// ```rust,ignore
137 /// let mut job = Job::new("0/1 * * * * *".parse().unwrap(), || {
138 /// println!("I get executed every 1 seconds!");
139 /// });
140 /// job.last_tick(Some(Local::now()));
141 /// ```
142 pub fn last_tick(&mut self, last_tick: Option<DateTime<Local>>) {
143 self.last_tick = last_tick;
144 }
145}
146
147#[derive(Default)]
148/// The JobScheduler of rcron contains and executes the scheduled jobs.
149pub struct JobScheduler<'a> {
150 jobs: Vec<Job<'a>>,
151}
152
153impl<'a> JobScheduler<'a> {
154 /// Create a new `JobScheduler`.
155 pub fn new() -> JobScheduler<'a> {
156 JobScheduler { jobs: Vec::new() }
157 }
158
159 /// Add a job to the `JobScheduler`
160 ///
161 /// ```rust,ignore
162 /// let mut sched = JobScheduler::new();
163 /// sched.add(Job::new("1/10 * * * * *".parse().unwrap(), || {
164 /// println!("I get executed every 10 seconds!");
165 /// }));
166 /// ```
167 pub fn add(&mut self, job: Job<'a>) -> Uuid {
168 let job_id = job.job_id;
169 self.jobs.push(job);
170
171 job_id
172 }
173
174 /// Remove a job from the `JobScheduler`
175 ///
176 /// ```rust,ignore
177 /// let mut sched = JobScheduler::new();
178 /// let job_id = sched.add(Job::new("1/10 * * * * *".parse().unwrap(), || {
179 /// println!("I get executed every 10 seconds!");
180 /// }));
181 /// sched.remove(job_id);
182 /// ```
183 pub fn remove(&mut self, job_id: Uuid) -> bool {
184 let mut found_index = None;
185 for (i, job) in self.jobs.iter().enumerate() {
186 if job.job_id == job_id {
187 found_index = Some(i);
188 break;
189 }
190 }
191
192 if found_index.is_some() {
193 self.jobs.remove(found_index.unwrap());
194 }
195
196 found_index.is_some()
197 }
198
199 /// The `tick` method increments time for the JobScheduler and executes
200 /// any pending jobs. It is recommended to sleep for at least 500
201 /// milliseconds between invocations of this method.
202 ///
203 /// ```rust,ignore
204 /// loop {
205 /// sched.tick();
206 /// std::thread::sleep(Duration::from_millis(500));
207 /// }
208 /// ```
209 pub fn tick(&mut self) {
210 for job in &mut self.jobs {
211 job.tick();
212 }
213 }
214
215 /// The `time_till_next_job` method returns the duration till the next job
216 /// is supposed to run. This can be used to sleep until then without waking
217 /// up at a fixed interval.AsMut
218 ///
219 /// ```rust, ignore
220 /// loop {
221 /// sched.tick();
222 /// std::thread::sleep(sched.time_till_next_job());
223 /// }
224 /// ```
225 pub fn time_till_next_job(&self) -> std::time::Duration {
226 if self.jobs.is_empty() {
227 // Take a guess if there are no jobs.
228 return std::time::Duration::from_millis(500);
229 }
230
231 let mut duration = Duration::zero();
232 let now = Local::now();
233 for job in self.jobs.iter() {
234 for event in job.schedule.upcoming(Local).take(1) {
235 let d = event - now;
236 if duration.is_zero() || d < duration {
237 duration = d;
238 }
239 }
240 }
241 duration.to_std().unwrap()
242 }
243}
244
245#[cfg(test)]
246mod test {
247 #[test]
248 fn it_works() {
249 println!("rcron");
250 }
251
252 #[test]
253 fn test_run() {
254 use super::Job;
255 use super::JobScheduler;
256 use std::time::Duration;
257 let mut sched = JobScheduler::new();
258 sched.add(Job::new("1/2 * * * * *".parse().unwrap(), || {
259 println!("I get executed every 2 seconds!");
260 }));
261
262 loop {
263 sched.tick();
264
265 std::thread::sleep(Duration::from_millis(500));
266 }
267 }
268}