aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRuben Pollan2020-05-19 11:29:25 +0200
committerRuben Pollan2020-05-19 11:29:25 +0200
commit098ac045545d4149b0062bbb365bf7467eb2bff3 (patch)
tree7f12ea9a368ee36ce1b0ebfa4eff4caf53ffde6b
parent365e8d37f1d97bc2f0f062f4594fbae1a90be93a (diff)
Add support for ical's BYDAY recursion rule
-rw-r--r--src/date.rs32
-rw-r--r--src/errors.rs1
-rw-r--r--src/periodic.rs101
3 files changed, 107 insertions, 27 deletions
diff --git a/src/date.rs b/src/date.rs
index ab1c2c8..98e052a 100644
--- a/src/date.rs
+++ b/src/date.rs
@@ -1,10 +1,10 @@
use std::cmp::{Ordering, Ord};
-use std::ops::Add;
+use std::ops::{Add, Sub};
use errors::EventError;
use chrono;
-use chrono::{TimeZone, Duration, Datelike, Local};
+use chrono::{TimeZone, Duration, Datelike, Local, Weekday};
use chrono::offset::Utc;
use chrono_tz::{Tz, UTC};
@@ -79,6 +79,13 @@ impl Date {
}
}
+ pub fn weekday(&self) -> Weekday {
+ match *self {
+ Date::Time(t) => t.weekday(),
+ Date::AllDay(d) => d.weekday(),
+ }
+ }
+
pub fn month(&self) -> u32 {
match *self {
Date::Time(t) => t.month(),
@@ -144,6 +151,27 @@ impl Add<Duration> for Date {
}
}
+impl Sub<Date> for Date {
+ type Output = Duration;
+
+ fn sub(self, other: Self) -> Duration {
+ match self {
+ Date::Time(t1) => {
+ match other {
+ Date::Time(t2) => t1 - t2,
+ Date::AllDay(d) => t1.date() - d,
+ }
+ }
+ Date::AllDay(d1) => {
+ match other {
+ Date::Time(t) => d1 - t.date(),
+ Date::AllDay(d2) => d1 - d2,
+ }
+ }
+ }
+ }
+}
+
fn cmp_date_time<T: TimeZone>(date: &chrono::Date<T>, time: &chrono::DateTime<T>) -> Ordering {
let d2 = time.date();
if date.eq(&d2) {
diff --git a/src/errors.rs b/src/errors.rs
index 9f570d2..431e6e4 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -9,6 +9,7 @@ pub enum EventError {
IntError(ParseIntError),
StatusError,
FreqError,
+ BydayError,
}
impl From<parser::errors::Error> for EventError {
diff --git a/src/periodic.rs b/src/periodic.rs
index d69b817..b557ef7 100644
--- a/src/periodic.rs
+++ b/src/periodic.rs
@@ -1,7 +1,8 @@
use std::fmt;
use std::str::FromStr;
+use std::collections::HashSet;
-use chrono::Duration;
+use chrono::{Duration, Weekday};
use date::Date;
use event::{Event, End};
@@ -14,6 +15,8 @@ pub struct Periodic {
pub interval: i64,
pub count: Option<i64>,
pub until: Option<Date>,
+ pub byday: Option<HashSet<Weekday>>,
+ pub wkst: Weekday,
}
#[derive(Debug)]
@@ -35,6 +38,8 @@ impl Periodic {
interval: 1,
count: None,
until: None,
+ byday: None,
+ wkst: Weekday::Mon,
}
}
@@ -44,6 +49,8 @@ impl Periodic {
"INTERVAL" => self.interval = value.parse()?,
"COUNT" => self.count = Some(value.parse()?),
"UNTIL" => self.until = Some(Date::parse(&value, "")?),
+ "BYDAY" => self.byday = Some(parse_byday(value)?),
+ "WKST" => self.wkst = parse_weekday(value)?,
_ => (),
}
Ok(())
@@ -82,14 +89,59 @@ impl<'a> Iterator for Iter<'a> {
event.start = self.start;
event.end = End::Date(self.end);
+ let duration = self.next_duration(self.start);
+ self.start = self.start + duration;
+ self.end = self.end + duration;
self.count += 1;
- self.start = p.freq.next_date(self.start, p.interval);
- self.end = p.freq.next_date(self.end, p.interval);
Some(event)
}
}
+impl<'a> Iter<'a> {
+ fn next_duration(&self, date: Date) -> Duration {
+ let p = self.periodic;
+ match p.freq {
+ Freq::Secondly => Duration::seconds(p.interval),
+ Freq::Minutely => Duration::minutes(p.interval),
+ Freq::Hourly => Duration::hours(p.interval),
+ Freq::Daily => Duration::days(p.interval),
+ Freq::Weekly => {
+ match &p.byday {
+ None => Duration::weeks(p.interval),
+ Some(byday) => {
+ let mut weekday = date.weekday().succ();
+ let mut days = 1;
+ if weekday == p.wkst {
+ days += 7 * (p.interval - 1);
+ }
+ while !byday.contains(&weekday) {
+ weekday = weekday.succ();
+ days += 1;
+ if weekday == p.wkst {
+ days += 7 * (p.interval - 1);
+ }
+ }
+ Duration::days(days)
+ }
+ }
+ }
+ Freq::Monthly => {
+ let new_date = if date.month() == 12 {
+ date.with_month(1)
+ .unwrap()
+ .with_year(date.year() + 1)
+ .unwrap()
+ } else {
+ date.with_month(date.month() + 1).unwrap()
+ };
+ new_date - date
+ }
+ Freq::Yearly => date.with_year(date.year() + 1).unwrap() - date,
+ }
+ }
+}
+
impl fmt::Display for Periodic {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.freq)?;
@@ -101,28 +153,6 @@ impl fmt::Display for Periodic {
}
}
-impl Freq {
- pub fn next_date(&self, date: Date, count: i64) -> Date {
- match self {
- Freq::Secondly => date + Duration::seconds(count),
- Freq::Minutely => date + Duration::minutes(count),
- Freq::Hourly => date + Duration::hours(count),
- Freq::Daily => date + Duration::days(count),
- Freq::Weekly => date + Duration::weeks(count),
- Freq::Monthly => {
- let month = date.month();
- if month == 12 {
- let date = date.with_month(1).unwrap();
- date.with_year(date.year() + 1).unwrap()
- } else {
- date.with_month(month + 1).unwrap()
- }
- }
- Freq::Yearly => date.with_year(date.year() + 1).unwrap(),
- }
- }
-}
-
impl FromStr for Freq {
type Err = EventError;
@@ -139,3 +169,24 @@ impl FromStr for Freq {
}
}
}
+
+fn parse_byday(s: &str) -> Result<HashSet<Weekday>, EventError> {
+ let mut byday = HashSet::new();
+ for v in s.split(",") {
+ byday.insert(parse_weekday(v)?);
+ }
+ Ok(byday)
+}
+
+fn parse_weekday(s: &str) -> Result<Weekday, EventError> {
+ match s {
+ "MO" => Ok(Weekday::Mon),
+ "TU" => Ok(Weekday::Tue),
+ "WE" => Ok(Weekday::Wed),
+ "TH" => Ok(Weekday::Thu),
+ "FR" => Ok(Weekday::Fri),
+ "SA" => Ok(Weekday::Sat),
+ "SU" => Ok(Weekday::Sun),
+ _ => Err(EventError::BydayError),
+ }
+}