izta 0.1.2

Izta is a drop-in job queue for Rust
Documentation
//! Set cron-like task schedules
//!
//! # Example
//! To set a schedule of every 15th at 5:30 pm, write
//! ```rust
//! use izta::cron::Cron;
//!
//! let payroll = Cron::new().day_of_month(15).hours(17).minutes(30);
//! ```
use chrono::{DateTime, Duration, Utc};

/// Cron represents a cron-like schedule for a task
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct Cron {
    /// 0-59 - the minute to run task
    pub minutes: Option<u32>,

    /// 0-23 - the hour to run task
    pub hours: Option<u32>,

    /// 1-31 - the day of the month to run task
    pub day_of_month: Option<u32>,

    /// 1-12 - the month to run task
    pub month: Option<u32>,

    /// 0-6, Sunday=0 - day of the week to run task
    pub day_of_week: Option<u32>,
}

macro_rules! cron_meth {
    ($field:ident) => {
        pub fn $field(self, m: u32) -> Cron {
            let mut rv = self.clone();
            rv.$field = Some(m);
            rv
        }
    };
}

impl Cron {
    /// Creates a new `Cron`
    pub fn new() -> Cron {
        Cron::default()
    }

    /// Set minutes
    cron_meth!(minutes);

    /// Set hours
    cron_meth!(hours);

    /// Set day of month
    cron_meth!(day_of_month);

    /// Set month
    cron_meth!(month);

    /// Set day of week
    cron_meth!(day_of_week);

    // Based on https://stackoverflow.com/a/322058
    /// Return the next scheduled time (in milliseconds since epoch) past `cur` (never equals `cur`)
    pub fn next_run(&self, cur: DateTime<Utc>) -> i64 {
        use chrono::prelude::*;

        // Smallest unit of time we're concerned with is minutes
        let mut next = cur
            .clone()
            .with_second(0)
            .unwrap()
            .with_nanosecond(0)
            .unwrap()
            + Duration::minutes(1);

        let mut done = false;
        while !done {
            if let Some(cron_minute) = self.minutes {
                if next.minute() != cron_minute {
                    if next.minute() > cron_minute {
                        next = next + Duration::hours(1);
                    }
                    next = next.with_minute(cron_minute).unwrap();
                    continue;
                }
            }

            if let Some(cron_hour) = self.hours {
                if next.hour() != cron_hour {
                    if next.hour() > cron_hour {
                        next = next.with_hour(cron_hour).unwrap().with_minute(0).unwrap()
                            + Duration::days(1);
                        continue;
                    }
                    next = next.with_hour(cron_hour).unwrap().with_minute(0).unwrap();
                    continue;
                }
            }

            if let Some(cron_day_of_week) = self.day_of_week {
                let next_day_of_week = next.weekday().num_days_from_sunday();
                if next_day_of_week != cron_day_of_week {
                    let mut delta_days = cron_day_of_week as i32 - next_day_of_week as i32;
                    if delta_days < 0 {
                        delta_days += 7;
                    }
                    next = next.with_hour(0).unwrap().with_minute(0).unwrap()
                        + Duration::days(delta_days as i64);
                    continue;
                }
            }

            if let Some(cron_day) = self.day_of_month {
                if next.day() != cron_day {
                    if next.day() > cron_day || next.with_day(cron_day).is_none() {
                        next = next
                            .with_day(1)
                            .unwrap()
                            .with_hour(0)
                            .unwrap()
                            .with_minute(0)
                            .unwrap();
                        next = next
                            .with_month(next.month() + 1)
                            .unwrap_or_else(|| next.with_month(1).unwrap());
                        continue;
                    }

                    next = next
                        .with_hour(0)
                        .unwrap()
                        .with_minute(0)
                        .unwrap()
                        .with_day(cron_day)
                        .unwrap();
                    continue;
                }
            }

            if let Some(cron_month) = self.month {
                if next.month() != cron_month {
                    if next.month() > cron_month {
                        next = next
                            .with_month(12 - next.month() + cron_month)
                            .unwrap()
                            .with_day(1)
                            .unwrap()
                            .with_hour(0)
                            .unwrap()
                            .with_minute(0)
                            .unwrap();
                        continue;
                    }
                    next = next
                        .with_month(cron_month)
                        .unwrap()
                        .with_day(1)
                        .unwrap()
                        .with_hour(0)
                        .unwrap()
                        .with_minute(0)
                        .unwrap();
                    continue;
                }
            }
            done = true;
        }

        next.timestamp_millis()
    }
}