recurdates/
lib.rs

1#[macro_use]
2extern crate serde_derive;
3extern crate serde;
4extern crate chrono;
5
6use chrono::{DateTime, Utc, TimeZone};
7use chrono::naive::{MAX_DATE, MIN_DATE};
8
9macro_rules! max_datetime {
10    () => (Utc.from_utc_datetime(&MAX_DATE.and_hms(23, 59, 59)))
11}
12
13macro_rules! min_datetime {
14    () => (Utc.from_utc_datetime(&MIN_DATE.and_hms(0, 0, 0)))
15}
16
17#[cfg(test)]
18mod tests;
19pub mod repeat_every;
20
21use repeat_every::RepeatEvery;
22
23/// `ReDateTime` struct represents recurring datetime object
24/// starting at `at` and repeating every `repeat` up to `until` (inclusive).
25#[derive(Serialize, Deserialize, Clone, Debug)]
26pub struct ReDateTime {
27    pub at: DateTime<Utc>,
28    pub repeat: RepeatEvery,
29    pub until: DateTime<Utc>,
30}
31
32#[derive(Debug)]
33pub struct ReDateTimeIter<'a> {
34    from: &'a ReDateTime,
35    adder: Option<RepeatEvery>,
36}
37
38impl ReDateTime {
39    /// `ReDateTime` with no repeat.
40    pub fn at(dt: DateTime<Utc>) -> Self {
41        ReDateTime { at: dt, repeat: RepeatEvery::zero(), until: max_datetime!() }
42    }
43
44    /// `ReDateTime` with infinite repeat.
45    pub fn repeat(dt: DateTime<Utc>, dur: RepeatEvery) -> Self {
46        let lim = if dur.is_zero() { min_datetime!() } else { max_datetime!()
47        };
48        ReDateTime { at: dt, repeat: dur, until: lim }
49    }
50
51    /// `ReDateTime` with a finite number of repeats.
52    pub fn repeat_until(dt: DateTime<Utc>, dur: RepeatEvery, til: DateTime<Utc>) -> Self {
53        ReDateTime { at: dt, repeat: dur, until: til }
54    }
55
56    /// True if there is no repeat after or on `dt`.
57    pub fn done_before(&self, dt: &DateTime<Utc>) -> bool {
58        if self.repeat.is_zero() {
59            return self.at < *dt;
60        }
61
62        self.until != max_datetime!()
63            && (self.until < *dt || self.iter().last().unwrap() < *dt)
64    }
65
66    /// True if there is a repeat between `df` (inclusive) and `dt` (inclusive). `df` always comes earlier than `dt`.
67    pub fn between(&self, df: &DateTime<Utc>, dt: &DateTime<Utc>) -> bool {
68        self.first_after(df).map_or(false, |d| d <= *dt)
69    }
70
71    /// First repeat after `df` (inclusive).
72    pub fn first_after(&self, df: &DateTime<Utc>) -> Option<DateTime<Utc>> {
73        if self.repeat.is_zero() {
74            if self.at < *df {
75                None
76            } else {
77                Some(self.at)
78            }
79        } else {
80            if self.until < *df {
81                None
82            } else {
83                self.iter().skip_while(|x| *x < *df).next()
84            }
85        }
86    }
87
88    /// An iterator visiting datetimes in order. If `repeat` is negative, the iterator runs in reverse-chronological order.
89    pub fn iter(&self) -> ReDateTimeIter {
90        let a = if self.repeat.is_zero() {
91            None } else {
92            Some(RepeatEvery::zero())
93        };
94        ReDateTimeIter { from: &self, adder: a }
95    }
96}
97
98impl<'a> Iterator for ReDateTimeIter<'a> {
99    type Item = DateTime<Utc>;
100
101    fn next(&mut self) -> Option<Self::Item> {
102        if self.adder.is_none() {
103            return None;
104        }
105
106        let x = self.adder.clone().unwrap();
107        self.adder = x.checked_add(&self.from.repeat);
108        if let Some(n) = x.add_to(&self.from.at) {
109            if n <= self.from.until {
110                return Some(n);
111            }
112        }
113        None
114    }
115}