diff options
| author | Ruben Pollan | 2020-05-19 11:29:25 +0200 | 
|---|---|---|
| committer | Ruben Pollan | 2020-05-19 11:29:25 +0200 | 
| commit | 098ac045545d4149b0062bbb365bf7467eb2bff3 (patch) | |
| tree | 7f12ea9a368ee36ce1b0ebfa4eff4caf53ffde6b | |
| parent | 365e8d37f1d97bc2f0f062f4594fbae1a90be93a (diff) | |
Add support for ical's BYDAY recursion rule
Diffstat (limited to '')
| -rw-r--r-- | src/date.rs | 32 | ||||
| -rw-r--r-- | src/errors.rs | 1 | ||||
| -rw-r--r-- | src/periodic.rs | 101 | 
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), +    } +} | 
