systemd_duration/
duration.rs1use std::convert::TryFrom;
13
14use crate::error;
15
16#[derive(Copy, Clone, Debug)]
18pub enum Duration {
19 Year(f64),
20 Month(f64),
21 Week(f64),
22 Day(f64),
23 Hour(f64),
24 Minute(f64),
25 Second(f64),
26 Millisecond(f64),
27 Microsecond(f64),
28 Nanosecond(i64),
29}
30
31#[derive(Clone, Debug)]
33pub struct Container(Vec<Duration>);
34
35impl Container {
36 #[must_use]
38 pub const fn new(durations: Vec<Duration>) -> Self {
39 Self(durations)
40 }
41}
42
43#[allow(clippy::module_name_repetitions)]
45struct Convert;
46
47impl Convert {
53 const SECS_PER_MIN: f64 = 60.0;
54 const SECS_PER_HOUR: f64 = 60.0 * Self::SECS_PER_MIN;
55 const SECS_PER_DAY: f64 = 24.0 * Self::SECS_PER_HOUR;
56 const SECS_PER_WEEK: f64 = 7.0 * Self::SECS_PER_DAY;
57 const SECS_PER_MONTH: f64 = 30.436_875_f64 * Self::SECS_PER_DAY;
58 const SECS_PER_YEAR: f64 = 365.242_5_f64 * Self::SECS_PER_DAY;
59 const NANOS_PER_SEC: f64 = 1_000_000_000.0;
60 const NANOS_PER_MILLI: f64 = Self::NANOS_PER_SEC / 1_000.0;
61 const NANOS_PER_MICRO: f64 = Self::NANOS_PER_MILLI / 1_000.0;
62}
63
64pub mod stdtime {
66 use super::{error, Container, Convert, Duration, TryFrom};
67
68 macro_rules! duration_ge_second {
69 ($secs_per_interval:expr, $count:expr) => {{
70 let sign = ($count).signum();
71 if sign <= -1.0 || sign.is_nan() {
72 return Err(error::Error::DurationOverflow);
73 }
74
75 ::std::time::Duration::from_secs_f64(($secs_per_interval) * ($count))
76 }};
77 }
78
79 macro_rules! duration_lt_second {
80 ($nanos_per_interval:expr, $count:expr) => {{
81 let nanos: f64 = ($nanos_per_interval) * ($count);
82 if !nanos.is_finite() {
83 return Err(error::Error::DurationOverflow);
84 }
85
86 let rounded = nanos.round();
87 #[allow(clippy::cast_possible_truncation)]
88 let int_nanos = rounded as i64;
89
90 #[allow(clippy::cast_precision_loss)]
92 if (int_nanos as f64 - rounded).abs() > f64::EPSILON {
93 return Err(error::Error::DurationOverflow);
94 }
95
96 match u64::try_from(int_nanos) {
97 Ok(valid) => ::std::time::Duration::from_nanos(valid),
98 Err(_) => return Err(error::Error::DurationOverflow),
99 }
100 }};
101 }
102
103 impl TryFrom<Container> for std::time::Duration {
104 type Error = error::Error;
105
106 fn try_from(durations: Container) -> Result<Self, Self::Error> {
108 let mut duration_sum = Self::new(0, 0);
109
110 for duration in &durations.0 {
111 duration_sum += match duration {
112 Duration::Year(count) => {
113 duration_ge_second!(Convert::SECS_PER_YEAR, count)
114 }
115 Duration::Month(count) => {
116 duration_ge_second!(Convert::SECS_PER_MONTH, count)
117 }
118 Duration::Week(count) => {
119 duration_ge_second!(Convert::SECS_PER_WEEK, count)
120 }
121 Duration::Day(count) => {
122 duration_ge_second!(Convert::SECS_PER_DAY, count)
123 }
124 Duration::Hour(count) => {
125 duration_ge_second!(Convert::SECS_PER_HOUR, count)
126 }
127 Duration::Minute(count) => {
128 duration_ge_second!(Convert::SECS_PER_MIN, count)
129 }
130 Duration::Second(count) => duration_ge_second!(1.0, count),
131 Duration::Millisecond(count) => {
132 duration_lt_second!(Convert::NANOS_PER_MILLI, count)
133 }
134 Duration::Microsecond(count) => {
135 duration_lt_second!(Convert::NANOS_PER_MICRO, count)
136 }
137 Duration::Nanosecond(count) => {
138 if *count < 0 {
139 return Err(error::Error::DurationOverflow);
140 }
141
142 #[allow(clippy::cast_sign_loss)]
144 Self::from_nanos(*count as u64)
145 }
146 }
147 }
148
149 Ok(duration_sum)
150 }
151 }
152}
153
154#[cfg(feature = "with-chrono")]
156pub mod chrono {
157 use super::{error, Container, Convert, Duration, TryFrom};
158
159 macro_rules! duration_ge_second {
160 ($secs_per_interval:expr, $count:expr) => {{
161 let seconds = ($secs_per_interval) * ($count);
162 if seconds.is_infinite() || seconds > i64::MAX as f64 || seconds < i64::MIN as f64 {
163 return Err(error::Error::DurationOverflow);
164 }
165 let (seconds, nanos) = (
166 seconds.trunc(),
167 (seconds - seconds.trunc()) * Convert::NANOS_PER_SEC,
168 );
169 ::chrono::TimeDelta::new(seconds as i64, nanos as u32).unwrap()
170 }};
171 }
172
173 macro_rules! duration_lt_second {
174 ($nanos_per_interval:expr, $count:expr) => {{
175 let nanos = ($nanos_per_interval) * ($count);
176 if nanos.is_infinite() || nanos > i64::MAX as f64 || nanos < i64::MIN as f64 {
177 return Err(error::Error::DurationOverflow);
178 }
179 ::chrono::TimeDelta::nanoseconds(nanos.round() as i64)
180 }};
181 }
182
183 impl TryFrom<Container> for ::chrono::TimeDelta {
184 type Error = error::Error;
185
186 fn try_from(durations: Container) -> Result<Self, Self::Error> {
188 let mut duration_sum = Self::new(0, 0).unwrap();
189 for duration in &durations.0 {
190 duration_sum += match duration {
191 Duration::Year(count) => {
192 duration_ge_second!(Convert::SECS_PER_YEAR, count)
193 }
194 Duration::Month(count) => {
195 duration_ge_second!(Convert::SECS_PER_MONTH, count)
196 }
197 Duration::Week(count) => {
198 duration_ge_second!(Convert::SECS_PER_WEEK, count)
199 }
200 Duration::Day(count) => {
201 duration_ge_second!(Convert::SECS_PER_DAY, count)
202 }
203 Duration::Hour(count) => {
204 duration_ge_second!(Convert::SECS_PER_HOUR, count)
205 }
206 Duration::Minute(count) => {
207 duration_ge_second!(Convert::SECS_PER_MIN, count)
208 }
209 Duration::Second(count) => duration_ge_second!(1.0f64, count),
210 Duration::Millisecond(count) => {
211 duration_lt_second!(Convert::NANOS_PER_MILLI, count)
212 }
213 Duration::Microsecond(count) => {
214 duration_lt_second!(Convert::NANOS_PER_MICRO, count)
215 }
216 Duration::Nanosecond(count) => Self::nanoseconds(*count),
217 };
218 }
219
220 Ok(duration_sum)
221 }
222 }
223}
224
225#[cfg(feature = "with-time")]
227pub mod time {
228 use super::{error, Container, Convert, Duration, TryFrom};
229
230 macro_rules! duration_ge_second {
231 ($secs_per_interval:expr, $count:expr) => {{
232 ::time::Duration::checked_seconds_f64(($secs_per_interval) * ($count))
233 .ok_or(error::Error::DurationOverflow)?
234 }};
235 }
236
237 macro_rules! duration_lt_second {
238 ($nanos_per_interval:expr, $count:expr) => {{
239 let nanos = ($nanos_per_interval) * ($count);
240 if nanos.is_infinite() || nanos > i64::MAX as f64 || nanos < i64::MIN as f64 {
241 return Err(error::Error::DurationOverflow);
242 }
243 ::time::Duration::nanoseconds(nanos.round() as i64)
244 }};
245 }
246
247 impl TryFrom<Container> for ::time::Duration {
249 type Error = error::Error;
250
251 fn try_from(durations: Container) -> Result<Self, Self::Error> {
252 let mut duration_sum = Self::new(0, 0);
253
254 for duration in &durations.0 {
255 duration_sum += match duration {
256 Duration::Year(count) => {
257 duration_ge_second!(Convert::SECS_PER_YEAR, count)
258 }
259 Duration::Month(count) => {
260 duration_ge_second!(Convert::SECS_PER_MONTH, count)
261 }
262 Duration::Week(count) => {
263 duration_ge_second!(Convert::SECS_PER_WEEK, count)
264 }
265 Duration::Day(count) => {
266 duration_ge_second!(Convert::SECS_PER_DAY, count)
267 }
268 Duration::Hour(count) => {
269 duration_ge_second!(Convert::SECS_PER_HOUR, count)
270 }
271 Duration::Minute(count) => {
272 duration_ge_second!(Convert::SECS_PER_MIN, count)
273 }
274 Duration::Second(count) => duration_ge_second!(1.0, count),
275 Duration::Millisecond(count) => {
276 duration_lt_second!(Convert::NANOS_PER_MILLI, count)
277 }
278 Duration::Microsecond(count) => {
279 duration_lt_second!(Convert::NANOS_PER_MICRO, count)
280 }
281 Duration::Nanosecond(count) => Self::nanoseconds(*count),
282 }
283 }
284
285 Ok(duration_sum)
286 }
287 }
288}