Implement Iterator for SessionDayIter

This commit is contained in:
Leonard Steppy 2025-02-17 22:40:49 +01:00
parent 4fd43332e0
commit bc3c4dedf9
3 changed files with 112 additions and 41 deletions

View File

@ -2,7 +2,7 @@ pub mod iter;
pub mod rule;
use crate::day::Day;
use crate::session::rule::{SessionRule, SessionRuleLike, WeekdayOfMonth};
use crate::session::rule::{SessionRule, WeekdayOfMonth};
use chrono::NaiveDate;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
@ -98,6 +98,7 @@ impl RegularSession {
self
}
///gets the next session day, where no except applies. Can possibly return the `current_date`.
pub fn next_regular_session_day<D>(&self, current_date: D) -> Option<Day>
where
D: Into<Day>,
@ -148,13 +149,14 @@ impl_opt_noted!(Except with Alternation, Cancellation);
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default, Serialize, Deserialize)]
pub struct Alternation {
pub note: Note,
pub day: Option<Day>,
///the date when the alternation should show up in the calendar, or the original date
pub new_day: Option<Day>,
}
impl From<Day> for Alternation {
fn from(day: Day) -> Self {
Self {
day: Some(day),
new_day: Some(day),
..Default::default()
}
}

View File

@ -2,14 +2,14 @@ use crate::day::Day;
use crate::session::{
Alternation, Cancellation, Dated, Except, ExtraSession, OptNoted, RegularSession, Session,
};
use chrono::Days;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::collections::{BTreeMap, VecDeque};
use std::hash::Hash;
#[derive(Debug, Clone)]
pub struct DatedSessionIter {
current_date: Day,
sessions: BTreeMap<Day, Vec<DatedSession>>,
sessions: BTreeMap<Day, VecDeque<DatedSession>>,
}
impl DatedSessionIter {
@ -20,7 +20,7 @@ impl DatedSessionIter {
{
let current_date = start_date.into();
//map every session and their exceptions to a date when they should show up in the calendar
//map every session and their excepts to a date when they should show up in the calendar
let sessions =
sessions
.into_iter()
@ -28,10 +28,9 @@ impl DatedSessionIter {
Session::Regular(session) => session
.except
.iter()
.filter(|(&day, _)| current_date <= day)
.map(|(&day, except)| match except {
Except::Alternation(alternation) => DatedSession::Altered {
day: alternation.day.unwrap_or(day),
day,
regular: session.clone(),
cause: alternation.clone(),
},
@ -48,31 +47,55 @@ impl DatedSessionIter {
}
}))
.collect(),
Session::Extra(extra) => {
//filter out old extra sessions
if current_date <= extra.day() {
vec![extra.into()]
} else {
vec![]
}
}
Session::Extra(extra) => vec![extra.into()],
})
.fold(BTreeMap::<_, Vec<_>>::new(), |mut map, dated_session| {
map
.entry(dated_session.day())
.or_default()
.push(dated_session);
map
});
//filter out and entries which would lay in the past
.filter(|dated_session| dated_session.day() >= current_date)
//group sessions on the same day together
.fold(
BTreeMap::<_, VecDeque<_>>::new(),
|mut map, dated_session| {
map
.entry(dated_session.day())
.or_default()
.push_back(dated_session);
map
},
);
Self {
current_date,
sessions,
}
Self { sessions }
}
}
//TODO iter implementation for DatedSessionIter
impl Iterator for DatedSessionIter {
type Item = DatedSession;
fn next(&mut self) -> Option<Self::Item> {
let mut entry = self.sessions.first_entry()?;
let session = entry.get_mut().pop_front()?;
if entry.get().is_empty() {
entry.remove();
}
//make sure regular sessions remain in map
if let DatedSession::Regular { ref session, day } = session {
//calculate next day, because next_regular_session_day of a session day returns that very session day
if let Some(next_day) = day.checked_add_days(Days::new(1)) {
if let Some(next_session_day) = session.next_regular_session_day(next_day) {
self
.sessions
.entry(next_session_day)
.or_default()
.push_back(DatedSession::Regular {
day: next_session_day,
session: session.clone(),
})
}
}
}
Some(session)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum DatedSession {
@ -106,7 +129,7 @@ impl Dated for DatedSession {
DatedSession::Regular { day, .. } => day,
DatedSession::Extra(ref extra) => extra.day(),
DatedSession::Canceled { day, .. } => day,
DatedSession::Altered { day, ref cause, .. } => cause.day.unwrap_or(day),
DatedSession::Altered { day, ref cause, .. } => cause.new_day.unwrap_or(day),
}
}
}
@ -121,3 +144,46 @@ impl OptNoted for DatedSession {
}
}
}
#[cfg(test)]
mod test {
use crate::session::iter::{DatedSession, DatedSessionIter};
use crate::session::rule::WeekdayOfMonth;
use crate::session::{RegularSession, WithNote};
use crate::test_util::{date, day};
use chrono::Weekday;
#[test]
fn test_regular() {
let tue_session =
RegularSession::from(WeekdayOfMonth::new(3, Weekday::Tue)).with_note("18:30 Uhr");
let sun_session =
RegularSession::from(WeekdayOfMonth::new(1, Weekday::Sun)).with_note("10:00 Uhr");
let mut iter = DatedSessionIter::new(
date(17, 2, 2025),
[tue_session.clone().into(), sun_session.clone().into()],
);
assert_eq!(
iter.next(),
Some(DatedSession::Regular {
day: day(18, 2, 2025),
session: tue_session.clone()
})
);
assert_eq!(
iter.next(),
Some(DatedSession::Regular {
day: day(2, 3, 2025),
session: sun_session.clone()
})
);
assert_eq!(
iter.next(),
Some(DatedSession::Regular {
day: day(18, 3, 2025),
session: tue_session.clone()
})
);
}
}

