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;
|
extern crate chrono;
|
||||||
use chrono::{Datelike, Timelike, DateTime, Weekday, TimeZone};
|
use chrono::{Datelike, Timelike, DateTime, Weekday, TimeZone};
|
||||||
|
|
||||||
|
mod util;
|
||||||
|
use util::Increment;
|
||||||
|
|
||||||
|
//use std::default::Default;
|
||||||
|
|
||||||
pub enum Frequency {
|
pub enum Frequency {
|
||||||
Yearly,
|
Yearly,
|
||||||
Weekly,
|
Weekly,
|
||||||
@ -21,6 +26,7 @@ pub struct Recurrence<Tz: TimeZone> {
|
|||||||
pub count: Option<u32>,
|
pub count: Option<u32>,
|
||||||
pub interval: Option<i32>,
|
pub interval: Option<i32>,
|
||||||
|
|
||||||
|
pub bysecond: Option<Vec<u32>>,
|
||||||
pub byminute: Option<Vec<u32>>,
|
pub byminute: Option<Vec<u32>>,
|
||||||
pub byhour: Option<Vec<u32>>,
|
pub byhour: Option<Vec<u32>>,
|
||||||
pub byday: Option<Vec<ByDay>>,
|
pub byday: Option<Vec<ByDay>>,
|
||||||
@ -33,32 +39,127 @@ pub struct Recurrence<Tz: TimeZone> {
|
|||||||
|
|
||||||
impl<Tz: TimeZone> Recurrence<Tz> {
|
impl<Tz: TimeZone> Recurrence<Tz> {
|
||||||
pub fn iter (&self, start: DateTime<Tz>) -> DateTimeIterator<Tz> {
|
pub fn iter (&self, start: DateTime<Tz>) -> DateTimeIterator<Tz> {
|
||||||
DateTimeIterator {
|
DateTimeIterator::new(self, start)
|
||||||
recurrence: &self,
|
|
||||||
start: start,
|
|
||||||
next_datetimes: vec![]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DateTimeIterator<'a, Tz: TimeZone + 'a> {
|
pub struct DateTimeIterator<'a, Tz: TimeZone + 'a> {
|
||||||
recurrence: &'a Recurrence<Tz>,
|
recurrence: &'a Recurrence<Tz>,
|
||||||
start: DateTime<Tz>,
|
start: DateTime<Tz>,
|
||||||
|
next_datetimes: Vec<DateTime<Tz>>,
|
||||||
next_datetimes: Vec<DateTime<Tz>>
|
count: u32
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Tz: TimeZone + 'a> DateTimeIterator<'a, Tz> {
|
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) {
|
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);
|
let interval = self.recurrence.interval.unwrap_or(1);
|
||||||
self.start = match self.recurrence.frequency {
|
self.start = match self.recurrence.frequency {
|
||||||
Frequency::Yearly => self.start.with_year(self.start.year() + interval).unwrap(),
|
Frequency::Yearly => self.start.add_years(interval),
|
||||||
Frequency::Monthly => self.start.with_month(self.start.month() + interval as u32).unwrap(),
|
Frequency::Monthly => self.start.add_months(interval as u32),
|
||||||
Frequency::Weekly => self.start.with_day(self.start.day() + (7 * interval as u32)).unwrap(),
|
Frequency::Weekly => self.start.add_weeks(interval as u32),
|
||||||
Frequency::Daily => self.start.with_day(self.start.day() + interval as u32).unwrap(),
|
Frequency::Daily => self.start.add_days(interval as u32),
|
||||||
Frequency::Hourly => self.start.with_hour(self.start.hour() + interval as u32).unwrap(),
|
Frequency::Hourly => self.start.add_hours(interval as u32),
|
||||||
Frequency::Minutely => self.start.with_minute(self.start.minute() + interval as u32).unwrap(),
|
Frequency::Minutely => self.start.add_minutes(interval as u32),
|
||||||
Frequency::Secondly => self.start.with_second(self.start.second() + interval as u32).unwrap()
|
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.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()
|
self.next_datetimes.pop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use {Recurrence, Frequency};
|
||||||
|
use chrono::DateTime;
|
||||||
|
use chrono::offset::Local;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_works() {
|
fn test_daily_recurrence () {
|
||||||
assert_eq!(2 + 2, 4);
|
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