wei_job_scheduler/
lib.rs

1//! # JobScheduler
2//!
3//! A simple cron-like job scheduling library for Rust.
4//!
5//! ## Usage
6//!
7//! Be sure to add the job_scheduler crate to your `Cargo.toml`:
8//!
9//! ```toml
10//! [dependencies]
11//! job_scheduler = "*"
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,ignore
40//! extern crate job_scheduler;
41//! use job_scheduler::{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!("I get executed every 10 seconds!");
49//!     }));
50//!
51//!     loop {
52//!         sched.tick();
53//!
54//!         std::thread::sleep(Duration::from_millis(500));
55//!     }
56//! }
57//! ```
58
59extern crate chrono;
60extern crate cron;
61extern crate uuid;
62
63use chrono::{offset, DateTime, Duration, Utc};
64pub use cron::Schedule;
65pub use uuid::Uuid;
66
67/// A schedulable `Job`.
68pub struct Job<'a> {
69    schedule: Schedule,
70    run: Box<dyn (FnMut() -> ()) + 'a>,
71    last_tick: Option<DateTime<Utc>>,
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 = Utc::now();
101        if self.last_tick.is_none() {
102            self.last_tick = Some(now);
103            return;
104        }
105        if self.limit_missed_runs > 0 {
106            for event in self
107                .schedule
108                .after(&self.last_tick.unwrap())
109                .take(self.limit_missed_runs)
110            {
111                if event > now {
112                    break;
113                }
114                (self.run)();
115            }
116        } else {
117            for event in self.schedule.after(&self.last_tick.unwrap()) {
118                if event > now {
119                    break;
120                }
121                (self.run)();
122            }
123        }
124
125        self.last_tick = Some(now);
126    }
127
128    /// Set the limit for missed jobs in the case of delayed runs. Setting to 0 means unlimited.
129    ///
130    /// ```rust,ignore
131    /// let mut job = Job::new("0/1 * * * * *".parse().unwrap(), || {
132    ///     println!("I get executed every 1 seconds!");
133    /// });
134    /// job.limit_missed_runs(99);
135    /// ```
136    pub fn limit_missed_runs(&mut self, limit: usize) {
137        self.limit_missed_runs = limit;
138    }
139
140    /// Set last tick to force re-running of missed runs.
141    ///
142    /// ```rust,ignore
143    /// let mut job = Job::new("0/1 * * * * *".parse().unwrap(), || {
144    ///     println!("I get executed every 1 seconds!");
145    /// });
146    /// job.last_tick(Some(Utc::now()));
147    /// ```
148    pub fn last_tick(&mut self, last_tick: Option<DateTime<Utc>>) {
149        self.last_tick = last_tick;
150    }
151}
152
153#[derive(Default)]
154/// The JobScheduler contains and executes the scheduled jobs.
155pub struct JobScheduler<'a> {
156    jobs: Vec<Job<'a>>,
157}
158
159impl<'a> JobScheduler<'a> {
160    /// Create a new `JobScheduler`.
161    pub fn new() -> JobScheduler<'a> {
162        JobScheduler { jobs: Vec::new() }
163    }
164
165    /// Add a job to the `JobScheduler`
166    ///
167    /// ```rust,ignore
168    /// let mut sched = JobScheduler::new();
169    /// sched.add(Job::new("1/10 * * * * *".parse().unwrap(), || {
170    ///     println!("I get executed every 10 seconds!");
171    /// }));
172    /// ```
173    pub fn add(&mut self, job: Job<'a>) -> Uuid {
174        let job_id = job.job_id;
175        self.jobs.push(job);
176
177        job_id
178    }
179
180    /// Remove a job from the `JobScheduler`
181    ///
182    /// ```rust,ignore
183    /// let mut sched = JobScheduler::new();
184    /// let job_id = sched.add(Job::new("1/10 * * * * *".parse().unwrap(), || {
185    ///     println!("I get executed every 10 seconds!");
186    /// }));
187    /// sched.remove(job_id);
188    /// ```
189    pub fn remove(&mut self, job_id: Uuid) -> bool {
190        let mut found_index = None;
191        for (i, job) in self.jobs.iter().enumerate() {
192            if job.job_id == job_id {
193                found_index = Some(i);
194                break;
195            }
196        }
197
198        if found_index.is_some() {
199            self.jobs.remove(found_index.unwrap());
200        }
201
202        found_index.is_some()
203    }
204
205    /// The `tick` method increments time for the JobScheduler and executes
206    /// any pending jobs. It is recommended to sleep for at least 500
207    /// milliseconds between invocations of this method.
208    ///
209    /// ```rust,ignore
210    /// loop {
211    ///     sched.tick();
212    ///     std::thread::sleep(Duration::from_millis(500));
213    /// }
214    /// ```
215    pub fn tick(&mut self) {
216        for job in &mut self.jobs {
217            job.tick();
218        }
219    }
220
221    /// The `time_till_next_job` method returns the duration till the next job
222    /// is supposed to run. This can be used to sleep until then without waking
223    /// up at a fixed interval.AsMut
224    ///
225    /// ```rust, ignore
226    /// loop {
227    ///     sched.tick();
228    ///     std::thread::sleep(sched.time_till_next_job());
229    /// }
230    /// ```
231    pub fn time_till_next_job(&self) -> std::time::Duration {
232        if self.jobs.is_empty() {
233            // Take a guess if there are no jobs.
234            return std::time::Duration::from_millis(500);
235        }
236        let mut duration = Duration::zero();
237        let now = Utc::now();
238        for job in self.jobs.iter() {
239            for event in job.schedule.upcoming(offset::Utc).take(1) {
240                let d = event - now;
241                if duration.is_zero() || d < duration {
242                    duration = d;
243                }
244            }
245        }
246        duration.to_std().unwrap()
247    }
248}