View File

@ -1,5 +1,5 @@
use crate::day::Day;
use chrono::{Datelike, Months, NaiveDate, Weekday};
use chrono::{Datelike, Days, Months, NaiveDate, Weekday};
use serde::{Deserialize, Serialize};
use std::ops::Deref;
@ -21,7 +21,7 @@ impl SessionRule {
{
SessionDayIter {
rule: self,
current_date: Some(start_date.into()),
start_date: Some(start_date.into()),
}
}
}
@ -45,20 +45,23 @@ impl SessionRuleLike for SessionRule {
#[derive(Debug, Clone)]
pub struct SessionDayIter<R> {
pub rule: R,
pub current_date: Option<Day>,
pub start_date: Option<Day>,
}
impl<R> Iterator for SessionDayIter<R>
impl<R, S> Iterator for SessionDayIter<R>
where
R: Deref<Target = SessionRule>,
R: Deref<Target = S>,
S: SessionRuleLike,
{
type Item = Day;
fn next(&mut self) -> Option<Self::Item> {
self.current_date = self
.current_date
.and_then(|date| self.rule.determine_next_date(date));
self.current_date
let start_date = self.start_date?;
let session_date = self.rule.determine_next_date(start_date);
self.start_date = session_date
.and_then(|session_date| session_date.checked_add_days(Days::new(1)))
.map(Day::from);
session_date
}
}
@ -69,7 +72,7 @@ where
pub fn to_owned(&self) -> SessionDayIter<SessionRule> {
SessionDayIter {
rule: self.rule.clone(),
current_date: self.current_date,
start_date: self.start_date,
}
}
}
@ -116,7 +119,7 @@ impl SessionRuleLike for WeekdayOfMonth {
}
pub trait SessionRuleLike {
/// Determines the next session date in form of a [Day], possibly including the `start_date`.
/// Determines the next session date in form of a [Day], possibly including the `current_date`.
fn determine_next_date(&self, current_date: Day) -> Option<Day>;
/// Whether this rule would be able to produce the given `day`.