Skip to main content

wallclock_timer/timers/
chrono.rs

1use crate::timers::WallClockTimer;
2use snafu::prelude::*;
3use std::time::Duration;
4
5/// Errors that can occur when scheduling with `chrono::DateTime`.
6#[derive(Debug, Snafu)]
7pub enum ChronoTimerError<E>
8where
9    E: std::error::Error + Send + Sync + 'static,
10{
11    /// The `chrono` time could not be converted into a `SystemTime`.
12    #[snafu(display("chrono::DateTime is out of range for SystemTime"))]
13    ChronoOutOfRange,
14    /// The underlying timer implementation returned an error.
15    #[snafu(display("timer error while scheduling chrono DateTime: {source}"))]
16    ImplementationSpecific { source: E },
17}
18
19/// Convenience API for scheduling with `chrono::DateTime`.
20pub trait ChronoTimer: WallClockTimer {
21    /// Schedule the `state` to be triggered at the given `chrono` deadline.
22    ///
23    /// Note that these get eagerly converted to system time and are not stable with respect to
24    /// timezone changes.
25    fn schedule_at_datetime<Tz>(
26        &mut self,
27        deadline: chrono::DateTime<Tz>,
28        state: Self::State,
29    ) -> Result<(), ChronoTimerError<Self::Error>>
30    where
31        Tz: chrono::TimeZone,
32        Tz::Offset: Send + Sync;
33}
34
35impl<T> ChronoTimer for T
36where
37    T: WallClockTimer,
38{
39    fn schedule_at_datetime<Tz>(
40        &mut self,
41        deadline: chrono::DateTime<Tz>,
42        state: Self::State,
43    ) -> Result<(), ChronoTimerError<Self::Error>>
44    where
45        Tz: chrono::TimeZone,
46        Tz::Offset: Send + Sync,
47    {
48        let delta = deadline
49            .with_timezone(&chrono::Utc)
50            .signed_duration_since(chrono::DateTime::<chrono::Utc>::UNIX_EPOCH);
51        // Drop the chrono error type, it contains no more information anyway.
52        let duration = delta.to_std().ok().context(ChronoOutOfRangeSnafu)?;
53        let deadline = std::time::UNIX_EPOCH
54            .checked_add(Duration::from_secs(duration.as_secs()))
55            .and_then(|t| t.checked_add(Duration::from_nanos(duration.subsec_nanos() as u64)))
56            .context(ChronoOutOfRangeSnafu)?;
57        self.schedule_at(deadline, state)
58            .context(ImplementationSpecificSnafu)
59    }
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65    use crate::timers::{State, WallClockTimer};
66
67    #[derive(Debug)]
68    struct TestState {
69        id: u64,
70    }
71
72    impl State for TestState {
73        type Id = u64;
74
75        fn id(&self) -> &Self::Id {
76            &self.id
77        }
78
79        fn trigger(self) {}
80    }
81
82    #[derive(Debug)]
83    struct StubTimer {
84        last_deadline: Option<std::time::SystemTime>,
85    }
86
87    #[derive(Debug)]
88    struct StubError;
89
90    impl std::fmt::Display for StubError {
91        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92            write!(f, "stub")
93        }
94    }
95
96    impl std::error::Error for StubError {}
97
98    impl WallClockTimer for StubTimer {
99        type Id = u64;
100        type State = TestState;
101        type Error = StubError;
102
103        fn schedule_at(
104            &mut self,
105            deadline: std::time::SystemTime,
106            _state: Self::State,
107        ) -> Result<(), Self::Error> {
108            self.last_deadline = Some(deadline);
109            Ok(())
110        }
111
112        fn cancel(&mut self, _id: Self::Id) -> Result<(), Self::Error> {
113            Ok(())
114        }
115    }
116
117    #[test]
118    fn chrono_datetime_converts_to_systemtime() {
119        let mut timer = StubTimer {
120            last_deadline: None,
121        };
122        let dt = chrono::DateTime::<chrono::Utc>::from_timestamp(1, 0).expect("timestamp");
123        timer
124            .schedule_at_datetime(dt, TestState { id: 1 })
125            .expect("schedule");
126        let expected = std::time::UNIX_EPOCH + std::time::Duration::from_secs(1);
127        assert_eq!(timer.last_deadline, Some(expected));
128    }
129}