diff options
author | Fredrik Meringdal <fmeringdal@hotmail.com> | 2020-10-23 23:27:15 +0200 |
---|---|---|
committer | Fredrik Meringdal <fmeringdal@hotmail.com> | 2020-10-23 23:27:15 +0200 |
commit | 99d5b3d0fea0c3b40dc84ea18953c4f9cfae1ace (patch) | |
tree | 9eb984aef9d632aa19030b72673c5b959ba31c7e | |
parent | de3e90bbd09eae03538fd781191acbd257901397 (diff) | |
download | rust_rrule-99d5b3d0fea0c3b40dc84ea18953c4f9cfae1ace.zip |
init for rrulestr making partial options
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/iter.rs | 24 | ||||
-rw-r--r-- | src/lib.rs | 2 | ||||
-rw-r--r-- | src/rrule.rs | 12 | ||||
-rw-r--r-- | src/rruleset.rs | 81 | ||||
-rw-r--r-- | src/rrulestr.rs | 524 |
6 files changed, 585 insertions, 59 deletions
@@ -10,3 +10,4 @@ edition = "2018" chrono = "0.4.19" chrono-tz = "0.5.3" once_cell = "1.4.1" +regex = "1.4.1" diff --git a/src/iter.rs b/src/iter.rs index ace4e1c..5a3da4d 100644 --- a/src/iter.rs +++ b/src/iter.rs @@ -6,7 +6,7 @@ use crate::poslist::*; use crate::yearinfo::*; use chrono::prelude::*; use chrono::Duration; -use chrono_tz::{Tz, UTC}; +use chrono_tz::Tz; pub enum QueryMethodTypes { ALL, @@ -17,9 +17,9 @@ pub enum QueryMethodTypes { pub struct IterArgs { pub inc: bool, - pub before: DateTime<Tz>, - pub after: DateTime<Tz>, - pub dt: DateTime<Tz>, + pub before: Option<DateTime<Tz>>, + pub after: Option<DateTime<Tz>>, + pub dt: Option<DateTime<Tz>>, } pub struct RRuleIterRes { @@ -34,15 +34,17 @@ pub struct RRuleIterRes { impl RRuleIterRes { pub fn new(method: QueryMethodTypes, args: IterArgs) -> Self { let (max_date, min_date) = match method { - QueryMethodTypes::BETWEEN if args.inc => (Some(args.before), Some(args.after)), + QueryMethodTypes::BETWEEN if args.inc => { + (Some(args.before.unwrap()), Some(args.after.unwrap())) + } QueryMethodTypes::BETWEEN => ( - Some(args.before - Duration::milliseconds(1)), - Some(args.after + Duration::milliseconds(1)), + Some(args.before.unwrap() - Duration::milliseconds(1)), + Some(args.after.unwrap() + Duration::milliseconds(1)), ), - QueryMethodTypes::BEFORE if args.inc => (Some(args.dt), None), - QueryMethodTypes::BEFORE => (Some(args.dt - Duration::milliseconds(1)), None), - QueryMethodTypes::AFTER if args.inc => (None, Some(args.dt)), - QueryMethodTypes::AFTER => (None, Some(args.dt + Duration::milliseconds(1))), + QueryMethodTypes::BEFORE if args.inc => (Some(args.dt.unwrap()), None), + QueryMethodTypes::BEFORE => (Some(args.dt.unwrap() - Duration::milliseconds(1)), None), + QueryMethodTypes::AFTER if args.inc => (None, Some(args.dt.unwrap())), + QueryMethodTypes::AFTER => (None, Some(args.dt.unwrap() + Duration::milliseconds(1))), _ => (None, None), }; @@ -1,6 +1,7 @@ extern crate chrono; extern crate chrono_tz; extern crate once_cell; +extern crate regex; mod datetime; mod easter; @@ -10,6 +11,7 @@ mod iterinfo; mod masks; mod monthinfo; mod poslist; +mod rrulestr; mod yearinfo; pub mod options; diff --git a/src/rrule.rs b/src/rrule.rs index 706c30a..4b4e6c5 100644 --- a/src/rrule.rs +++ b/src/rrule.rs @@ -25,9 +25,9 @@ impl RRule { pub fn all(&mut self) -> Vec<DateTime<Tz>> { let iter_args = IterArgs { inc: true, - before: UTC.ymd(2020, 1, 1).and_hms(0, 0, 0), - after: UTC.ymd(2020, 1, 1).and_hms(0, 0, 0), - dt: UTC.ymd(2020, 1, 1).and_hms(0, 0, 0), + before: None, + after: None, + dt: None, }; let mut iter_res = RRuleIterRes::new(QueryMethodTypes::ALL, iter_args); @@ -43,9 +43,9 @@ impl RRule { ) -> Vec<DateTime<Tz>> { let iter_args = IterArgs { inc, - before: before.clone(), - after: after.clone(), - dt: UTC.ymd(2020, 1, 1).and_hms(0, 0, 0), + before: Some(before.clone()), + after: Some(after.clone()), + dt: None, }; let mut iter_res = RRuleIterRes::new(QueryMethodTypes::ALL, iter_args); diff --git a/src/rruleset.rs b/src/rruleset.rs index 47c1eb7..29093ae 100644 --- a/src/rruleset.rs +++ b/src/rruleset.rs @@ -6,13 +6,13 @@ use chrono::prelude::*; use chrono_tz::{Tz, UTC}; use std::collections::HashMap; -struct RRuleSet { +#[derive(Debug)] +pub struct RRuleSet { rrule: Vec<RRule>, rdate: Vec<DateTime<Utc>>, exrule: Vec<RRule>, exdate: Vec<DateTime<Utc>>, - dtstart: Option<DateTime<Utc>>, - exdate_hash: HashMap<i64, ()>, + pub dtstart: Option<DateTime<Utc>>, } struct RRuleSetIter<'a> { @@ -25,9 +25,9 @@ impl<'a> RRuleSetIter<'a> { pub fn new(rrule_set: &'a mut RRuleSet) -> Self { let iter_args = IterArgs { inc: true, - before: UTC.ymd(2020, 1, 1).and_hms(0, 0, 0), - after: UTC.ymd(2020, 1, 1).and_hms(0, 0, 0), - dt: UTC.ymd(2020, 1, 1).and_hms(0, 0, 0), + before: None, + after: None, + dt: None, }; let iter_res = RRuleIterRes::new(QueryMethodTypes::ALL, iter_args); @@ -41,9 +41,9 @@ impl<'a> RRuleSetIter<'a> { pub fn from_before(rrule_set: &'a mut RRuleSet, dt: DateTime<Tz>, inc: bool) -> Self { let iter_args = IterArgs { inc, - before: UTC.ymd(2020, 1, 1).and_hms(0, 0, 0), - after: UTC.ymd(2020, 1, 1).and_hms(0, 0, 0), - dt, + before: None, + after: None, + dt: Some(dt), }; let iter_res = RRuleIterRes::new(QueryMethodTypes::BEFORE, iter_args); @@ -57,9 +57,9 @@ impl<'a> RRuleSetIter<'a> { pub fn from_after(rrule_set: &'a mut RRuleSet, dt: DateTime<Tz>, inc: bool) -> Self { let iter_args = IterArgs { inc, - before: UTC.ymd(2020, 1, 1).and_hms(0, 0, 0), - after: UTC.ymd(2020, 1, 1).and_hms(0, 0, 0), - dt, + before: None, + after: None, + dt: Some(dt), }; let iter_res = RRuleIterRes::new(QueryMethodTypes::AFTER, iter_args); @@ -70,13 +70,17 @@ impl<'a> RRuleSetIter<'a> { } } - - pub fn from_between(rrule_set: &'a mut RRuleSet, after: DateTime<Tz>, before: DateTime<Tz>, inc: bool) -> Self { + pub fn from_between( + rrule_set: &'a mut RRuleSet, + after: DateTime<Tz>, + before: DateTime<Tz>, + inc: bool, + ) -> Self { let iter_args = IterArgs { inc, - before, - after, - dt: UTC.ymd(2020, 1, 1).and_hms(0, 0, 0), + before: Some(before), + after: Some(after), + dt: None, }; let iter_res = RRuleIterRes::new(QueryMethodTypes::BETWEEN, iter_args); @@ -131,8 +135,8 @@ impl<'a> RRuleSetIter<'a> { match &self.iter_res.method { QueryMethodTypes::BETWEEN => { self.eval_exdate( - &self.iter_res.args.after.clone(), - &self.iter_res.args.before.clone(), + &self.iter_res.args.after.unwrap().clone(), + &self.iter_res.args.before.unwrap().clone(), ); } _ => (), @@ -176,7 +180,6 @@ impl RRuleSet { exrule: vec![], exdate: vec![], dtstart: None, - exdate_hash: HashMap::new(), } } @@ -198,7 +201,6 @@ impl RRuleSet { pub fn all(&mut self) -> Vec<DateTime<Tz>> { let mut iter = RRuleSetIter::new(self); - // self.iter(&mut iter_res, None) iter.iter(None) } @@ -207,7 +209,6 @@ impl RRuleSet { /// With inc == true, if dt itself is an occurrence, it will be returned. pub fn before(&mut self, date: DateTime<Tz>, inc: bool) -> Vec<DateTime<Tz>> { let mut iter = RRuleSetIter::from_before(self, date, inc); - // self.iter(&mut iter_res, None) iter.iter(None) } @@ -216,7 +217,6 @@ impl RRuleSet { /// With inc == true, if dt itself is an occurrence, it will be returned. pub fn after(&mut self, date: DateTime<Tz>, inc: bool) -> Vec<DateTime<Tz>> { let mut iter = RRuleSetIter::from_after(self, date, inc); - // self.iter(&mut iter_res, None) iter.iter(None) } @@ -224,9 +224,13 @@ impl RRuleSet { /// The inc keyword defines what happens if after and/or before are /// themselves occurrences. With inc == True, they will be included in the /// list, if they are found in the recurrence set. - pub fn between(&mut self, after: DateTime<Tz>, before: DateTime<Tz>, inc: bool) -> Vec<DateTime<Tz>> { + pub fn between( + &mut self, + after: DateTime<Tz>, + before: DateTime<Tz>, + inc: bool, + ) -> Vec<DateTime<Tz>> { let mut iter = RRuleSetIter::from_between(self, after, before, inc); - // self.iter(&mut iter_res, None) iter.iter(None) } @@ -234,7 +238,6 @@ impl RRuleSet { let mut result = vec![]; if !self.rrule.is_empty() && self.dtstart.is_some() { - //result = result.concat(optionsToString({ dtstart: this._dtstart })) result.push(String::from("yeah")); } @@ -288,7 +291,7 @@ mod test_iter_set { month: u32, day: u32, hour: u32, - minute: u32, + minute: u32, second: u32, ) -> DateTime<Tz> { UTC.ymd(year, month, day).and_hms(hour, minute, second) @@ -508,7 +511,6 @@ mod test_iter_set { let rrule = RRule::new(options); set.rrule(rrule); - let options = ParsedOptions { freq: Frequenzy::YEARLY, count: Some(10), @@ -543,7 +545,6 @@ mod test_iter_set { ); } - #[test] fn before() { let mut set = RRuleSet::new(); @@ -572,7 +573,6 @@ mod test_iter_set { let rrule = RRule::new(options); set.rrule(rrule); - let options = ParsedOptions { freq: Frequenzy::YEARLY, count: Some(10), @@ -598,10 +598,8 @@ mod test_iter_set { set.exrule(rrule); test_recurring( - set.before(ymd_hms_2(2015, 9, 2, 9, 0,0), false), - vec![ - ymd_hms_2(2014, 9, 2, 9, 0, 0), - ], + set.before(ymd_hms_2(2015, 9, 2, 9, 0, 0), false), + vec![ymd_hms_2(2014, 9, 2, 9, 0, 0)], ); } @@ -633,7 +631,6 @@ mod test_iter_set { let rrule = RRule::new(options); set.rrule(rrule); - let options = ParsedOptions { freq: Frequenzy::YEARLY, count: Some(10), @@ -659,10 +656,8 @@ mod test_iter_set { set.exrule(rrule); test_recurring( - set.after(ymd_hms_2(2000, 9, 2, 9, 0,0), false), - vec![ - ymd_hms_2(2007, 9, 2, 9, 0, 0), - ], + set.after(ymd_hms_2(2000, 9, 2, 9, 0, 0), false), + vec![ymd_hms_2(2007, 9, 2, 9, 0, 0)], ); } @@ -694,7 +689,6 @@ mod test_iter_set { let rrule = RRule::new(options); set.rrule(rrule); - let options = ParsedOptions { freq: Frequenzy::YEARLY, count: Some(10), @@ -720,7 +714,11 @@ mod test_iter_set { set.exrule(rrule); test_recurring( - set.between(ymd_hms_2(2000, 9, 2, 9, 0,0),ymd_hms_2(2010, 9, 2, 9, 0,0), false), + set.between( + ymd_hms_2(2000, 9, 2, 9, 0, 0), + ymd_hms_2(2010, 9, 2, 9, 0, 0), + false, + ), vec![ ymd_hms_2(2007, 9, 2, 9, 0, 0), ymd_hms_2(2008, 9, 2, 9, 0, 0), @@ -729,7 +727,6 @@ mod test_iter_set { ); } - #[test] fn before_70s() { let mut set = RRuleSet::new(); diff --git a/src/rrulestr.rs b/src/rrulestr.rs new file mode 100644 index 0000000..607513f --- /dev/null +++ b/src/rrulestr.rs @@ -0,0 +1,524 @@ +use crate::options::*; +use crate::rrule::RRule; +use crate::rruleset::RRuleSet; +use chrono::prelude::*; +use chrono::DateTime; +use chrono_tz::Tz; +use once_cell::sync::Lazy; +use regex::Regex; + +static DATESTR_RE: Lazy<Regex> = + Lazy::new(|| Regex::new(r"(?m)^(\d{4})(\d{2})(\d{2})(T(\d{2})(\d{2})(\d{2})Z?)?$").unwrap()); +static DTSTART_RE: Lazy<Regex> = + Lazy::new(|| Regex::new(r"(?m)DTSTART(?:;TZID=([^:=]+?))?(?::|=)([^;\s]+)").unwrap()); + +static RRUle_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^(?:RRULE|EXRULE):").unwrap()); + +#[derive(Debug, Clone)] +pub struct PartialOptions { + pub freq: Option<Frequenzy>, + pub interval: Option<usize>, + pub count: Option<u32>, + pub until: Option<DateTime<Utc>>, + pub tzid: Option<String>, + pub dtstart: Option<DateTime<Utc>>, + pub wkst: Option<usize>, + pub bysetpos: Option<Vec<isize>>, + pub bymonth: Option<Vec<usize>>, + pub bymonthday: Option<Vec<isize>>, + pub bynmonthday: Option<Vec<isize>>, + pub byyearday: Option<Vec<isize>>, + pub byweekno: Option<Vec<isize>>, + pub byweekday: Option<Vec<usize>>, + pub byhour: Option<Vec<usize>>, + pub byminute: Option<Vec<usize>>, + pub bysecond: Option<Vec<usize>>, + pub bynweekday: Option<Vec<Vec<isize>>>, + pub byeaster: Option<isize>, +} + +impl PartialOptions { + pub fn new() -> Self { + Self { + freq: None, + interval: None, + count: None, + until: None, + tzid: None, + dtstart: None, + wkst: None, + bysetpos: None, + bymonth: None, + bymonthday: None, + bynmonthday: None, + byyearday: None, + byweekno: None, + byweekday: None, + byhour: None, + byminute: None, + bysecond: None, + bynweekday: None, + byeaster: None, + } + } + + 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 + } + + pub fn concat(opt1: &Self, opt2: &Self) -> Self { + Self { + freq: Self::is_some_or_none(&opt1.freq, &opt2.freq).clone(), + interval: Self::is_some_or_none(&opt1.interval, &opt2.interval).clone(), + count: Self::is_some_or_none(&opt1.count, &opt2.count).clone(), + until: Self::is_some_or_none(&opt1.until, &opt2.until).clone(), + tzid: Self::is_some_or_none(&opt1.tzid, &opt2.tzid).clone(), + dtstart: Self::is_some_or_none(&opt1.dtstart, &opt2.dtstart).clone(), + wkst: Self::is_some_or_none(&opt1.wkst, &opt2.wkst).clone(), + bysetpos: Self::is_some_or_none(&opt1.bysetpos, &opt2.bysetpos).clone(), + bymonth: Self::is_some_or_none(&opt1.bymonth, &opt2.bymonth).clone(), + bymonthday: Self::is_some_or_none(&opt1.bymonthday, &opt2.bymonthday).clone(), + bynmonthday: Self::is_some_or_none(&opt1.bynmonthday, &opt2.bynmonthday).clone(), + byyearday: Self::is_some_or_none(&opt1.byyearday, &opt2.byyearday).clone(), + byweekno: Self::is_some_or_none(&opt1.byweekno, &opt2.byweekno).clone(), + byweekday: Self::is_some_or_none(&opt1.byweekday, &opt2.byweekday).clone(), + byhour: Self::is_some_or_none(&opt1.byhour, &opt2.byhour).clone(), + byminute: Self::is_some_or_none(&opt1.byminute, &opt2.byminute).clone(), + bysecond: Self::is_some_or_none(&opt1.bysecond, &opt2.bysecond).clone(), + bynweekday: Self::is_some_or_none(&opt1.bynweekday, &opt2.bynweekday).clone(), + byeaster: Self::is_some_or_none(&opt1.byeaster, &opt2.byeaster).clone(), + } + } +} + +fn datestring_to_date(dt: &str) -> DateTime<Utc> { + let bits = DATESTR_RE.captures(dt).unwrap(); + return Utc + .ymd( + bits.get(1).unwrap().as_str().parse::<i32>().unwrap(), + bits.get(2).unwrap().as_str().parse::<u32>().unwrap(), + bits.get(3).unwrap().as_str().parse::<u32>().unwrap(), + ) + .and_hms( + bits.get(5).unwrap().as_str().parse::<u32>().unwrap(), + bits.get(6).unwrap().as_str().parse::<u32>().unwrap(), + bits.get(7).unwrap().as_str().parse::<u32>().unwrap(), + ); +} + +fn parse_dtstart(s: &str) -> Option<PartialOptions> { + let caps = DTSTART_RE.captures(s); + println!("captures -----------------------"); + println!("{:?}", caps); + + match caps { + Some(caps) => { + let mut options = PartialOptions::new(); + options.dtstart = Some(datestring_to_date(caps.get(2).unwrap().as_str())); + options.tzid = if let Some(tzid) = caps.get(1) { + Some(String::from(tzid.as_str())) + } else { + Some(String::from("UTC")) + }; + Some(options) + } + None => None, + } +} + +fn from_str_to_freq(s: &str) -> Option<Frequenzy> { + match s.to_uppercase().as_str() { + "YEARLY" => Some(Frequenzy::YEARLY), + "MONTHLY" => Some(Frequenzy::MONTHLY), + "WEEKLY" => Some(Frequenzy::WEEKLY), + "DAILY" => Some(Frequenzy::DAILY), + "HOURLY" => Some(Frequenzy::HOURLY), + "MINUTELY" => Some(Frequenzy::MINUTELY), + "SECONDLY" => Some(Frequenzy::SECONDLY), + _ => None, + } +} + +fn parse_rrule(line: &str) -> PartialOptions { + let stripped_line = if line.starts_with("RRULE:") { + &line[6..] + } else { + line + }; + + + let mut options = parse_dtstart(stripped_line).unwrap_or(PartialOptions::new()); + + let attrs = RRUle_RE.replace(line, ""); + let attrs = attrs.split(";"); + + for attr in attrs { + println!("Attr: {}", attr); + let l: Vec<&str> = attr.split("=").collect(); + + let key = l[0]; + let mut value = ""; + if l.len() > 1 { + value = l[1]; + } + println!("Ket: {}", key); + match key.to_uppercase().as_str() { + "FREQ" => { + options.freq = Some(from_str_to_freq(value).unwrap()); + } + "WKST" => { + options.wkst = Some(value.parse::<usize>().unwrap()); + } + "COUNT" => { + options.count = Some(value.parse::<u32>().unwrap()); + } + "INTERVAL" => { + options.interval = Some(value.parse::<usize>().unwrap()); + } + "BYSETPOS" => { + let bysetpos = value.split(",").map(|d| d.parse::<isize>().unwrap()).collect(); + options.bysetpos = Some(bysetpos); + } + "BYMONTH" => { + let bymonth = value.split(",").map(|d| d.parse::<usize>().unwrap()).collect(); + options.bymonth = Some(bymonth); + } + "BYMONTHDAY" => { + let bymonthday = value.split(",").map(|d| d.parse::<isize>().unwrap()).collect(); + options.bymonthday = Some(bymonthday); + } + "BYYEARDAY" => { + let byyearday = value.split(",").map(|d| d.parse::<isize>().unwrap()).collect(); + options.byyearday = Some(byyearday); + } + "BYWEEKNO" => { + let byweekno = value.split(",").map(|d| d.parse::<isize>().unwrap()).collect(); + options.byweekno = Some(byweekno); + } + "BYHOUR" => { + let byhour = value.split(",").map(|d| d.parse::<usize>().unwrap()).collect(); + options.byhour = Some(byhour); + } + "BYMINUTE" => { + let byminute = value.split(",").map(|d| d.parse::<usize>().unwrap()).collect(); + options.byminute = Some(byminute); + } + "BYSECOND" => { + let bysecond = value.split(",").map(|d| d.parse::<usize>().unwrap()).collect(); + options.bysecond = Some(bysecond); + } + "BYWEEKDAY" | "BYDAY" => { + options.byweekday = Some(parse_weekday(value)); + } + "DTSTART" | "TZID" => { + // for backwards compatibility + let dtstart_opts = parse_dtstart(line).unwrap(); + println!("After parsing tzid"); + println!("{:?}", dtstart_opts); + options.tzid = Some(dtstart_opts.tzid.unwrap()); + options.dtstart = Some(dtstart_opts.dtstart.unwrap()); + } + "UNTIL" => { + options.until = Some(datestring_to_date(value)); + } + "BYEASTER" => { + options.byeaster = Some(value.parse::<isize>().unwrap()); + } + _ => panic!("Invalid property: {}", key), + }; + } + + options +} + +fn str_to_weekday(d: &str) -> usize { + match d.to_uppercase().as_str() { + "MO" => 0, + "TU" => 1, + "WE" => 2, + "TH" => 3, + "FR" => 4, + "SA" => 5, + "SU" => 6, + _ => panic!("Invalid weekday: {}", d), + } +} + +fn parse_weekday(val: &str) -> Vec<usize> { + 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) + + return str_to_weekday(day); + }).collect() +} + +fn parse_line(rfc_string: &str) -> Option<PartialOptions> { + let re = Regex::new(r"(?m)^\s+|\s+$").unwrap(); + let rfc_string = re.replace(rfc_string, ""); + if rfc_string.is_empty() { + return None; + } + + let re = Regex::new(r"(?m)^([A-Z]+?)[:;]").unwrap(); + let rfc_string_upper = rfc_string.to_uppercase(); + let header = re.captures(&rfc_string_upper); + + + + let rfc_string = rfc_string.to_string(); + if header.is_none() { + return Some(parse_rrule(&rfc_string)); + } + let header = header.unwrap(); + let key = header.get(1).unwrap().as_str(); + + match key { + "EXRULE" | "RRULE" => Some(parse_rrule(&rfc_string)), + "DTSTART" => Some(parse_dtstart(&rfc_string).unwrap()), + _ => panic!("Unsupported RFC prop {} in {}", key, &rfc_string) + } +} + +#[derive(Debug)] +struct ParsedLine { + name: String, + params: Vec<String>, + value: String +} + +fn break_down_line(line: &str) -> ParsedLine { + let parsed_line_name = extract_name(String::from(line)); + let params: Vec<&str> = parsed_line_name.name.split(";").collect(); + + ParsedLine { + name: params[0].to_uppercase(), + params: params[1..].iter().map(|s| String::from(*s)).collect(), + value: String::from(parsed_line_name.value) + } +} + +struct LineName { + name: String, + value: String +} + +fn extract_name(line: String) -> LineName { + if !line.contains(":") { + return LineName { + name: String::from("RRULE"), + value: line + }; + } + + let parts: Vec<&str> = line.split(":").collect(); + let name = parts[0]; + let value = parts[1..].join(""); + + LineName { + name: String::from(name), + value + } +} + +fn parse_string(rfc_string: &str) -> PartialOptions { + let options: Vec<PartialOptions> = rfc_string.split("\n").map(|l| parse_line(l)).filter(|x| x.is_some()) + .map(|o| o.unwrap()) + .collect(); + + if options.len() == 1 { + println!("Options i got from str: {}", rfc_string); + println!("{:?}", options[0]); + return options[0].clone(); + } + + PartialOptions::concat(&options[0], &options[1]) +} + +#[derive(Debug)] +struct ParsedInput { + rrule_vals: Vec<PartialOptions>, + rdate_vals: Vec<DateTime<Utc>>, + exrule_vals: Vec<PartialOptions>, + exdate_vals: Vec<DateTime<Utc>>, + dtstart: Option<DateTime<Utc>>, + tzid: Option<String>, +} + +fn parse_input(s: &str) -> ParsedInput { + let mut rrule_vals = vec![]; + let mut rdate_vals = vec![]; + let mut exrule_vals = vec![]; + let mut exdate_vals = vec![]; + + let PartialOptions { + dtstart, + mut tzid, + .. + } = parse_dtstart(s).unwrap(); + + + let mut lines: Vec<&str> = s.split("\n").collect(); + for line in &lines { + let parsed_line = break_down_line(line); + println!("Parsed line: {:?}", parsed_line); + + match parsed_line.name.to_uppercase().as_str() { + "RRULE" => { + if !parsed_line.params.is_empty() { + panic!("Unsupported RRULE value"); + } + if parsed_line.value.is_empty() { + continue; + } + + rrule_vals.push(parse_string(line)); + } + "RDATE" => { + let re = Regex::new(r"(?m)RDATE(?:;TZID=([^:=]+))?").unwrap(); + let matches = re.captures(line).unwrap(); + if tzid.is_none() && matches.get(1).is_some() { + tzid = Some(String::from(matches.get(1).unwrap().as_str())); + } + + rdate_vals.append(&mut parse_rdate(&parsed_line.value, parsed_line.params)); + } + "EXRULE" => { + exrule_vals.push(parse_string(&parsed_line.value)); + } + "EXDATE" => { + exdate_vals.append(&mut parse_rdate(&parsed_line.value, parsed_line.params)); + } + "DTSTART" => (), + _ => panic!("Unsupported property: {}", parsed_line.name) + } + } + + return ParsedInput { + dtstart, + tzid, + // fn it_works_2() { + // let options = build_rule("DTSTART:19970902T090000Z\nRRULE:FREQ=YEARLY;COUNT=3\n"); + // println!("?????????????=================?????????????"); + // println!("{:?}", options); + // } + // fn it_works_2() { + // let options = build_rule("DTSTART:19970902T090000Z\nRRULE:FREQ=YEARLY;COUNT=3\n"); + // println!("?????????????=================?????????????"); + // println!("{:?}", options); + // } + rrule_vals, + rdate_vals, + exrule_vals, + exdate_vals + } +} + +fn validate_date_param(params: Vec<&str>){ + let re = Regex::new(r"(?m)(VALUE=DATE(-TIME)?)|(TZID=)").unwrap(); + + for param in ¶ms { + if re.captures(param).unwrap().len() == 0 { + panic!("Unsupported RDATE/EXDATE parm: {}", param); + } + } +} + +fn parse_rdate(rdateval: &str, params: Vec<String>) -> Vec<DateTime<Utc>> { + let params: Vec<&str> = params.iter().map(|p| p.as_str()).collect(); + validate_date_param(params); + + rdateval.split(",").map(|datestr| datestring_to_date(datestr)).collect() +} + +fn build_rule(s: &str) -> RRuleSet { + let ParsedInput { + mut rrule_vals, + rdate_vals, + mut exrule_vals, + exdate_vals, + dtstart, + tzid, + .. + } = parse_input(s); + + let mut rset = RRuleSet::new(); + if rrule_vals.len() > 1 || + !rdate_vals.is_empty() || + !exrule_vals.is_empty() || + !exdate_vals.is_empty() { + + rset.dtstart = dtstart; + // rset.tzid(tzid || undefined); + + for rruleval in rrule_vals.iter_mut() { + rruleval.tzid = tzid.clone(); + rruleval.dtstart = dtstart; + + // let rrule = RRule::new(rruleval.clone()); + // rset.rrule(rrule); + } + + for rdate in rdate_vals { + rset.rdate(rdate); + } + + for exrule in exrule_vals.iter_mut() { + exrule.tzid = tzid.clone(); + exrule.dtstart = dtstart; + + // let exrule = RRule::new(exrule.clone()); + // rset.rrule(exrule); + } + + for exdate in exdate_vals { + rset.exdate(exdate); + } + + // if (options.compatible && options.dtstart) rset.rdate(dtstart!); + return rset; + } + + + rset +} + + +#[cfg(test)] +mod test { + use super::*; + + // #[test] + // fn it_works_2() { + // let options = build_rule("DTSTART:19970902T090000Z\nRRULE:FREQ=YEARLY;COUNT=3\n"); + // println!("?????????????=================?????????????"); + // println!("{:?}", options); + // } + + // #[test] + // fn it_works() { + // let options = build_rule("RRULE:UNTIL=19990404T110000Z;DTSTART=19990104T110000Z;FREQ=WEEKLY;BYDAY=TU,WE"); + // println!("?????????????=================?????????????"); + // println!("{:?}", options); + // } + + #[test] + fn it_works() { + let options = build_rule("RRULE:UNTIL=19990404T110000Z;DTSTART;TZID=America/New_York:19990104T110000Z;FREQ=WEEKLY;BYDAY=TU,WE"); + println!("?????????????=================?????????????"); + println!("{:?}", options); + } +} |