systemd_duration/
duration.rs

1use std::convert::TryFrom;
2
3use crate::error;
4
5/// A measurement of a given span of time.
6#[derive(Copy, Clone, Debug)]
7pub enum Duration {
8    Year(f64),
9    Month(f64),
10    Week(f64),
11    Day(f64),
12    Hour(f64),
13    Minute(f64),
14    Second(f64),
15    Millisecond(f64),
16    Microsecond(f64),
17    Nanosecond(i64),
18}
19
20/// A container of durations, which when summed give the total duration.
21#[derive(Clone, Debug)]
22pub struct Container(Vec<Duration>);
23
24impl Container {
25    /// Create a new container object from the given durations.
26    #[must_use]
27    pub fn new(durations: Vec<Duration>) -> Self {
28        Self(durations)
29    }
30}
31
32/// Just a place to shove conversion factors.
33#[allow(clippy::module_name_repetitions)]
34struct Convert;
35
36// Systemd uses 365.25 (Julian average) which has an error of 0.0075 days per year relative to the
37// Gregorian calendar, or about one in every 133⅓ years.
38//
39// For the durations systemd deals with, this is not a practical issue in reality. However,
40// because the deviation is small, there's no harm in being more accurate vs. being "incompatible."
41impl Convert {
42    const SECS_PER_MIN: f64 = 60.0;
43    const SECS_PER_HOUR: f64 = 60.0 * Self::SECS_PER_MIN;
44    const SECS_PER_DAY: f64 = 24.0 * Self::SECS_PER_HOUR;
45    const SECS_PER_WEEK: f64 = 7.0 * Self::SECS_PER_DAY;
46    const SECS_PER_MONTH: f64 = 30.436_875f64 * Self::SECS_PER_DAY;
47    const SECS_PER_YEAR: f64 = 365.2_425f64 * Self::SECS_PER_DAY;
48    const NANOS_PER_SEC: f64 = 1_000_000_000.0;
49    const NANOS_PER_MILLI: f64 = Self::NANOS_PER_SEC / 1_000.0;
50    const NANOS_PER_MICRO: f64 = Self::NANOS_PER_MILLI / 1_000.0;
51}
52
53/// Conversions from [`Duration`] to [`std::time::Duration`]
54pub mod stdtime {
55    use super::{error, Container, Convert, Duration, TryFrom};
56
57    macro_rules! duration_ge_second {
58        ($secs_per_interval:expr, $count:expr) => {{
59            let sign = ($count).signum();
60            if sign <= -1.0 || sign.is_nan() {
61                return Err(error::Error::DurationOverflow);
62            }
63
64            std::time::Duration::from_secs_f64(($secs_per_interval) * ($count))
65        }};
66    }
67
68    macro_rules! duration_lt_second {
69        ($nanos_per_interval:expr, $count:expr) => {{
70            let nanos = ($nanos_per_interval) * ($count);
71            if nanos.is_infinite() || nanos > i64::MAX as f64 || nanos < 0.0f64 {
72                return Err(error::Error::DurationOverflow);
73            }
74            std::time::Duration::from_nanos(nanos.round() as u64)
75        }};
76    }
77
78    impl TryFrom<Container> for std::time::Duration {
79        type Error = error::Error;
80
81        /// Convert a [`Duration`] into an [`std::time::Duration`]
82        fn try_from(durations: Container) -> Result<Self, Self::Error> {
83            let mut duration_sum = Self::new(0, 0);
84
85            for duration in &durations.0 {
86                duration_sum += match duration {
87                    Duration::Year(count) => {
88                        duration_ge_second!(Convert::SECS_PER_YEAR, count)
89                    }
90                    Duration::Month(count) => {
91                        duration_ge_second!(Convert::SECS_PER_MONTH, count)
92                    }
93                    Duration::Week(count) => {
94                        duration_ge_second!(Convert::SECS_PER_WEEK, count)
95                    }
96                    Duration::Day(count) => {
97                        duration_ge_second!(Convert::SECS_PER_DAY, count)
98                    }
99                    Duration::Hour(count) => {
100                        duration_ge_second!(Convert::SECS_PER_HOUR, count)
101                    }
102                    Duration::Minute(count) => {
103                        duration_ge_second!(Convert::SECS_PER_MIN, count)
104                    }
105                    Duration::Second(count) => duration_ge_second!(1.0, count),
106                    Duration::Millisecond(count) => {
107                        duration_lt_second!(Convert::NANOS_PER_MILLI, count)
108                    }
109                    Duration::Microsecond(count) => {
110                        duration_lt_second!(Convert::NANOS_PER_MICRO, count)
111                    }
112                    Duration::Nanosecond(count) => {
113                        if *count < 0 {
114                            return Err(error::Error::DurationOverflow);
115                        }
116
117                        // Checked above
118                        #[allow(clippy::cast_sign_loss)]
119                        Self::from_nanos(*count as u64)
120                    }
121                }
122            }
123
124            Ok(duration_sum)
125        }
126    }
127}
128
129/// Conversions from [`Duration`] into [`chrono::TimeDelta`][::chrono::TimeDelta]
130#[cfg(feature = "with-chrono")]
131pub mod chrono {
132    use super::{error, Container, Convert, Duration, TryFrom};
133
134    macro_rules! duration_ge_second {
135        ($secs_per_interval:expr, $count:expr) => {{
136            let seconds = ($secs_per_interval) * ($count);
137            if seconds.is_infinite() || seconds > i64::MAX as f64 || seconds < i64::MIN as f64 {
138                return Err(error::Error::DurationOverflow);
139            }
140            let (seconds, nanos) = (
141                seconds.trunc(),
142                (seconds - seconds.trunc()) * Convert::NANOS_PER_SEC,
143            );
144            ::chrono::TimeDelta::new(seconds as i64, nanos as u32).unwrap()
145        }};
146    }
147
148    macro_rules! duration_lt_second {
149        ($nanos_per_interval:expr, $count:expr) => {{
150            let nanos = ($nanos_per_interval) * ($count);
151            if nanos.is_infinite() || nanos > i64::MAX as f64 || nanos < i64::MIN as f64 {
152                return Err(error::Error::DurationOverflow);
153            }
154            ::chrono::TimeDelta::nanoseconds(nanos.round() as i64)
155        }};
156    }
157
158    impl TryFrom<Container> for ::chrono::TimeDelta {
159        type Error = error::Error;
160
161        /// Convert a [`Duration`] into a [`::chrono::TimeDelta`]
162        fn try_from(durations: Container) -> Result<Self, Self::Error> {
163            let mut duration_sum = Self::new(0, 0).unwrap();
164            for duration in &durations.0 {
165                duration_sum += match duration {
166                    Duration::Year(count) => {
167                        duration_ge_second!(Convert::SECS_PER_YEAR, count)
168                    }
169                    Duration::Month(count) => {
170                        duration_ge_second!(Convert::SECS_PER_MONTH, count)
171                    }
172                    Duration::Week(count) => {
173                        duration_ge_second!(Convert::SECS_PER_WEEK, count)
174                    }
175                    Duration::Day(count) => {
176                        duration_ge_second!(Convert::SECS_PER_DAY, count)
177                    }
178                    Duration::Hour(count) => {
179                        duration_ge_second!(Convert::SECS_PER_HOUR, count)
180                    }
181                    Duration::Minute(count) => {
182                        duration_ge_second!(Convert::SECS_PER_MIN, count)
183                    }
184                    Duration::Second(count) => duration_ge_second!(1.0f64, count),
185                    Duration::Millisecond(count) => {
186                        duration_lt_second!(Convert::NANOS_PER_MILLI, count)
187                    }
188                    Duration::Microsecond(count) => {
189                        duration_lt_second!(Convert::NANOS_PER_MICRO, count)
190                    }
191                    Duration::Nanosecond(count) => Self::nanoseconds(*count),
192                };
193            }
194
195            Ok(duration_sum)
196        }
197    }
198}
199
200/// Conversions from [`Duration`] into [`::time::Duration`]
201#[cfg(feature = "with-time")]
202pub mod time {
203    use super::{error, Container, Convert, Duration, TryFrom};
204
205    macro_rules! duration_ge_second {
206        ($secs_per_interval:expr, $count:expr) => {{
207            ::time::Duration::checked_seconds_f64(($secs_per_interval) * ($count))
208                .ok_or(error::Error::DurationOverflow)?
209        }};
210    }
211
212    macro_rules! duration_lt_second {
213        ($nanos_per_interval:expr, $count:expr) => {{
214            let nanos = ($nanos_per_interval) * ($count);
215            if nanos.is_infinite() || nanos > i64::MAX as f64 || nanos < i64::MIN as f64 {
216                return Err(error::Error::DurationOverflow);
217            }
218            ::time::Duration::nanoseconds(nanos.round() as i64)
219        }};
220    }
221
222    /// Convert a [`Duration`] into a [`::time::Duration`]
223    impl TryFrom<Container> for ::time::Duration {
224        type Error = error::Error;
225
226        fn try_from(durations: Container) -> Result<Self, Self::Error> {
227            let mut duration_sum = Self::new(0, 0);
228
229            for duration in &durations.0 {
230                duration_sum += match duration {
231                    Duration::Year(count) => {
232                        duration_ge_second!(Convert::SECS_PER_YEAR, count)
233                    }
234                    Duration::Month(count) => {
235                        duration_ge_second!(Convert::SECS_PER_MONTH, count)
236                    }
237                    Duration::Week(count) => {
238                        duration_ge_second!(Convert::SECS_PER_WEEK, count)
239                    }
240                    Duration::Day(count) => {
241                        duration_ge_second!(Convert::SECS_PER_DAY, count)
242                    }
243                    Duration::Hour(count) => {
244                        duration_ge_second!(Convert::SECS_PER_HOUR, count)
245                    }
246                    Duration::Minute(count) => {
247                        duration_ge_second!(Convert::SECS_PER_MIN, count)
248                    }
249                    Duration::Second(count) => duration_ge_second!(1.0, count),
250                    Duration::Millisecond(count) => {
251                        duration_lt_second!(Convert::NANOS_PER_MILLI, count)
252                    }
253                    Duration::Microsecond(count) => {
254                        duration_lt_second!(Convert::NANOS_PER_MICRO, count)
255                    }
256                    Duration::Nanosecond(count) => Self::nanoseconds(*count),
257                }
258            }
259
260            Ok(duration_sum)
261        }
262    }
263}