use std::{cmp::Ordering, ops::Range};
use chrono::{Datelike, Timelike, LocalResult, Utc, TimeZone};
use crate::{
date_or_date_time::DateOrDateTime,
rrule::{Options, RRule},
by_day::ByDay,
VEvent,
};
use chrono::Duration;
#[derive(Debug, Clone)]
pub struct VEventIterator<'a> {
event: &'a VEvent,
last_occurrence: Option<DateOrDateTime>,
count: u32,
}
impl<'a> VEventIterator<'a> {
pub(crate) fn new(event: &'a VEvent) -> Self {
Self {
event,
last_occurrence: None,
count: 0,
}
}
fn get_next_occurrence_according_to_rule(
&mut self,
last_occurrence: DateOrDateTime,
rrule: &RRule,
) -> Option<DateOrDateTime> {
match rrule {
RRule::Yearly(_rrule) => {
let next_occurrence = last_occurrence.inc_year(1);
if !rrule.is_expired(next_occurrence) {
self.last_occurrence = Some(next_occurrence);
self.last_occurrence
} else {
None
}
}
RRule::YearlyByMonthByDay(rrule) => {
let next_occurrence = last_occurrence.next_by_day(&rrule.day);
if next_occurrence.year() == last_occurrence.year()
&& next_occurrence.month() == rrule.month as u32
&& next_occurrence > last_occurrence
{
if !rrule.is_expired(next_occurrence) {
self.last_occurrence = Some(next_occurrence);
self.last_occurrence
} else {
None
}
} else {
let next_year = last_occurrence.year() + rrule.common_options().interval.unwrap_or(1) as i32;
let target_month_start = last_occurrence
.substitute(
Some(next_year),
Some(rrule.month as u32),
Some(1),
None,
None,
None,
)
.unwrap();
let mut next_occurrence = target_month_start;
match &rrule.day {
ByDay::Simple(weekdays) => {
if !weekdays.contains(&target_month_start.date().weekday()) {
next_occurrence = target_month_start.next_by_day(&rrule.day);
}
}
ByDay::Delta(_) => {
next_occurrence = target_month_start.next_by_day(&rrule.day);
}
}
if !rrule.is_expired(next_occurrence) {
self.last_occurrence = Some(next_occurrence);
self.last_occurrence
} else {
None
}
}
}
RRule::YearlyByMonthByMonthDay(_rrule) => {
let next_occurrence = last_occurrence.inc_year(1);
if !rrule.is_expired(next_occurrence) {
self.last_occurrence = Some(next_occurrence);
self.last_occurrence
} else {
None
}
}
RRule::MonthlyByMonthDay(rrule) => {
let mut year = last_occurrence.year();
let mut month = last_occurrence.month() + 1;
if month > 12 {
month = 1;
year += 1;
}
let target_day = rrule.month_day as u32;
let mut d = target_day;
let mut date = Utc.with_ymd_and_hms(year, month, d, 0, 0, 0);
while matches!(date, LocalResult::None) && d > 1 {
d -= 1;
date = Utc.with_ymd_and_hms(year, month, d, 0, 0, 0);
}
let target_date = date.unwrap();
let next_occurrence = match last_occurrence {
DateOrDateTime::WholeDay(_) => DateOrDateTime::WholeDay(target_date),
DateOrDateTime::DateTime(dt) => DateOrDateTime::DateTime(
Utc.with_ymd_and_hms(
target_date.year(),
target_date.month(),
target_date.day(),
dt.hour(),
dt.minute(),
dt.second(),
)
.unwrap(),
),
};
if !rrule.is_expired(next_occurrence) {
self.last_occurrence = Some(next_occurrence);
self.last_occurrence
} else {
None
}
}
RRule::MonthlyByDay(rrule) => {
let next_month = last_occurrence
.substitute(
Some(if last_occurrence.month() == 12 {
last_occurrence.year() + 1
} else {
last_occurrence.year()
}),
Some(if last_occurrence.month() == 12 {
1
} else {
last_occurrence.month() + 1
}),
Some(1),
None,
None,
None,
)
.unwrap();
let next_occurrence = next_month.next_by_day(&rrule.day);
if !rrule.is_expired(next_occurrence) {
self.last_occurrence = Some(next_occurrence);
self.last_occurrence
} else {
None
}
}
RRule::Weekly(rrule) => {
let next_occurrence = last_occurrence + Duration::days(7);
if !rrule.is_expired(next_occurrence) {
self.last_occurrence = Some(next_occurrence);
self.last_occurrence
} else {
None
}
}
RRule::WeeklyByDay(rrule) => {
let interval = rrule.common_options().interval.unwrap_or(1) as i64;
let mut next_occurrence = last_occurrence.next_by_day(&rrule.day);
loop {
let dt_start_monday = self.event.dt_start.date() - Duration::days(self.event.dt_start.date().weekday().num_days_from_monday() as i64);
let next_monday = next_occurrence.date() - Duration::days(next_occurrence.date().weekday().num_days_from_monday() as i64);
let diff_days = (next_monday - dt_start_monday).num_days();
let diff_weeks = diff_days / 7;
if diff_weeks % interval == 0 {
break;
}
next_occurrence = next_occurrence.next_by_day(&rrule.day);
}
if !rrule.is_expired(next_occurrence) {
self.last_occurrence = Some(next_occurrence);
self.last_occurrence
} else {
None
}
}
RRule::Daily(rrule) => {
let next_occurrence = last_occurrence + Duration::days(1);
if !rrule.is_expired(next_occurrence) {
self.last_occurrence = Some(next_occurrence);
self.last_occurrence
} else {
None
}
}
}
}
fn get_next_occurrence_according_to_rule_and_iterations(&mut self) -> Option<DateOrDateTime> {
if let Some(last_occurrence) = self.last_occurrence {
self.event.rrule.as_ref().and_then(|rrule| {
if rrule.is_out_of_count(self.count) {
return None;
}
let mut next_occurrence = Some(last_occurrence);
let mut iterations = match rrule {
RRule::WeeklyByDay(_) | RRule::YearlyByMonthByDay(_) => 1,
_ => rrule.common_options().interval.unwrap_or(1),
};
while iterations > 0 && next_occurrence.is_some() {
next_occurrence =
self.get_next_occurrence_according_to_rule(next_occurrence.unwrap(), rrule);
iterations -= 1;
}
next_occurrence
})
} else {
self.last_occurrence = Some(self.event.dt_start);
Some(self.event.dt_start)
}
}
}
impl<'a> Iterator for VEventIterator<'a> {
type Item = Range<DateOrDateTime>;
fn next(&mut self) -> Option<Self::Item> {
log::trace!("function next({:?}) called", self);
let mut next = self.get_next_occurrence_according_to_rule_and_iterations();
log::trace!("next == {:?}", next);
loop {
if let Some(next_non_empty) = next {
log::trace!("next_non_empty == {:?}", next_non_empty);
if !self.event.exdates.iter().any(|exdate| {
next_non_empty.date().cmp(&exdate.date_time.date()) == Ordering::Equal
}) {
self.count += 1;
let delta = self.event.dt_end - self.event.dt_start;
let next_non_empty_end = next_non_empty + delta;
return Some(Range {
start: next_non_empty,
end: next_non_empty_end,
});
} else {
next = self.get_next_occurrence_according_to_rule_and_iterations();
}
} else {
return None;
}
}
}
}