use std::{fmt, marker::PhantomData};
use chrono::{DateTime, Local, NaiveTime, TimeZone};
use crate::{
intervals::{parse_time, RunConfig},
timeprovider::{ChronoTimeProvider, TimeProvider},
Interval, NextTime,
};
#[doc(hidden)]
pub trait WithSchedule<Tz, Tp>
where
Tz: TimeZone,
Tp: TimeProvider,
{
fn schedule_mut(&mut self) -> &mut JobSchedule<Tz, Tp>;
fn schedule(&self) -> &JobSchedule<Tz, Tp>;
}
pub struct Repeating<'a, T, Tz, Tp> {
job: &'a mut T,
interval: Interval,
_tz: PhantomData<Tz>,
_tp: PhantomData<Tp>,
}
impl<'a, T, Tz, Tp> Repeating<'a, T, Tz, Tp>
where
T: WithSchedule<Tz, Tp>,
Tz: TimeZone,
Tp: TimeProvider,
{
pub(crate) fn new(job: &'a mut T, interval: Interval) -> Repeating<'a, T, Tz, Tp> {
Self {
job,
interval,
_tz: PhantomData,
_tp: PhantomData,
}
}
pub fn times(self, n: usize) -> &'a mut T {
if n >= 1 {
self.job.schedule_mut().repeat_config = Some(RepeatConfig {
repeats: n,
repeat_interval: self.interval,
repeats_left: 0,
});
}
self.job
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RunCount {
Never,
Times(usize),
Forever,
}
#[derive(Debug, Clone)]
pub(crate) struct RepeatConfig {
repeats: usize,
repeat_interval: Interval,
repeats_left: usize,
}
pub struct JobSchedule<Tz = Local, Tp = ChronoTimeProvider>
where
Tz: TimeZone,
Tp: TimeProvider,
{
frequency: Vec<RunConfig>,
next_run: Option<DateTime<Tz>>,
last_run: Option<DateTime<Tz>>,
run_count: RunCount,
repeat_config: Option<RepeatConfig>,
tz: Tz,
_tp: PhantomData<Tp>,
}
impl<Tz, Tp> fmt::Debug for JobSchedule<Tz, Tp>
where
Tz: TimeZone,
Tp: TimeProvider,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("JobSchedule")
.field("frequency", &self.frequency)
.field("next_run", &self.next_run)
.field("last_run", &self.last_run)
.field("run_count", &self.run_count)
.field("repeat_config", &self.repeat_config)
.finish()
}
}
impl<Tz, Tp> JobSchedule<Tz, Tp>
where
Tz: chrono::TimeZone + Sync + Send,
Tp: TimeProvider,
{
pub(crate) fn new(ival: Interval, tz: Tz) -> Self {
Self {
frequency: vec![RunConfig::from_interval(ival)],
next_run: None,
last_run: None,
run_count: RunCount::Forever,
repeat_config: None,
tz,
_tp: PhantomData,
}
}
fn last_frequency(&mut self) -> &mut RunConfig {
let last_idx = self.frequency.len() - 1;
&mut self.frequency[last_idx]
}
pub fn at(&mut self, time: &str) -> &mut Self {
self.try_at(time)
.expect("Could not convert value into a time")
}
pub fn try_at(&mut self, time: &str) -> Result<&mut Self, chrono::ParseError> {
Ok(self.at_time(parse_time(time)?))
}
pub fn at_time(&mut self, time: NaiveTime) -> &mut Self {
{
let frequency = self.last_frequency();
*frequency = frequency.with_time(time);
}
self
}
pub fn plus(&mut self, ival: Interval) -> &mut Self {
{
let frequency = self.last_frequency();
*frequency = frequency.with_subinterval(ival);
}
self
}
pub fn and_every(&mut self, ival: Interval) -> &mut Self {
self.frequency.push(RunConfig::from_interval(ival));
self
}
pub fn once(&mut self) -> &mut Self {
self.run_count = RunCount::Times(1);
self
}
pub fn forever(&mut self) -> &mut Self {
self.run_count = RunCount::Forever;
self
}
pub fn count(&mut self, count: usize) -> &mut Self {
self.run_count = RunCount::Times(count);
self
}
fn next_run_time(&self, now: &DateTime<Tz>) -> Option<DateTime<Tz>> {
match self.run_count {
RunCount::Never => None,
_ => self.frequency.iter().map(|freq| freq.next(now)).min(),
}
}
pub fn can_run_again(&self) -> bool {
self.run_count != RunCount::Never
}
pub fn start_schedule(&mut self) -> &mut Self {
if let None = self.next_run {
let now = Tp::now(&self.tz);
self.next_run = self.next_run_time(&now);
match &mut self.repeat_config {
Some(RepeatConfig {
repeats,
repeats_left,
..
}) => {
*repeats_left = *repeats;
}
None => (),
}
}
self
}
pub fn is_pending(&self, now: &DateTime<Tz>) -> bool {
match &self.next_run {
Some(dt) => *dt <= *now,
None => false,
}
}
pub fn schedule_next(&mut self, now: &DateTime<Tz>) {
if self.run_count == RunCount::Never {
return;
}
let next_run_time = self.next_run_time(now);
match &mut self.repeat_config {
Some(RepeatConfig {
repeats,
repeats_left,
repeat_interval,
}) => {
if *repeats_left > 0 {
*repeats_left -= 1;
let mut next = self.next_run.as_ref().unwrap_or(now).clone();
loop {
next = repeat_interval.next_from(&next);
if next > *now {
break;
}
}
self.next_run = Some(next);
} else {
self.next_run = next_run_time;
*repeats_left = *repeats;
}
}
None => self.next_run = next_run_time,
}
self.last_run = Some(now.clone());
self.run_count = match self.run_count {
RunCount::Never => RunCount::Never,
RunCount::Times(n) if n > 1 => RunCount::Times(n - 1),
RunCount::Times(_) => RunCount::Never,
RunCount::Forever => RunCount::Forever,
};
}
}
#[cfg(test)]
mod test {
use super::JobSchedule;
use crate::{intervals::*, timeprovider::TimeProvider, Job, SyncJob};
use chrono::prelude::*;
#[test]
fn test_repeating() {
fn utc_hms(h: u32, m: u32, s: u32) -> DateTime<Utc> {
Utc.from_utc_datetime(&NaiveDate::from_ymd(2020, 6, 16).and_hms(h, m, s))
}
struct TestTimeProvider;
impl TimeProvider for TestTimeProvider {
fn now<Tz>(tz: &Tz) -> chrono::DateTime<Tz>
where
Tz: chrono::TimeZone + Sync + Send,
{
utc_hms(7, 58, 0).with_timezone(tz)
}
}
let mut job = SyncJob::<Utc, TestTimeProvider>::new(1.hour(), Utc);
job.repeating_every(45.minutes()).times(2);
job.run(|| {});
assert!(!job.is_pending(&utc_hms(7, 59, 0)));
assert!(job.is_pending(&utc_hms(8, 0, 0)));
job.execute(&utc_hms(8, 0, 0));
assert!(!job.is_pending(&utc_hms(8, 44, 0)));
assert!(job.is_pending(&utc_hms(8, 45, 0)));
job.execute(&utc_hms(8, 45, 0));
assert!(!job.is_pending(&utc_hms(9, 0, 0)));
assert!(!job.is_pending(&utc_hms(9, 29, 0)));
assert!(job.is_pending(&utc_hms(9, 30, 0)));
job.execute(&utc_hms(9, 30, 0));
assert!(!job.is_pending(&utc_hms(9, 59, 0)));
assert!(job.is_pending(&utc_hms(10, 0, 0)));
}
#[test]
fn test_time_coercion() {
let mut job = JobSchedule::<Utc>::new(1.day(), Utc);
job.try_at("12:32").unwrap();
job.try_at(&format!("{}:{}", 12, 32)).unwrap();
job.at_time(NaiveTime::from_hms(12, 32, 0));
}
}