From 325e98c77466715b53920b16f76ad2709b91974e Mon Sep 17 00:00:00 2001 From: Lucas Colombo Date: Tue, 16 Apr 2024 06:29:33 -0300 Subject: [PATCH] =?UTF-8?q?feat(sched):=20=E2=9C=A8=20wrapping=20ranges?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/sched/rules/recurrent/rule_unit.rs | 111 ++++++++++++++++-- lib/sched/rules/recurrent/ruleset/builder.rs | 31 ++++- .../tests/recurrence_rules_by_val.rs | 2 +- 3 files changed, 130 insertions(+), 14 deletions(-) diff --git a/lib/sched/rules/recurrent/rule_unit.rs b/lib/sched/rules/recurrent/rule_unit.rs index 08a8bbd..862b20d 100644 --- a/lib/sched/rules/recurrent/rule_unit.rs +++ b/lib/sched/rules/recurrent/rule_unit.rs @@ -32,18 +32,25 @@ where match matcher { Rule::Val(v) => value == *v, Rule::Range(start, end, step) => { - if *step == T::one() { - value >= *start && value <= *end - } else { - let mut current = *start; - while current <= *end { - if current == value { - return true; - } - // Move to the next value based on the step size - current = current + *step; + if *start == *end { + return value == *start; + } + + if *step == T::zero() || *step == T::one() { + if *start < *end { + return value >= *start && value <= *end; + } else { + return value >= *start || value <= *end; + } + } else { + if *start < *end { + return value >= *start + && value <= *end + && (value - *start) % *step == T::zero(); + } else { + return (value >= *start || value <= *end) + && (*start - value) % *step == T::zero(); } - false } } Rule::Many(matcher) => matcher.iter().any(|v| Self::_matches(&Rule::Val(*v), value)), @@ -84,3 +91,85 @@ pub fn many(values: Vec) -> Rule { pub fn ranges(ranges: Vec<(T, T, T)>) -> Rule { Rule::Ranges(ranges) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_rule_val() { + let rule = val(5); + assert!(rule.matches(5)); + assert!(!rule.matches(6)); + } + + #[test] + fn test_rule_range() { + let rule = range(5, 10, 1); + assert!(rule.matches(5)); + assert!(rule.matches(6)); + assert!(rule.matches(10)); + assert!(!rule.matches(11)); + } + + #[test] + fn test_rule_range_step() { + let rule = range(5, 10, 2); + assert!(rule.matches(5)); + assert!(!rule.matches(6)); + assert!(rule.matches(7)); + assert!(!rule.matches(8)); + assert!(rule.matches(9)); + assert!(!rule.matches(10)); + assert!(!rule.matches(11)); + } + + #[test] + fn test_rule_wrapping_range() { + // imagine a week where 0 is Sunday and 6 is Saturday + // s | m | t | w | t | f | s + // 0 | 1 | 2 | 3 | 4 | 5 | 6 + + // if a range rules goes from 5 to 2, it should match 5, 6, 0, 1, 2 + // since our rule doesn't have max and min values, it should match any value + // >= 5 and any value <= 2, (well, taking step into account, obviously) + + // TODO: maybe in the future we could add another rule type "WrappingRange" that would + // handle this case more explicitly and taking into account the min-max wrapping + // limits (like 0 and 6 in this case). It obviously would need its WrappingRanges + // analogs just like we have Ranges for Range... for now we can just use the + // Range rule and be happy with it + + let rule = range(5, 2, 1); + assert!(rule.matches(5)); + assert!(rule.matches(6)); + assert!(rule.matches(0)); + assert!(rule.matches(1)); + assert!(rule.matches(2)); + assert!(!rule.matches(3)); + assert!(!rule.matches(4)); + + // would match any value >= 5 too + assert!(rule.matches(7)); + } + + #[test] + fn test_rule_many() { + let rule = many(vec![5, 10, 15]); + assert!(rule.matches(5)); + assert!(rule.matches(10)); + assert!(rule.matches(15)); + assert!(!rule.matches(11)); + } + + #[test] + fn test_rule_ranges() { + let rule = ranges(vec![(5, 10, 1), (15, 20, 1)]); + assert!(rule.matches(5)); + assert!(rule.matches(6)); + assert!(rule.matches(10)); + assert!(rule.matches(15)); + assert!(rule.matches(20)); + assert!(!rule.matches(11)); + } +} diff --git a/lib/sched/rules/recurrent/ruleset/builder.rs b/lib/sched/rules/recurrent/ruleset/builder.rs index 21733d4..61cc2a6 100644 --- a/lib/sched/rules/recurrent/ruleset/builder.rs +++ b/lib/sched/rules/recurrent/ruleset/builder.rs @@ -92,11 +92,38 @@ impl RecurrenceRuleSet { self.time_rule(Rule::Val(hour), Rule::Val(minute), Rule::Val(second)) } - /// 🧉 » set the day of the week rule as a single value from primitive - pub fn on_dow(&mut self, value: Weekday) -> &mut Self { + /// 🧉 » set the day of the week rule as a single value + pub fn on_weekday(&mut self, value: Weekday) -> &mut Self { self.dow_rule(Rule::Val(value.num_days_from_sunday())) } + /// 🧉 » set the day of the week rule as a single value (`dow` from Sunday 0) + pub fn on_dow(&mut self, value: u32) -> &mut Self { + self.dow_rule(Rule::Val(value)) + } + + /// 🧉 » set the day of the week rule as a range between two `Weekday` + pub fn from_to_weekdays(&mut self, from: Weekday, to: Weekday) -> &mut Self { + if from == to { + return self; + } + + self.dow_rule(Rule::Range( + from.num_days_from_sunday(), + to.num_days_from_sunday(), + 1, + )) + } + + /// 🧉 » set the day of the week rule as a range between two values (`dow` from Sunday 0) + pub fn from_to_dow(&mut self, from: u32, to: u32) -> &mut Self { + if from == to { + return self; + } + + self.dow_rule(Rule::Range(from, to, 1)) + } + /// 🧉 » set the day of the month rule as a single value from primitive pub fn on_day(&mut self, value: u32) -> &mut Self { self.day_ryle(Rule::Val(value)) diff --git a/lib/sched/rules/recurrent/tests/recurrence_rules_by_val.rs b/lib/sched/rules/recurrent/tests/recurrence_rules_by_val.rs index b72550b..410910f 100644 --- a/lib/sched/rules/recurrent/tests/recurrence_rules_by_val.rs +++ b/lib/sched/rules/recurrent/tests/recurrence_rules_by_val.rs @@ -97,7 +97,7 @@ fn each_wednesday() { let mut next_wednesday = Local.with_ymd_and_hms(2024, 4, 10, 0, 0, 0).unwrap(); let mut rules = ruleset(); - rules.on_dow(Weekday::Wed).at_time(0, 0, 0); + rules.on_weekday(Weekday::Wed).at_time(0, 0, 0); let mut next = date;