tsuki_scheduler/schedule/
period.rs1use chrono::{TimeDelta, Utc};
2
3use super::{IntoSchedule, Schedule};
4use crate::Dtu;
5
6#[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}