begin implementing the most basic recurrences
This commit is contained in:
parent
0b1c6e3dff
commit
f3b4175627
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
/target/
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
185
src/lib.rs
185
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<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
72
src/util.rs
Normal 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()
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user