diff options
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | README.md | 8 | ||||
-rw-r--r-- | src/iter_set.rs | 2 | ||||
-rw-r--r-- | src/lib.rs | 62 | ||||
-rw-r--r-- | src/rrule.rs | 39 | ||||
-rw-r--r-- | src/rruleset.rs | 4 | ||||
-rw-r--r-- | src/rrulestr.rs | 26 |
7 files changed, 123 insertions, 21 deletions
@@ -2,7 +2,8 @@ name = "rrule" description = "A pure Rust (partial) implementation of recurrence rules as defined in the iCalendar RFC." license = "MIT" -version = "0.2.3" +version = "0.2.4" +repository = "https://github.com/fmeringdal/rust_rrule" authors = ["Fredrik Meringdal"] edition = "2018" @@ -19,14 +19,16 @@ let mut rrule_set = build_rule("RRULE:UNTIL=19990404T110000Z;DTSTART;TZID=Americ let occurences = rrule_set.all(); ``` +# Documentation and more examples + +[Documentation and more examples](https://docs.rs/rrule) + # Inspired by - [python-dateutil library](http://labix.org/python-dateutil/) - [rrule.js](https://github.com/jakubroztocil/rrule) -# todos +# Todos - tests for minutes and seconds FREQ -- timezone -- subseconds - cache diff --git a/src/iter_set.rs b/src/iter_set.rs index 3e96231..60eced6 100644 --- a/src/iter_set.rs +++ b/src/iter_set.rs @@ -4,7 +4,7 @@ use crate::options::*; use crate::poslist::*; use chrono::offset::TimeZone; use chrono::prelude::*; -use chrono::{DateTime, Utc}; +use chrono::DateTime; use chrono_tz::*; pub trait IterResult { @@ -1,3 +1,63 @@ +//! A partial implementation of recurrence rules as defined in the iCalendar RFC. +//! +//! This map implementation allows reads and writes to execute entirely in parallel, with no +//! implicit synchronization overhead. Reads never take locks on their critical path, and neither +//! do writes assuming there is a single writer (multi-writer is possible using a `Mutex`), which +//! significantly improves performance under contention. +//! +//! The trade-off exposed by this module is one of eventual consistency: writes are not visible to +//! readers except following explicit synchronization. Specifically, readers only see the +//! operations that preceeded the last call to `WriteHandle::refresh` by a writer. This lets +//! writers decide how stale they are willing to let reads get. They can refresh the map after +//! every write to emulate a regular concurrent `HashMap`, or they can refresh only occasionally to +//! reduce the synchronization overhead at the cost of stale reads. +//! +//! For read-heavy workloads, the scheme used by this module is particularly useful. Writers can +//! afford to refresh after every write, which provides up-to-date reads, and readers remain fast +//! as they do not need to ever take locks. +//! +//! The map is multi-value, meaning that every key maps to a *collection* of values. This +//! introduces some memory cost by adding a layer of indirection through a `Vec` for each value, +//! but enables more advanced use. This choice was made as it would not be possible to emulate such +//! functionality on top of the semantics of this map (think about it -- what would the operational +//! log contain?). +//! +//! To faciliate more advanced use-cases, each of the two maps also carry some customizeable +//! meta-information. The writers may update this at will, and when a refresh happens, the current +//! meta will also be made visible to readers. This could be useful, for example, to indicate what +//! time the refresh happened. +//! +//! # Examples +//! +//! RRule +//! +//! ``` +//! extern crate rrule; +//! extern crate chrono; +//! +//! use chrono::prelude::*; +//! use rrule::build_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"); +//! assert_eq!(rrule.all().len(), 21); +//! ``` +//! +//! +//! RRuleSet +//! +//! ``` +//! extern crate rrule; +//! extern crate chrono; +//! +//! use chrono::prelude::*; +//! 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"); +//! assert_eq!(rrule.all().len(), 6); +//! ``` + extern crate chrono; extern crate chrono_tz; extern crate once_cell; @@ -20,5 +80,5 @@ mod rruleset; pub use crate::rrule::RRule; pub use crate::rruleset::RRuleSet; -pub use crate::rrulestr::build_rule; +pub use crate::rrulestr::{build_rrule, build_rruleset}; pub use crate::options::{Frequenzy, ParsedOptions, PartialOptions};
\ No newline at end of file diff --git a/src/rrule.rs b/src/rrule.rs index 08ebd7d..6904d6c 100644 --- a/src/rrule.rs +++ b/src/rrule.rs @@ -2,7 +2,7 @@ use crate::iter::*; use crate::iter_set::iter_v2; use crate::options::*; use chrono::prelude::*; -use chrono_tz::{Tz, UTC}; +use chrono_tz::Tz; #[derive(Clone, Debug)] pub struct RRule { @@ -29,21 +29,44 @@ impl RRule { res } + pub fn before(&mut self, dt: DateTime<Tz>, inc: bool) -> Vec<DateTime<Tz>> { + let iter_args = IterArgs { + inc, + before: None, + after: None, + dt: Some(dt), + }; + let mut iter_res = RRuleIterRes::new(QueryMethodTypes::Before, iter_args); + + iter_v2(&mut iter_res, &mut self.options) + } + + pub fn after(&mut self, dt: DateTime<Tz>, inc: bool) -> Vec<DateTime<Tz>> { + let iter_args = IterArgs { + inc, + before: None, + after: None, + dt: Some(dt), + }; + let mut iter_res = RRuleIterRes::new(QueryMethodTypes::After, iter_args); + + iter_v2(&mut iter_res, &mut self.options) + } + pub fn between( &mut self, - after: &DateTime<Tz>, - before: &DateTime<Tz>, + after: DateTime<Tz>, + before: DateTime<Tz>, inc: bool, ) -> Vec<DateTime<Tz>> { let iter_args = IterArgs { inc, - before: Some(before.clone()), - after: Some(after.clone()), + before: Some(before), + after: Some(after), dt: None, }; - let mut iter_res = RRuleIterRes::new(QueryMethodTypes::All, iter_args); + let mut iter_res = RRuleIterRes::new(QueryMethodTypes::Between, iter_args); - let res = iter_v2(&mut iter_res, &mut self.options); - res + iter_v2(&mut iter_res, &mut self.options) } } diff --git a/src/rruleset.rs b/src/rruleset.rs index ab8437f..630bc27 100644 --- a/src/rruleset.rs +++ b/src/rruleset.rs @@ -1,6 +1,5 @@ use crate::iter::*; use crate::iter_set::{iter_v2, IterResult}; -use crate::options::*; use crate::rrule::*; use chrono::prelude::*; use chrono_tz::{Tz, UTC}; @@ -93,7 +92,7 @@ impl<'a> RRuleSetIter<'a> { pub fn eval_exdate(&mut self, after: &DateTime<Tz>, before: &DateTime<Tz>) { for rrule in self.rrule_set.exrule.iter_mut() { - for date in rrule.between(after, before, true) { + for date in rrule.between(after.clone(), before.clone(), true) { self.exdate_hash.insert(date.timestamp(), ()); } } @@ -238,6 +237,7 @@ impl RRuleSet { #[cfg(test)] mod test_iter_set { use super::*; + use crate::options::*; fn ymd_hms( year: i32, diff --git a/src/rrulestr.rs b/src/rrulestr.rs index 1f90b4a..1584306 100644 --- a/src/rrulestr.rs +++ b/src/rrulestr.rs @@ -4,7 +4,6 @@ use crate::rrule::RRule; use crate::rruleset::RRuleSet; use chrono::prelude::*; use chrono::DateTime; -use chrono_tz::{Tz, UTC}; use once_cell::sync::Lazy; use regex::Regex; @@ -356,7 +355,7 @@ fn parse_rdate(rdateval: &str, params: Vec<String>) -> Vec<DateTime<Utc>> { } -pub fn build_rule(s: &str) -> RRuleSet { +pub fn build_rruleset(s: &str) -> RRuleSet { let ParsedInput { mut rrule_vals, rdate_vals, @@ -411,21 +410,38 @@ pub fn build_rule(s: &str) -> RRuleSet { rset } +pub fn build_rrule(s: &str) -> RRule { + let ParsedInput { + mut rrule_vals, + tzid, + dtstart, + .. + } = parse_input(s); + + rrule_vals[0].tzid = tzid; + rrule_vals[0].dtstart = dtstart; + + let parsed_opts = parse_options(&rrule_vals[0]); + + RRule::new(parsed_opts) +} + #[cfg(test)] mod test { use super::*; + use chrono_tz::{Tz, UTC}; #[test] fn it_works_1() { - let options = build_rule("DTSTART:19970902T090000Z\nRRULE:FREQ=YEARLY;COUNT=3\n"); + let options = build_rruleset("DTSTART:19970902T090000Z\nRRULE:FREQ=YEARLY;COUNT=3\n"); println!("?????????????=================?????????????"); println!("{:?}", options); } #[test] fn it_works_2() { - let mut options = build_rule("RRULE:UNTIL=19990404T110000Z;DTSTART=19990104T110000Z;FREQ=WEEKLY;BYDAY=TU,WE"); + let mut options = build_rruleset("RRULE:UNTIL=19990404T110000Z;DTSTART=19990104T110000Z;FREQ=WEEKLY;BYDAY=TU,WE"); println!("?????????????=================?????????????"); println!("{:?}", options); println!("?????????????=== ALLL ==============?????????????"); @@ -434,7 +450,7 @@ mod test { #[test] fn it_works_3() { - let mut options = build_rule("RRULE:UNTIL=19990404T110000Z;DTSTART;TZID=America/Denver:19990104T110000Z;FREQ=WEEKLY;BYDAY=TU,WE"); + let mut options = build_rruleset("RRULE:UNTIL=19990404T110000Z;DTSTART;TZID=America/Denver:19990104T110000Z;FREQ=WEEKLY;BYDAY=TU,WE"); println!("?????????????=================?????????????"); println!("{:?}", options); let tzid: Tz = "America/Denver".parse().unwrap(); |