Skip to main content

irox_units/units/
duration.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2025 IROX Contributors
3//
4
5//!
6//! Contains [`Duration`] and [`DurationUnit`], a Physical Quantity of amount of Time passed.
7//!
8
9use crate::units::{FromUnits, Unit};
10use core::fmt::{Display, Formatter, Write};
11use core::num::ParseFloatError;
12use core::str::Utf8Error;
13use irox_tools::buf::{Buffer, FixedU8Buf};
14use irox_tools::cfg_feature_serde;
15use irox_tools::irox_bits::{BitsError, Error, ErrorKind, FormatBits, MutBits};
16
17///
18/// Represents a specific duration unit - SI or otherwise.
19#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
20#[non_exhaustive]
21pub enum DurationUnit {
22    /// SI Base Unit for Duration - Second.
23    ///
24    /// The second division of the hour by 60.
25    #[default]
26    Second,
27
28    /// A milli-second, one-thousandth of a second (1e-3)
29    Millisecond,
30
31    /// A micro-second, one-millionth of a second (1e-6)
32    Microsecond,
33
34    /// A nano-second, one-billionth of a second (1e-9)
35    Nanosecond,
36
37    /// a pico-second, one-trillionth of a second (1e-12)
38    Picosecond,
39
40    /// A minute, the first division of an hour by 60.
41    Minute,
42
43    /// An hour, 24 in a day.
44    Hour,
45
46    /// NIST 811 defines a "Day" as 86400 seconds, without the concept of leap seconds.
47    Day,
48
49    /// NIST 811 defines a "Year" as 365 days, or 31_536_000 seconds, without the concept of leap
50    /// days.
51    Year,
52}
53
54impl DurationUnit {
55    ///
56    /// Converts the specified value into Seconds
57    pub fn as_seconds(self, value: f64) -> f64 {
58        Duration::new(value, self)
59            .as_unit(DurationUnit::Second)
60            .value
61    }
62    ///
63    /// Converts the specified value into Milliseconds
64    pub fn as_millis(self, value: f64) -> f64 {
65        Duration::new(value, self)
66            .as_unit(DurationUnit::Millisecond)
67            .value
68    }
69
70    ///
71    /// Converts the specified value into Microseconds
72    pub fn as_micros(self, value: f64) -> f64 {
73        Duration::new(value, self)
74            .as_unit(DurationUnit::Microsecond)
75            .value
76    }
77
78    ///
79    /// Converts the specified value into Nanoseconds
80    pub fn as_nanos(self, value: f64) -> f64 {
81        Duration::new(value, self)
82            .as_unit(DurationUnit::Nanosecond)
83            .value
84    }
85
86    ///
87    /// Converts the specified value into Picoseconds
88    pub fn as_picos(self, value: f64) -> f64 {
89        Duration::new(value, self)
90            .as_unit(DurationUnit::Picosecond)
91            .value
92    }
93
94    ///
95    /// Converts the specified value into Minutes
96    pub fn as_minutes(self, value: f64) -> f64 {
97        Duration::new(value, self)
98            .as_unit(DurationUnit::Minute)
99            .value
100    }
101
102    ///
103    /// Converts the specified value into Hours
104    pub fn as_hours(self, value: f64) -> f64 {
105        Duration::new(value, self).as_unit(DurationUnit::Hour).value
106    }
107
108    pub fn abbreviation(&self) -> &'static str {
109        match self {
110            DurationUnit::Second => "s",
111            DurationUnit::Millisecond => "ms",
112            DurationUnit::Microsecond => "us",
113            DurationUnit::Nanosecond => "ns",
114            DurationUnit::Picosecond => "ps",
115            DurationUnit::Minute => "min",
116            DurationUnit::Hour => "hr",
117            DurationUnit::Day => "dy",
118            DurationUnit::Year => "yr",
119        }
120    }
121}
122
123macro_rules! from_units_duration {
124    ($type:ident) => {
125        impl crate::units::FromUnits<$type> for DurationUnit {
126            fn from(&self, value: $type, units: Self) -> $type {
127                match self {
128                    // target
129                    DurationUnit::Picosecond => match units {
130                        // source
131                        DurationUnit::Picosecond => value as $type,
132                        DurationUnit::Nanosecond => value * NANOS_TO_PICOS as $type,
133                        DurationUnit::Microsecond => value * MICROS_TO_PICOS as $type,
134                        DurationUnit::Millisecond => value * MILLIS_TO_PICOS as $type,
135                        DurationUnit::Second => value * SEC_TO_PICOS as $type,
136                        DurationUnit::Minute => value * MIN_TO_PICOS as $type,
137                        DurationUnit::Hour => value * HOUR_TO_PICOS as $type,
138                        DurationUnit::Day => value * DAY_TO_PICOS as $type,
139                        DurationUnit::Year => value * YEAR_TO_PICOS as $type,
140                    },
141                    DurationUnit::Nanosecond => match units {
142                        // source
143                        DurationUnit::Picosecond => value * PICOS_TO_NANOS as $type,
144                        DurationUnit::Nanosecond => value as $type,
145                        DurationUnit::Microsecond => value * MICROS_TO_NANOS as $type,
146                        DurationUnit::Millisecond => value * MILLIS_TO_NANOS as $type,
147                        DurationUnit::Second => value * SEC_TO_NANOS as $type,
148                        DurationUnit::Minute => value * MIN_TO_NANOS as $type,
149                        DurationUnit::Hour => value * HOUR_TO_NANOS as $type,
150                        DurationUnit::Day => value * DAY_TO_NANOS as $type,
151                        DurationUnit::Year => value * YEAR_TO_NANOS as $type,
152                    },
153                    DurationUnit::Microsecond => match units {
154                        // source
155                        DurationUnit::Picosecond => value * PICOS_TO_MICROS as $type,
156                        DurationUnit::Nanosecond => value * NANOS_TO_MICROS as $type,
157                        DurationUnit::Microsecond => value as $type,
158                        DurationUnit::Millisecond => value * MILLIS_TO_MICROS as $type,
159                        DurationUnit::Second => value * SEC_TO_MILLIS as $type,
160                        DurationUnit::Minute => value * MIN_TO_MICROS as $type,
161                        DurationUnit::Hour => value * HOUR_TO_MICROS as $type,
162                        DurationUnit::Day => value * DAY_TO_MICROS as $type,
163                        DurationUnit::Year => value * YEAR_TO_MICROS as $type,
164                    },
165                    DurationUnit::Millisecond => match units {
166                        // source
167                        DurationUnit::Picosecond => value * PICOS_TO_MILLIS as $type,
168                        DurationUnit::Nanosecond => value * NANOS_TO_MILLIS as $type,
169                        DurationUnit::Microsecond => value * MICROS_TO_MILLIS as $type,
170                        DurationUnit::Millisecond => value as $type,
171                        DurationUnit::Second => value * SEC_TO_MILLIS as $type,
172                        DurationUnit::Minute => value * MIN_TO_MILLIS as $type,
173                        DurationUnit::Hour => value * HOUR_TO_MILLIS as $type,
174                        DurationUnit::Day => value * DAY_TO_MILLIS as $type,
175                        DurationUnit::Year => value * YEAR_TO_MILLIS as $type,
176                    },
177                    DurationUnit::Second => match units {
178                        // source
179                        DurationUnit::Picosecond => value * PICOS_TO_SEC as $type,
180                        DurationUnit::Nanosecond => value * NANOS_TO_SEC as $type,
181                        DurationUnit::Microsecond => value * MICROS_TO_SECS as $type,
182                        DurationUnit::Millisecond => value * MILLIS_TO_SEC as $type,
183                        DurationUnit::Second => value as $type,
184                        DurationUnit::Minute => value * MIN_TO_SEC as $type,
185                        DurationUnit::Hour => value * HOUR_TO_SEC as $type,
186                        DurationUnit::Day => value * DAY_TO_SEC as $type,
187                        DurationUnit::Year => value * YEAR_TO_SEC as $type,
188                    },
189                    DurationUnit::Minute => match units {
190                        // source
191                        DurationUnit::Picosecond => value * PICOS_TO_MIN as $type,
192                        DurationUnit::Nanosecond => value * NANOS_TO_MIN as $type,
193                        DurationUnit::Microsecond => value * MICROS_TO_MIN as $type,
194                        DurationUnit::Millisecond => value * MILLIS_TO_MIN as $type,
195                        DurationUnit::Second => value * SEC_TO_MIN as $type,
196                        DurationUnit::Minute => value as $type,
197                        DurationUnit::Hour => value * HOUR_TO_MIN as $type,
198                        DurationUnit::Day => value * DAY_TO_MIN as $type,
199                        DurationUnit::Year => value * YEAR_TO_MIN as $type,
200                    },
201                    DurationUnit::Hour => match units {
202                        // source
203                        DurationUnit::Picosecond => value * PICOS_TO_HOUR as $type,
204                        DurationUnit::Nanosecond => value * NANOS_TO_HOUR as $type,
205                        DurationUnit::Microsecond => value * MICROS_TO_HOUR as $type,
206                        DurationUnit::Millisecond => value * MILLIS_TO_HOUR as $type,
207                        DurationUnit::Second => value * SEC_TO_HOUR as $type,
208                        DurationUnit::Minute => value * MIN_TO_HOUR as $type,
209                        DurationUnit::Hour => value as $type,
210                        DurationUnit::Day => value * DAY_TO_HOUR as $type,
211                        DurationUnit::Year => value * YEAR_TO_HOUR as $type,
212                    },
213                    DurationUnit::Day => match units {
214                        // source
215                        DurationUnit::Picosecond => value * PICOS_TO_DAY as $type,
216                        DurationUnit::Nanosecond => value * NANOS_TO_DAY as $type,
217                        DurationUnit::Microsecond => value * MICROS_TO_DAY as $type,
218                        DurationUnit::Millisecond => value * MILLIS_TO_DAY as $type,
219                        DurationUnit::Second => value * SEC_TO_DAY as $type,
220                        DurationUnit::Minute => value * MIN_TO_DAY as $type,
221                        DurationUnit::Hour => value * HOUR_TO_DAY as $type,
222                        DurationUnit::Day => value as $type,
223                        DurationUnit::Year => value * YEAR_TO_DAY as $type,
224                    },
225                    DurationUnit::Year => match units {
226                        // source
227                        DurationUnit::Picosecond => value * PICOS_TO_YEAR as $type,
228                        DurationUnit::Nanosecond => value * NANOS_TO_YEAR as $type,
229                        DurationUnit::Microsecond => value * MICROS_TO_YEAR as $type,
230                        DurationUnit::Millisecond => value * MILLIS_TO_YEAR as $type,
231                        DurationUnit::Second => value * SEC_TO_YEAR as $type,
232                        DurationUnit::Minute => value * MIN_TO_YEAR as $type,
233                        DurationUnit::Hour => value * HOUR_TO_YEAR as $type,
234                        DurationUnit::Day => value * DAY_TO_YEAR as $type,
235                        DurationUnit::Year => value as $type,
236                    },
237                }
238            }
239        }
240    };
241}
242
243basic_unit!(Duration, DurationUnit, Second);
244from_units_duration!(u32);
245from_units_duration!(i32);
246from_units_duration!(u64);
247from_units_duration!(i64);
248from_units_duration!(f32);
249from_units_duration!(f64);
250
251impl From<core::time::Duration> for Duration {
252    fn from(value: core::time::Duration) -> Self {
253        Duration::new(value.as_secs_f64(), DurationUnit::Second)
254    }
255}
256
257impl From<Duration> for core::time::Duration {
258    fn from(value: Duration) -> Self {
259        let secs = value.as_seconds();
260        let frac_sec = value.as_seconds_f64() - secs as f64;
261        let nanos = DurationUnit::Second.as_nanos(frac_sec) as u32;
262        core::time::Duration::new(secs, nanos)
263    }
264}
265
266impl Duration {
267    ///
268    /// Creates a new duration using the specified number of seconds
269    pub const fn new_seconds(value: f64) -> Duration {
270        Duration {
271            value,
272            units: DurationUnit::Second,
273        }
274    }
275
276    ///
277    /// Returns this duration as (Years, Days, Hours, Minutes, Seconds)
278    pub fn as_ydhms(&self) -> (u64, u16, u8, u8, u8) {
279        let mut rem = *self;
280        let years = rem.as_years();
281        rem -= Duration::from_years(years);
282        let (d, h, m, s) = rem.as_dhms();
283        (years, d as u16, h, m, s)
284    }
285
286    ///
287    /// Returns this duration as (Days, Hours, Minutes, Seconds)
288    pub fn as_dhms(&self) -> (u64, u8, u8, u8) {
289        let mut rem = *self;
290        let days = rem.as_days();
291        rem -= Duration::from_days(days);
292        let (h, m, s) = rem.as_hms();
293        (days, h as u8, m, s)
294    }
295
296    ///
297    /// Returns this duration as (Hours, Minutes, Seconds)
298    pub fn as_hms(&self) -> (u64, u8, u8) {
299        let mut rem = *self;
300        let hours = rem.as_hours();
301        rem -= Duration::from_hours(hours);
302        let minutes = rem.as_minutes();
303        rem -= Duration::from_minutes(minutes);
304        let seconds = rem.as_seconds();
305        (hours, minutes as u8, seconds as u8)
306    }
307
308    /// Returns the value of this duration as whole seconds, with any fractional
309    /// element truncated off.
310    pub fn as_seconds(&self) -> u64 {
311        self.as_unit(DurationUnit::Second).value() as u64
312    }
313
314    /// Returns the value of this duration in fractional seconds
315    pub fn as_seconds_f64(&self) -> f64 {
316        self.as_unit(DurationUnit::Second).value()
317    }
318
319    /// Returns the value of this duration in fractional seconds
320    pub fn as_seconds_f32(&self) -> f32 {
321        self.as_unit(DurationUnit::Second).value() as f32
322    }
323
324    /// Returns the value of this duration as whole milliseconds, with any
325    /// fractional element truncated off.
326    pub fn as_millis(&self) -> u64 {
327        self.as_unit(DurationUnit::Millisecond).value() as u64
328    }
329    /// Returns the value of this duration in fractional milliseconds
330    pub fn as_millis_f64(&self) -> f64 {
331        self.as_unit(DurationUnit::Millisecond).value()
332    }
333    /// Returns the value of this duration in fractional milliseconds
334    pub fn as_millis_f32(&self) -> f32 {
335        self.as_unit(DurationUnit::Millisecond).value() as f32
336    }
337
338    /// Returns the value of this duration as whole microseconds, with any
339    /// fractional element truncated off.
340    pub fn as_micros(&self) -> u64 {
341        self.as_unit(DurationUnit::Microsecond).value() as u64
342    }
343    /// Returns the value of this duration in fractional microseconds
344    pub fn as_micros_f64(&self) -> f64 {
345        self.as_unit(DurationUnit::Microsecond).value()
346    }
347    /// Returns the value of this duration in fractional microseconds
348    pub fn as_micros_f32(&self) -> f32 {
349        self.as_unit(DurationUnit::Microsecond).value() as f32
350    }
351
352    /// Returns the value of this duration as whole microseconds, with any
353    /// fractional element truncated off.
354    pub fn as_nanos(&self) -> u64 {
355        self.as_unit(DurationUnit::Nanosecond).value() as u64
356    }
357    /// Returns the value of this duration in fractional nanoseconds
358    pub fn as_nanos_f64(&self) -> f64 {
359        self.as_unit(DurationUnit::Nanosecond).value()
360    }
361    /// Returns the value of this duration in fractional nanoseconds
362    pub fn as_nanos_f32(&self) -> f32 {
363        self.as_unit(DurationUnit::Nanosecond).value() as f32
364    }
365
366    /// Returns the value of this duration as whole microseconds, with any
367    /// fractional element truncated off.
368    pub fn as_picos(&self) -> u64 {
369        self.as_unit(DurationUnit::Picosecond).value() as u64
370    }
371    /// Returns the value of this duration in fractional picoseconds
372    pub fn as_picos_f64(&self) -> f64 {
373        self.as_unit(DurationUnit::Picosecond).value()
374    }
375    /// Returns the value of this duration in fractional picoseconds
376    pub fn as_picos_f32(&self) -> f32 {
377        self.as_unit(DurationUnit::Picosecond).value() as f32
378    }
379
380    /// Returns the value of this duration as whole minutes, with any fractional
381    /// element truncated off
382    pub fn as_minutes(&self) -> u64 {
383        self.as_unit(DurationUnit::Minute).value() as u64
384    }
385
386    /// Returns the value of this duration as whole hours, with any fractional
387    /// element truncated off
388    pub fn as_hours(&self) -> u64 {
389        self.as_unit(DurationUnit::Hour).value() as u64
390    }
391
392    /// Returns the value of this duration as whole days, with any fractional
393    /// element truncated off
394    pub fn as_days(&self) -> u64 {
395        self.as_unit(DurationUnit::Day).value() as u64
396    }
397
398    /// Returns the value of this duration as whole years, with any fractional
399    /// element truncated off
400    pub fn as_years(&self) -> u64 {
401        self.as_unit(DurationUnit::Year).value() as u64
402    }
403}
404
405// Backwards compatibility for [`core::time::Duration`] drop-in creation
406impl Duration {
407    /// Creates a new `Duration` from the specified number of microseconds.
408    ///
409    /// # Examples
410    ///
411    /// ```
412    /// use irox_units::units::duration::Duration;
413    ///
414    /// let duration = Duration::from_micros(1_000_002);
415    ///
416    /// assert_eq!(1, duration.as_seconds());
417    /// ```
418    pub const fn from_micros(micros: u64) -> Duration {
419        Duration::new(micros as f64, DurationUnit::Microsecond)
420    }
421
422    /// Creates a new `Duration` from the specified number of milliseconds.
423    ///
424    /// # Examples
425    ///
426    /// ```
427    /// use irox_units::units::duration::Duration;
428    ///
429    /// let duration = Duration::from_millis(2569);
430    ///
431    /// assert_eq!(2, duration.as_seconds());
432    /// ```
433    pub const fn from_millis(millis: u64) -> Duration {
434        Duration::new(millis as f64, DurationUnit::Millisecond)
435    }
436
437    /// Creates a new `Duration` from the specified number of nanoseconds.
438    ///
439    /// # Examples
440    ///
441    /// ```
442    /// use irox_units::units::duration::Duration;
443    ///
444    /// let duration = Duration::from_nanos(1_000_000_123);
445    ///
446    /// assert_eq!(1, duration.as_seconds());
447    /// ```
448    pub const fn from_nanos(nanos: u64) -> Duration {
449        Duration::new(nanos as f64, DurationUnit::Nanosecond)
450    }
451
452    /// Creates a new `Duration` from the specified number of picoseconds.
453    ///
454    /// # Examples
455    ///
456    /// ```
457    /// use irox_units::units::duration::Duration;
458    ///
459    /// let duration = Duration::from_picos(1_000_000_000_123);
460    ///
461    /// assert_eq!(1, duration.as_seconds());
462    /// ```
463    pub const fn from_picos(picos: u64) -> Duration {
464        Duration::new(picos as f64, DurationUnit::Picosecond)
465    }
466
467    /// Creates a new `Duration` from the specified number of minutes.
468    ///
469    /// # Examples
470    ///
471    /// ```
472    /// use irox_units::units::duration::Duration;
473    ///
474    /// let duration = Duration::from_minutes(1);
475    ///
476    /// assert_eq!(60, duration.as_seconds());
477    /// ```
478    pub const fn from_minutes(minutes: u64) -> Duration {
479        Duration::new(minutes as f64, DurationUnit::Minute)
480    }
481
482    /// Creates a new `Duration` from the specified number of hours.
483    ///
484    /// # Examples
485    ///
486    /// ```
487    /// use irox_units::units::duration::Duration;
488    ///
489    /// let duration = Duration::from_hours(1);
490    ///
491    /// assert_eq!(3600, duration.as_seconds());
492    /// ```
493    pub const fn from_hours(hours: u64) -> Duration {
494        Duration::new(hours as f64, DurationUnit::Hour)
495    }
496
497    /// Creates a new `Duration` from the specified number of NIST 811 Days where 1 Day = 86400 Seconds
498    ///
499    /// # Examples
500    ///
501    /// ```
502    /// use irox_units::units::duration::Duration;
503    ///
504    /// let duration = Duration::from_days(1);
505    ///
506    /// assert_eq!(86400, duration.as_seconds());
507    /// ```
508    pub const fn from_days(days: u64) -> Duration {
509        Duration::new(days as f64, DurationUnit::Day)
510    }
511
512    /// Creates a new `Duration` from the specified number of NIST 811 Years where 1 Year = 365 Days.
513    ///
514    /// # Examples
515    ///
516    /// ```
517    /// use irox_units::units::duration::Duration;
518    ///
519    /// let duration = Duration::from_years(1);
520    ///
521    /// assert_eq!(31_536_000, duration.as_seconds());
522    /// ```
523    pub const fn from_years(years: u64) -> Duration {
524        Duration::new(years as f64, DurationUnit::Year)
525    }
526
527    /// Creates a new `Duration` from the specified number of seconds.
528    ///
529    /// # Examples
530    ///
531    /// ```
532    /// use irox_units::units::duration::Duration;
533    ///
534    /// let duration = Duration::from_seconds(100);
535    ///
536    /// assert_eq!(100, duration.as_seconds());
537    /// ```
538    pub const fn from_seconds(seconds: u64) -> Duration {
539        Duration::new(seconds as f64, DurationUnit::Second)
540    }
541
542    /// Creates a new `Duration` from the specified number of f64 seconds.
543    ///
544    /// # Examples
545    ///
546    /// ```
547    /// use irox_units::units::duration::Duration;
548    ///
549    /// let duration = Duration::from_seconds_f64(25.5);
550    ///
551    /// assert_eq!(25.5, duration.as_seconds_f64());
552    /// ```
553    pub const fn from_seconds_f64(seconds: f64) -> Duration {
554        Duration::new(seconds, DurationUnit::Second)
555    }
556
557    pub fn from_hms(hours: u64, minutes: u64, seconds: u64) -> Duration {
558        Duration::from_hours(hours)
559            + Duration::from_minutes(minutes)
560            + Duration::from_seconds(seconds)
561    }
562}
563
564impl Display for Duration {
565    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
566        f.write_fmt(format_args!("{} {}", self.value, self.units.abbreviation()))
567    }
568}
569
570// going up
571pub const PICOS_TO_NANOS: f64 = 1e-3;
572pub const NANOS_TO_MICROS: f64 = 1e-3;
573pub const MICROS_TO_MILLIS: f64 = 1e-3;
574pub const MILLIS_TO_SEC: f64 = 1e-3;
575pub const SEC_TO_MIN: f64 = 1. / MIN_TO_SEC;
576pub const MIN_TO_HOUR: f64 = 1. / HOUR_TO_MIN;
577pub const HOUR_TO_DAY: f64 = 1. / DAY_TO_HOUR;
578pub const DAY_TO_YEAR: f64 = 1. / YEAR_TO_DAY;
579
580// going down
581pub const YEAR_TO_DAY: f64 = 365_f64;
582pub const DAY_TO_HOUR: f64 = 24_f64;
583pub const HOUR_TO_MIN: f64 = 60_f64;
584pub const MIN_TO_SEC: f64 = 60_f64;
585pub const SEC_TO_MILLIS: f64 = 1e3;
586pub const MILLIS_TO_MICROS: f64 = 1e3;
587pub const MICROS_TO_NANOS: f64 = 1e3;
588pub const NANOS_TO_PICOS: f64 = 1e3;
589
590// going down double jumps
591pub const YEAR_TO_HOUR: f64 = YEAR_TO_DAY * DAY_TO_HOUR;
592pub const DAY_TO_MIN: f64 = DAY_TO_HOUR * HOUR_TO_MIN;
593pub const HOUR_TO_SEC: f64 = HOUR_TO_MIN * MIN_TO_SEC;
594pub const MIN_TO_MILLIS: f64 = MIN_TO_SEC * SEC_TO_MILLIS;
595pub const SEC_TO_MICROS: f64 = SEC_TO_MILLIS * MILLIS_TO_MICROS;
596pub const MILLIS_TO_NANOS: f64 = MILLIS_TO_MICROS * MICROS_TO_NANOS;
597pub const MICROS_TO_PICOS: f64 = MICROS_TO_NANOS * NANOS_TO_PICOS;
598
599// going up double jumps
600pub const PICOS_TO_MICROS: f64 = PICOS_TO_NANOS * NANOS_TO_MICROS;
601pub const NANOS_TO_MILLIS: f64 = NANOS_TO_MICROS * MICROS_TO_MILLIS;
602pub const MICROS_TO_SECS: f64 = MICROS_TO_MILLIS * MILLIS_TO_SEC;
603pub const MILLIS_TO_MIN: f64 = MILLIS_TO_SEC * SEC_TO_MIN;
604pub const SEC_TO_HOUR: f64 = SEC_TO_MIN * MIN_TO_HOUR;
605pub const MIN_TO_DAY: f64 = MIN_TO_HOUR * HOUR_TO_DAY;
606pub const HOUR_TO_YEAR: f64 = HOUR_TO_DAY * DAY_TO_YEAR;
607
608// going down triples
609pub const YEAR_TO_MIN: f64 = YEAR_TO_HOUR * HOUR_TO_MIN;
610pub const DAY_TO_SEC: f64 = DAY_TO_MIN * MIN_TO_SEC;
611pub const HOUR_TO_MILLIS: f64 = HOUR_TO_SEC * SEC_TO_MILLIS;
612pub const MIN_TO_MICROS: f64 = MIN_TO_MILLIS * MILLIS_TO_MICROS;
613pub const SEC_TO_NANOS: f64 = SEC_TO_MICROS * MICROS_TO_NANOS;
614pub const MILLIS_TO_PICOS: f64 = MILLIS_TO_NANOS * NANOS_TO_PICOS;
615
616// going up triples
617pub const PICOS_TO_MILLIS: f64 = PICOS_TO_MICROS * MICROS_TO_MILLIS;
618pub const NANOS_TO_SEC: f64 = NANOS_TO_MILLIS * MILLIS_TO_SEC;
619pub const MICROS_TO_MIN: f64 = MICROS_TO_SECS * SEC_TO_MIN;
620pub const MILLIS_TO_HOUR: f64 = MILLIS_TO_MIN * MIN_TO_HOUR;
621pub const SEC_TO_DAY: f64 = SEC_TO_HOUR * HOUR_TO_DAY;
622pub const MIN_TO_YEAR: f64 = MIN_TO_DAY * DAY_TO_YEAR;
623
624// going down quads
625pub const YEAR_TO_SEC: f64 = YEAR_TO_MIN * MIN_TO_SEC;
626pub const DAY_TO_MILLIS: f64 = DAY_TO_SEC * SEC_TO_MILLIS;
627pub const HOUR_TO_MICROS: f64 = HOUR_TO_MILLIS * MILLIS_TO_MICROS;
628pub const MIN_TO_NANOS: f64 = MIN_TO_MICROS * MICROS_TO_NANOS;
629pub const SEC_TO_PICOS: f64 = SEC_TO_NANOS * NANOS_TO_PICOS;
630
631// going up quads
632pub const PICOS_TO_SEC: f64 = PICOS_TO_MILLIS * MILLIS_TO_SEC;
633pub const NANOS_TO_MIN: f64 = NANOS_TO_SEC * SEC_TO_MIN;
634pub const MICROS_TO_HOUR: f64 = MICROS_TO_MIN * MIN_TO_HOUR;
635pub const MILLIS_TO_DAY: f64 = MILLIS_TO_HOUR * HOUR_TO_DAY;
636pub const SEC_TO_YEAR: f64 = SEC_TO_DAY * DAY_TO_YEAR;
637
638// going down pentas
639pub const YEAR_TO_MILLIS: f64 = YEAR_TO_SEC * SEC_TO_MILLIS;
640pub const DAY_TO_MICROS: f64 = DAY_TO_MILLIS * MILLIS_TO_MICROS;
641pub const HOUR_TO_NANOS: f64 = HOUR_TO_MICROS * MICROS_TO_NANOS;
642pub const MIN_TO_PICOS: f64 = MIN_TO_NANOS * NANOS_TO_PICOS;
643
644// going up pentas
645pub const PICOS_TO_MIN: f64 = PICOS_TO_SEC * SEC_TO_MIN;
646pub const NANOS_TO_HOUR: f64 = NANOS_TO_MIN * MIN_TO_HOUR;
647pub const MICROS_TO_DAY: f64 = MICROS_TO_HOUR * HOUR_TO_DAY;
648pub const MILLIS_TO_YEAR: f64 = MILLIS_TO_DAY * DAY_TO_YEAR;
649
650// going down hexas
651pub const YEAR_TO_MICROS: f64 = YEAR_TO_MILLIS * MILLIS_TO_MICROS;
652pub const DAY_TO_NANOS: f64 = DAY_TO_MICROS * MICROS_TO_NANOS;
653pub const HOUR_TO_PICOS: f64 = HOUR_TO_NANOS * NANOS_TO_PICOS;
654
655// going up hexas
656pub const PICOS_TO_HOUR: f64 = PICOS_TO_MIN * MIN_TO_HOUR;
657pub const NANOS_TO_DAY: f64 = NANOS_TO_HOUR * HOUR_TO_DAY;
658pub const MICROS_TO_YEAR: f64 = MICROS_TO_DAY * DAY_TO_YEAR;
659
660// going down septs
661pub const YEAR_TO_NANOS: f64 = YEAR_TO_MICROS * MICROS_TO_NANOS;
662pub const DAY_TO_PICOS: f64 = DAY_TO_NANOS * NANOS_TO_PICOS;
663
664// going up septs
665pub const PICOS_TO_DAY: f64 = PICOS_TO_HOUR * HOUR_TO_DAY;
666pub const NANOS_TO_YEAR: f64 = NANOS_TO_DAY * DAY_TO_YEAR;
667
668// going down octs
669pub const YEAR_TO_PICOS: f64 = YEAR_TO_NANOS * NANOS_TO_PICOS;
670
671// going up octs
672pub const PICOS_TO_YEAR: f64 = PICOS_TO_DAY * DAY_TO_YEAR;
673
674impl Duration {
675    pub fn write_iso8601_exact_to<T: MutBits>(&self, fmt: &mut T) -> Result<(), core::fmt::Error> {
676        let mut fmt = FormatBits(fmt);
677        fmt.write_str("P")?;
678        let y = self.as_years();
679        if y > 0 {
680            write!(fmt, "{y}Y")?;
681        }
682        let mut r = self - Duration::from_years(y);
683        let (d, h, m, _s) = r.as_dhms();
684        if d > 0 {
685            write!(fmt, "{d}D")?;
686            r -= Duration::from_days(d);
687        }
688        if r.as_seconds_f64() > 0.0 {
689            fmt.write_str("T")?;
690        }
691        if h > 0 {
692            write!(fmt, "{h}H")?;
693            r -= Duration::from_hours(h as u64);
694        }
695        if m > 0 {
696            write!(fmt, "{m}M")?;
697            r -= Duration::from_minutes(m as u64);
698        }
699        let s = r.as_seconds_f64();
700        if s.abs() > 0.0 {
701            write!(fmt, "{s:02.}S")?;
702        }
703
704        Ok(())
705    }
706    pub fn write_iso8601_sec_to<T: MutBits>(&self, fmt: &mut T) -> Result<(), core::fmt::Error> {
707        let mut fmt = FormatBits(fmt);
708        fmt.write_str("P")?;
709        let y = self.as_years();
710        if y > 0 {
711            write!(fmt, "{y}Y")?;
712        }
713        let mut r = self - Duration::from_years(y);
714        let (d, h, m, s) = r.as_dhms();
715        if d > 0 {
716            write!(fmt, "{d}D")?;
717            r -= Duration::from_days(d);
718        }
719        if r.as_seconds_f64() > 0.0 {
720            fmt.write_str("T")?;
721        }
722        if h > 0 {
723            write!(fmt, "{h}H")?;
724            r -= Duration::from_hours(h as u64);
725        }
726        if m > 0 {
727            write!(fmt, "{m}M")?;
728            r -= Duration::from_minutes(m as u64);
729        }
730        if s > 0 {
731            write!(fmt, "{s}S")?;
732        }
733
734        Ok(())
735    }
736
737    ///
738    /// Parse a ISO-8601 Duration String like `P1Y2M3DT4H5M2.5S`
739    ///
740    /// This function assumes 1 month = 30 days.
741    pub fn parse_iso8601_from(val: &str) -> Result<Self, ParseError> {
742        let b = val.as_bytes();
743        let mut seconds = 0.0f64;
744        let mut minutes = 0.0f64;
745        let mut hours = 0.0f64;
746        let mut days = 0.0f64;
747        let mut months = 0.0f64;
748        let mut years = 0.0f64;
749
750        let mut encountered_t = false;
751        let mut buf: FixedU8Buf<25> = Default::default();
752        for v in b {
753            match v {
754                b'S' | b's' => {
755                    seconds += buf.as_f64()?;
756                    buf.clear();
757                }
758                b'M' | b'm' => {
759                    if encountered_t {
760                        minutes += buf.as_f64()?;
761                    } else {
762                        months += buf.as_f64()?;
763                    }
764                    buf.clear();
765                }
766                b'H' | b'h' => {
767                    hours += buf.as_f64()?;
768                    buf.clear();
769                }
770                b'D' | b'd' => {
771                    days += buf.as_f64()?;
772                    buf.clear();
773                }
774                b'Y' | b'y' => {
775                    years += buf.as_f64()?;
776                    buf.clear();
777                }
778                b'0'..=b'9' | b'.' => {
779                    if buf.push_back(*v).is_err() {
780                        return Err(Error::new(
781                            ErrorKind::OutOfMemory,
782                            "Buffer overflowed parsing number in Duration::parse_iso8601_from",
783                        )
784                        .into());
785                    }
786                }
787                b'T' | b't' => {
788                    encountered_t = true;
789                }
790                _ => {}
791            }
792        }
793        Ok(Duration::new(seconds, DurationUnit::Second)
794            + Duration::new(minutes, DurationUnit::Minute)
795            + Duration::new(hours, DurationUnit::Hour)
796            + Duration::new(days, DurationUnit::Day)
797            + Duration::new(months * 30., DurationUnit::Day)
798            + Duration::new(years, DurationUnit::Year))
799    }
800}
801#[derive(Eq, PartialEq, Clone, Debug)]
802pub enum ParseError {
803    UTF8(Utf8Error),
804    Bits(BitsError),
805    Float(ParseFloatError),
806}
807impl Display for ParseError {
808    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
809        match self {
810            ParseError::UTF8(u) => {
811                write!(f, "UTF8Error: {u}")
812            }
813            ParseError::Bits(b) => {
814                write!(f, "BitsError: {b}")
815            }
816            ParseError::Float(e) => {
817                write!(f, "FloatError: {e}")
818            }
819        }
820    }
821}
822macro_rules! implerrorfrom {
823    ($id:ident, $en:ident) => {
824        impl From<$id> for ParseError {
825            fn from(value: $id) -> Self {
826                Self::$en(value)
827            }
828        }
829    };
830}
831implerrorfrom!(Utf8Error, UTF8);
832implerrorfrom!(BitsError, Bits);
833implerrorfrom!(ParseFloatError, Float);
834
835cfg_feature_serde! {
836    struct DurationVisitor;
837    impl serde::de::Visitor<'_> for DurationVisitor {
838        type Value = Duration;
839
840        fn expecting(&self, fmt: &mut Formatter<'_>) -> Result<(), core::fmt::Error> {
841            write!(fmt, "The visitor expects to receive a string formatted as a ISO 8601 Duration")
842        }
843        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> where E: serde::de::Error {
844            Duration::parse_iso8601_from(v).map_err(serde::de::Error::custom)
845        }
846    }
847    impl<'de> serde::Deserialize<'de> for Duration {
848        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: serde::Deserializer<'de> {
849            deserializer.deserialize_str(DurationVisitor)
850        }
851    }
852    impl serde::Serialize for Duration {
853        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: serde::Serializer {
854            let mut buf : FixedU8Buf<128> = Default::default();
855            self.write_iso8601_exact_to(&mut buf).map_err(serde::ser::Error::custom)?;
856            let s = buf.as_str().map_err(serde::ser::Error::custom)?;
857            serializer.serialize_str(s)
858        }
859    }
860}
861
862#[cfg(all(test, feature = "alloc"))]
863mod tests {
864    use core::fmt::Error;
865    extern crate alloc;
866    use crate::units::duration::Duration;
867
868    #[test]
869    pub fn test_iso8601() -> Result<(), Error> {
870        let mut s = alloc::string::String::new();
871        let d = Duration::from_years(1);
872        d.write_iso8601_exact_to(&mut s)?;
873        assert_eq!("P1Y", s);
874
875        assert_eq!(Ok(d), Duration::parse_iso8601_from(&s));
876
877        let d = Duration::from_hms(1, 2, 3);
878        s.clear();
879        d.write_iso8601_sec_to(&mut s)?;
880        assert_eq!("PT1H2M3S", s);
881        assert_eq!(Ok(d), Duration::parse_iso8601_from(&s));
882
883        let d = Duration::from_seconds_f64(123456.789);
884        s.clear();
885        d.write_iso8601_exact_to(&mut s)?;
886        assert_eq!("P1DT10H17M36.78900000000431S", s);
887        assert_eq!(Ok(d), Duration::parse_iso8601_from(&s));
888
889        Ok(())
890    }
891
892    #[test]
893    #[cfg(all(feature = "serde", feature = "std"))]
894    pub fn serde_test() -> Result<(), Error> {
895        #[derive(serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)]
896        struct Test {
897            a: Duration,
898        }
899        impl Default for Test {
900            fn default() -> Self {
901                Self {
902                    a: Duration::default(),
903                }
904            }
905        }
906        let a = Test {
907            a: Duration::from_days(1),
908        };
909        let s = serde_json::to_string(&a).unwrap_or_default();
910        assert_eq!(s, "{\"a\":\"P1D\"}");
911        let b: Test = serde_json::from_str(&s).unwrap();
912        assert_eq!(a, b);
913        Ok(())
914    }
915}