risq 0.4.1

Re-implementation of Bisq (https://github.com/bisq-network/bisq) in rust
use chrono::*;
use lazy_static::lazy_static;
use std::{ops::Add, time::SystemTime};

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum Interval {
    Minute,
    HalfHour,
    Hour,
    HalfDay,
    Day,
    Week,
    Month,
    Year,
}

lazy_static! {
    static ref ONE_MINUTE: chrono::Duration = chrono::Duration::minutes(1);
    static ref THIRTY_MINUTES: chrono::Duration = chrono::Duration::minutes(30);
    static ref ONE_HOUR: chrono::Duration = chrono::Duration::hours(1);
    static ref TWELVE_HOURS: chrono::Duration = chrono::Duration::hours(12);
    static ref ONE_DAY: chrono::Duration = chrono::Duration::days(1);
    static ref THREE_DAYS: chrono::Duration = chrono::Duration::days(3);
    static ref ONE_WEEK: chrono::Duration = chrono::Duration::weeks(1);
    static ref SIXTY_DAYS: chrono::Duration = chrono::Duration::days(60);
    static ref ONE_YEAR: chrono::Duration = chrono::Duration::days(365);
    static ref FIVE_YEARS: chrono::Duration = chrono::Duration::days(1826);
}

pub struct IntervalIterator {
    current: DateTime<Utc>,
    end: DateTime<Utc>,
    interval: Interval,
}
impl Iterator for IntervalIterator {
    type Item = (SystemTime, SystemTime);
    fn next(&mut self) -> Option<Self::Item> {
        if self.current < self.end {
            let next = self.current + self.interval;
            let ret = (self.current.into(), next.into());
            self.current = next;
            Some(ret)
        } else {
            None
        }
    }
}

impl Interval {
    pub fn default_for_range(from: &SystemTime, to: &SystemTime) -> Self {
        if let Ok(dur) = to.duration_since(*from) {
            match Duration::from_std(dur) {
                Ok(dur) if dur <= *ONE_HOUR => Interval::Minute,
                Ok(dur) if dur <= *ONE_DAY => Interval::HalfHour,
                Ok(dur) if dur <= *THREE_DAYS => Interval::Hour,
                Ok(dur) if dur <= *ONE_WEEK => Interval::HalfDay,
                Ok(dur) if dur <= *SIXTY_DAYS => Interval::Day,
                Ok(dur) if dur <= *ONE_YEAR => Interval::Week,
                Ok(dur) if dur <= *FIVE_YEARS => Interval::Month,
                _ => Interval::Year,
            }
        } else {
            Interval::Year
        }
    }

    pub fn intervals(self, start: SystemTime, end: SystemTime) -> IntervalIterator {
        let start: DateTime<Utc> = self.appropriate_floor(start.into());
        let end: DateTime<Utc> = self.appropriate_floor(end.into()) + self;
        IntervalIterator {
            current: start,
            end,
            interval: self,
        }
    }

    fn appropriate_floor(self, time: DateTime<Utc>) -> DateTime<Utc> {
        let time = time.with_second(0).unwrap();
        let time = match self {
            Interval::Minute => return time,
            Interval::HalfHour if time.minute() >= 30 => return time.with_minute(30).unwrap(),
            Interval::Hour => return time.with_minute(0).unwrap(),
            _ => time.with_minute(0).unwrap(),
        };
        let time = match self {
            Interval::HalfDay if time.hour() >= 12 => return time.with_hour(12).unwrap(),
            Interval::Day => return time.with_hour(0).unwrap(),
            _ => time.with_hour(0).unwrap(),
        };
        match self {
            Interval::Week => time - Duration::days(time.weekday().num_days_from_monday() as i64),
            Interval::Month => time.with_day0(0).unwrap(),
            Interval::Year => time.with_day0(0).and_then(|t| t.with_month0(0)).unwrap(),
            _ => unreachable!(),
        }
    }
}

impl Add<Interval> for DateTime<Utc> {
    type Output = DateTime<Utc>;

    fn add(self, interval: Interval) -> Self {
        match interval {
            Interval::Minute => self + *ONE_MINUTE,
            Interval::HalfHour => self + *THIRTY_MINUTES,
            Interval::Hour => self + *ONE_HOUR,
            Interval::HalfDay => self + *TWELVE_HOURS,
            Interval::Day => self + *ONE_DAY,
            Interval::Week => self + *ONE_WEEK,
            Interval::Month if self.month() != 12 => self.with_month(self.month() + 1).unwrap(),
            Interval::Month => self
                .with_month0(0)
                .and_then(|d| d.with_year(self.year() + 1))
                .unwrap(),
            Interval::Year => self.with_year(self.year() + 1).unwrap(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn appropriate_floor() {
        let date = Utc.ymd(2016, 7, 8).and_hms(13, 45, 11);
        assert!(Interval::Minute.appropriate_floor(date) == Utc.ymd(2016, 7, 8).and_hms(13, 45, 0));
        assert!(
            Interval::HalfHour.appropriate_floor(date) == Utc.ymd(2016, 7, 8).and_hms(13, 30, 0)
        );
        assert!(Interval::Hour.appropriate_floor(date) == Utc.ymd(2016, 7, 8).and_hms(13, 0, 0));
        assert!(Interval::HalfDay.appropriate_floor(date) == Utc.ymd(2016, 7, 8).and_hms(12, 0, 0));
        assert!(Interval::Day.appropriate_floor(date) == Utc.ymd(2016, 7, 8).and_hms(0, 0, 0));
        assert!(Interval::Week.appropriate_floor(date) == Utc.ymd(2016, 7, 4).and_hms(0, 0, 0));
        assert!(Interval::Month.appropriate_floor(date) == Utc.ymd(2016, 7, 1).and_hms(0, 0, 0));
        assert!(Interval::Year.appropriate_floor(date) == Utc.ymd(2016, 1, 1).and_hms(0, 0, 0));
    }

    #[test]
    fn interval_addition() {
        let date = Utc.ymd(2016, 7, 8).and_hms(0, 0, 0);
        assert!(date + Interval::Minute == Utc.ymd(2016, 7, 8).and_hms(0, 1, 0));
        assert!(date + Interval::HalfHour == Utc.ymd(2016, 7, 8).and_hms(0, 30, 0));
        assert!(date + Interval::Hour == Utc.ymd(2016, 7, 8).and_hms(1, 0, 0));
        assert!(date + Interval::HalfDay == Utc.ymd(2016, 7, 8).and_hms(12, 0, 0));
        assert!(date + Interval::Day == Utc.ymd(2016, 7, 9).and_hms(0, 0, 0));
        assert!(date + Interval::Week == Utc.ymd(2016, 7, 15).and_hms(0, 0, 0));
        assert!(date + Interval::Month == Utc.ymd(2016, 8, 8).and_hms(0, 0, 0));
        assert!(date + Interval::Year == Utc.ymd(2017, 7, 8).and_hms(0, 0, 0));
    }
}