aboutsummaryrefslogtreecommitdiff
path: root/src/model
diff options
context:
space:
mode:
Diffstat (limited to 'src/model')
-rw-r--r--src/model/event.rs13
-rw-r--r--src/model/repetition.rs142
2 files changed, 118 insertions, 37 deletions
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<Repetition>,
}
-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<usize>,
+}
+
+#[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<NaiveDate> {
let repeat = |mut date, next: Box<dyn Fn(NaiveDate) -> 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<usize> {
+ 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(),
+ }
+ }
}