From f3b4175627a89e720e945728e0c53a89a4184b71 Mon Sep 17 00:00:00 2001 From: Adrian Malacoda Date: Sun, 25 Feb 2018 22:30:54 -0600 Subject: [PATCH] begin implementing the most basic recurrences --- .gitignore | 4 ++ src/lib.rs | 185 +++++++++++++++++++++++++++++++++++++++++++++++----- src/util.rs | 72 ++++++++++++++++++++ 3 files changed, 245 insertions(+), 16 deletions(-) create mode 100644 .gitignore create mode 100644 src/util.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..143b1ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ + +/target/ +**/*.rs.bk +Cargo.lock diff --git a/src/lib.rs b/src/lib.rs index c3b2792..ad7d224 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,11 @@ extern crate chrono; use chrono::{Datelike, Timelike, DateTime, Weekday, TimeZone}; +mod util; +use util::Increment; + +//use std::default::Default; + pub enum Frequency { Yearly, Weekly, @@ -21,6 +26,7 @@ pub struct Recurrence { pub count: Option, pub interval: Option, + pub bysecond: Option>, pub byminute: Option>, pub byhour: Option>, pub byday: Option>, @@ -33,32 +39,127 @@ pub struct Recurrence { impl Recurrence { pub fn iter (&self, start: DateTime) -> DateTimeIterator { - DateTimeIterator { - recurrence: &self, - start: start, - next_datetimes: vec![] - } + DateTimeIterator::new(self, start) } } pub struct DateTimeIterator<'a, Tz: TimeZone + 'a> { recurrence: &'a Recurrence, start: DateTime, - - next_datetimes: Vec> + next_datetimes: Vec>, + count: u32 } impl<'a, Tz: TimeZone + 'a> DateTimeIterator<'a, Tz> { + fn new (recurrence: &Recurrence, start: DateTime) -> DateTimeIterator { + DateTimeIterator { + recurrence: &recurrence, + start: start, + next_datetimes: vec![], + count: 0 + } + } + fn generate_next_datetimes (&mut self) { + self.next_datetimes = self.generate_by_month( + self.generate_by_monthday( + self.generate_by_hour( + self.generate_by_minute( + self.generate_by_second(vec![self.start.clone()]) + ) + ) + ) + ); + let interval = self.recurrence.interval.unwrap_or(1); self.start = match self.recurrence.frequency { - Frequency::Yearly => self.start.with_year(self.start.year() + interval).unwrap(), - Frequency::Monthly => self.start.with_month(self.start.month() + interval as u32).unwrap(), - Frequency::Weekly => self.start.with_day(self.start.day() + (7 * interval as u32)).unwrap(), - Frequency::Daily => self.start.with_day(self.start.day() + interval as u32).unwrap(), - Frequency::Hourly => self.start.with_hour(self.start.hour() + interval as u32).unwrap(), - Frequency::Minutely => self.start.with_minute(self.start.minute() + interval as u32).unwrap(), - Frequency::Secondly => self.start.with_second(self.start.second() + interval as u32).unwrap() + Frequency::Yearly => self.start.add_years(interval), + Frequency::Monthly => self.start.add_months(interval as u32), + Frequency::Weekly => self.start.add_weeks(interval as u32), + Frequency::Daily => self.start.add_days(interval as u32), + Frequency::Hourly => self.start.add_hours(interval as u32), + Frequency::Minutely => self.start.add_minutes(interval as u32), + Frequency::Secondly => self.start.add_seconds(interval as u32) + }; + } + + fn generate_by_second (&self, starting_datetimes: Vec>) -> Vec> { + if let Some(ref bysecond) = self.recurrence.bysecond { + let mut next_datetimes = vec![]; + for start in starting_datetimes { + for second in bysecond { + if let Some(datetime) = start.with_second(*second) { + next_datetimes.push(datetime); + } + } + } + next_datetimes + } else { + starting_datetimes + } + } + + fn generate_by_minute (&self, starting_datetimes: Vec>) -> Vec> { + if let Some(ref byminute) = self.recurrence.byminute { + let mut next_datetimes = vec![]; + for start in starting_datetimes { + for minute in byminute { + if let Some(datetime) = start.with_minute(*minute) { + next_datetimes.push(datetime); + } + } + } + next_datetimes + } else { + starting_datetimes + } + } + + fn generate_by_hour (&self, starting_datetimes: Vec>) -> Vec> { + if let Some(ref byhour) = self.recurrence.byhour { + let mut next_datetimes = vec![]; + for start in starting_datetimes { + for hour in byhour { + if let Some(datetime) = start.with_hour(*hour) { + next_datetimes.push(datetime); + } + } + } + next_datetimes + } else { + starting_datetimes + } + } + + fn generate_by_monthday (&self, starting_datetimes: Vec>) -> Vec> { + if let Some(ref bymonthday) = self.recurrence.bymonthday { + let mut next_datetimes = vec![]; + for start in starting_datetimes { + for monthday in bymonthday { + if let Some(datetime) = start.with_day(*monthday) { + next_datetimes.push(datetime); + } + } + } + next_datetimes + } else { + starting_datetimes + } + } + + fn generate_by_month (&self, starting_datetimes: Vec>) -> Vec> { + if let Some(ref bymonth) = self.recurrence.bymonth { + let mut next_datetimes = vec![]; + for start in starting_datetimes { + for month in bymonth { + if let Some(datetime) = start.with_month(*month) { + next_datetimes.push(datetime); + } + } + } + next_datetimes + } else { + starting_datetimes } } } @@ -71,14 +172,66 @@ impl<'a, Tz: TimeZone + 'a> Iterator for DateTimeIterator<'a, Tz> { self.generate_next_datetimes(); } + self.count = self.count + 1; + if let Some(count) = self.recurrence.count { + if count < self.count { + return None; + } + } + self.next_datetimes.pop() } } #[cfg(test)] mod tests { + use {Recurrence, Frequency}; + use chrono::DateTime; + use chrono::offset::Local; + #[test] - fn it_works() { - assert_eq!(2 + 2, 4); + fn test_daily_recurrence () { + let recurrence = Recurrence { + frequency: Frequency::Daily, + count: Some(10), + interval: Some(1), + + byhour: Some(vec![4]), + bysecond: Some(vec![0]), + byminute: Some(vec![20]), + byday: None, + byweekno: None, + bymonth: None, + bymonthday: None, + + until: None + }; + + for datetime in recurrence.iter(Local::now()) { + println!("{:?}", datetime); + } + } + + #[test] + fn test_yearly_recurrence () { + let recurrence = Recurrence { + frequency: Frequency::Yearly, + count: Some(10), + interval: Some(1), + + byhour: None, + bysecond: None, + byminute: None, + byday: None, + byweekno: None, + bymonth: Some(vec![2]), + bymonthday: Some(vec![14]), + + until: None + }; + + for datetime in recurrence.iter(Local::now()) { + println!("{:?}", datetime); + } } } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..0405d09 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,72 @@ +use chrono::{Datelike, DateTime, TimeZone, Timelike, NaiveDate}; + +pub trait Increment { + fn add_years (&self, interval: i32) -> Self; + fn add_months (&self, interval: u32) -> Self; + fn add_weeks (&self, interval: u32) -> Self; + fn add_days (&self, interval: u32) -> Self; + fn add_hours (&self, interval: u32) -> Self; + fn add_minutes (&self, interval: u32) -> Self; + fn add_seconds (&self, interval: u32) -> Self; +} + +impl Increment for DateTime { + fn add_years (&self, interval: i32) -> DateTime { + self.with_year(self.year() + interval).unwrap() + } + + fn add_months (&self, interval: u32) -> DateTime { + let months = self.month() + interval; + if months >= 12 { + self.with_month(months % 12).unwrap().add_years((months / 12) as i32) + } else { + self.with_month(months).unwrap() + } + } + + fn add_weeks (&self, interval: u32) -> DateTime { + self.add_days(interval * 7) + } + + fn add_days (&self, interval: u32) -> DateTime { + let last_day = last_day_of_month(self.year(), self.month()); + let days = self.day() + interval; + if days > last_day { + self.with_day(days % last_day).unwrap().add_months(days / last_day) + } else { + self.with_day(days).unwrap() + } + } + + fn add_hours (&self, interval: u32) -> DateTime { + let hours = self.hour() + interval; + if hours >= 24 { + self.with_hour(hours % 24).unwrap().add_days(hours / 24) + } else { + self.with_hour(hours).unwrap() + } + } + + fn add_minutes (&self, interval: u32) -> DateTime { + let minutes = self.minute() + interval; + if minutes >= 60 { + self.with_minute(minutes % 60).unwrap().add_hours(minutes / 60) + } else { + self.with_minute(minutes).unwrap() + } + } + + fn add_seconds (&self, interval: u32) -> DateTime { + let seconds = self.second() + interval; + if seconds >= 60 { + self.with_second(seconds % 60).unwrap().add_minutes(seconds / 60) + } else { + self.with_second(seconds).unwrap() + } + } +} + +// from https://github.com/chronotope/chrono/issues/29#issuecomment-84492746 +pub fn last_day_of_month (year: i32, month: u32) -> u32 { + NaiveDate::from_ymd_opt(year, month + 1, 1).unwrap_or(NaiveDate::from_ymd(year + 1, 1, 1)).pred().day() +}