tsuki_scheduler/schedule/
period.rs

1use chrono::{TimeDelta, Utc};
2
3use super::{IntoSchedule, Schedule};
4use crate::Dtu;
5
6/// A schedule that runs at a fixed interval.
7#[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq)]
8pub struct Period {
9    period: TimeDelta,
10    next: Dtu,
11}
12
13impl Period {
14    pub fn new(period: TimeDelta, from: Dtu) -> Self {
15        assert!(period > TimeDelta::zero(), "Period must be positive");
16        assert!(
17            from > Utc::now() - period,
18            "start time must be in the future"
19        );
20        Self { period, next: from }
21    }
22    pub fn period(&self) -> TimeDelta {
23        self.period
24    }
25    pub fn get_next(&self) -> Dtu {
26        self.next
27    }
28}
29
30fn time_mod(x: TimeDelta, p: TimeDelta) -> TimeDelta {
31    if p == TimeDelta::zero() {
32        panic!("Period must be positive")
33    }
34    if x < p {
35        return x;
36    }
37    let s_x = x.num_seconds();
38    let n_x = x.subsec_nanos() as i64;
39    let s_p = p.num_seconds();
40    let n_p = p.subsec_nanos() as i64;
41    const NANOS_PER_SEC: i64 = 1_000_000_000;
42    if s_p == 0 {
43        let nanos_in_total = (n_x % n_p) + ((NANOS_PER_SEC % n_p) * ((s_x) % n_p) % n_p);
44        let secs = nanos_in_total / NANOS_PER_SEC;
45        let nanos = (nanos_in_total % NANOS_PER_SEC) as u32;
46        return TimeDelta::new(secs, nanos).expect("invalid time delta");
47    }
48    let q_0 = s_x / (s_p + 1);
49    let s_r = (s_x % (s_p + 1)) + ((n_p * q_0) / NANOS_PER_SEC);
50    if n_p == 0 {
51        return TimeDelta::new(s_r, n_x as u32).expect("invalid time delta");
52    }
53    let n_r = n_x % n_p + ((NANOS_PER_SEC % n_p) * (s_r % n_p) % n_p);
54    let x = TimeDelta::new(s_r, n_r as u32)
55        .ok_or((s_r, n_r))
56        .expect("invalid time delta");
57    time_mod(x, p)
58}
59impl Schedule for Period {
60    fn peek_next(&mut self) -> Option<Dtu> {
61        Some(self.next)
62    }
63
64    fn next(&mut self) -> Option<Dtu> {
65        let next = self.next;
66        self.next += self.period;
67        Some(next)
68    }
69
70    fn forward_to(&mut self, dtu: Dtu) {
71        if self.next < dtu {
72            let diff = dtu - self.next;
73            if diff < self.period {
74                self.next += self.period;
75                return;
76            }
77            let rest = time_mod(diff, self.period);
78            self.next = dtu + self.period - rest;
79        }
80    }
81}
82
83impl IntoSchedule for TimeDelta {
84    type Output = Period;
85    fn into_schedule(self) -> Self::Output {
86        Period::new(self, Utc::now())
87    }
88}