summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFredrik Meringdal <fmeringdal@hotmail.com>2020-11-01 22:36:38 +0100
committerFredrik Meringdal <fmeringdal@hotmail.com>2020-11-01 22:36:38 +0100
commit000c7c9900cbe93f5a72bbbef3ad4eaa3906b132 (patch)
treee74d1bfe48f446f75a00dac875988a71d47fb1b4
parented603b788b23535b086f2a7491627e018e8dce5d (diff)
downloadrust_rrule-000c7c9900cbe93f5a72bbbef3ad4eaa3906b132.zip
FromStr impl for RRule and RRuleSet + docs
-rw-r--r--README.md10
-rw-r--r--src/datetime.rs2
-rw-r--r--src/iter/mod.rs33
-rw-r--r--src/iter/monthinfo.rs2
-rw-r--r--src/iter/poslist.rs2
-rw-r--r--src/iter/yearinfo.rs2
-rw-r--r--src/lib.rs71
-rw-r--r--src/parse_options.rs10
-rw-r--r--src/rrule.rs16
-rw-r--r--src/rruleset.rs11
-rw-r--r--src/rrulestr.rs78
-rw-r--r--src/utils.rs (renamed from src/iter/utils.rs)7
12 files changed, 163 insertions, 81 deletions
diff --git a/README.md b/README.md
index f394759..c66070f 100644
--- a/README.md
+++ b/README.md
@@ -11,9 +11,9 @@
```rust
extern crate rrule;
-use rrule::build_rrule;
+use rrule::RRule;
-let mut rrule = build_rrule("DTSTART:20120201T093000Z\nRRULE:FREQ=DAILY;COUNT=3").unwrap();
+let mut rrule: RRule = "DTSTART:20120201T093000Z\nRRULE:FREQ=DAILY;COUNT=3".parse().unwrap();
// Get all recurrences of the rrule
let recurrences = rrule.all();
@@ -24,6 +24,12 @@ assert_eq!(recurrences.len(), 3);
[Documentation and more examples](https://docs.rs/rrule)
+## License
+
+This project is licensed under the [MIT license].
+
+[mit license]: https://github.com/fmeringdal/rust_rrule/blob/main/LICENSE
+
# Inspired by
- [python-dateutil library](http://labix.org/python-dateutil/)
diff --git a/src/datetime.rs b/src/datetime.rs
index 8099ad4..3740018 100644
--- a/src/datetime.rs
+++ b/src/datetime.rs
@@ -1,6 +1,6 @@
use chrono::prelude::*;
use chrono::Utc;
-use chrono_tz::{Tz};
+use chrono_tz::Tz;
pub type DTime = DateTime<Tz>;
diff --git a/src/iter/mod.rs b/src/iter/mod.rs
index c9fa858..5e6e4d1 100644
--- a/src/iter/mod.rs
+++ b/src/iter/mod.rs
@@ -6,7 +6,6 @@ mod poslist;
use poslist::build_poslist;
mod easter;
mod masks;
-mod utils;
use crate::datetime::{from_ordinal, get_weekday_val, DTime, Time};
use crate::options::*;
@@ -210,24 +209,26 @@ pub fn increment_counter_date(
let hours = counter_date.hour() as usize;
if filtered {
// Jump to one iteration before next day
- minutes_inc = (1439. - ((hours*60+minutes) as f32/options.interval as f32)).floor() as usize *options.interval;
+ minutes_inc = (1439. - ((hours * 60 + minutes) as f32 / options.interval as f32))
+ .floor() as usize
+ * options.interval;
}
-
let mut counter_date = counter_date + Duration::minutes(minutes_inc as i64);
loop {
counter_date = counter_date + Duration::minutes(options.interval as i64);
let minutes = counter_date.minute() as usize;
let hours = counter_date.hour() as usize;
- if (options.byhour.is_empty() || includes(&options.byhour, &hours)) && (
- options.byminute.is_empty() || includes(&options.byminute, &minutes)) {
- break;
+ if (options.byhour.is_empty() || includes(&options.byhour, &hours))
+ && (options.byminute.is_empty() || includes(&options.byminute, &minutes))
+ {
+ break;
}
}
counter_date
- },
+ }
Frequenzy::Secondly => {
let mut seconds_inc = 0;
let seconds = counter_date.second() as usize;
@@ -235,10 +236,12 @@ pub fn increment_counter_date(
let hours = counter_date.hour() as usize;
if filtered {
// Jump to one iteration before next day
- seconds_inc = (86399. - ((hours*3600+minutes*60+seconds) as f32/options.interval as f32)).floor() as usize *options.interval;
+ seconds_inc = (86399.
+ - ((hours * 3600 + minutes * 60 + seconds) as f32 / options.interval as f32))
+ .floor() as usize
+ * options.interval;
}
-
let mut counter_date = counter_date + Duration::seconds(seconds_inc as i64);
loop {
counter_date = counter_date + Duration::seconds(options.interval as i64);
@@ -246,16 +249,16 @@ pub fn increment_counter_date(
let minutes = counter_date.minute() as usize;
let hours = counter_date.hour() as usize;
- if (options.byhour.is_empty() || includes(&options.byhour, &hours)) && (
- options.byminute.is_empty() || includes(&options.byminute, &minutes)) && (
- options.bysecond.is_empty() || includes(&options.bysecond, &seconds)
- ) {
- break;
+ if (options.byhour.is_empty() || includes(&options.byhour, &hours))
+ && (options.byminute.is_empty() || includes(&options.byminute, &minutes))
+ && (options.bysecond.is_empty() || includes(&options.bysecond, &seconds))
+ {
+ break;
}
}
counter_date
- },
+ }
}
}
diff --git a/src/iter/monthinfo.rs b/src/iter/monthinfo.rs
index 43fcef8..b5becfe 100644
--- a/src/iter/monthinfo.rs
+++ b/src/iter/monthinfo.rs
@@ -1,5 +1,5 @@
-use crate::iter::utils::pymod;
use crate::options::*;
+use crate::utils::pymod;
#[derive(Debug)]
pub struct MonthInfo {
diff --git a/src/iter/poslist.rs b/src/iter/poslist.rs
index 9166073..635c1d3 100644
--- a/src/iter/poslist.rs
+++ b/src/iter/poslist.rs
@@ -1,7 +1,7 @@
use crate::datetime::from_ordinal;
use crate::datetime::{DTime, Time};
use crate::iter::iterinfo::IterInfo;
-use crate::iter::utils::pymod;
+use crate::utils::pymod;
use chrono::prelude::*;
use chrono_tz::Tz;
diff --git a/src/iter/yearinfo.rs b/src/iter/yearinfo.rs
index 308a205..eb422f1 100644
--- a/src/iter/yearinfo.rs
+++ b/src/iter/yearinfo.rs
@@ -1,7 +1,7 @@
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 crate::utils::pymod;
use chrono::prelude::*;
#[derive(Debug)]
diff --git a/src/lib.rs b/src/lib.rs
index 6685810..00e6805 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,37 +1,49 @@
-//! A partial implementation of recurrence rules as defined in the iCalendar RFC.
+//! A performant rust implementation of recurrence rules as defined in the iCalendar RFC.
+//!
+//! RRule provides two main types for working with recurrence rules:
+//! - `RRule`: For working with a single recurrence rule without any exception dates (exdates / exrules) and no additonal dates (rdate).
+//! - `RRuleSet`: For working with a collection of rrule`s, exrule`s, rdate`s and exdate`s. Both the rrule and exrule
+//! properties are represented by the `RRule` type and the rdate and exdate properties are represented by the DateTime<Tz> type
+//! provided by the [chrono](https://crates.io/crates/chrono) and [chrono-tz](https://crates.io/crates/chrono-tz) crates.
+//!
+//! # Building RRule and RRuleSet types
+//! Both of the types implements the `std::str::FromStr` trait so that they can be parsed and built from a `str`. `RRule`
+//! can additionally be constructured from the `Option` type which help build the recurrence rule. `RRuleSet`
+//! can also be built by composing mutliple `RRule`s for its rrule and exrule properties and DateTime<Tz> for its
+//! exdate and rdate properties. See the examples below.
+//!
+//! # Interface
+//! `RRule` and `RRuleSet` have the same interface for generating recurrences. The four methods for "querying" recurrences are:
+//! - `all`: Generate all recurrences that matches the rules
+//! - `between`: Generate all recurrences that matches the rules and are between two given dates
+//! - `before`: Generate the last recurrence that matches the rules and is before a given date
+//! - `after`: Generate the first recurrence that matches the rules and is after a given date
//!
//!
-//!
//! # Examples
//!
-//! RRule quickstart from rrulestring
+//! Quick start by parsing strings
//!
//! ```
//! extern crate rrule;
//!
-//! use rrule::build_rrule;
+//! use rrule::RRule;
//!
-//! // Parse a RRule string, return a RRule type
-//! let mut rrule = build_rrule("DTSTART:20120201T093000Z\nRRULE:FREQ=WEEKLY;INTERVAL=5;UNTIL=20130130T230000Z;BYDAY=MO,FR").unwrap();
+//! // Parse a RRule string, return a RRul to get e type
+//! let mut rrule: RRule = "DTSTART:20120201T093000Z\nRRULE:FREQ=WEEKLY;INTERVAL=5;UNTIL=20130130T230000Z;BYDAY=MO,FR".parse().unwrap();
//! assert_eq!(rrule.all().len(), 21);
-//! ```
-//!
//!
-//! RRuleSet quickstart from rrulestring
-//!
-//! ```
-//! extern crate rrule;
//!
-//! use rrule::build_rruleset;
+//! use rrule::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);
+//! let mut rrule_set: RRuleSet = "DTSTART:20120201T023000Z\nRRULE:FREQ=MONTHLY;COUNT=5\nRDATE:20120701T023000Z,20120702T023000Z\nEXRULE:FREQ=MONTHLY;COUNT=2\nEXDATE:20120601T023000Z".parse().unwrap();
+//! assert_eq!(rrule_set.all().len(), 4);
//! ```
//!
//!
//!
-//! Using `Options` instead of rrule strings to build RRule and RRuleSet
+//! Using `Options` to build RRule
//!
//! ```
//! extern crate rrule;
@@ -40,7 +52,7 @@
//!
//! use chrono::prelude::*;
//! use chrono_tz::UTC;
-//! use rrule::{RRule, RRuleSet, Options, Frequenzy, Weekday};
+//! use rrule::{RRule, Options, Frequenzy, Weekday};
//!
//! // Build options that starts first day in 2020 at 9:00AM and occurs daily 5 times
//! let mut options = Options::new()
@@ -60,15 +72,21 @@
//! assert_eq!(recurrences[i].hour(), 9);
//! }
//! assert_eq!(recurrences.len(), 5);
+//! ```
//!
//!
+//! Construct RRuleSet from one rrule and exrule.
+//! The rrule will occur weekly on Tuesday and Wednesday and the exrule
+//! will occur weekly on Wednesday, and therefore the end result will contain
+//! weekly recurrences just on Wednesday.
+//! ```
+//! extern crate rrule;
+//! extern crate chrono;
+//! extern crate chrono_tz;
//!
-//!
-//!
-//! // Construct RRuleSet from one rrule and exrule
-//! // The rrule will occur weekly on Tuesday and Wednesday and the exrule
-//! // will occur weekly on Wednesday, and therefore the end result will contain
-//! // weekly recurrences just on Wednesday.
+//! use chrono::prelude::*;
+//! use chrono_tz::UTC;
+//! use rrule::{RRule, RRuleSet, Options, Frequenzy, Weekday};
//!
//!
//! // Build options for rrule that occurs weekly on Tuesday and Wednesday
@@ -102,7 +120,8 @@
//! rrule_set.exrule(exrule);
//!
//! let recurrences = rrule_set.all();
-//!
+//!
+//! // Check that all the recurrences are on a Tuesday
//! for occurence in &recurrences {
//! assert_eq!(occurence.weekday(), Weekday::Tue);
//! }
@@ -162,7 +181,7 @@
//! // If you want to get back the DateTimes in another timezone you can just iterate over the result
//! // and convert them to another timezone by using the with_timzone method provided by the DateTime type.
//! // Refer to the chrono and chrono-tz crates for more documenation on working with the DateTime type.
-//!
+//!
//! // Example of converting to mocow timezone
//! use chrono_tz::Europe::Moscow;
//!
@@ -191,9 +210,9 @@ mod rrule_iter;
mod rruleset;
mod rruleset_iter;
mod rrulestr;
+mod utils;
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 chrono::Weekday;
diff --git a/src/parse_options.rs b/src/parse_options.rs
index 0387e51..1f86311 100644
--- a/src/parse_options.rs
+++ b/src/parse_options.rs
@@ -1,8 +1,9 @@
use crate::options::{Frequenzy, Options, ParsedOptions, RRuleParseError};
+use crate::utils::is_some_and_not_empty;
use chrono::prelude::*;
use chrono_tz::{Tz, UTC};
-// TODO: Validation
+// TODO: More validation here
pub fn parse_options(options: &Options) -> Result<ParsedOptions, RRuleParseError> {
let mut default_partial_options = Options::new();
default_partial_options.interval = Some(1);
@@ -133,10 +134,3 @@ pub fn parse_options(options: &Options) -> Result<ParsedOptions, RRuleParseError
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,
- }
-}
diff --git a/src/rrule.rs b/src/rrule.rs
index 68f77ad..c2a080e 100644
--- a/src/rrule.rs
+++ b/src/rrule.rs
@@ -1,8 +1,10 @@
use crate::iter::iter;
use crate::options::*;
use crate::rrule_iter::*;
+use crate::rrulestr::build_rrule;
use chrono::prelude::*;
use chrono_tz::Tz;
+use std::str::FromStr;
#[derive(Clone, Debug)]
pub struct RRule {
@@ -27,7 +29,7 @@ impl RRule {
let res = iter(&mut iter_res, &mut self.options);
res
}
-
+
/// Returns the last recurrence before the given datetime instance.
/// The inc keyword defines what happens if dt is an recurrence.
/// With inc == true, if dt itself is an recurrence, it will be returned.
@@ -40,7 +42,7 @@ impl RRule {
};
let mut iter_res = RRuleIterRes::new(QueryMethodTypes::Before, iter_args);
- let recurrences = iter(&mut iter_res, &mut self.options);
+ let recurrences = iter(&mut iter_res, &mut self.options);
if recurrences.is_empty() {
None
} else {
@@ -59,7 +61,7 @@ impl RRule {
dt: Some(dt),
};
let mut iter_res = RRuleIterRes::new(QueryMethodTypes::After, iter_args);
- let recurrences = iter(&mut iter_res, &mut self.options);
+ let recurrences = iter(&mut iter_res, &mut self.options);
if recurrences.is_empty() {
None
} else {
@@ -88,3 +90,11 @@ impl RRule {
iter(&mut iter_res, &mut self.options)
}
}
+
+impl FromStr for RRule {
+ type Err = RRuleParseError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ build_rrule(s)
+ }
+}
diff --git a/src/rruleset.rs b/src/rruleset.rs
index 4bea086..e8ddf11 100644
--- a/src/rruleset.rs
+++ b/src/rruleset.rs
@@ -1,8 +1,11 @@
use crate::datetime::DTime;
+use crate::options::RRuleParseError;
use crate::rrule::RRule;
use crate::rruleset_iter::RRuleSetIter;
+use crate::rrulestr::build_rruleset;
use chrono::prelude::*;
use chrono_tz::{Tz, UTC};
+use std::str::FromStr;
#[derive(Debug)]
pub struct RRuleSet {
@@ -87,6 +90,14 @@ impl RRuleSet {
}
}
+impl FromStr for RRuleSet {
+ type Err = RRuleParseError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ build_rruleset(s)
+ }
+}
+
#[cfg(test)]
mod test_iter_set {
use super::*;
diff --git a/src/rrulestr.rs b/src/rrulestr.rs
index 62a2e29..b798275 100644
--- a/src/rrulestr.rs
+++ b/src/rrulestr.rs
@@ -22,7 +22,6 @@ static EXDATE_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)EXDATE(?:;TZID=([^
static DATETIME_RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r"(?m)(VALUE=DATE(-TIME)?)|(TZID=)").unwrap());
-
fn parse_datestring_bit<T: FromStr>(
bits: &regex::Captures,
i: usize,
@@ -106,7 +105,11 @@ fn stringval_to_int<T: FromStr>(val: &str, err_msg: String) -> Result<T, RRulePa
}
}
-fn stringval_to_intvec<T: FromStr + Ord + PartialEq + Copy, F: Fn(T) -> bool>(val: &str, accept: F, err_msg: String) -> Result<Vec<T>, RRuleParseError> {
+fn stringval_to_intvec<T: FromStr + Ord + PartialEq + Copy, F: Fn(T) -> bool>(
+ val: &str,
+ accept: F,
+ err_msg: String,
+) -> Result<Vec<T>, RRuleParseError> {
let mut parsed_vals = vec![];
for val in val.split(",") {
let val = stringval_to_int(val, err_msg.clone())?;
@@ -168,35 +171,58 @@ fn parse_rrule(line: &str) -> Result<Options, RRuleParseError> {
options.interval = Some(interval);
}
"BYSETPOS" => {
- let bysetpos = stringval_to_intvec(value, |_pos| true, format!("Invalid bysetpos value"))?;
+ let bysetpos =
+ stringval_to_intvec(value, |_pos| true, format!("Invalid bysetpos value"))?;
options.bysetpos = Some(bysetpos);
}
"BYMONTH" => {
- let bymonth = stringval_to_intvec(value, |month| month <= 11, format!("Invalid bymonth value"))?;
+ let bymonth = stringval_to_intvec(
+ value,
+ |month| month <= 11,
+ format!("Invalid bymonth value"),
+ )?;
options.bymonth = Some(bymonth);
}
"BYMONTHDAY" => {
- let bymonthday = stringval_to_intvec(value, |monthday| monthday >= 0 && monthday <= 31, format!("Invalid bymonthday value"))?;
+ let bymonthday = stringval_to_intvec(
+ value,
+ |monthday| monthday >= 0 && monthday <= 31,
+ format!("Invalid bymonthday value"),
+ )?;
options.bymonthday = Some(bymonthday);
}
"BYYEARDAY" => {
- let byyearday = stringval_to_intvec(value, |yearday| yearday >= -366 && yearday <= 366, format!("Invalid byyearday value"))?;
+ let byyearday = stringval_to_intvec(
+ value,
+ |yearday| yearday >= -366 && yearday <= 366,
+ format!("Invalid byyearday value"),
+ )?;
options.byyearday = Some(byyearday);
}
"BYWEEKNO" => {
- let byweekno = stringval_to_intvec(value, |weekno| weekno >= 0 && weekno <= 53, format!("Invalid byweekno value"))?;
+ let byweekno = stringval_to_intvec(
+ value,
+ |weekno| weekno >= 0 && weekno <= 53,
+ format!("Invalid byweekno value"),
+ )?;
options.byweekno = Some(byweekno);
}
"BYHOUR" => {
- let byhour = stringval_to_intvec(value, |hour| hour < 24, format!("Invalid byhour value"))?;
+ let byhour =
+ stringval_to_intvec(value, |hour| hour < 24, format!("Invalid byhour value"))?;
options.byhour = Some(byhour);
}
"BYMINUTE" => {
- let byminute = stringval_to_intvec(value, |minute| minute < 60, format!("Invalid byminute value"))?;
+ let byminute = stringval_to_intvec(
+ value,
+ |minute| minute < 60,
+ format!("Invalid byminute value"),
+ )?;
options.byminute = Some(byminute);
}
"BYSECOND" => {
- let bysecond = stringval_to_intvec(value, |sec| sec < 60, format!("Invalid bysecond value"))?;
+ let bysecond =
+ stringval_to_intvec(value, |sec| sec < 60, format!("Invalid bysecond value"))?;
options.bysecond = Some(bysecond);
}
"BYWEEKDAY" | "BYDAY" => {
@@ -275,7 +301,12 @@ fn parse_line(rfc_string: &str) -> Result<Option<Options>, RRuleParseError> {
let header = header.unwrap();
let key = match header.get(1) {
Some(k) => k.as_str(),
- None => return Err(RRuleParseError(format!("Invalid rfc_string: {}", rfc_string)))
+ None => {
+ return Err(RRuleParseError(format!(
+ "Invalid rfc_string: {}",
+ rfc_string
+ )))
+ }
};
match key {
@@ -345,7 +376,6 @@ fn parse_string(rfc_string: &str) -> Result<Options, RRuleParseError> {
Ok(Options::concat(&options[0], &options[1]))
}
-
#[derive(Debug)]
struct ParsedInput {
rrule_vals: Vec<Options>,
@@ -391,7 +421,7 @@ fn parse_input(s: &str) -> Result<ParsedInput, RRuleParseError> {
"RDATE" => {
let matches = match RDATE_RE.captures(line) {
Some(m) => m,
- None => return Err(RRuleParseError(format!("Invalid RDATE specified")))
+ None => return Err(RRuleParseError(format!("Invalid RDATE specified"))),
};
let mut tz = UTC;
if let Some(tzid) = matches.get(1) {
@@ -407,7 +437,7 @@ fn parse_input(s: &str) -> Result<ParsedInput, RRuleParseError> {
"EXDATE" => {
let matches = match EXDATE_RE.captures(line) {
Some(m) => m,
- None => return Err(RRuleParseError(format!("Invalid EXDATE specified")))
+ 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)
@@ -444,10 +474,12 @@ fn validate_date_param(params: Vec<&str>) -> Result<(), RRuleParseError> {
for param in &params {
match DATETIME_RE.captures(param) {
Some(caps) if caps.len() > 0 => (),
- _ => return Err(RRuleParseError(format!(
- "Unsupported RDATE/EXDATE parm: {}",
- param
- )))
+ _ => {
+ return Err(RRuleParseError(format!(
+ "Unsupported RDATE/EXDATE parm: {}",
+ param
+ )))
+ }
}
}
Ok(())
@@ -460,7 +492,7 @@ fn parse_rdate(
) -> Result<Vec<DTime>, RRuleParseError> {
let params: Vec<&str> = params.iter().map(|p| p.as_str()).collect();
validate_date_param(params)?;
-
+
let mut rdatevals = vec![];
for datestr in rdateval.split(",") {
rdatevals.push(datestring_to_date(datestr, tz)?);
@@ -612,8 +644,8 @@ mod test {
assert!(res.is_err());
assert_eq!(res.err().unwrap().0, "Invalid byhour value");
-
- let res = build_rruleset("DTSTART:20120201T120000Z\nRRULE:FREQ=DAILY;COUNT=5;BYHOUR=5,6,25");
+ let res =
+ build_rruleset("DTSTART:20120201T120000Z\nRRULE:FREQ=DAILY;COUNT=5;BYHOUR=5,6,25");
assert!(res.is_err());
assert_eq!(res.err().unwrap().0, "Invalid byhour value");
}
@@ -624,8 +656,8 @@ mod test {
assert!(res.is_err());
assert_eq!(res.err().unwrap().0, "Invalid byminute value");
-
- let res = build_rruleset("DTSTART:20120201T120000Z\nRRULE:FREQ=DAILY;COUNT=5;BYMINUTE=4,5,64");
+ let res =
+ build_rruleset("DTSTART:20120201T120000Z\nRRULE:FREQ=DAILY;COUNT=5;BYMINUTE=4,5,64");
assert!(res.is_err());
assert_eq!(res.err().unwrap().0, "Invalid byminute value");
}
diff --git a/src/iter/utils.rs b/src/utils.rs
index a134d9a..ef09d54 100644
--- a/src/iter/utils.rs
+++ b/src/utils.rs
@@ -6,3 +6,10 @@ pub fn pymod(a: isize, b: isize) -> isize {
}
r
}
+
+pub fn is_some_and_not_empty<T>(v: &Option<Vec<T>>) -> bool {
+ match v {
+ Some(v) => !v.is_empty(),
+ None => false,
+ }
+}