systemd_duration/
duration.rs1use std::convert::TryFrom;
2
3use crate::error;
4
5#[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#[derive(Clone, Debug)]
22pub struct Container(Vec<Duration>);
23
24impl Container {
25 #[must_use]
27 pub fn new(durations: Vec<Duration>) -> Self {
28 Self(durations)
29 }
30}
31
32#[allow(clippy::module_name_repetitions)]
34struct Convert;
35
36impl 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
53pub 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 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 #[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#[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 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#[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 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}