From 35cc74578e969bae4812afd2ff041eba3746142d Mon Sep 17 00:00:00 2001 From: Joris Date: Sat, 19 Mar 2022 22:03:58 +0100 Subject: Allow to repeat an event until a specific date Also provide a shortcut to modify a repetead event from a specific occurence. This set the end repetition date under the hood and create a new repeated event. --- src/model/event.rs | 52 +++++++------------------------------------------ src/model/mod.rs | 1 + src/model/repetition.rs | 21 ++++++++++++++++++++ src/model/time.rs | 22 +++++++++++++++++++++ 4 files changed, 51 insertions(+), 45 deletions(-) create mode 100644 src/model/time.rs (limited to 'src/model') diff --git a/src/model/event.rs b/src/model/event.rs index 5e92692..e556f6e 100644 --- a/src/model/event.rs +++ b/src/model/event.rs @@ -1,9 +1,10 @@ -use chrono::Timelike; use chrono::{NaiveDate, NaiveTime}; use std::collections::HashMap; use uuid::Uuid; use crate::model::repetition::Repetition; +use crate::model::time; +use crate::validation; pub static DATE_FORMAT: &str = "%d/%m/%Y"; @@ -19,10 +20,10 @@ pub struct Event { impl Event { pub fn pprint(&self) -> String { - let start = self.start.map(pprint_time).unwrap_or_default(); + let start = self.start.map(time::pprint).unwrap_or_default(); let end = self .end - .map(|t| format!("-{}", pprint_time(t))) + .map(|t| format!("-{}", time::pprint(t))) .unwrap_or_default(); let space = if self.start.is_some() || self.end.is_some() { " " @@ -52,27 +53,6 @@ pub fn repetitions_between( res } -pub fn pprint_time(t: NaiveTime) -> String { - if t.minute() == 0 { - format!("{}h", t.hour()) - } else { - format!("{}h{}", t.hour(), t.minute()) - } -} - -fn parse_time(t: &str) -> Option { - match t.split('h').collect::>()[..] { - [hours, minutes] => { - if minutes.trim().is_empty() { - NaiveTime::from_hms_opt(hours.parse().ok()?, 0, 0) - } else { - NaiveTime::from_hms_opt(hours.parse().ok()?, minutes.parse().ok()?, 0) - } - } - _ => None, - } -} - // Validation pub fn validate( @@ -83,8 +63,8 @@ pub fn validate( end: String, repetition: Option, ) -> Option { - let start = validate_time(start)?; - let end = validate_time(end)?; + let start = validation::time(start)?; + let end = validation::time(end)?; match (start, end) { (Some(s), Some(e)) if s > e => None?, @@ -94,27 +74,9 @@ pub fn validate( Some(Event { id, date: NaiveDate::parse_from_str(&date, DATE_FORMAT).ok()?, - name: validate_name(name)?, + name: validation::non_empty(name)?, start, end, repetition, }) } - -fn validate_time(time: String) -> Option> { - let time = time.trim(); - if time.is_empty() { - Some(None) - } else { - parse_time(time).map(Some) - } -} - -fn validate_name(name: String) -> Option { - let name = name.trim(); - if name.is_empty() { - None - } else { - Some(name.to_string()) - } -} diff --git a/src/model/mod.rs b/src/model/mod.rs index c1beb62..0aefbc6 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -1,2 +1,3 @@ pub mod event; pub mod repetition; +pub mod time; diff --git a/src/model/repetition.rs b/src/model/repetition.rs index 872944a..eb8cb6d 100644 --- a/src/model/repetition.rs +++ b/src/model/repetition.rs @@ -6,6 +6,7 @@ use std::collections::HashSet; pub struct Repetition { pub frequency: Frequency, pub removed_occurences: HashSet, + pub until: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -48,6 +49,7 @@ impl Repetition { let repeat = |mut date, next: Box NaiveDate>| { let mut repetitions = vec![]; let mut iteration: usize = 0; + let end = self.until.unwrap_or(end); while date <= end { if date >= event { if date >= start && !self.removed_occurences.contains(&iteration) { @@ -195,6 +197,7 @@ mod tests { let repetition = Repetition { frequency: Frequency::Daily { period: 2 }, removed_occurences: HashSet::from([0, 2, 3]), + until: None, }; assert_eq!( repetition.between(d(2020, 7, 1), d(2020, 7, 1), d(2020, 7, 9)), @@ -209,6 +212,7 @@ mod tests { day: DayOfMonth::Day { day: 8 }, }, removed_occurences: HashSet::from([1, 3]), + until: None, }; assert_eq!( repetition.between(d(2020, 1, 8), d(2020, 1, 8), d(2020, 4, 8)), @@ -225,6 +229,7 @@ mod tests { }, }, removed_occurences: HashSet::from([1, 2, 3]), + until: None, }; assert_eq!( repetition.between(d(2020, 2, 1), d(2020, 2, 1), d(2020, 7, 1)), @@ -237,6 +242,7 @@ mod tests { let repetition = Repetition { frequency: Frequency::Yearly, removed_occurences: HashSet::from([3]), + until: None, }; assert_eq!( repetition.between(d(2018, 5, 5), d(2019, 8, 1), d(2022, 5, 5)), @@ -249,6 +255,7 @@ mod tests { let repetition = Repetition { frequency: Frequency::Yearly, removed_occurences: HashSet::from([1]), + until: None, }; assert_eq!( repetition.occurence_index(d(2020, 1, 1), d(2022, 1, 1)), @@ -256,6 +263,19 @@ mod tests { ) } + #[test] + fn repetition_stops_after_until() { + let repetition = Repetition { + frequency: Frequency::Yearly, + removed_occurences: HashSet::new(), + until: Some(d(2022, 1, 1)), + }; + assert_eq!( + repetition.between(d(2020, 1, 1), d(2020, 1, 1), d(2024, 1, 1)), + vec!(d(2020, 1, 1), d(2021, 1, 1), d(2022, 1, 1)) + ) + } + fn d(y: i32, m: u32, d: u32) -> NaiveDate { NaiveDate::from_ymd(y, m, d) } @@ -264,6 +284,7 @@ mod tests { Repetition { frequency, removed_occurences: HashSet::new(), + until: None, } } } diff --git a/src/model/time.rs b/src/model/time.rs new file mode 100644 index 0000000..10cf6d3 --- /dev/null +++ b/src/model/time.rs @@ -0,0 +1,22 @@ +use chrono::{NaiveTime, Timelike}; + +pub fn pprint(t: NaiveTime) -> String { + if t.minute() == 0 { + format!("{}h", t.hour()) + } else { + format!("{}h{}", t.hour(), t.minute()) + } +} + +pub fn parse(t: &str) -> Option { + match t.split('h').collect::>()[..] { + [hours, minutes] => { + if minutes.trim().is_empty() { + NaiveTime::from_hms_opt(hours.parse().ok()?, 0, 0) + } else { + NaiveTime::from_hms_opt(hours.parse().ok()?, minutes.parse().ok()?, 0) + } + } + _ => None, + } +} -- cgit v1.2.3