use chrono::{Datelike, NaiveDate, NaiveDateTime, NaiveTime};
use frame::Frame;
use rule::{Rule, Weekdays};
use serde::Serialize;
pub mod frame;
pub mod rule;
pub mod util;
#[cfg(test)]
mod tests;
pub fn get_frames<T: Serialize + Clone>(
rules: &Vec<Rule<T>>,
start: NaiveDateTime,
end: NaiveDateTime,
) -> Vec<Frame<T>> {
if rules.is_empty() {
return vec![Frame {
start,
end,
state: false,
payload: None,
}];
}
let zero_hour: NaiveTime = match NaiveTime::from_hms_opt(0, 0, 0) {
Some(time) => time,
None => NaiveTime::default(),
};
let mut frames = Vec::new();
let mut traverse = start;
while traverse < end {
let traverse_date = traverse.date();
let traverse_time = traverse.time();
for (index, rule) in rules.iter().rev().enumerate() {
let prio = rules.len() - index - 1;
if !(traverse_date >= rule.start_date && traverse_date <= rule.end_date) {
continue;
}
if !((traverse_time >= rule.start_time && traverse_time < rule.end_time)
|| (rule.start_time == zero_hour && rule.end_time == zero_hour))
{
continue;
}
let end_date_time = match rule.weekdays {
Some(weekdays) => {
if !is_within_weekdays(traverse_date, weekdays) {
continue;
}
if rule.end_time == zero_hour {
traverse_date.and_time(end.time())
} else {
traverse_date.and_time(rule.end_time)
}
}
None => end,
};
let frame_end = get_frame_end(
&rules,
traverse_date,
end_date_time.date(),
traverse_time,
end_date_time.time(),
rule.state,
prio,
)
.min(end);
frames.push(Frame::<T> {
start: traverse_date.and_time(traverse_time),
end: frame_end,
state: rule.state.clone(),
payload: rule.payload.clone(),
});
traverse = frame_end;
break;
}
}
frames
}
fn get_frame_end<T: Serialize>(
rules: &Vec<Rule<T>>,
start_date: NaiveDate,
end_date: NaiveDate,
start_time: NaiveTime,
end_time: NaiveTime,
state: bool,
lowest_prio: usize,
) -> NaiveDateTime {
let end_of_day_hour: NaiveTime = match NaiveTime::from_hms_opt(23, 59, 59) {
Some(time) => time,
None => NaiveTime::default(),
};
let zero_hour: NaiveTime = match NaiveTime::from_hms_opt(0, 0, 0) {
Some(time) => time,
None => NaiveTime::default(),
};
for rule in rules.iter().skip(lowest_prio).rev() {
if !(start_date >= rule.start_date && end_date < rule.end_date && rule.state != state) {
continue;
}
if !((rule.start_time > start_time && rule.start_time < end_time)
|| (rule.start_time == zero_hour && rule.end_time == zero_hour))
{
continue;
}
let rule_start = match rule.weekdays {
Some(weekdays) => {
if !is_within_weekdays(start_date, weekdays) {
continue;
}
if rule.start_time == zero_hour {
start_date.and_time(end_time)
} else {
start_date.and_time(rule.start_time)
}
}
None => continue,
};
return rule_start;
}
if end_time == end_of_day_hour {
let next_date = match end_date.succ_opt() {
Some(date) => date,
None => end_date,
};
return next_date.and_time(zero_hour);
}
return end_date.and_time(end_time);
}
pub fn is_within_weekdays(time: NaiveDate, weekdays: Weekdays) -> bool {
let weekday = time.weekday();
let weekday = Weekdays::from_chrono_weekday(weekday);
weekdays.intersects(weekday)
}