From d584df359640176ec4bc06f59d1e8d42ab17a413 Mon Sep 17 00:00:00 2001 From: Joris Date: Sat, 12 Mar 2022 13:27:29 +0100 Subject: Update and delete recurring events --- src/model/event.rs | 13 +---- src/model/repetition.rs | 142 +++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 118 insertions(+), 37 deletions(-) (limited to 'src/model') diff --git a/src/model/event.rs b/src/model/event.rs index 249d077..5e92692 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -17,17 +17,6 @@ pub struct Event { pub repetition: Option, } -pub fn init(date: NaiveDate) -> Event { - Event { - id: Uuid::new_v4(), - date, - start: None, - end: None, - name: "".to_string(), - repetition: None, - } -} - impl Event { pub fn pprint(&self) -> String { let start = self.start.map(pprint_time).unwrap_or_default(); @@ -44,7 +33,7 @@ impl Event { } } -/// Repeated events in an included date range +/// Recurring events in an date range (inclusive) pub fn repetitions_between( events: &[Event], start: NaiveDate, diff --git a/src/model/repetition.rs b/src/model/repetition.rs index 2e790d1..872944a 100644 --- a/src/model/repetition.rs +++ b/src/model/repetition.rs @@ -1,8 +1,15 @@ use chrono::{Datelike, Duration, NaiveDate, Weekday}; use serde::{Deserialize, Serialize}; +use std::collections::HashSet; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum Repetition { +pub struct Repetition { + pub frequency: Frequency, + pub removed_occurences: HashSet, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum Frequency { Daily { period: u32 }, Monthly { day: DayOfMonth }, Yearly, @@ -40,39 +47,51 @@ impl Repetition { pub fn between(&self, event: NaiveDate, start: NaiveDate, end: NaiveDate) -> Vec { let repeat = |mut date, next: Box NaiveDate>| { let mut repetitions = vec![]; + let mut iteration: usize = 0; while date <= end { - if date >= event && date >= start { - repetitions.push(date) + if date >= event { + if date >= start && !self.removed_occurences.contains(&iteration) { + repetitions.push(date) + } + iteration += 1 } - date = next(date) + date = next(date); } repetitions }; - match self { - Repetition::Daily { period } => { - let n = start.signed_duration_since(event).num_days() % (*period as i64); - let duration = Duration::days(*period as i64); - repeat(start - Duration::days(n), Box::new(|d| d + duration)) + match self.frequency { + Frequency::Daily { period } => { + let duration = Duration::days(period as i64); + repeat(event, Box::new(|d| d + duration)) } - Repetition::Monthly { + Frequency::Monthly { day: DayOfMonth::Day { day }, - } => match start.with_day(*day as u32) { + } => match event.with_day(day as u32) { Some(first_repetition) => repeat(first_repetition, Box::new(next_month)), None => vec![], }, - Repetition::Monthly { + Frequency::Monthly { day: DayOfMonth::Weekday { weekday }, } => repeat( - first_weekday_of_month(start, *weekday), - Box::new(|d| first_weekday_of_month(next_month(d), *weekday)), + first_weekday_of_month(event, weekday), + Box::new(|d| first_weekday_of_month(next_month(d), weekday)), ), - Repetition::Yearly => repeat( - NaiveDate::from_ymd(start.year(), event.month(), event.day()), + Frequency::Yearly => repeat( + NaiveDate::from_ymd(event.year(), event.month(), event.day()), Box::new(|d| NaiveDate::from_ymd(d.year() + 1, d.month(), d.day())), ), } } + + pub fn occurence_index(&self, event: NaiveDate, date: NaiveDate) -> Option { + let mut without_removed_occurences = self.clone(); + without_removed_occurences.removed_occurences = HashSet::new(); + without_removed_occurences + .between(event, event, date) + .iter() + .position(|d| d == &date) + } } fn first_weekday_of_month(date: NaiveDate, weekday: Weekday) -> NaiveDate { @@ -93,7 +112,7 @@ mod tests { #[test] fn every_day_event_before() { - let repetition = Repetition::Daily { period: 1 }; + let repetition = from_freq(Frequency::Daily { period: 1 }); assert_eq!( repetition.between(d(2022, 6, 1), d(2022, 7, 1), d(2022, 8, 31)), d(2022, 7, 1) @@ -105,7 +124,7 @@ mod tests { #[test] fn every_day_event_between() { - let repetition = Repetition::Daily { period: 1 }; + let repetition = from_freq(Frequency::Daily { period: 1 }); assert_eq!( repetition.between(d(2022, 8, 10), d(2022, 7, 1), d(2022, 8, 31)), d(2022, 8, 10) @@ -117,7 +136,7 @@ mod tests { #[test] fn every_day_event_after() { - let repetition = Repetition::Daily { period: 1 }; + let repetition = from_freq(Frequency::Daily { period: 1 }); assert!(repetition .between(d(2022, 9, 1), d(2022, 7, 1), d(2022, 8, 31)) .is_empty()) @@ -125,7 +144,7 @@ mod tests { #[test] fn every_three_days() { - let repetition = Repetition::Daily { period: 3 }; + let repetition = from_freq(Frequency::Daily { period: 3 }); assert_eq!( repetition.between(d(2022, 2, 16), d(2022, 2, 21), d(2022, 3, 6)), vec!( @@ -140,9 +159,9 @@ mod tests { #[test] fn day_of_month() { - let repetition = Repetition::Monthly { + let repetition = from_freq(Frequency::Monthly { day: DayOfMonth::Day { day: 8 }, - }; + }); assert_eq!( repetition.between(d(2022, 2, 7), d(2022, 1, 1), d(2022, 4, 7)), vec!(d(2022, 2, 8), d(2022, 3, 8)) @@ -151,11 +170,11 @@ mod tests { #[test] fn weekday_of_month() { - let repetition = Repetition::Monthly { + let repetition = from_freq(Frequency::Monthly { day: DayOfMonth::Weekday { weekday: Weekday::Tue, }, - }; + }); assert_eq!( repetition.between(d(2022, 1, 5), d(2022, 1, 1), d(2022, 4, 4)), vec!(d(2022, 2, 1), d(2022, 3, 1)) @@ -164,14 +183,87 @@ mod tests { #[test] fn yearly() { - let repetition = Repetition::Yearly; + let repetition = from_freq(Frequency::Yearly); assert_eq!( repetition.between(d(2020, 5, 5), d(2018, 1, 1), d(2022, 5, 5)), vec!(d(2020, 5, 5), d(2021, 5, 5), d(2022, 5, 5)) ) } + #[test] + fn every_two_days_removed_occurence() { + let repetition = Repetition { + frequency: Frequency::Daily { period: 2 }, + removed_occurences: HashSet::from([0, 2, 3]), + }; + assert_eq!( + repetition.between(d(2020, 7, 1), d(2020, 7, 1), d(2020, 7, 9)), + vec!(d(2020, 7, 3), d(2020, 7, 9)) + ) + } + + #[test] + fn day_of_month_removed_occurence() { + let repetition = Repetition { + frequency: Frequency::Monthly { + day: DayOfMonth::Day { day: 8 }, + }, + removed_occurences: HashSet::from([1, 3]), + }; + assert_eq!( + repetition.between(d(2020, 1, 8), d(2020, 1, 8), d(2020, 4, 8)), + vec!(d(2020, 1, 8), d(2020, 3, 8)) + ) + } + + #[test] + fn weekday_of_month_removed_occurence() { + let repetition = Repetition { + frequency: Frequency::Monthly { + day: DayOfMonth::Weekday { + weekday: Weekday::Fri, + }, + }, + removed_occurences: HashSet::from([1, 2, 3]), + }; + assert_eq!( + repetition.between(d(2020, 2, 1), d(2020, 2, 1), d(2020, 7, 1)), + vec!(d(2020, 2, 7), d(2020, 6, 5)) + ) + } + + #[test] + fn yearly_removed_occurence() { + let repetition = Repetition { + frequency: Frequency::Yearly, + removed_occurences: HashSet::from([3]), + }; + assert_eq!( + repetition.between(d(2018, 5, 5), d(2019, 8, 1), d(2022, 5, 5)), + vec!(d(2020, 5, 5), d(2022, 5, 5)) + ) + } + + #[test] + fn occurence_index_after_removed_occurence() { + let repetition = Repetition { + frequency: Frequency::Yearly, + removed_occurences: HashSet::from([1]), + }; + assert_eq!( + repetition.occurence_index(d(2020, 1, 1), d(2022, 1, 1)), + Some(2) + ) + } + fn d(y: i32, m: u32, d: u32) -> NaiveDate { NaiveDate::from_ymd(y, m, d) } + + fn from_freq(frequency: Frequency) -> Repetition { + Repetition { + frequency, + removed_occurences: HashSet::new(), + } + } } -- cgit v1.2.3