use super::counter_date::DateTimeIter;
use super::utils::add_time_to_date;
use super::{build_pos_list, utils::from_ordinal, IterInfo, MAX_ITER_LOOP};
use crate::core::{get_hour, get_minute, get_second};
use crate::{core::DateTime, Frequency, RRule};
use chrono::Datelike;
use chrono::{NaiveTime, TimeZone};
use std::collections::VecDeque;
#[derive(Debug, Clone)]
pub(crate) struct RRuleIter<'a> {
pub(crate) counter_date: DateTimeIter,
pub(crate) ii: IterInfo<'a>,
pub(crate) timeset: Vec<NaiveTime>,
pub(crate) dt_start: DateTime,
pub(crate) buffer: VecDeque<DateTime>,
pub(crate) finished: bool,
pub(crate) count: Option<u32>,
pub(crate) limited: bool,
pub(crate) was_limited: bool,
}
impl<'a> RRuleIter<'a> {
pub(crate) fn new(rrule: &'a RRule, dt_start: &DateTime, limited: bool) -> Self {
let ii = IterInfo::new(rrule, dt_start);
let hour = get_hour(dt_start);
let minute = get_minute(dt_start);
let second = get_second(dt_start);
let timeset = ii.get_timeset(hour, minute, second);
let count = ii.rrule().count;
RRuleIter {
counter_date: dt_start.into(),
ii,
timeset,
dt_start: *dt_start,
buffer: VecDeque::new(),
finished: false,
count,
limited,
was_limited: false,
}
}
fn try_add_datetime(
dt: DateTime,
rrule: &RRule,
count: &mut Option<u32>,
buffer: &mut VecDeque<DateTime>,
dt_start: &DateTime,
) -> bool {
if matches!(rrule.until, Some(until) if dt > until) {
return true;
}
if dt >= *dt_start {
buffer.push_back(dt);
if let Some(count) = count {
*count -= 1;
if *count == 0 {
return true;
}
}
}
false
}
fn generate(&mut self) -> bool {
if self.finished {
return true;
}
if matches!(self.count, Some(count) if count == 0) {
return true;
}
let rrule = self.ii.rrule();
if rrule.interval == 0 {
return true;
}
let mut loop_counter: u32 = 0;
while self.buffer.is_empty() {
if self.limited {
loop_counter += 1;
if loop_counter >= MAX_ITER_LOOP {
self.finished = true;
self.was_limited = true;
log::warn!(
"Reached max loop counter (`{}`). \
See 'validator limits' in docs for more info.",
MAX_ITER_LOOP
);
return true;
}
}
let rrule = self.ii.rrule();
let dayset = self.ii.get_dayset(
rrule.freq,
self.counter_date.year,
self.counter_date.month,
self.counter_date.day,
);
if rrule.by_set_pos.is_empty() {
for current_day in &dayset {
let current_day = i64::try_from(*current_day).expect(
"We control the dayset and we know that it will always fit within an i64",
);
let year_ordinal = self.ii.year_ordinal();
let date = from_ordinal(year_ordinal + current_day);
let date = self
.dt_start
.timezone()
.ymd(date.year(), date.month(), date.day());
for time in &self.timeset {
let dt = match add_time_to_date(date, *time) {
Some(dt) => dt,
None => continue,
};
if Self::try_add_datetime(
dt,
rrule,
&mut self.count,
&mut self.buffer,
&self.dt_start,
) {
return true;
}
}
}
} else {
let pos_list = build_pos_list(
&rrule.by_set_pos,
&dayset,
&self.timeset,
self.ii.year_ordinal(),
self.dt_start.timezone(),
);
for dt in pos_list {
if Self::try_add_datetime(
dt,
rrule,
&mut self.count,
&mut self.buffer,
&self.dt_start,
) {
return true;
}
}
}
let increment_day = dayset.is_empty();
if self.counter_date.increment(rrule, increment_day).is_err() {
self.finished = true;
return true;
}
if matches!(
rrule.freq,
Frequency::Hourly | Frequency::Minutely | Frequency::Secondly
) {
let hour =
u8::try_from(self.counter_date.hour).expect("range 0-23 is covered by u8");
let minute =
u8::try_from(self.counter_date.minute).expect("range 0-59 is covered by u8");
let second =
u8::try_from(self.counter_date.second).expect("range 0-59 is covered by u8");
self.timeset = self.ii.get_timeset_unchecked(hour, minute, second);
}
self.ii.rebuild(&self.counter_date);
}
false
}
}
impl<'a> Iterator for RRuleIter<'a> {
type Item = DateTime;
fn next(&mut self) -> Option<Self::Item> {
if !self.buffer.is_empty() {
return self.buffer.pop_front();
}
if self.finished {
return None;
}
self.finished = self.generate();
if self.buffer.is_empty() {
self.finished = true;
}
self.buffer.pop_front()
}
}
pub(crate) trait WasLimited {
fn was_limited(&self) -> bool;
}
impl<'a> WasLimited for RRuleIter<'a> {
fn was_limited(&self) -> bool {
self.was_limited
}
}