begin implementing the most basic recurrences

This commit is contained in:
Adrian Malacoda 2018-02-25 22:30:54 -06:00
parent 0b1c6e3dff
commit f3b4175627
3 changed files with 245 additions and 16 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target/
**/*.rs.bk
Cargo.lock

View File

@ -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<Tz: TimeZone> {
pub count: Option<u32>,
pub interval: Option<i32>,
pub bysecond: Option<Vec<u32>>,
pub byminute: Option<Vec<u32>>,
pub byhour: Option<Vec<u32>>,
pub byday: Option<Vec<ByDay>>,
@ -33,32 +39,127 @@ pub struct Recurrence<Tz: TimeZone> {
impl<Tz: TimeZone> Recurrence<Tz> {
pub fn iter (&self, start: DateTime<Tz>) -> DateTimeIterator<Tz> {
DateTimeIterator {
recurrence: &self,
start: start,
next_datetimes: vec![]
}
DateTimeIterator::new(self, start)
}
}
pub struct DateTimeIterator<'a, Tz: TimeZone + 'a> {
recurrence: &'a Recurrence<Tz>,
start: DateTime<Tz>,
next_datetimes: Vec<DateTime<Tz>>
next_datetimes: Vec<DateTime<Tz>>,
count: u32
}
impl<'a, Tz: TimeZone + 'a> DateTimeIterator<'a, Tz> {
fn new (recurrence: &Recurrence<Tz>, start: DateTime<Tz>) -> DateTimeIterator<Tz> {
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<DateTime<Tz>>) -> Vec<DateTime<Tz>> {
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<DateTime<Tz>>) -> Vec<DateTime<Tz>> {
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<DateTime<Tz>>) -> Vec<DateTime<Tz>> {
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<DateTime<Tz>>) -> Vec<DateTime<Tz>> {
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<DateTime<Tz>>) -> Vec<DateTime<Tz>> {
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);
}
}
}

72
src/util.rs Normal file
View File

@ -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<Tz: TimeZone> Increment for DateTime<Tz> {
fn add_years (&self, interval: i32) -> DateTime<Tz> {
self.with_year(self.year() + interval).unwrap()
}
fn add_months (&self, interval: u32) -> DateTime<Tz> {
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<Tz> {
self.add_days(interval * 7)
}
fn add_days (&self, interval: u32) -> DateTime<Tz> {
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<Tz> {
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<Tz> {
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<Tz> {
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()
}