irox_units/units/
duration.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2023 IROX Contributors
3
4//!
5//! Contains [`Duration`] and [`DurationUnit`], a Physical Quantity of amount of Time passed.
6//!
7
8use crate::units::{FromUnits, Unit};
9use core::fmt::{Display, Formatter};
10
11///
12/// Represents a specific duration unit - SI or otherwise.
13#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
14#[non_exhaustive]
15pub enum DurationUnit {
16    /// SI Base Unit for Duration - Second.
17    ///
18    /// The second division of the hour by 60.
19    #[default]
20    Second,
21
22    /// A milli-second, one-thousandth of a second (1e-3)
23    Millisecond,
24
25    /// A micro-second, one-millionth of a second (1e-6)
26    Microsecond,
27
28    /// A nano-second, one-billionth of a second (1e-9)
29    Nanosecond,
30
31    /// A minute, the first division of an hour by 60.
32    Minute,
33
34    /// An hour, 24 in a day.
35    Hour,
36
37    /// NIST 811 defines a "Day" as 86400 seconds, without the concept of leap seconds.
38    Day,
39
40    /// NIST 811 defines a "Year" as 365 days, or 31_536_000 seconds, without the concept of leap
41    /// days.
42    Year,
43}
44
45impl DurationUnit {
46    ///
47    /// Converts the specified value into Seconds
48    pub fn as_seconds(self, value: f64) -> f64 {
49        Duration::new(value, self)
50            .as_unit(DurationUnit::Second)
51            .value
52    }
53    ///
54    /// Converts the specified value into Milliseconds
55    pub fn as_millis(self, value: f64) -> f64 {
56        Duration::new(value, self)
57            .as_unit(DurationUnit::Millisecond)
58            .value
59    }
60
61    ///
62    /// Converts the specified value into Microseconds
63    pub fn as_micros(self, value: f64) -> f64 {
64        Duration::new(value, self)
65            .as_unit(DurationUnit::Microsecond)
66            .value
67    }
68
69    ///
70    /// Converts the specified value into Nanoseconds
71    pub fn as_nanos(self, value: f64) -> f64 {
72        Duration::new(value, self)
73            .as_unit(DurationUnit::Nanosecond)
74            .value
75    }
76
77    ///
78    /// Converts the specified value into Minutes
79    pub fn as_minutes(self, value: f64) -> f64 {
80        Duration::new(value, self)
81            .as_unit(DurationUnit::Minute)
82            .value
83    }
84
85    ///
86    /// Converts the specified value into Hours
87    pub fn as_hours(self, value: f64) -> f64 {
88        Duration::new(value, self).as_unit(DurationUnit::Hour).value
89    }
90}
91
92macro_rules! from_units_duration {
93    ($type:ident) => {
94        impl crate::units::FromUnits<$type> for DurationUnit {
95            fn from(&self, value: $type, units: Self) -> $type {
96                match self {
97                    // target
98                    DurationUnit::Nanosecond => match units {
99                        // source
100                        DurationUnit::Nanosecond => value as $type,
101                        DurationUnit::Microsecond => value * MICROS_TO_NANOS as $type,
102                        DurationUnit::Millisecond => value * MILLIS_TO_NANOS as $type,
103                        DurationUnit::Second => value * SEC_TO_NANOS as $type,
104                        DurationUnit::Minute => value * MIN_TO_NANOS as $type,
105                        DurationUnit::Hour => value * HOUR_TO_NANOS as $type,
106                        DurationUnit::Day => value * DAY_TO_NANOS as $type,
107                        DurationUnit::Year => value * YEAR_TO_NANOS as $type,
108                    },
109                    DurationUnit::Microsecond => match units {
110                        // source
111                        DurationUnit::Nanosecond => value * NANOS_TO_MICROS as $type,
112                        DurationUnit::Microsecond => value as $type,
113                        DurationUnit::Millisecond => value * MILLIS_TO_MICROS as $type,
114                        DurationUnit::Second => value * SEC_TO_MILLIS as $type,
115                        DurationUnit::Minute => value * MIN_TO_MICROS as $type,
116                        DurationUnit::Hour => value * HOUR_TO_MICROS as $type,
117                        DurationUnit::Day => value * DAY_TO_MICROS as $type,
118                        DurationUnit::Year => value * YEAR_TO_MICROS as $type,
119                    },
120                    DurationUnit::Millisecond => match units {
121                        // source
122                        DurationUnit::Nanosecond => value * NANOS_TO_MILLIS as $type,
123                        DurationUnit::Microsecond => value * MICROS_TO_MILLIS as $type,
124                        DurationUnit::Millisecond => value as $type,
125                        DurationUnit::Second => value * SEC_TO_MILLIS as $type,
126                        DurationUnit::Minute => value * MIN_TO_MILLIS as $type,
127                        DurationUnit::Hour => value * HOUR_TO_MILLIS as $type,
128                        DurationUnit::Day => value * DAY_TO_MILLIS as $type,
129                        DurationUnit::Year => value * YEAR_TO_MILLIS as $type,
130                    },
131                    DurationUnit::Second => match units {
132                        // source
133                        DurationUnit::Nanosecond => value * NANOS_TO_SEC as $type,
134                        DurationUnit::Microsecond => value * MICROS_TO_SECS as $type,
135                        DurationUnit::Millisecond => value * MILLIS_TO_SEC as $type,
136                        DurationUnit::Second => value as $type,
137                        DurationUnit::Minute => value * MIN_TO_SEC as $type,
138                        DurationUnit::Hour => value * HOUR_TO_SEC as $type,
139                        DurationUnit::Day => value * DAY_TO_SEC as $type,
140                        DurationUnit::Year => value * YEAR_TO_SEC as $type,
141                    },
142                    DurationUnit::Minute => match units {
143                        // source
144                        DurationUnit::Nanosecond => value * NANOS_TO_MIN as $type,
145                        DurationUnit::Microsecond => value * MICROS_TO_MIN as $type,
146                        DurationUnit::Millisecond => value * MILLIS_TO_MIN as $type,
147                        DurationUnit::Second => value * SEC_TO_MIN as $type,
148                        DurationUnit::Minute => value as $type,
149                        DurationUnit::Hour => value * HOUR_TO_MIN as $type,
150                        DurationUnit::Day => value * DAY_TO_MIN as $type,
151                        DurationUnit::Year => value * YEAR_TO_MIN as $type,
152                    },
153                    DurationUnit::Hour => match units {
154                        // source
155                        DurationUnit::Nanosecond => value * NANOS_TO_HOUR as $type,
156                        DurationUnit::Microsecond => value * MICROS_TO_HOUR as $type,
157                        DurationUnit::Millisecond => value * MILLIS_TO_HOUR as $type,
158                        DurationUnit::Second => value * SEC_TO_HOUR as $type,
159                        DurationUnit::Minute => value * MIN_TO_HOUR as $type,
160                        DurationUnit::Hour => value as $type,
161                        DurationUnit::Day => value * DAY_TO_HOUR as $type,
162                        DurationUnit::Year => value * YEAR_TO_HOUR as $type,
163                    },
164                    DurationUnit::Day => match units {
165                        // source
166                        DurationUnit::Nanosecond => value * NANOS_TO_DAY as $type,
167                        DurationUnit::Microsecond => value * MICROS_TO_DAY as $type,
168                        DurationUnit::Millisecond => value * MILLIS_TO_DAY as $type,
169                        DurationUnit::Second => value * SEC_TO_DAY as $type,
170                        DurationUnit::Minute => value * MIN_TO_DAY as $type,
171                        DurationUnit::Hour => value * HOUR_TO_DAY as $type,
172                        DurationUnit::Day => value as $type,
173                        DurationUnit::Year => value * YEAR_TO_DAY as $type,
174                    },
175                    DurationUnit::Year => match units {
176                        // source
177                        DurationUnit::Nanosecond => value * NANOS_TO_YEAR as $type,
178                        DurationUnit::Microsecond => value * MICROS_TO_YEAR as $type,
179                        DurationUnit::Millisecond => value * MILLIS_TO_YEAR as $type,
180                        DurationUnit::Second => value * SEC_TO_YEAR as $type,
181                        DurationUnit::Minute => value * MIN_TO_YEAR as $type,
182                        DurationUnit::Hour => value * HOUR_TO_YEAR as $type,
183                        DurationUnit::Day => value * DAY_TO_YEAR as $type,
184                        DurationUnit::Year => value as $type,
185                    },
186                }
187            }
188        }
189    };
190}
191
192basic_unit!(Duration, DurationUnit, Second);
193from_units_duration!(u32);
194from_units_duration!(i32);
195from_units_duration!(u64);
196from_units_duration!(i64);
197from_units_duration!(f32);
198from_units_duration!(f64);
199
200impl From<core::time::Duration> for Duration {
201    fn from(value: core::time::Duration) -> Self {
202        Duration::new(value.as_secs_f64(), DurationUnit::Second)
203    }
204}
205
206impl From<Duration> for core::time::Duration {
207    fn from(value: Duration) -> Self {
208        let secs = value.as_seconds();
209        let frac_sec = value.as_seconds_f64() - secs as f64;
210        let nanos = DurationUnit::Second.as_nanos(frac_sec) as u32;
211        core::time::Duration::new(secs, nanos)
212    }
213}
214
215impl Duration {
216    ///
217    /// Creates a new duration using the specified number of seconds
218    pub const fn new_seconds(value: f64) -> Duration {
219        Duration {
220            value,
221            units: DurationUnit::Second,
222        }
223    }
224
225    ///
226    /// Returns this duration as (Years, Days, Hours, Minutes, Seconds)
227    pub fn as_ydhms(&self) -> (u64, u16, u8, u8, u8) {
228        let mut rem = *self;
229        let years = rem.as_years();
230        rem -= Duration::from_years(years);
231        let (d, h, m, s) = rem.as_dhms();
232        (years, d as u16, h, m, s)
233    }
234
235    ///
236    /// Returns this duration as (Days, Hours, Minutes, Seconds)
237    pub fn as_dhms(&self) -> (u64, u8, u8, u8) {
238        let mut rem = *self;
239        let days = rem.as_days();
240        rem -= Duration::from_days(days);
241        let (h, m, s) = rem.as_hms();
242        (days, h as u8, m, s)
243    }
244
245    ///
246    /// Returns this duration as (Hours, Minutes, Seconds)
247    pub fn as_hms(&self) -> (u64, u8, u8) {
248        let mut rem = *self;
249        let hours = rem.as_hours();
250        rem -= Duration::from_hours(hours);
251        let minutes = rem.as_minutes();
252        rem -= Duration::from_minutes(minutes);
253        let seconds = rem.as_seconds();
254        (hours, minutes as u8, seconds as u8)
255    }
256
257    /// Returns the value of this duration as whole seconds, with any fractional
258    /// element truncated off.
259    pub fn as_seconds(&self) -> u64 {
260        self.as_unit(DurationUnit::Second).value() as u64
261    }
262
263    /// Returns the value of this duration in fractional seconds
264    pub fn as_seconds_f64(&self) -> f64 {
265        self.as_unit(DurationUnit::Second).value()
266    }
267
268    /// Returns the value of this duration in fractional seconds
269    pub fn as_seconds_f32(&self) -> f32 {
270        self.as_unit(DurationUnit::Second).value() as f32
271    }
272
273    /// Returns the value of this duration as whole milliseconds, with any
274    /// fractional element truncated off.
275    pub fn as_millis(&self) -> u64 {
276        self.as_unit(DurationUnit::Millisecond).value() as u64
277    }
278
279    /// Returns the value of this duration as whole microseconds, with any
280    /// fractional element truncated off.
281    pub fn as_micros(&self) -> u64 {
282        self.as_unit(DurationUnit::Microsecond).value() as u64
283    }
284
285    /// Returns the value of this duration as whole microseconds, with any
286    /// fractional element truncated off.
287    pub fn as_nanos(&self) -> u64 {
288        self.as_unit(DurationUnit::Nanosecond).value() as u64
289    }
290
291    /// Returns the value of this duration as whole minutes, with any fractional
292    /// element truncated off
293    pub fn as_minutes(&self) -> u64 {
294        self.as_unit(DurationUnit::Minute).value() as u64
295    }
296
297    /// Returns the value of this duration as whole hours, with any fractional
298    /// element truncated off
299    pub fn as_hours(&self) -> u64 {
300        self.as_unit(DurationUnit::Hour).value() as u64
301    }
302
303    /// Returns the value of this duration as whole days, with any fractional
304    /// element truncated off
305    pub fn as_days(&self) -> u64 {
306        self.as_unit(DurationUnit::Day).value() as u64
307    }
308
309    /// Returns the value of this duration as whole years, with any fractional
310    /// element truncated off
311    pub fn as_years(&self) -> u64 {
312        self.as_unit(DurationUnit::Year).value() as u64
313    }
314}
315
316// Backwards compatibility for [`core::time::Duration`] drop-in creation
317impl Duration {
318    /// Creates a new `Duration` from the specified number of microseconds.
319    ///
320    /// # Examples
321    ///
322    /// ```
323    /// use irox_units::units::duration::Duration;
324    ///
325    /// let duration = Duration::from_micros(1_000_002);
326    ///
327    /// assert_eq!(1, duration.as_seconds());
328    /// ```
329    pub const fn from_micros(micros: u64) -> Duration {
330        Duration::new(micros as f64, DurationUnit::Microsecond)
331    }
332
333    /// Creates a new `Duration` from the specified number of milliseconds.
334    ///
335    /// # Examples
336    ///
337    /// ```
338    /// use irox_units::units::duration::Duration;
339    ///
340    /// let duration = Duration::from_millis(2569);
341    ///
342    /// assert_eq!(2, duration.as_seconds());
343    /// ```
344    pub const fn from_millis(millis: u64) -> Duration {
345        Duration::new(millis as f64, DurationUnit::Millisecond)
346    }
347
348    /// Creates a new `Duration` from the specified number of nanoseconds.
349    ///
350    /// # Examples
351    ///
352    /// ```
353    /// use irox_units::units::duration::Duration;
354    ///
355    /// let duration = Duration::from_nanos(1_000_000_123);
356    ///
357    /// assert_eq!(1, duration.as_seconds());
358    /// ```
359    pub const fn from_nanos(nanos: u64) -> Duration {
360        Duration::new(nanos as f64, DurationUnit::Nanosecond)
361    }
362
363    /// Creates a new `Duration` from the specified number of minutes.
364    ///
365    /// # Examples
366    ///
367    /// ```
368    /// use irox_units::units::duration::Duration;
369    ///
370    /// let duration = Duration::from_minutes(1);
371    ///
372    /// assert_eq!(60, duration.as_seconds());
373    /// ```
374    pub const fn from_minutes(minutes: u64) -> Duration {
375        Duration::new(minutes as f64, DurationUnit::Minute)
376    }
377
378    /// Creates a new `Duration` from the specified number of hours.
379    ///
380    /// # Examples
381    ///
382    /// ```
383    /// use irox_units::units::duration::Duration;
384    ///
385    /// let duration = Duration::from_hours(1);
386    ///
387    /// assert_eq!(3600, duration.as_seconds());
388    /// ```
389    pub const fn from_hours(hours: u64) -> Duration {
390        Duration::new(hours as f64, DurationUnit::Hour)
391    }
392
393    /// Creates a new `Duration` from the specified number of NIST 811 Days where 1 Day = 86400 Seconds
394    ///
395    /// # Examples
396    ///
397    /// ```
398    /// use irox_units::units::duration::Duration;
399    ///
400    /// let duration = Duration::from_days(1);
401    ///
402    /// assert_eq!(86400, duration.as_seconds());
403    /// ```
404    pub const fn from_days(days: u64) -> Duration {
405        Duration::new(days as f64, DurationUnit::Day)
406    }
407
408    /// Creates a new `Duration` from the specified number of NIST 811 Years where 1 Year = 365 Days.
409    ///
410    /// # Examples
411    ///
412    /// ```
413    /// use irox_units::units::duration::Duration;
414    ///
415    /// let duration = Duration::from_years(1);
416    ///
417    /// assert_eq!(31_536_000, duration.as_seconds());
418    /// ```
419    pub const fn from_years(years: u64) -> Duration {
420        Duration::new(years as f64, DurationUnit::Year)
421    }
422
423    /// Creates a new `Duration` from the specified number of seconds.
424    ///
425    /// # Examples
426    ///
427    /// ```
428    /// use irox_units::units::duration::Duration;
429    ///
430    /// let duration = Duration::from_seconds(100);
431    ///
432    /// assert_eq!(100, duration.as_seconds());
433    /// ```
434    pub const fn from_seconds(seconds: u64) -> Duration {
435        Duration::new(seconds as f64, DurationUnit::Second)
436    }
437
438    /// Creates a new `Duration` from the specified number of f64 seconds.
439    ///
440    /// # Examples
441    ///
442    /// ```
443    /// use irox_units::units::duration::Duration;
444    ///
445    /// let duration = Duration::from_seconds_f64(25.5);
446    ///
447    /// assert_eq!(25.5, duration.as_seconds_f64());
448    /// ```
449    pub const fn from_seconds_f64(seconds: f64) -> Duration {
450        Duration::new(seconds, DurationUnit::Second)
451    }
452}
453
454impl Display for Duration {
455    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
456        f.write_fmt(format_args!("{} {:?}", self.value, self.units))
457    }
458}
459
460// going up
461pub const NANOS_TO_MICROS: f64 = 1e-3;
462pub const MICROS_TO_MILLIS: f64 = 1e-3;
463pub const MILLIS_TO_SEC: f64 = 1e-3;
464pub const SEC_TO_MIN: f64 = 1. / MIN_TO_SEC;
465pub const MIN_TO_HOUR: f64 = 1. / HOUR_TO_MIN;
466pub const HOUR_TO_DAY: f64 = 1. / DAY_TO_HOUR;
467pub const DAY_TO_YEAR: f64 = 1. / YEAR_TO_DAY;
468
469// going down
470pub const YEAR_TO_DAY: f64 = 365_f64;
471pub const DAY_TO_HOUR: f64 = 24_f64;
472pub const HOUR_TO_MIN: f64 = 60_f64;
473pub const MIN_TO_SEC: f64 = 60_f64;
474pub const SEC_TO_MILLIS: f64 = 1e3;
475pub const MILLIS_TO_MICROS: f64 = 1e3;
476pub const MICROS_TO_NANOS: f64 = 1e3;
477
478// going down double jumps
479pub const YEAR_TO_HOUR: f64 = YEAR_TO_DAY * DAY_TO_HOUR;
480pub const DAY_TO_MIN: f64 = DAY_TO_HOUR * HOUR_TO_MIN;
481pub const HOUR_TO_SEC: f64 = HOUR_TO_MIN * MIN_TO_SEC;
482pub const MIN_TO_MILLIS: f64 = MIN_TO_SEC * SEC_TO_MILLIS;
483pub const SEC_TO_MICROS: f64 = SEC_TO_MILLIS * MILLIS_TO_MICROS;
484pub const MILLIS_TO_NANOS: f64 = MILLIS_TO_MICROS * MICROS_TO_NANOS;
485
486// going up double jumps
487pub const NANOS_TO_MILLIS: f64 = NANOS_TO_MICROS * MICROS_TO_MILLIS;
488pub const MICROS_TO_SECS: f64 = MICROS_TO_MILLIS * MILLIS_TO_SEC;
489pub const MILLIS_TO_MIN: f64 = MILLIS_TO_SEC * SEC_TO_MIN;
490pub const SEC_TO_HOUR: f64 = SEC_TO_MIN * MIN_TO_HOUR;
491pub const MIN_TO_DAY: f64 = MIN_TO_HOUR * HOUR_TO_DAY;
492pub const HOUR_TO_YEAR: f64 = HOUR_TO_DAY * DAY_TO_YEAR;
493
494// going down triples
495pub const YEAR_TO_MIN: f64 = YEAR_TO_HOUR * HOUR_TO_MIN;
496pub const DAY_TO_SEC: f64 = DAY_TO_MIN * MIN_TO_SEC;
497pub const HOUR_TO_MILLIS: f64 = HOUR_TO_SEC * SEC_TO_MILLIS;
498pub const MIN_TO_MICROS: f64 = MIN_TO_MILLIS * MILLIS_TO_MICROS;
499pub const SEC_TO_NANOS: f64 = SEC_TO_MICROS * MICROS_TO_NANOS;
500
501// going up triples
502pub const NANOS_TO_SEC: f64 = NANOS_TO_MILLIS * MILLIS_TO_SEC;
503pub const MICROS_TO_MIN: f64 = MICROS_TO_SECS * SEC_TO_MIN;
504pub const MILLIS_TO_HOUR: f64 = MILLIS_TO_MIN * MIN_TO_HOUR;
505pub const SEC_TO_DAY: f64 = SEC_TO_HOUR * HOUR_TO_DAY;
506pub const MIN_TO_YEAR: f64 = MIN_TO_DAY * DAY_TO_YEAR;
507
508// going down quads
509pub const YEAR_TO_SEC: f64 = YEAR_TO_MIN * MIN_TO_SEC;
510pub const DAY_TO_MILLIS: f64 = DAY_TO_SEC * SEC_TO_MILLIS;
511pub const HOUR_TO_MICROS: f64 = HOUR_TO_MILLIS * MILLIS_TO_MICROS;
512pub const MIN_TO_NANOS: f64 = MIN_TO_MICROS * MICROS_TO_NANOS;
513
514// going up quads
515pub const NANOS_TO_MIN: f64 = NANOS_TO_SEC * SEC_TO_MIN;
516pub const MICROS_TO_HOUR: f64 = MICROS_TO_MIN * MIN_TO_HOUR;
517pub const MILLIS_TO_DAY: f64 = MILLIS_TO_HOUR * HOUR_TO_DAY;
518pub const SEC_TO_YEAR: f64 = SEC_TO_DAY * DAY_TO_YEAR;
519
520// going down pentas
521pub const YEAR_TO_MILLIS: f64 = YEAR_TO_SEC * SEC_TO_MILLIS;
522pub const DAY_TO_MICROS: f64 = DAY_TO_MILLIS * MILLIS_TO_MICROS;
523pub const HOUR_TO_NANOS: f64 = HOUR_TO_MICROS * MICROS_TO_NANOS;
524
525// going up pentas
526pub const NANOS_TO_HOUR: f64 = NANOS_TO_MIN * MIN_TO_HOUR;
527pub const MICROS_TO_DAY: f64 = MICROS_TO_HOUR * HOUR_TO_DAY;
528pub const MILLIS_TO_YEAR: f64 = MILLIS_TO_DAY * DAY_TO_YEAR;
529
530// going down hexas
531pub const YEAR_TO_MICROS: f64 = YEAR_TO_MILLIS * MILLIS_TO_MICROS;
532pub const DAY_TO_NANOS: f64 = DAY_TO_MICROS * MICROS_TO_NANOS;
533
534// going up hexas
535pub const NANOS_TO_DAY: f64 = NANOS_TO_HOUR * HOUR_TO_DAY;
536pub const MICROS_TO_YEAR: f64 = MICROS_TO_DAY * DAY_TO_YEAR;
537
538// going down septs
539pub const YEAR_TO_NANOS: f64 = YEAR_TO_MICROS * MICROS_TO_NANOS;
540
541// going up septs
542pub const NANOS_TO_YEAR: f64 = NANOS_TO_DAY * DAY_TO_YEAR;