summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFredrik Meringdal <fmeringdal@hotmail.com>2020-12-04 16:13:58 +0100
committerFredrik Meringdal <fmeringdal@hotmail.com>2020-12-04 16:13:58 +0100
commit3931287312234b6a5f5da225c2a5b30f8d29e3c6 (patch)
tree7cd4549e9a23a468e5e4db2d013528445cfa2e07 /src
parentc437b568f9dad67b212a9b17d2ac32dd40263571 (diff)
downloadrust_rrule-3931287312234b6a5f5da225c2a5b30f8d29e3c6.zip
support for nweekday
Diffstat (limited to 'src')
-rw-r--r--src/iter/masks.rs1
-rw-r--r--src/options.rs49
-rw-r--r--src/parse_options.rs22
-rw-r--r--src/rrulestr.rs92
4 files changed, 120 insertions, 44 deletions
diff --git a/src/iter/masks.rs b/src/iter/masks.rs
index 726d270..d789d00 100644
--- a/src/iter/masks.rs
+++ b/src/iter/masks.rs
@@ -4,7 +4,6 @@
// Every mask is 7 days longer to handle cross-year weekly periods.
-
lazy_static! {
pub static ref MASKS: Masks = Masks::default();
}
diff --git a/src/options.rs b/src/options.rs
index 0863294..ac35315 100644
--- a/src/options.rs
+++ b/src/options.rs
@@ -1,8 +1,8 @@
use crate::datetime::{get_weekday_val, DTime};
use crate::parse_options::parse_options;
use chrono::prelude::*;
-use serde::{Serialize, Deserialize};
use chrono_tz::{Tz, UTC};
+use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fmt::{Display, Formatter};
@@ -17,6 +17,40 @@ pub enum Frequenzy {
Secondly = 6,
}
+#[derive(Copy, Clone, Debug)]
+pub struct NWeekday {
+ pub weekday: usize,
+ pub n: isize,
+}
+
+impl NWeekday {
+ pub fn new(weekday: usize, n: isize) -> Self {
+ // if (n === 0) throw new Error("Can't create weekday with n == 0")
+ Self { weekday, n }
+ }
+
+ pub fn from(weekday: &Weekday, n: isize) -> Self {
+ // if (n === 0) throw new Error("Can't create weekday with n == 0")
+ Self {
+ weekday: get_weekday_val(weekday),
+ n,
+ }
+ }
+
+ pub fn nth(&self, n: isize) -> Self {
+ if self.n == n {
+ return self.clone();
+ }
+ Self::new(self.weekday, n)
+ }
+}
+
+impl PartialEq for NWeekday {
+ fn eq(&self, other: &Self) -> bool {
+ self.n == other.n && self.weekday == other.weekday
+ }
+}
+
#[derive(Debug, Clone)]
pub struct ParsedOptions {
pub freq: Frequenzy,
@@ -33,10 +67,10 @@ pub struct ParsedOptions {
pub byyearday: Vec<isize>,
pub byweekno: Vec<isize>,
pub byweekday: Vec<usize>,
+ pub bynweekday: Vec<Vec<isize>>,
pub byhour: Vec<usize>,
pub byminute: Vec<usize>,
pub bysecond: Vec<usize>,
- pub bynweekday: Vec<Vec<isize>>,
pub byeaster: Option<isize>,
}
@@ -54,7 +88,7 @@ pub struct Options {
pub bymonthday: Option<Vec<isize>>,
pub byyearday: Option<Vec<isize>>,
pub byweekno: Option<Vec<isize>>,
- pub byweekday: Option<Vec<usize>>,
+ pub byweekday: Option<Vec<NWeekday>>,
pub byhour: Option<Vec<usize>>,
pub byminute: Option<Vec<usize>>,
pub bysecond: Option<Vec<usize>>,
@@ -198,7 +232,11 @@ impl Options {
/// When given, these variables will define the weekdays where the recurrence
/// will be applied.
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))
+ .map(|w| NWeekday::new(w, 1))
+ .collect();
self.byweekday = Some(byweekday);
self
}
@@ -250,7 +288,6 @@ impl Display for RRuleParseError {
impl Error for RRuleParseError {}
-
pub fn weekday_from_str(val: &str) -> Result<Weekday, String> {
match val {
"MO" => Ok(Weekday::Mon),
@@ -262,4 +299,4 @@ pub fn weekday_from_str(val: &str) -> Result<Weekday, String> {
"SU" => Ok(Weekday::Sun),
_ => Err(format!("Invalid weekday: {}", val)),
}
-} \ No newline at end of file
+}
diff --git a/src/parse_options.rs b/src/parse_options.rs
index 1f86311..028686d 100644
--- a/src/parse_options.rs
+++ b/src/parse_options.rs
@@ -1,4 +1,4 @@
-use crate::options::{Frequenzy, Options, ParsedOptions, RRuleParseError};
+use crate::options::{Frequenzy, NWeekday, Options, ParsedOptions, RRuleParseError};
use crate::utils::is_some_and_not_empty;
use chrono::prelude::*;
use chrono_tz::{Tz, UTC};
@@ -69,7 +69,8 @@ pub fn parse_options(options: &Options) -> Result<ParsedOptions, RRuleParseError
partial_options.bymonthday = Some(vec![dtstart.day() as isize]);
}
Frequenzy::Weekly => {
- partial_options.byweekday = Some(vec![dtstart.weekday() as usize]);
+ partial_options.byweekday =
+ Some(vec![NWeekday::new(dtstart.weekday() as usize, 1)]);
}
_ => (),
};
@@ -92,9 +93,18 @@ pub fn parse_options(options: &Options) -> Result<ParsedOptions, RRuleParseError
}
}
+ let mut byweekday = vec![];
+ let mut bynweekday: Vec<Vec<isize>> = vec![];
// byweekday / bynweekday // ! more to do here
- if partial_options.byweekday.is_some() {
- // partial_options.bynweekday = None;
+
+ if let Some(opts_byweekday) = partial_options.byweekday {
+ for wday in opts_byweekday {
+ if wday.n == 1 {
+ byweekday.push(wday.weekday);
+ } else {
+ bynweekday.push(vec![wday.weekday as isize, wday.n]);
+ }
+ }
}
// byhour
@@ -126,11 +136,11 @@ pub fn parse_options(options: &Options) -> Result<ParsedOptions, RRuleParseError
bynmonthday,
byyearday: partial_options.byyearday.unwrap_or(vec![]),
byweekno: partial_options.byweekno.unwrap_or(vec![]),
- byweekday: partial_options.byweekday.unwrap_or(vec![]),
+ byweekday,
+ bynweekday,
byhour: partial_options.byhour.unwrap_or(vec![]),
byminute: partial_options.byminute.unwrap_or(vec![]),
bysecond: partial_options.bysecond.unwrap_or(vec![]),
- bynweekday,
byeaster: partial_options.byeaster,
})
}
diff --git a/src/rrulestr.rs b/src/rrulestr.rs
index 1d263ed..756a5ae 100644
--- a/src/rrulestr.rs
+++ b/src/rrulestr.rs
@@ -1,9 +1,9 @@
+use crate::datetime::get_weekday_val;
use crate::datetime::DTime;
use crate::options::*;
use crate::parse_options::parse_options;
use crate::rrule::RRule;
use crate::rruleset::RRuleSet;
-use crate::datetime::get_weekday_val;
use chrono::prelude::*;
use chrono_tz::{Tz, UTC};
use regex::Regex;
@@ -21,6 +21,7 @@ lazy_static! {
static ref RDATE_RE: Regex = Regex::new(r"(?m)RDATE(?:;TZID=([^:=]+))?").unwrap();
static ref EXDATE_RE: Regex = Regex::new(r"(?m)EXDATE(?:;TZID=([^:=]+))?").unwrap();
static ref DATETIME_RE: Regex = Regex::new(r"(?m)(VALUE=DATE(-TIME)?)|(TZID=)").unwrap();
+ static ref NWEEKDAY_REGEX: Regex = Regex::new(r"(?m)^([+-]?\d{1,2})([A-Z]{2})$").unwrap();
}
fn parse_datestring_bit<T: FromStr>(
@@ -38,7 +39,6 @@ fn parse_datestring_bit<T: FromStr>(
}
fn datestring_to_date(dt: &str, tz: &Tz) -> Result<DTime, RRuleParseError> {
-
let bits = DATESTR_RE.captures(dt);
if bits.is_none() {
return Err(RRuleParseError(format!("Invalid datetime: {}", dt)));
@@ -71,7 +71,7 @@ fn parse_dtstart(s: &str) -> Result<Options, RRuleParseError> {
} else {
UTC
};
-
+
let dtstart_str = match caps.get(2) {
Some(dt) => dt.as_str(),
None => return Err(RRuleParseError(format!("Invalid datetime: {}", s))),
@@ -154,16 +154,14 @@ fn parse_rrule(line: &str) -> Result<Options, RRuleParseError> {
Some(freq) => options.freq = Some(freq),
None => return Err(RRuleParseError(format!("Invalid frequenzy: {}", value))),
},
- "WKST" => {
- match weekday_from_str(value) {
- Ok(weekday) => {
- options.wkst = Some(get_weekday_val(&weekday));
- }
- Err(e) => {
- return Err(RRuleParseError(e));
- }
+ "WKST" => match weekday_from_str(value) {
+ Ok(weekday) => {
+ options.wkst = Some(get_weekday_val(&weekday));
}
- }
+ Err(e) => {
+ return Err(RRuleParseError(e));
+ }
+ },
"COUNT" => {
let count = stringval_to_int(value, format!("Invalid count"))?;
options.count = Some(count);
@@ -266,25 +264,23 @@ 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);
- }
+fn parse_weekday(val: &str) -> Result<Vec<NWeekday>, RRuleParseError> {
+ let mut wdays = vec![];
+ for day in val.split(",") {
+ if day.len() == 2 {
+ // MO, TU, ...
+ let wday = str_to_weekday(day)?;
+ wdays.push(NWeekday::new(wday, 1));
+ continue;
+ }
- // ! 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()
+ let parts = NWEEKDAY_REGEX.captures(day).unwrap();
+ let n = parts.get(1).unwrap().as_str().parse().unwrap();
+ let wdaypart = parts.get(2).unwrap();
+ let wday = str_to_weekday(wdaypart.as_str())?;
+ wdays.push(NWeekday::new(wday, n));
+ }
+ Ok(wdays)
}
fn parse_line(rfc_string: &str) -> Result<Option<Options>, RRuleParseError> {
@@ -504,7 +500,8 @@ fn parse_rdate(
}
fn preprocess_rrule_string(s: &str) -> String {
- s.replace("DTSTART;VALUE=DATETIME", "DTSTART").replace("DTSTART;VALUE=DATE", "DTSTART")
+ s.replace("DTSTART;VALUE=DATETIME", "DTSTART")
+ .replace("DTSTART;VALUE=DATE", "DTSTART")
}
pub fn build_rruleset(s: &str) -> Result<RRuleSet, RRuleParseError> {
@@ -677,6 +674,39 @@ mod test {
}
#[test]
+ fn parses_byday_with_n() {
+ let cases = vec![
+ "DTSTART:20200901T174500\nRRULE:FREQ=MONTHLY;UNTIL=20210504T154500Z;INTERVAL=1;BYDAY=1TU",
+ "DTSTART;VALUE=DATE:20200902\nRRULE:FREQ=MONTHLY;UNTIL=20210504T220000Z;INTERVAL=1;BYDAY=1WE",
+ "DTSTART:20200902T100000\nRRULE:FREQ=MONTHLY;UNTIL=20210505T080000Z;INTERVAL=1;BYDAY=1WE",
+ "DTSTART;VALUE=DATE:20200812\nRRULE:FREQ=MONTHLY;UNTIL=20210524T090000Z;INTERVAL=1;BYDAY=4MO"
+ ];
+ for case in &cases {
+ let res = build_rruleset(case);
+ assert!(res.is_ok());
+ }
+ let cases = vec![
+ "RRULE:FREQ=MONTHLY;UNTIL=20210504T154500Z;INTERVAL=1;BYDAY=1TU",
+ "RRULE:FREQ=MONTHLY;UNTIL=20210504T220000Z;INTERVAL=1;BYDAY=1WE",
+ "RRULE:FREQ=MONTHLY;UNTIL=20210505T080000Z;INTERVAL=1;BYDAY=-1WE",
+ "RRULE:FREQ=MONTHLY;UNTIL=20210505T080000Z;INTERVAL=1;BYDAY=12SU",
+ "RRULE:FREQ=MONTHLY;UNTIL=20210524T090000Z;INTERVAL=1;BYDAY=4MO",
+ ];
+ let opts = vec![
+ vec![NWeekday::new(1,1)],
+ vec![NWeekday::new(2,1)],
+ vec![NWeekday::new(2,-1)],
+ vec![NWeekday::new(6, 12)],
+ vec![NWeekday::new(0,4)]
+ ];
+ for i in 0..cases.len() {
+ let opts_or_err = parse_string(cases[i]);
+ assert!(opts_or_err.is_ok());
+ assert_eq!(opts_or_err.unwrap().byweekday.unwrap(), opts[i]);
+ }
+ }
+
+ #[test]
#[ignore = "Only for benching"]
fn bench() {
let now = std::time::SystemTime::now();