diff options
author | Fredrik Meringdal <fmeringdal@hotmail.com> | 2020-10-31 18:15:21 +0100 |
---|---|---|
committer | Fredrik Meringdal <fmeringdal@hotmail.com> | 2020-10-31 18:15:21 +0100 |
commit | a600a4e88053a89670aa4bdec0800089018f5c1c (patch) | |
tree | 8911ff0dbb9ba184573630aca58a241157a4234a | |
parent | 93a7edadb0b920752d17c170aa02eda1d4953134 (diff) | |
download | rust_rrule-a600a4e88053a89670aa4bdec0800089018f5c1c.zip |
removed more unwraps
-rw-r--r-- | src/datetime.rs | 2 | ||||
-rw-r--r-- | src/iter/iterinfo.rs | 8 | ||||
-rw-r--r-- | src/iter/mod.rs | 44 | ||||
-rw-r--r-- | src/iter/monthinfo.rs | 2 | ||||
-rw-r--r-- | src/iter/poslist.rs | 11 | ||||
-rw-r--r-- | src/iter/utils.rs | 2 | ||||
-rw-r--r-- | src/iter/yearinfo.rs | 5 | ||||
-rw-r--r-- | src/lib.rs | 54 | ||||
-rw-r--r-- | src/options.rs | 10 | ||||
-rw-r--r-- | src/parse_options.rs | 140 | ||||
-rw-r--r-- | src/rrule.rs | 6 | ||||
-rw-r--r-- | src/rruleset.rs | 15 | ||||
-rw-r--r-- | src/rruleset_iter.rs | 12 | ||||
-rw-r--r-- | src/rrulestr.rs | 296 | ||||
-rw-r--r-- | tests/rrule.rs | 4 |
15 files changed, 338 insertions, 273 deletions
diff --git a/src/datetime.rs b/src/datetime.rs index 46b1643..4702e0e 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -1,7 +1,6 @@ use chrono::prelude::*; use chrono_tz::Tz; - pub type DTime = DateTime<Tz>; // pub fn from_ordinal(ordinal: isize) -> DateTime<Utc> { @@ -15,7 +14,6 @@ pub fn from_ordinal(ordinal: isize, tz: &Tz) -> DTime { tz.timestamp(timestamp as i64, 0) } - pub fn to_ordinal(date: &DateTime<Utc>) -> isize { (date.timestamp() / 60 / 60 / 24) as isize } diff --git a/src/iter/iterinfo.rs b/src/iter/iterinfo.rs index 1d401dd..7c47645 100644 --- a/src/iter/iterinfo.rs +++ b/src/iter/iterinfo.rs @@ -1,8 +1,8 @@ -use crate::datetime::{Time, to_ordinal}; +use crate::datetime::{to_ordinal, Time}; use crate::iter::easter::easter; -use crate::iter::monthinfo::{MonthInfo, rebuild_month}; -use crate::iter::yearinfo::{YearInfo, rebuild_year}; -use crate::options::{ParsedOptions, Frequenzy}; +use crate::iter::monthinfo::{rebuild_month, MonthInfo}; +use crate::iter::yearinfo::{rebuild_year, YearInfo}; +use crate::options::{Frequenzy, ParsedOptions}; use chrono::prelude::*; pub struct IterInfo<'a> { diff --git a/src/iter/mod.rs b/src/iter/mod.rs index 8e0d4a5..790ef67 100644 --- a/src/iter/mod.rs +++ b/src/iter/mod.rs @@ -1,6 +1,6 @@ -mod yearinfo; -mod monthinfo; mod iterinfo; +mod monthinfo; +mod yearinfo; use iterinfo::IterInfo; mod poslist; use poslist::build_poslist; @@ -8,8 +8,8 @@ mod easter; mod masks; mod utils; +use crate::datetime::{from_ordinal, get_weekday_val, DTime, Time}; use crate::options::*; -use crate::datetime::{Time, DTime, from_ordinal, get_weekday_val}; use chrono::offset::TimeZone; use chrono::prelude::*; use chrono::Duration; @@ -19,11 +19,7 @@ pub trait IterResult { fn get_value(&self) -> Vec<DTime>; } -pub fn iter<T: IterResult>( - iter_result: &mut T, - options: &mut ParsedOptions, -) -> Vec<DTime> { - +pub fn iter<T: IterResult>(iter_result: &mut T, options: &mut ParsedOptions) -> Vec<DTime> { if (options.count.is_some() && options.count.unwrap() == 0) || options.interval == 0 { return iter_result.get_value(); } @@ -54,7 +50,15 @@ pub fn iter<T: IterResult>( let filtered = remove_filtered_days(&mut dayset, start, end, &ii, options); if not_empty(&options.bysetpos) { - let poslist = build_poslist(&options.bysetpos, ×et, start, end, &ii, &dayset, &options.tzid); + let poslist = build_poslist( + &options.bysetpos, + ×et, + start, + end, + &ii, + &dayset, + &options.tzid, + ); for j in 0..poslist.len() { let res = poslist[j]; @@ -85,11 +89,14 @@ pub fn iter<T: IterResult>( let current_day = current_day.unwrap(); let date = from_ordinal(ii.yearordinal().unwrap() + current_day, &options.tzid); for k in 0..timeset.len() { - let res = options.tzid.ymd(date.year(), date.month(), date.day()).and_hms( - timeset[k].hour as u32, - timeset[k].minute as u32, - timeset[k].second as u32, - ); + let res = options + .tzid + .ymd(date.year(), date.month(), date.day()) + .and_hms( + timeset[k].hour as u32, + timeset[k].minute as u32, + timeset[k].second as u32, + ); if options.until.is_some() && res > options.until.unwrap() { return iter_result.get_value(); } @@ -97,7 +104,7 @@ pub fn iter<T: IterResult>( if !iter_result.accept(res) { return iter_result.get_value(); } - + if count > 0 { count -= 1; if count == 0 { @@ -137,7 +144,6 @@ pub fn iter<T: IterResult>( } } - pub fn increment_counter_date( counter_date: DTime, options: &ParsedOptions, @@ -289,11 +295,7 @@ pub fn build_timeset(options: &ParsedOptions) -> Vec<Time> { timeset } -pub fn make_timeset( - ii: &IterInfo, - counter_date: &DTime, - options: &ParsedOptions, -) -> Vec<Time> { +pub fn make_timeset(ii: &IterInfo, counter_date: &DTime, options: &ParsedOptions) -> Vec<Time> { if options.freq < Frequenzy::Hourly { return build_timeset(options); } diff --git a/src/iter/monthinfo.rs b/src/iter/monthinfo.rs index 9da2ca6..43fcef8 100644 --- a/src/iter/monthinfo.rs +++ b/src/iter/monthinfo.rs @@ -1,5 +1,5 @@ -use crate::options::*; use crate::iter::utils::pymod; +use crate::options::*; #[derive(Debug)] pub struct MonthInfo { diff --git a/src/iter/poslist.rs b/src/iter/poslist.rs index fd564d4..9166073 100644 --- a/src/iter/poslist.rs +++ b/src/iter/poslist.rs @@ -1,7 +1,7 @@ -use crate::datetime::{Time, DTime}; +use crate::datetime::from_ordinal; +use crate::datetime::{DTime, Time}; use crate::iter::iterinfo::IterInfo; use crate::iter::utils::pymod; -use crate::datetime::from_ordinal; use chrono::prelude::*; use chrono_tz::Tz; @@ -12,7 +12,7 @@ pub fn build_poslist( end: usize, ii: &IterInfo, dayset: &Vec<Option<isize>>, - tz: &Tz + tz: &Tz, ) -> Vec<DTime> { let mut poslist = vec![]; @@ -31,8 +31,9 @@ pub fn build_poslist( let mut tmp = vec![]; for k in start..end { let val = dayset[k]; - if val.is_some() { - tmp.push(val.unwrap()); + match val { + Some(v) => tmp.push(v), + None => (), } } diff --git a/src/iter/utils.rs b/src/iter/utils.rs index c168c92..a134d9a 100644 --- a/src/iter/utils.rs +++ b/src/iter/utils.rs @@ -5,4 +5,4 @@ pub fn pymod(a: isize, b: isize) -> isize { return r + b; } r -}
\ No newline at end of file +} diff --git a/src/iter/yearinfo.rs b/src/iter/yearinfo.rs index 40b48ce..308a205 100644 --- a/src/iter/yearinfo.rs +++ b/src/iter/yearinfo.rs @@ -1,9 +1,8 @@ +use crate::datetime::{get_weekday_val, get_year_len, to_ordinal}; use crate::iter::masks::MASKS; +use crate::iter::utils::pymod; use crate::options::*; use chrono::prelude::*; -use crate::datetime::{to_ordinal, get_year_len, get_weekday_val}; -use crate::iter::utils::pymod; - #[derive(Debug)] pub struct YearInfo { @@ -22,7 +22,7 @@ //! extern crate rrule; //! //! use rrule::build_rruleset; -//! +//! //! // Parse a RRuleSet string, return a RRuleSet type //! let mut rrule = build_rruleset("DTSTART:20120201T023000Z\nRRULE:FREQ=MONTHLY;COUNT=5\nRDATE:20120701T023000Z,20120702T023000Z\nEXRULE:FREQ=MONTHLY;COUNT=2\nEXDATE:20120601T023000Z").unwrap(); //! assert_eq!(rrule.all().len(), 4); @@ -34,13 +34,13 @@ //! //! ``` //! extern crate rrule; -//! extern crate chrono; -//! extern crate chrono_tz; +//! extern crate chrono; +//! extern crate chrono_tz; //! //! use chrono::prelude::*; //! use chrono_tz::UTC; //! use rrule::{RRule, RRuleSet, Options, Frequenzy, Weekday}; -//! +//! //! // Build options that starts first day in 2020 at 9:00AM and occurs daily 5 times //! let mut options = Options::new() //! .dtstart(UTC.ymd(2020, 1, 1).and_hms(9, 0, 0)) @@ -48,8 +48,8 @@ //! .freq(Frequenzy::Daily) //! .build() //! .unwrap(); -//! -//! // Construct `RRule` from options +//! +//! // Construct `RRule` from options //! let mut rrule = RRule::new(options); //! let occurences = rrule.all(); //! for i in 0..5 { @@ -78,8 +78,8 @@ //! .byweekday(vec![Weekday::Tue, Weekday::Wed]) //! .build() //! .unwrap(); -//! -//! // Construct `RRule` from options +//! +//! // Construct `RRule` from options //! let mut rrule = RRule::new(rrule_options); //! //! @@ -91,21 +91,21 @@ //! .byweekday(vec![Weekday::Wed]) //! .build() //! .unwrap(); -//! -//! // Construct `RRule` from options +//! +//! // Construct `RRule` from options //! let mut exrule = RRule::new(exrule_options); //! //! // Now create the RRuleSet and add rrule and exrule //! let mut rrule_set = RRuleSet::new(); //! rrule_set.rrule(rrule); //! rrule_set.exrule(exrule); -//! +//! //! let occurences = rrule_set.all(); //! //! for occurence in &occurences { //! assert_eq!(occurence.weekday(), Weekday::Tue); //! } -//! +//! //! assert_eq!(occurences.len(), 2); //! ``` //! @@ -116,8 +116,8 @@ //! //! ``` //! extern crate rrule; -//! extern crate chrono; -//! extern crate chrono_tz; +//! extern crate chrono; +//! extern crate chrono_tz; //! //! use chrono::prelude::*; //! use chrono_tz::{UTC, Tz}; @@ -125,13 +125,13 @@ //! use rrule::{RRule, RRuleSet, Options, Frequenzy, Weekday}; //! //! // SOME NOTES: -//! // Occurences produces by RRule or RRuleSet will be in the same timezone -//! // as the start datetime provided (dtstart). The `until` datetime MUST +//! // Occurences produces by RRule or RRuleSet will be in the same timezone +//! // as the start datetime provided (dtstart). The `until` datetime MUST //! // always be specified with the UTC timezone if it is specified. //! // Example: -//! // The following examples uses an RRuleSet with an RRule that yields occurences -//! // in the Europe/Berlin timezone, and contains one EXDATE that is specified +//! // The following examples uses an RRuleSet with an RRule that yields occurences +//! // in the Europe/Berlin timezone, and contains one EXDATE that is specified //! // in UTC and collides (and therefore filters away) with one of those occurences. //! //! @@ -142,25 +142,25 @@ //! .freq(Frequenzy::Daily) //! .build() //! .unwrap(); -//! +//! //! let mut rrule = RRule::new(rrule_options); //! -//! // Exdate in the UTC at 8 oclock which is 9 oclock in Berlin and therefore +//! // Exdate in the UTC at 8 oclock which is 9 oclock in Berlin and therefore //! // collides with one of the rrule occurences. //! let exdate = UTC.ymd(2020, 1, 2).and_hms(8, 0, 0); -//! +//! //! // Now create the RRuleSet and add rrule and exdate //! let mut rrule_set = RRuleSet::new(); //! rrule_set.rrule(rrule); //! rrule_set.exdate(exdate); -//! +//! //! let occurences = rrule_set.all(); //! // RRule contained 4 occurences but 1 was filtered away by the exdate //! assert_eq!(occurences.len(), 3); //! //! // If you want to get back the DateTimes in another timezone (In this case Moscow). //! // Refer to the chrono and chrono-tz crates for more documentation on how to work with -//! // their DateTime type and timezones. +//! // their DateTime type and timezones. //! let occurences_in_moscow_tz: Vec<DateTime<Tz>> = occurences.iter() //! .map(|d| d.with_timezone(&Moscow)).collect(); //! ``` @@ -172,16 +172,16 @@ extern crate regex; mod datetime; mod iter; -mod parse_options; mod options; -mod rrulestr; +mod parse_options; mod rrule; mod rrule_iter; mod rruleset; mod rruleset_iter; +mod rrulestr; +pub use crate::options::{Frequenzy, Options, ParsedOptions}; pub use crate::rrule::RRule; pub use crate::rruleset::RRuleSet; pub use crate::rrulestr::{build_rrule, build_rruleset}; -pub use crate::options::{Frequenzy, ParsedOptions, Options}; -pub use chrono::Weekday;
\ No newline at end of file +pub use chrono::Weekday; diff --git a/src/options.rs b/src/options.rs index 7b5c7e3..884ecb6 100644 --- a/src/options.rs +++ b/src/options.rs @@ -1,7 +1,7 @@ +use crate::datetime::{get_weekday_val, DTime}; +use crate::parse_options::parse_options; use chrono::prelude::*; use chrono_tz::Tz; -use crate::datetime::{DTime, get_weekday_val}; -use crate::parse_options::parse_options; use std::error::Error; use std::fmt::{Display, Formatter}; @@ -92,7 +92,7 @@ impl Options { fn is_some_or_none<'a, T>(prop1: &'a Option<T>, prop2: &'a Option<T>) -> &'a Option<T> { if prop2.is_some() { return prop2; - } + } prop1 } @@ -177,7 +177,7 @@ impl Options { } pub fn byweekday(mut self, byweekday: Vec<Weekday>) -> Self { - let byweekday = byweekday.iter().map(|w| get_weekday_val(w)).collect(); + let byweekday = byweekday.iter().map(|w| get_weekday_val(w)).collect(); self.byweekday = Some(byweekday); self } @@ -216,4 +216,4 @@ impl Display for RRuleParseError { } } -impl Error for RRuleParseError{}
\ No newline at end of file +impl Error for RRuleParseError {} diff --git a/src/parse_options.rs b/src/parse_options.rs index ef9efb7..727da86 100644 --- a/src/parse_options.rs +++ b/src/parse_options.rs @@ -1,5 +1,5 @@ +use crate::options::{Frequenzy, Options, ParsedOptions, RRuleParseError}; use chrono::prelude::*; -use crate::options::{ParsedOptions, Frequenzy, Options, RRuleParseError}; use chrono_tz::{Tz, UTC}; // TODO: Validation @@ -8,14 +8,13 @@ pub fn parse_options(options: &Options) -> Result<ParsedOptions, RRuleParseError default_partial_options.interval = Some(1); default_partial_options.freq = Some(Frequenzy::Yearly); default_partial_options.wkst = Some(0); - + let tzid: Tz = if options.tzid.is_some() { options.tzid.clone().unwrap() } else { UTC }; - let mut partial_options = Options::concat(&default_partial_options, options); if partial_options.byeaster.is_some() { @@ -34,60 +33,61 @@ pub fn parse_options(options: &Options) -> Result<ParsedOptions, RRuleParseError if let Some(bysetpos) = &partial_options.bysetpos { for pos in bysetpos { if *pos == 0 || !(*pos >= -366 && *pos <= 366) { - return Err(RRuleParseError(String::from("Bysetpos must be between 1 and 366, or between -366 and -1"))); + return Err(RRuleParseError(String::from( + "Bysetpos must be between 1 and 366, or between -366 and -1", + ))); } } } - let dtstart = if partial_options.dtstart.is_some() { partial_options.dtstart.unwrap() } else { return Err(RRuleParseError(String::from("Dtstart was not specified"))); }; - if !( - partial_options.byweekno.is_some() || - is_some_and_not_empty(&partial_options.byweekno) || - is_some_and_not_empty(&partial_options.byyearday) || - partial_options.bymonthday.is_some() || - is_some_and_not_empty(&partial_options.bymonthday) || - partial_options.byweekday.is_some() || - partial_options.byeaster.is_some() - ) { + if !(partial_options.byweekno.is_some() + || is_some_and_not_empty(&partial_options.byweekno) + || is_some_and_not_empty(&partial_options.byyearday) + || partial_options.bymonthday.is_some() + || is_some_and_not_empty(&partial_options.bymonthday) + || partial_options.byweekday.is_some() + || partial_options.byeaster.is_some()) + { match &freq { Frequenzy::Yearly => { if partial_options.bymonth.is_none() { partial_options.bymonth = Some(vec![dtstart.month() as usize]); } partial_options.bymonthday = Some(vec![dtstart.day() as isize]); - }, + } Frequenzy::Monthly => { - partial_options.bymonthday = Some(vec![dtstart.day() as isize]); - }, + partial_options.bymonthday = Some(vec![dtstart.day() as isize]); + } Frequenzy::Weekly => { partial_options.byweekday = Some(vec![dtstart.weekday() as usize]); - }, - _ => () + } + _ => (), }; } - if partial_options.bymonthday.is_none() { - partial_options.bynmonthday = None; - } else { - let mut bymonthday = vec![]; - let mut bynmonthday = vec![]; - - for v in &partial_options.bymonthday.unwrap() { - if *v > 0 { - bymonthday.push(*v); - } else if *v < 0 { - bynmonthday.push(*v); - } - } - - partial_options.bymonthday = Some(bymonthday); - partial_options.bynmonthday = Some(bynmonthday); + match &partial_options.bymonthday { + None => partial_options.bynmonthday = None, + Some(opts_bymonthday) => { + let mut bymonthday = vec![]; + let mut bynmonthday = vec![]; + + for v in opts_bymonthday { + if *v > 0 { + bymonthday.push(*v); + } else if *v < 0 { + bynmonthday.push(*v); + } + } + + partial_options.bymonthday = Some(bymonthday); + partial_options.bynmonthday = Some(bynmonthday); + } } // byweekday / bynweekday @@ -95,49 +95,47 @@ pub fn parse_options(options: &Options) -> Result<ParsedOptions, RRuleParseError partial_options.bynweekday = None; } - // byhour - if partial_options.byhour.is_none() && freq < Frequenzy::Hourly { + // byhour + if partial_options.byhour.is_none() && freq < Frequenzy::Hourly { partial_options.byhour = Some(vec![dtstart.hour() as usize]); - } + } - // byminute - if partial_options.byminute.is_none() && freq < Frequenzy::Minutely { - partial_options.byminute = Some(vec![dtstart.minute() as usize]); -} + // byminute + if partial_options.byminute.is_none() && freq < Frequenzy::Minutely { + partial_options.byminute = Some(vec![dtstart.minute() as usize]); + } + // bysecond + if partial_options.bysecond.is_none() && freq < Frequenzy::Secondly { + partial_options.bysecond = Some(vec![dtstart.second() as usize]); + } - // bysecond - if partial_options.bysecond.is_none() && freq < Frequenzy::Secondly { - partial_options.bysecond = Some(vec![dtstart.second() as usize]); - } - - - Ok(ParsedOptions { - freq, - interval: partial_options.interval.unwrap(), - count: partial_options.count, - until: partial_options.until, - tzid, - dtstart, - wkst: partial_options.wkst.unwrap(), - bysetpos: partial_options.bysetpos.unwrap_or(vec![]), - bymonth: partial_options.bymonth.unwrap_or(vec![]), - bymonthday: partial_options.bymonthday.unwrap_or(vec![]), - bynmonthday: partial_options.bynmonthday.unwrap_or(vec![]), - byyearday: partial_options.byyearday.unwrap_or(vec![]), - byweekno: partial_options.byweekno.unwrap_or(vec![]), - byweekday: partial_options.byweekday.unwrap_or(vec![]), - byhour: partial_options.byhour.unwrap_or(vec![]), - byminute: partial_options.byminute.unwrap_or(vec![]), - bysecond: partial_options.bysecond.unwrap_or(vec![]), - bynweekday: partial_options.bynweekday.unwrap_or(vec![]), - byeaster: partial_options.byeaster, - }) + Ok(ParsedOptions { + freq, + interval: partial_options.interval.unwrap(), + count: partial_options.count, + until: partial_options.until, + tzid, + dtstart, + wkst: partial_options.wkst.unwrap(), + bysetpos: partial_options.bysetpos.unwrap_or(vec![]), + bymonth: partial_options.bymonth.unwrap_or(vec![]), + bymonthday: partial_options.bymonthday.unwrap_or(vec![]), + bynmonthday: partial_options.bynmonthday.unwrap_or(vec![]), + byyearday: partial_options.byyearday.unwrap_or(vec![]), + byweekno: partial_options.byweekno.unwrap_or(vec![]), + byweekday: partial_options.byweekday.unwrap_or(vec![]), + byhour: partial_options.byhour.unwrap_or(vec![]), + byminute: partial_options.byminute.unwrap_or(vec![]), + bysecond: partial_options.bysecond.unwrap_or(vec![]), + bynweekday: partial_options.bynweekday.unwrap_or(vec![]), + byeaster: partial_options.byeaster, + }) } fn is_some_and_not_empty<T>(v: &Option<Vec<T>>) -> bool { match v { Some(v) => !v.is_empty(), - None => false + None => false, } -}
\ No newline at end of file +} diff --git a/src/rrule.rs b/src/rrule.rs index d7aa198..5083161 100644 --- a/src/rrule.rs +++ b/src/rrule.rs @@ -1,6 +1,6 @@ use crate::iter::iter; -use crate::rrule_iter::*; use crate::options::*; +use crate::rrule_iter::*; use chrono::prelude::*; use chrono_tz::Tz; @@ -11,9 +11,7 @@ pub struct RRule { impl RRule { pub fn new(options: ParsedOptions) -> Self { - Self { - options - } + Self { options } } pub fn all(&mut self) -> Vec<DateTime<Tz>> { diff --git a/src/rruleset.rs b/src/rruleset.rs index 810c18e..4a3caa5 100644 --- a/src/rruleset.rs +++ b/src/rruleset.rs @@ -1,8 +1,8 @@ -use chrono::prelude::*; -use chrono_tz::{Tz, UTC}; -use crate::rrule::RRule; use crate::datetime::DTime; +use crate::rrule::RRule; use crate::rruleset_iter::RRuleSetIter; +use chrono::prelude::*; +use chrono_tz::{Tz, UTC}; #[derive(Debug)] pub struct RRuleSet { @@ -81,14 +81,7 @@ mod test_iter_set { use super::*; use crate::options::*; - fn ymd_hms( - year: i32, - month: u32, - day: u32, - hour: u32, - minute: u32, - second: u32, - ) -> DTime { + fn ymd_hms(year: i32, month: u32, day: u32, hour: u32, minute: u32, second: u32) -> DTime { UTC.ymd(year, month, day).and_hms(hour, minute, second) } diff --git a/src/rruleset_iter.rs b/src/rruleset_iter.rs index 495bbf3..0b3fe35 100644 --- a/src/rruleset_iter.rs +++ b/src/rruleset_iter.rs @@ -1,9 +1,9 @@ use crate::iter::{iter, IterResult}; -use chrono::prelude::*; -use crate::rruleset::RRuleSet; use crate::rrule_iter::*; -use std::collections::HashMap; +use crate::rruleset::RRuleSet; +use chrono::prelude::*; use chrono_tz::Tz; +use std::collections::HashMap; /// Result iterator for the RRuleSet type. It mostly just wraps /// `RRuleIterRes` and also before accepting any date makes sure that @@ -99,7 +99,10 @@ impl<'a> RRuleSetIter<'a> { fn accept_when_unknown_bounds(&mut self, date: DateTime<Tz>) -> bool { let dt = date.timestamp(); if !self.exdate_hash.contains_key(&dt) { - self.eval_exdate(&date.timezone().timestamp(dt - 1, 0), &date.timezone().timestamp(dt + 1, 0)); + self.eval_exdate( + &date.timezone().timestamp(dt - 1, 0), + &date.timezone().timestamp(dt + 1, 0), + ); if !self.exdate_hash.contains_key(&dt) { self.exdate_hash.insert(dt, ()); return self.iter_res.accept(date); @@ -122,7 +125,6 @@ impl<'a> RRuleSetIter<'a> { } pub fn iter(&mut self) -> Vec<DateTime<Tz>> { - // Add all exdates to exdate_hash for date in &self.rrule_set.exdate { println!("Exdate timestamp: {}", date.timestamp()); diff --git a/src/rrulestr.rs b/src/rrulestr.rs index 4fceee4..87fedaa 100644 --- a/src/rrulestr.rs +++ b/src/rrulestr.rs @@ -1,10 +1,10 @@ +use crate::datetime::DTime; use crate::options::*; use crate::parse_options::parse_options; use crate::rrule::RRule; -use crate::datetime::DTime; use crate::rruleset::RRuleSet; use chrono::prelude::*; -use chrono_tz::{UTC, Tz}; +use chrono_tz::{Tz, UTC}; use once_cell::sync::Lazy; use regex::Regex; use std::str::FromStr; @@ -19,15 +19,24 @@ static RRULE_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^(?:RRULE|EXRULE):" static PARSE_LINE_RE_1: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s+|\s+$").unwrap()); static PARSE_LINE_RE_2: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^([A-Z]+?)[:;]").unwrap()); +static RDATE_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)RDATE(?:;TZID=([^:=]+))?").unwrap()); +static EXDATE_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)EXDATE(?:;TZID=([^:=]+))?").unwrap()); -fn parse_datestring_bit<T: FromStr>(bits: ®ex::Captures, i: usize, dt: &str) -> Result<T, RRuleParseError> { +static DATETIME_RE: Lazy<Regex> = + Lazy::new(|| Regex::new(r"(?m)(VALUE=DATE(-TIME)?)|(TZID=)").unwrap()); + +fn parse_datestring_bit<T: FromStr>( + bits: ®ex::Captures, + i: usize, + dt: &str, +) -> Result<T, RRuleParseError> { match bits.get(i) { Some(bit) => match bit.as_str().parse::<T>() { Err(_) => Err(RRuleParseError(format!("Invalid datetime: {}", dt))), - Ok(val) => Ok(val) - } - _ => Err(RRuleParseError(format!("Invalid datetime: {}", dt))) + Ok(val) => Ok(val), + }, + _ => Err(RRuleParseError(format!("Invalid datetime: {}", dt))), } } @@ -56,7 +65,6 @@ fn datestring_to_date(dt: &str, tz: &Tz) -> Result<DTime, RRuleParseError> { fn parse_dtstart(s: &str) -> Result<Options, RRuleParseError> { let caps = DTSTART_RE.captures(s); - match caps { Some(caps) => { @@ -66,8 +74,13 @@ fn parse_dtstart(s: &str) -> Result<Options, RRuleParseError> { UTC }; + let dtstart_str = match caps.get(2) { + Some(dt) => dt.as_str(), + None => return Err(RRuleParseError(format!("Invalid datetime: {}", s))), + }; + let mut options = Options::new(); - options.dtstart = Some(datestring_to_date(caps.get(2).unwrap().as_str(), &tzid)?); + options.dtstart = Some(datestring_to_date(dtstart_str, &tzid)?); options.tzid = Some(tzid); Ok(options) } @@ -88,6 +101,14 @@ fn from_str_to_freq(s: &str) -> Option<Frequenzy> { } } +fn stringval_to_int<T: FromStr>(val: &str, err_msg: String) -> Result<T, RRuleParseError> { + if let Ok(val) = val.parse() { + Ok(val) + } else { + return Err(RRuleParseError(err_msg)); + } +} + fn parse_rrule(line: &str) -> Result<Options, RRuleParseError> { let stripped_line = if line.starts_with("RRULE:") { &line[6..] @@ -101,7 +122,6 @@ fn parse_rrule(line: &str) -> Result<Options, RRuleParseError> { let attrs = attrs.split(";"); for attr in attrs { - let l: Vec<&str> = attr.split("=").collect(); let key = l[0]; @@ -109,54 +129,93 @@ fn parse_rrule(line: &str) -> Result<Options, RRuleParseError> { if l.len() > 1 { value = l[1]; } - + match key.to_uppercase().as_str() { - "FREQ" => { - match from_str_to_freq(value) { - Some(freq) => options.freq = Some(freq), - None => return Err(RRuleParseError(format!("Invalid frequenzy: {}", value))) - } - } + "FREQ" => match from_str_to_freq(value) { + Some(freq) => options.freq = Some(freq), + None => return Err(RRuleParseError(format!("Invalid frequenzy: {}", value))), + }, "WKST" => { - options.wkst = Some(value.parse::<usize>().unwrap()); + let wkst = stringval_to_int(value, format!("Invalid weekstart value"))?; + if wkst > 6 { + return Err(RRuleParseError(format!( + "Invalid wkst value: {}. It must be between 0 and 6", + wkst + ))); + }; + options.wkst = Some(wkst); } "COUNT" => { - options.count = Some(value.parse::<u32>().unwrap()); + let count = stringval_to_int(value, format!("Invalid count"))?; + options.count = Some(count); } "INTERVAL" => { - options.interval = Some(value.parse::<usize>().unwrap()); + let interval = stringval_to_int(value, format!("Invalid interval"))?; + options.interval = Some(interval); } "BYSETPOS" => { - let bysetpos = value.split(",").map(|d| d.parse::<isize>().unwrap()).collect(); - options.bysetpos = Some(bysetpos); + let mut bysetpos = vec![]; + for val in value.split(",") { + let val = stringval_to_int(val, format!("Invalid bysetpos value"))?; + bysetpos.push(val); + } + options.bysetpos = Some(bysetpos); } "BYMONTH" => { - let bymonth = value.split(",").map(|d| d.parse::<usize>().unwrap()).collect(); - options.bymonth = Some(bymonth); + let mut bymonth = vec![]; + for val in value.split(",") { + let val = stringval_to_int(val, format!("Invalid bymonth value"))?; + bymonth.push(val); + } + options.bymonth = Some(bymonth); } - "BYMONTHDAY" => { - let bymonthday = value.split(",").map(|d| d.parse::<isize>().unwrap()).collect(); - options.bymonthday = Some(bymonthday); + "BYMONTHDAY" => { + let mut bymonthday = vec![]; + for val in value.split(",") { + let val = stringval_to_int(val, format!("Invalid bymonthday value"))?; + bymonthday.push(val); + } + options.bymonthday = Some(bymonthday); } "BYYEARDAY" => { - let byyearday = value.split(",").map(|d| d.parse::<isize>().unwrap()).collect(); - options.byyearday = Some(byyearday); + let mut byyearday = vec![]; + for val in value.split(",") { + let val = stringval_to_int(val, format!("Invalid byyearday value"))?; + byyearday.push(val); + } + options.byyearday = Some(byyearday); } "BYWEEKNO" => { - let byweekno = value.split(",").map(|d| d.parse::<isize>().unwrap()).collect(); - options.byweekno = Some(byweekno); + let mut byweekno = vec![]; + for val in value.split(",") { + let val = stringval_to_int(val, format!("Invalid byweekno value"))?; + byweekno.push(val); + } + options.byweekno = Some(byweekno); } - "BYHOUR" => { - let byhour = value.split(",").map(|d| d.parse::<usize>().unwrap()).collect(); - options.byhour = Some(byhour); + "BYHOUR" => { + let mut byhour = vec![]; + for val in value.split(",") { + let val = stringval_to_int(val, format!("Invalid byhour value"))?; + byhour.push(val); + } + options.byhour = Some(byhour); } - "BYMINUTE" => { - let byminute = value.split(",").map(|d| d.parse::<usize>().unwrap()).collect(); - options.byminute = Some(byminute); + "BYMINUTE" => { + let mut byminute = vec![]; + for val in value.split(",") { + let val = stringval_to_int(val, format!("Invalid byminute value"))?; + byminute.push(val); + } + options.byminute = Some(byminute); } "BYSECOND" => { - let bysecond = value.split(",").map(|d| d.parse::<usize>().unwrap()).collect(); - options.bysecond = Some(bysecond); + let mut bysecond = vec![]; + for val in value.split(",") { + let val = stringval_to_int(val, format!("Invalid bysecond value"))?; + bysecond.push(val); + } + options.bysecond = Some(bysecond); } "BYWEEKDAY" | "BYDAY" => { options.byweekday = Some(parse_weekday(value)?); @@ -172,7 +231,10 @@ fn parse_rrule(line: &str) -> Result<Options, RRuleParseError> { options.until = Some(datestring_to_date(value, &UTC)?); } "BYEASTER" => { - options.byeaster = Some(value.parse::<isize>().unwrap()); + options.byeaster = Some(stringval_to_int( + value, + format!("Invalid byeaster val: {}", value), + )?); } _ => return Err(RRuleParseError(format!("Invalid property: {}", key))), }; @@ -195,48 +257,52 @@ fn str_to_weekday(d: &str) -> Result<usize, RRuleParseError> { } fn parse_weekday(val: &str) -> Result<Vec<usize>, RRuleParseError> { - val.split(",").map(|day| { - if day.len() == 2 { - // MO, TU, ... - return str_to_weekday(day); - } + val.split(",") + .map(|day| { + if day.len() == 2 { + // MO, TU, ... + return str_to_weekday(day); + } + + // ! NOT SUPPORTED YET + // -1MO, +3FR, 1SO, 13TU ... + // let regex = Regex::new(r"(?m)^([+-]?\d{1,2})([A-Z]{2})$").unwrap(); + // let parts = regex.captures(day).unwrap(); + // let n = parts.get(1).unwrap(); + // let wdaypart = parts.get(2).unwrap(); + // let wday = str_to_weekday(d) - // ! NOT SUPPORTED YET - // -1MO, +3FR, 1SO, 13TU ... - // let regex = Regex::new(r"(?m)^([+-]?\d{1,2})([A-Z]{2})$").unwrap(); - // let parts = regex.captures(day).unwrap(); - // let n = parts.get(1).unwrap(); - // let wdaypart = parts.get(2).unwrap(); - // let wday = str_to_weekday(d) - - return str_to_weekday(day); - }).collect() + return str_to_weekday(day); + }) + .collect() } fn parse_line(rfc_string: &str) -> Result<Option<Options>, RRuleParseError> { - // let re = Regex::new(r"(?m)^\s+|\s+$").unwrap(); let rfc_string = PARSE_LINE_RE_1.replace(rfc_string, ""); if rfc_string.is_empty() { return Ok(None); } - // let re = Regex::new(r"(?m)^([A-Z]+?)[:;]").unwrap(); let rfc_string_upper = rfc_string.to_uppercase(); let header = PARSE_LINE_RE_2.captures(&rfc_string_upper); - - let rfc_string = rfc_string.to_string(); if header.is_none() { return Ok(Some(parse_rrule(&rfc_string)?)); } let header = header.unwrap(); - let key = header.get(1).unwrap().as_str(); + let key = match header.get(1) { + Some(k) => k.as_str(), + None => return Err(RRuleParseError(format!("Invalid rfc_string: {}", rfc_string))) + }; match key { "EXRULE" | "RRULE" => Ok(Some(parse_rrule(&rfc_string)?)), "DTSTART" => Ok(Some(parse_dtstart(&rfc_string)?)), - _ => Err(RRuleParseError(format!("Unsupported RFC prop {} in {}", key, &rfc_string))) + _ => Err(RRuleParseError(format!( + "Unsupported RFC prop {} in {}", + key, &rfc_string + ))), } } @@ -244,7 +310,7 @@ fn parse_line(rfc_string: &str) -> Result<Option<Options>, RRuleParseError> { struct ParsedLine { name: String, params: Vec<String>, - value: String + value: String, } fn break_down_line(line: &str) -> ParsedLine { @@ -254,20 +320,20 @@ fn break_down_line(line: &str) -> ParsedLine { ParsedLine { name: params[0].to_uppercase(), params: params[1..].iter().map(|s| String::from(*s)).collect(), - value: String::from(parsed_line_name.value) + value: String::from(parsed_line_name.value), } } struct LineName { name: String, - value: String + value: String, } fn extract_name(line: String) -> LineName { if !line.contains(":") { return LineName { name: String::from("RRULE"), - value: line + value: line, }; } @@ -277,9 +343,9 @@ fn extract_name(line: String) -> LineName { LineName { name: String::from(name), - value + value, } -} +} fn parse_string(rfc_string: &str) -> Result<Options, RRuleParseError> { let mut options = vec![]; @@ -288,7 +354,6 @@ fn parse_string(rfc_string: &str) -> Result<Options, RRuleParseError> { if let Some(parsed_line) = parsed_line { options.push(parsed_line); } - } if options.len() == 1 { @@ -298,6 +363,7 @@ fn parse_string(rfc_string: &str) -> Result<Options, RRuleParseError> { Ok(Options::concat(&options[0], &options[1])) } + #[derive(Debug)] struct ParsedInput { rrule_vals: Vec<Options>, @@ -314,12 +380,7 @@ fn parse_input(s: &str) -> Result<ParsedInput, RRuleParseError> { let mut exrule_vals = vec![]; let mut exdate_vals = vec![]; - let Options { - dtstart, - tzid, - .. - } = parse_dtstart(s)?; - + let Options { dtstart, tzid, .. } = parse_dtstart(s)?; let lines: Vec<&str> = s.split("\n").collect(); for line in &lines { @@ -332,7 +393,7 @@ fn parse_input(s: &str) -> Result<ParsedInput, RRuleParseError> { if parsed_line.value.is_empty() { continue; } - + rrule_vals.push(parse_string(line)?); } "EXRULE" => { @@ -342,58 +403,80 @@ fn parse_input(s: &str) -> Result<ParsedInput, RRuleParseError> { if parsed_line.value.is_empty() { continue; } - // TODO: why is it parsed_line.value here and line for RRULE ?? Do some testing + // TODO: why is it parsed_line.value here and line for RRULE ?? Do some testing exrule_vals.push(parse_string(&parsed_line.value)?); } "RDATE" => { - let re = Regex::new(r"(?m)RDATE(?:;TZID=([^:=]+))?").unwrap(); - let matches = re.captures(line).unwrap(); + let matches = match RDATE_RE.captures(line) { + Some(m) => m, + None => return Err(RRuleParseError(format!("Invalid RDATE specified"))) + }; let mut tz = UTC; if let Some(tzid) = matches.get(1) { tz = String::from(tzid.as_str()).parse().unwrap_or(UTC); } - - rdate_vals.append(&mut parse_rdate(&parsed_line.value, parsed_line.params, &tz)?); + + rdate_vals.append(&mut parse_rdate( + &parsed_line.value, + parsed_line.params, + &tz, + )?); } "EXDATE" => { - let re = Regex::new(r"(?m)EXDATE(?:;TZID=([^:=]+))?").unwrap(); - let matches = re.captures(line).unwrap(); + let matches = match EXDATE_RE.captures(line) { + Some(m) => m, + None => return Err(RRuleParseError(format!("Invalid EXDATE specified"))) + }; let tz: Tz = if let Some(tzid) = matches.get(1) { String::from(tzid.as_str()).parse().unwrap_or(UTC) } else { UTC }; - exdate_vals.append(&mut parse_rdate(&parsed_line.value, parsed_line.params, &tz)?); + exdate_vals.append(&mut parse_rdate( + &parsed_line.value, + parsed_line.params, + &tz, + )?); } "DTSTART" => (), - _ => return Err(RRuleParseError(format!("Unsupported property: {}", parsed_line.name))) + _ => { + return Err(RRuleParseError(format!( + "Unsupported property: {}", + parsed_line.name + ))) + } } } - return Ok(ParsedInput { dtstart, tzid, rrule_vals, rdate_vals, exrule_vals, - exdate_vals - }) + exdate_vals, + }); } -fn validate_date_param(params: Vec<&str>) -> Result<(), RRuleParseError>{ - let re = Regex::new(r"(?m)(VALUE=DATE(-TIME)?)|(TZID=)").unwrap(); - +fn validate_date_param(params: Vec<&str>) -> Result<(), RRuleParseError> { for param in ¶ms { - if re.captures(param).unwrap().len() == 0 { - return Err(RRuleParseError(format!("Unsupported RDATE/EXDATE parm: {}", param))); + match DATETIME_RE.captures(param) { + Some(caps) if caps.len() > 0 => (), + _ => return Err(RRuleParseError(format!( + "Unsupported RDATE/EXDATE parm: {}", + param + ))) } } Ok(()) } -// ! works needs to be done here -fn parse_rdate(rdateval: &str, params: Vec<String>, tz: &Tz) -> Result<Vec<DTime>, RRuleParseError> { +// ! work needs to be done here +fn parse_rdate( + rdateval: &str, + params: Vec<String>, + tz: &Tz, +) -> Result<Vec<DTime>, RRuleParseError> { let params: Vec<&str> = params.iter().map(|p| p.as_str()).collect(); validate_date_param(params)?; // let re_timezone = Regex::new(r"(?m)TZID=(.+):").unwrap(); @@ -407,7 +490,6 @@ fn parse_rdate(rdateval: &str, params: Vec<String>, tz: &Tz) -> Result<Vec<DTime Ok(rdatevals) } - pub fn build_rruleset(s: &str) -> Result<RRuleSet, RRuleParseError> { let ParsedInput { mut rrule_vals, @@ -419,10 +501,9 @@ pub fn build_rruleset(s: &str) -> Result<RRuleSet, RRuleParseError> { .. } = parse_input(s)?; - let mut rset = RRuleSet::new(); rset.dtstart = dtstart; - + for rruleval in rrule_vals.iter_mut() { rruleval.tzid = tzid.clone(); rruleval.dtstart = dtstart; @@ -441,14 +522,13 @@ pub fn build_rruleset(s: &str) -> Result<RRuleSet, RRuleParseError> { let parsed_opts = parse_options(&exrule)?; let exrule = RRule::new(parsed_opts); - rset.exrule(exrule); + rset.exrule(exrule); } for exdate in exdate_vals { rset.exdate(exdate); } - Ok(rset) } @@ -468,7 +548,6 @@ pub fn build_rrule(s: &str) -> Result<RRule, RRuleParseError> { Ok(RRule::new(parsed_opts)) } - #[cfg(test)] mod test { use super::*; @@ -495,6 +574,7 @@ mod test { #[test] fn it_works_4() { let res = build_rruleset("DTSTART:20120201T120000Z\nRRULE:FREQ=DAILY;COUNT=5\nEXDATE;TZID=Europe/Berlin:20120202T130000Z,20120203T130000Z"); + println!("{:?}", res); assert!(res.is_ok()); } @@ -511,7 +591,9 @@ mod test { #[test] fn exrule() { - let res = build_rruleset("DTSTART:20120201T120000Z\nRRULE:FREQ=DAILY;COUNT=5\nEXRULE:FREQ=WEEKLY;INTERVAL=2"); + let res = build_rruleset( + "DTSTART:20120201T120000Z\nRRULE:FREQ=DAILY;COUNT=5\nEXRULE:FREQ=WEEKLY;INTERVAL=2", + ); assert!(res.is_ok()); let res = res.unwrap(); assert_eq!(res.exrule.len(), 1); @@ -519,24 +601,18 @@ mod test { assert_eq!(res.exrule[0].options.freq, Frequenzy::Weekly); } - //////////////////////////////////////////////////// // Invalid stuff //////////////////////////////////////////////////// #[test] fn garbage_strings() { - let test_cases = vec![ - "helloworld", - "foo bar", - "hello\nworld", - "RRUle:test", - ]; + let test_cases = vec!["helloworld", "foo bar", "hello\nworld", "RRUle:test"]; for test_case in &test_cases { let res = build_rruleset(test_case); assert!(res.is_err()); } } - + #[test] fn invalid_dtstart() { let res = build_rruleset("DTSTART:20120201120000Z\nRRULE:FREQ=DAILY;COUNT=5"); @@ -557,7 +633,7 @@ mod test { let now = std::time::SystemTime::now(); for _ in 0..10000 { let mut res = build_rruleset("RRULE:UNTIL=19990404T110000Z;DTSTART;TZID=America/New_York:19990104T110000Z;FREQ=WEEKLY;BYDAY=TU,WE").unwrap(); - + // println!("Parsing took: {:?}", now.elapsed().unwrap().as_millis()); let tmp_now = std::time::SystemTime::now(); @@ -565,7 +641,5 @@ mod test { println!("All took: {:?}", tmp_now.elapsed().unwrap().as_nanos()); } println!("Time took: {:?}", now.elapsed().unwrap().as_millis()); - } } - diff --git a/tests/rrule.rs b/tests/rrule.rs index 38a76e2..439f29b 100644 --- a/tests/rrule.rs +++ b/tests/rrule.rs @@ -3,8 +3,8 @@ extern crate chrono_tz; extern crate rrule; use chrono::prelude::*; -use chrono_tz::{UTC, Tz}; -use rrule::{RRule, ParsedOptions, Frequenzy}; +use chrono_tz::{Tz, UTC}; +use rrule::{Frequenzy, ParsedOptions, RRule}; #[cfg(test)] mod test { |