Skip to main content

fundu_core/
time.rs

1// Copyright (c) 2023 Joining7943 <joining@posteo.de>
2//
3// This software is released under the MIT License.
4// https://opensource.org/licenses/MIT
5
6//! Contains all time related structures used in fundu like [`TimeUnit`] and [`Duration`]
7
8use std::cmp::Ordering;
9use std::fmt::Display;
10use std::hash::{Hash, Hasher};
11use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign};
12
13use TimeUnit::*;
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Serialize};
16
17use crate::error::TryFromDurationError;
18
19/// The default identifier of [`TimeUnit::NanoSecond`]
20pub const DEFAULT_ID_NANO_SECOND: &str = "ns";
21/// The default identifier of [`TimeUnit::MicroSecond`]
22pub const DEFAULT_ID_MICRO_SECOND: &str = "Ms";
23/// The default identifier of [`TimeUnit::MicroSecond`]
24pub const DEFAULT_ID_MILLI_SECOND: &str = "ms";
25/// The default identifier of [`TimeUnit::Second`]
26pub const DEFAULT_ID_SECOND: &str = "s";
27/// The default identifier of [`TimeUnit::Minute`]
28pub const DEFAULT_ID_MINUTE: &str = "m";
29/// The default identifier of [`TimeUnit::Hour`]
30pub const DEFAULT_ID_HOUR: &str = "h";
31/// The default identifier of [`TimeUnit::Day`]
32pub const DEFAULT_ID_DAY: &str = "d";
33/// The default identifier of [`TimeUnit::Week`]
34pub const DEFAULT_ID_WEEK: &str = "w";
35/// The default identifier of [`TimeUnit::Month`]
36pub const DEFAULT_ID_MONTH: &str = "M";
37/// The default identifier of [`TimeUnit::Year`]
38pub const DEFAULT_ID_YEAR: &str = "y";
39
40pub(crate) const DEFAULT_TIME_UNIT: TimeUnit = Second;
41
42const SECS_PER_MINUTE: u64 = 60;
43const SECS_PER_HOUR: u64 = 3600;
44const SECS_PER_DAY: u64 = 86400;
45const SECS_PER_WEEK: u64 = 86400 * 7;
46
47const NANOS_PER_MILLI: i32 = 1_000_000;
48const NANOS_PER_MICRO: i32 = 1_000;
49
50const MILLIS_PER_SEC: i128 = 1_000;
51const MICROS_PER_SEC: i128 = 1_000_000;
52const NANOS_PER_SEC: i128 = 1_000_000_000;
53
54/// The time units used to define possible time units in the input string
55///
56/// The parser calculates the final [`Duration`] based on the parsed number and time unit. Each
57/// `TimeUnit` has an inherent [`Multiplier`] and a default id. The [`Multiplier`] influences the
58/// final value of the [`Duration`] and is seconds based. See also the documentation of
59/// [`Multiplier`]
60///
61/// # Examples
62///
63/// ```rust
64/// use fundu_core::time::Multiplier;
65/// use fundu_core::time::TimeUnit::*;
66///
67/// assert_eq!(NanoSecond.default_identifier(), "ns");
68/// assert_eq!(Second.default_identifier(), "s");
69/// assert_eq!(Hour.default_identifier(), "h");
70///
71/// assert_eq!(NanoSecond.multiplier(), Multiplier(1, -9));
72/// assert_eq!(MilliSecond.multiplier(), Multiplier(1, -3));
73/// assert_eq!(Second.multiplier(), Multiplier(1, 0));
74/// assert_eq!(Hour.multiplier(), Multiplier(60 * 60, 0));
75/// ```
76///
77/// [`Multiplier`]: crate::time::Multiplier
78/// [`Duration`]: crate::time::Duration
79#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Hash)]
80#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
81pub enum TimeUnit {
82    /// Represents the lowest possible time unit. The default id is given by
83    /// [`DEFAULT_ID_NANO_SECOND`] = `ns`
84    NanoSecond,
85    /// The default id is given by [`DEFAULT_ID_MICRO_SECOND`] = `Ms`
86    MicroSecond,
87    /// The default id is given by [`DEFAULT_ID_MILLI_SECOND`] = `ms`
88    MilliSecond,
89    /// The default if no time unit is given. The default id is given by [`DEFAULT_ID_SECOND`] =
90    /// `s`
91    Second,
92    /// The default id is given by [`DEFAULT_ID_MINUTE`] = `m`
93    Minute,
94    /// The default id is given by [`DEFAULT_ID_HOUR`] = `h`
95    Hour,
96    /// The default id is given by [`DEFAULT_ID_DAY`] = `d`
97    Day,
98    /// The default id is given by [`DEFAULT_ID_WEEK`] = `w`
99    Week,
100    /// The default id is given by [`DEFAULT_ID_MONTH`] = `M`
101    Month,
102    /// Represents the hightest possible time unit. The default id is given by [`DEFAULT_ID_YEAR`]
103    /// = `y`
104    Year,
105}
106
107impl Default for TimeUnit {
108    fn default() -> Self {
109        DEFAULT_TIME_UNIT
110    }
111}
112
113impl TimeUnit {
114    /// Return the default identifier
115    pub const fn default_identifier(&self) -> &'static str {
116        match self {
117            NanoSecond => DEFAULT_ID_NANO_SECOND,
118            MicroSecond => DEFAULT_ID_MICRO_SECOND,
119            MilliSecond => DEFAULT_ID_MILLI_SECOND,
120            Second => DEFAULT_ID_SECOND,
121            Minute => DEFAULT_ID_MINUTE,
122            Hour => DEFAULT_ID_HOUR,
123            Day => DEFAULT_ID_DAY,
124            Week => DEFAULT_ID_WEEK,
125            Month => DEFAULT_ID_MONTH,
126            Year => DEFAULT_ID_YEAR,
127        }
128    }
129
130    /// Return the base [`Multiplier`] of this [`TimeUnit`].
131    ///
132    /// This multiplier is always seconds based so for example:
133    ///
134    /// ```ignore
135    /// NanoSecond: Multiplier(1, -9)
136    /// Second: Multiplier(1, 0)
137    /// Year: Multiplier(31557600, 0)
138    /// ```
139    pub const fn multiplier(&self) -> Multiplier {
140        const MULTIPLIERS: [Multiplier; 10] = [
141            Multiplier(1, -9),
142            Multiplier(1, -6),
143            Multiplier(1, -3),
144            Multiplier(1, 0),
145            Multiplier(60, 0),
146            Multiplier(3_600, 0),
147            Multiplier(86_400, 0),
148            Multiplier(604_800, 0),
149            Multiplier(2_629_800, 0),  // Year / 12
150            Multiplier(31_557_600, 0), // 365.25 days
151        ];
152        MULTIPLIERS[*self as usize]
153    }
154}
155
156/// To be able to use the basic [`crate::parse::Parser`] this trait needs to be implemented
157///
158/// Usually, time units are a fixed set of strings and implementing `TimeUnitsLike` is a simple
159/// straight forward process. For more advanced usages see for example the implementations of fundu.
160/// The most important method is [`TimeUnitsLike::get`]. See there for additional information.
161///
162/// # Examples
163///
164/// Example for a simple and small fixed set of time units.
165///
166/// ```rust
167/// use fundu_core::time::{Multiplier, TimeUnit, TimeUnitsLike};
168///
169/// struct TimeUnits {}
170/// impl TimeUnitsLike for TimeUnits {
171///     fn is_empty(&self) -> bool {
172///         false
173///     }
174///
175///     fn get(&self, identifier: &str) -> Option<(TimeUnit, Multiplier)> {
176///         match identifier {
177///             "s" | "sec" => Some((TimeUnit::Second, Multiplier(1, 0))),
178///             "min" | "minutes" => Some((TimeUnit::Minute, Multiplier(1, 0))),
179///             _ => None,
180///         }
181///     }
182/// }
183/// let time_units = TimeUnits {};
184///
185/// assert_eq!(time_units.is_empty(), false);
186/// assert_eq!(
187///     time_units.get("sec"),
188///     Some((TimeUnit::Second, Multiplier(1, 0)))
189/// );
190/// assert_eq!(
191///     time_units.get("minutes"),
192///     Some((TimeUnit::Minute, Multiplier(1, 0)))
193/// );
194/// assert_eq!(time_units.get("does_not_exist"), None);
195/// ```
196pub trait TimeUnitsLike {
197    /// Return true if there are no time units configured to be matched against
198    ///
199    /// # Examples
200    ///
201    /// Example for an empty set of time units and `is_empty` returning `true`
202    ///
203    /// ```rust
204    /// use fundu_core::time::{Multiplier, TimeUnit, TimeUnitsLike};
205    ///
206    /// struct TimeUnits {}
207    /// impl TimeUnitsLike for TimeUnits {
208    ///     fn is_empty(&self) -> bool {
209    ///         true
210    ///     }
211    ///
212    ///     fn get(&self, string: &str) -> Option<(TimeUnit, Multiplier)> {
213    ///         None
214    ///     }
215    /// }
216    /// let time_units = TimeUnits {};
217    ///
218    /// assert!(time_units.is_empty());
219    /// ```
220    ///
221    /// Example for a fixed set of time units. To keep the example small only a few possibilities
222    /// are given, but the essential point is, that as soon as the `string` can be matched against
223    /// one time unit in the `get` method, `is_empty` must return false.
224    ///
225    /// ```rust
226    /// use fundu_core::time::{Multiplier, TimeUnit, TimeUnitsLike};
227    ///
228    /// struct TimeUnits {}
229    /// impl TimeUnitsLike for TimeUnits {
230    ///     fn is_empty(&self) -> bool {
231    ///         false
232    ///     }
233    ///
234    ///     fn get(&self, identifier: &str) -> Option<(TimeUnit, Multiplier)> {
235    ///         match identifier {
236    ///             "s" | "sec" => Some((TimeUnit::Second, Multiplier(1, 0))),
237    ///             "min" | "minutes" => Some((TimeUnit::Minute, Multiplier(1, 0))),
238    ///             _ => None,
239    ///         }
240    ///     }
241    /// }
242    /// let time_units = TimeUnits {};
243    ///
244    /// assert_eq!(time_units.is_empty(), false);
245    /// ```
246    fn is_empty(&self) -> bool;
247
248    /// Return the result of the match of the `identifier` against a set of time units
249    ///
250    /// This method must return `None` if the `identifier` does not match one of the time units and
251    /// return `Some` tuple with a [`TimeUnit`] and an additional [`Multiplier`]. The [`Multiplier`]
252    /// is not the multiplier of the `TimeUnit` but is instead applied additionally to the inherent
253    /// `Multiplier` of the `TimeUnit`. A [`TimeUnit`] ranges from [`TimeUnit::NanoSecond`] to
254    /// [`TimeUnit::Year`]. The additional `Multiplier` allows to create other time units derived
255    /// from the existent ones like `century` `(TimeUnit::Year, Multiplier(100, 0))` or `fortnight`
256    /// `(TimeUnit::Week, Multiplier(2, 0))` ...
257    ///
258    /// # Problems
259    ///
260    /// This method is time critical and the `identifier` can theoretically range from `0` to
261    /// `usize::MAX` length. So, every measure to avoid unnecessary calculations should be taken.
262    ///
263    /// # Examples
264    ///
265    /// Full example for a fixed fantasy set of time units with a derived time unit `fortnight` and
266    /// micro seconds with an utf-8 multi-byte character
267    ///
268    /// ```rust
269    /// use fundu_core::time::{Multiplier, TimeUnit, TimeUnitsLike};
270    ///
271    /// struct TimeUnits {}
272    /// impl TimeUnitsLike for TimeUnits {
273    ///     fn is_empty(&self) -> bool {
274    ///         false
275    ///     }
276    ///
277    ///     fn get(&self, identifier: &str) -> Option<(TimeUnit, Multiplier)> {
278    ///         match identifier {
279    ///             "ns" | "nsec" => Some((TimeUnit::NanoSecond, Multiplier(1, 0))),
280    ///             "µs" | "us" | "usec" => Some((TimeUnit::MicroSecond, Multiplier(1, 0))),
281    ///             "ms" | "msec" => Some((TimeUnit::MilliSecond, Multiplier(1, 0))),
282    ///             "s" | "sec" => Some((TimeUnit::Second, Multiplier(1, 0))),
283    ///             "m" | "min" | "mins" | "minutes" => Some((TimeUnit::Minute, Multiplier(1, 0))),
284    ///             "h" | "hr" | "hrs" => Some((TimeUnit::Hour, Multiplier(1, 0))),
285    ///             "d" | "day" | "days" => Some((TimeUnit::Day, Multiplier(1, 0))),
286    ///             "w" | "weeks" => Some((TimeUnit::Week, Multiplier(1, 0))),
287    ///             "fortnight" | "fortnights" => Some((TimeUnit::Week, Multiplier(2, 0))),
288    ///             "month" | "months" => Some((TimeUnit::Month, Multiplier(1, 0))),
289    ///             "year" | "years" => Some((TimeUnit::Year, Multiplier(1, 0))),
290    ///             _ => None,
291    ///         }
292    ///     }
293    /// }
294    /// let time_units = TimeUnits {};
295    ///
296    /// assert_eq!(time_units.is_empty(), false);
297    /// assert_eq!(
298    ///     time_units.get("nsec"),
299    ///     Some((TimeUnit::NanoSecond, Multiplier(1, 0)))
300    /// );
301    /// assert_eq!(
302    ///     time_units.get("fortnight"),
303    ///     Some((TimeUnit::Week, Multiplier(2, 0)))
304    /// );
305    /// assert_eq!(
306    ///     time_units.get("years"),
307    ///     Some((TimeUnit::Year, Multiplier(1, 0)))
308    /// );
309    /// assert_eq!(time_units.get("does_not_match"), None);
310    /// ```
311    ///
312    /// Whatever may seems reasonable to match against the set of time units is allowed like
313    /// matching case insensitive. See the following short example which also tries to avoid any
314    /// unnecessary calculation to the lowercase pendant of the original `identifier`
315    ///
316    /// ```rust
317    /// use fundu_core::time::{Multiplier, TimeUnit, TimeUnitsLike};
318    ///
319    /// struct TimeUnits {}
320    /// impl TimeUnitsLike for TimeUnits {
321    ///     fn is_empty(&self) -> bool {
322    ///         false
323    ///     }
324    ///
325    ///     fn get(&self, identifier: &str) -> Option<(TimeUnit, Multiplier)> {
326    ///         // We use the fact that none of our time units have a string length greater than `4`
327    ///         // and lower than `1`
328    ///         if identifier.len() >= 1 && identifier.len() <= 4 {
329    ///             match identifier.to_ascii_lowercase().as_str() {
330    ///                 "µs" | "us" | "usec" => Some((TimeUnit::MicroSecond, Multiplier(1, 0))),
331    ///                 "ms" | "msec" => Some((TimeUnit::MilliSecond, Multiplier(1, 0))),
332    ///                 "s" | "sec" => Some((TimeUnit::Second, Multiplier(1, 0))),
333    ///                 _ => None,
334    ///             }
335    ///         } else {
336    ///             None
337    ///         }
338    ///     }
339    /// }
340    /// let time_units = TimeUnits {};
341    ///
342    /// assert_eq!(time_units.is_empty(), false);
343    /// assert_eq!(
344    ///     time_units.get("USEC"),
345    ///     Some((TimeUnit::MicroSecond, Multiplier(1, 0)))
346    /// );
347    /// assert_eq!(
348    ///     time_units.get("MSec"),
349    ///     Some((TimeUnit::MilliSecond, Multiplier(1, 0)))
350    /// );
351    /// assert_eq!(
352    ///     time_units.get("sEc"),
353    ///     Some((TimeUnit::Second, Multiplier(1, 0)))
354    /// );
355    /// assert_eq!(time_units.get("does_not_match"), None);
356    /// ```
357    fn get(&self, identifier: &str) -> Option<(TimeUnit, Multiplier)>;
358}
359
360/// The multiplier of a [`TimeUnit`].
361///
362/// The multiplier consists of two numbers `(m, e)` which are applied to another number `x` as
363/// follows:
364///
365/// `x * m * 10 ^ e`
366///
367/// Examples:
368///
369/// ```ignore
370/// let nano_second = Multiplier(1, -9);
371/// let second = Multiplier(1, 0);
372/// let hour = Multiplier(3600, 0);
373/// ```
374#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
375#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
376pub struct Multiplier(pub i64, pub i16);
377
378impl Default for Multiplier {
379    fn default() -> Self {
380        Self(1, 0)
381    }
382}
383
384impl Multiplier {
385    /// Return the coefficient component of the `Multiplier`
386    ///
387    /// # Examples
388    ///
389    /// ```rust
390    /// use fundu_core::time::Multiplier;
391    ///
392    /// let multiplier = Multiplier(123, 45);
393    /// assert_eq!(multiplier.coefficient(), 123);
394    /// ```
395    #[inline]
396    pub const fn coefficient(&self) -> i64 {
397        self.0
398    }
399
400    /// Return the exponent component of the `Multiplier`
401    ///
402    /// # Examples
403    ///
404    /// ```rust
405    /// use fundu_core::time::Multiplier;
406    ///
407    /// let multiplier = Multiplier(123, 45);
408    /// assert_eq!(multiplier.exponent(), 45);
409    /// ```
410    #[inline]
411    pub const fn exponent(&self) -> i16 {
412        self.1
413    }
414
415    /// Return true if the `Multiplier` is negative
416    ///
417    /// # Examples
418    ///
419    /// ```rust
420    /// use fundu_core::time::Multiplier;
421    ///
422    /// let multiplier = Multiplier(-123, 45);
423    /// assert!(multiplier.is_negative());
424    /// ```
425    #[inline]
426    pub const fn is_negative(&self) -> bool {
427        !self.is_positive()
428    }
429
430    /// Return true if the `Multiplier` is positive
431    ///
432    /// # Examples
433    ///
434    /// ```rust
435    /// use fundu_core::time::Multiplier;
436    ///
437    /// let multiplier = Multiplier(123, 45);
438    /// assert!(multiplier.is_positive());
439    /// ```
440    #[inline]
441    pub const fn is_positive(&self) -> bool {
442        self.0 == 0 || self.0.is_positive()
443    }
444
445    /// Checked `Multiplier` multiplication. Computes `self * other`, returning `None` if an
446    /// overflow occurred.
447    ///
448    /// Let `a, b` be multipliers, with `m` being the coefficient and `e` the exponent. The
449    /// multiplication is performed such that `(x.m, x.e) = (a.m * b.m, a.e + b.e)`
450    ///
451    /// # Examples
452    ///
453    /// ```rust
454    /// use fundu_core::time::Multiplier;
455    ///
456    /// assert_eq!(
457    ///     Multiplier(1, 2).checked_mul(Multiplier(3, 4)),
458    ///     Some(Multiplier(3, 6))
459    /// );
460    /// assert_eq!(
461    ///     Multiplier(-1, 2).checked_mul(Multiplier(3, -4)),
462    ///     Some(Multiplier(-3, -2))
463    /// );
464    /// assert_eq!(Multiplier(2, 0).checked_mul(Multiplier(i64::MAX, 1)), None);
465    /// assert_eq!(Multiplier(1, 2).checked_mul(Multiplier(1, i16::MAX)), None);
466    /// ```
467    #[inline]
468    pub const fn checked_mul(&self, rhs: Self) -> Option<Self> {
469        if let Some(coefficient) = self.0.checked_mul(rhs.0) {
470            if let Some(exponent) = self.1.checked_add(rhs.1) {
471                return Some(Self(coefficient, exponent));
472            }
473        }
474        None
475    }
476
477    /// Saturating negation. Computes `-self`, returning `i64::MAX` if `self.coefficient() ==
478    /// i64::MIN` instead of overflowing.
479    ///
480    /// # Examples
481    ///
482    /// ```rust
483    /// use fundu_core::time::Multiplier;
484    ///
485    /// assert_eq!(Multiplier(1, 2).saturating_neg(), Multiplier(-1, 2));
486    /// assert_eq!(Multiplier(-1, 2).saturating_neg(), Multiplier(1, 2));
487    /// assert_eq!(
488    ///     Multiplier(i64::MIN, 2).saturating_neg(),
489    ///     Multiplier(i64::MAX, 2)
490    /// );
491    /// ```
492    #[inline]
493    pub const fn saturating_neg(&self) -> Self {
494        Self(self.0.saturating_neg(), self.1)
495    }
496}
497
498impl Mul for Multiplier {
499    type Output = Self;
500
501    fn mul(self, rhs: Self) -> Self::Output {
502        self.checked_mul(rhs)
503            .expect("Multiplier: Overflow when multiplying")
504    }
505}
506
507/// Conversion which saturates at the maximum or maximum instead of overflowing
508pub trait SaturatingInto<T>: Sized {
509    /// Performs the saturating conversion
510    fn saturating_into(self) -> T;
511}
512
513/// The duration which is returned by the parser
514///
515/// The `Duration` of this library implements conversions to a [`std::time::Duration`] and if the
516/// feature is activated into a [`time::Duration`] respectively [`chrono::Duration`]. This crates
517/// duration is a superset of the aforementioned durations, so converting to fundu's duration with
518/// `From` or `Into` is lossless. Converting from [`crate::time::Duration`] to the other durations
519/// can overflow the other duration's value range, but `TryFrom` is implement for all of these
520/// durations. Note that fundu's duration also implements [`SaturatingInto`] for the above durations
521/// which performs the conversion saturating at the maximum or minimum of these durations.
522///
523/// # Examples
524///
525/// Basic conversions from [`Duration`] to [`std::time::Duration`].
526///
527/// ```rust
528/// use std::time::Duration as StdDuration;
529///
530/// use fundu_core::error::TryFromDurationError;
531/// use fundu_core::time::{Duration, SaturatingInto};
532///
533/// let result: Result<StdDuration, TryFromDurationError> = Duration::positive(1, 2).try_into();
534/// assert_eq!(result, Ok(StdDuration::new(1, 2)));
535///
536/// let result: Result<StdDuration, TryFromDurationError> = Duration::negative(1, 2).try_into();
537/// assert_eq!(result, Err(TryFromDurationError::NegativeDuration));
538///
539/// let duration: StdDuration = Duration::negative(1, 2).saturating_into();
540/// assert_eq!(duration, StdDuration::ZERO);
541///
542/// let duration: StdDuration = Duration::MAX.saturating_into();
543/// assert_eq!(duration, StdDuration::MAX);
544/// ```
545///
546/// [`time::Duration`]: https://docs.rs/time/latest/time/struct.Duration.html
547#[derive(Debug, Eq, Clone, Copy, Default)]
548#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
549pub struct Duration {
550    is_negative: bool,
551    inner: std::time::Duration,
552}
553
554impl Duration {
555    /// A duration of zero time
556    ///
557    /// # Examples
558    ///
559    /// ```rust
560    /// use fundu_core::time::Duration;
561    ///
562    /// let duration = Duration::ZERO;
563    /// assert!(duration.is_zero());
564    /// ```
565    pub const ZERO: Self = Self {
566        is_negative: false,
567        inner: std::time::Duration::ZERO,
568    };
569
570    /// The minimum duration
571    ///
572    /// # Examples
573    ///
574    /// ```rust
575    /// use fundu_core::time::Duration;
576    ///
577    /// let duration = Duration::MIN;
578    /// assert_eq!(Duration::negative(u64::MAX, 999_999_999), duration);
579    /// ```
580    pub const MIN: Self = Self {
581        is_negative: true,
582        inner: std::time::Duration::MAX,
583    };
584
585    /// The maximum duration
586    ///
587    /// # Examples
588    ///
589    /// ```rust
590    /// use fundu_core::time::Duration;
591    ///
592    /// let duration = Duration::MAX;
593    /// assert_eq!(Duration::positive(u64::MAX, 999_999_999), duration);
594    /// ```
595    pub const MAX: Self = Self {
596        is_negative: false,
597        inner: std::time::Duration::MAX,
598    };
599
600    /// Creates a new `Duration` from a [`std::time::Duration`] which can be negative or positive
601    ///
602    /// # Examples
603    ///
604    /// ```rust
605    /// use fundu_core::time::Duration;
606    ///
607    /// let duration = Duration::from_std(false, std::time::Duration::new(1, 0));
608    /// assert_eq!(Duration::positive(1, 0), duration);
609    ///
610    /// let duration = Duration::from_std(true, std::time::Duration::new(1, 0));
611    /// assert_eq!(Duration::negative(1, 0), duration);
612    /// ```
613    pub const fn from_std(is_negative: bool, inner: std::time::Duration) -> Self {
614        Self { is_negative, inner }
615    }
616
617    /// Creates a new positive `Duration`
618    ///
619    /// # Panics
620    ///
621    /// This constructor will panic if creating a [`std::time::Duration`] with the same parameters
622    /// would panic
623    ///
624    /// # Examples
625    ///
626    /// ```rust
627    /// use fundu_core::time::Duration;
628    ///
629    /// let duration = Duration::positive(1, 0);
630    /// assert!(duration.is_positive());
631    ///
632    /// let duration = Duration::positive(0, 0);
633    /// assert!(duration.is_positive());
634    /// ```
635    pub const fn positive(secs: u64, nanos: u32) -> Self {
636        Self {
637            is_negative: false,
638            inner: std::time::Duration::new(secs, nanos),
639        }
640    }
641
642    /// Creates a new negative `Duration`
643    ///
644    /// # Panics
645    ///
646    /// This constructor will panic if creating a [`std::time::Duration`] with the same parameters
647    /// would panic
648    ///
649    /// # Examples
650    ///
651    /// ```rust
652    /// use fundu_core::time::Duration;
653    ///
654    /// let duration = Duration::negative(1, 0);
655    /// assert!(duration.is_negative());
656    /// ```
657    pub const fn negative(secs: u64, nanos: u32) -> Self {
658        Self {
659            is_negative: true,
660            inner: std::time::Duration::new(secs, nanos),
661        }
662    }
663
664    // factor must be greater than 1
665    const fn as_whole(&self, factor: u64) -> i64 {
666        debug_assert!(factor > 1);
667
668        #[allow(clippy::cast_possible_wrap)]
669        let result = (self.inner.as_secs() / factor) as i64;
670        if self.is_negative { -result } else { result }
671    }
672
673    /// Return the number of *whole* weeks in the `Duration`
674    ///
675    /// # Examples
676    ///
677    /// ```rust
678    /// use fundu_core::time::Duration;
679    ///
680    /// assert_eq!(Duration::positive(86400 * 7, 0).as_weeks(), 1);
681    /// assert_eq!(Duration::negative(86400 * 7, 0).as_weeks(), -1);
682    /// assert_eq!(Duration::positive(1, 0).as_weeks(), 0);
683    /// assert_eq!(Duration::positive(1_500_000, 0).as_weeks(), 2);
684    /// ```
685    #[inline]
686    pub const fn as_weeks(&self) -> i64 {
687        self.as_whole(SECS_PER_WEEK)
688    }
689
690    /// Return the number of *whole* days in the `Duration`
691    ///
692    /// # Examples
693    ///
694    /// ```rust
695    /// use fundu_core::time::Duration;
696    ///
697    /// assert_eq!(Duration::positive(86400, 0).as_days(), 1);
698    /// assert_eq!(Duration::negative(86400, 0).as_days(), -1);
699    /// assert_eq!(Duration::positive(1, 0).as_days(), 0);
700    /// assert_eq!(Duration::positive(1_500_000, 0).as_days(), 17);
701    /// ```
702    #[inline]
703    pub const fn as_days(&self) -> i64 {
704        self.as_whole(SECS_PER_DAY)
705    }
706
707    /// Return the number of *whole* hours in the `Duration`
708    ///
709    /// # Examples
710    ///
711    /// ```rust
712    /// use fundu_core::time::Duration;
713    ///
714    /// assert_eq!(Duration::positive(3600, 0).as_hours(), 1);
715    /// assert_eq!(Duration::negative(3600, 0).as_hours(), -1);
716    /// assert_eq!(Duration::positive(1, 0).as_hours(), 0);
717    /// assert_eq!(Duration::positive(1_500_000, 0).as_hours(), 416);
718    /// ```
719    #[inline]
720    pub const fn as_hours(&self) -> i64 {
721        self.as_whole(SECS_PER_HOUR)
722    }
723
724    /// Return the number of *whole* minutes in the `Duration`
725    ///
726    /// # Examples
727    ///
728    /// ```rust
729    /// use fundu_core::time::Duration;
730    ///
731    /// assert_eq!(Duration::positive(60, 0).as_minutes(), 1);
732    /// assert_eq!(Duration::negative(60, 0).as_minutes(), -1);
733    /// assert_eq!(Duration::positive(1, 0).as_minutes(), 0);
734    /// assert_eq!(Duration::positive(1_500_000, 0).as_minutes(), 25_000);
735    /// ```
736    #[inline]
737    pub const fn as_minutes(&self) -> i64 {
738        self.as_whole(SECS_PER_MINUTE)
739    }
740
741    /// Return the number of *whole* seconds in the `Duration`
742    ///
743    /// # Examples
744    ///
745    /// ```rust
746    /// use fundu_core::time::Duration;
747    ///
748    /// assert_eq!(Duration::positive(1, 0).as_seconds(), 1);
749    /// assert_eq!(Duration::negative(1, 0).as_seconds(), -1);
750    /// assert_eq!(Duration::positive(0, 1_000_000).as_seconds(), 0);
751    /// assert_eq!(Duration::positive(1_500_000, 0).as_seconds(), 1_500_000);
752    /// ```
753    #[inline]
754    pub const fn as_seconds(&self) -> i128 {
755        let seconds = self.inner.as_secs() as i128;
756        if self.is_negative { -seconds } else { seconds }
757    }
758
759    /// Return the total number of *whole* milliseconds in the `Duration`
760    ///
761    /// # Examples
762    ///
763    /// ```rust
764    /// use fundu_core::time::Duration;
765    ///
766    /// assert_eq!(Duration::positive(1, 0).as_millis(), 1_000);
767    /// assert_eq!(Duration::negative(1, 0).as_millis(), -1_000);
768    /// assert_eq!(Duration::positive(0, 1_000_000).as_millis(), 1);
769    /// assert_eq!(Duration::positive(12, 3_000_000).as_millis(), 12_003);
770    /// ```
771    #[inline]
772    pub const fn as_millis(&self) -> i128 {
773        self.as_seconds() * MILLIS_PER_SEC + self.subsec_millis() as i128
774    }
775
776    /// Return the total number of *whole* microseconds in the `Duration`
777    ///
778    /// # Examples
779    ///
780    /// ```rust
781    /// use fundu_core::time::Duration;
782    ///
783    /// assert_eq!(Duration::positive(1, 0).as_micros(), 1_000_000);
784    /// assert_eq!(Duration::negative(1, 0).as_micros(), -1_000_000);
785    /// assert_eq!(Duration::positive(0, 1_000).as_micros(), 1);
786    /// assert_eq!(Duration::positive(12, 3_000).as_micros(), 12_000_003);
787    /// ```
788    #[inline]
789    pub const fn as_micros(&self) -> i128 {
790        self.as_seconds() * MICROS_PER_SEC + self.subsec_micros() as i128
791    }
792
793    /// Return the total number of nanoseconds in the `Duration`
794    ///
795    /// # Examples
796    ///
797    /// ```rust
798    /// use fundu_core::time::Duration;
799    ///
800    /// assert_eq!(Duration::positive(1, 0).as_nanos(), 1_000_000_000);
801    /// assert_eq!(Duration::negative(1, 0).as_nanos(), -1_000_000_000);
802    /// assert_eq!(Duration::positive(0, 1).as_nanos(), 1);
803    /// assert_eq!(Duration::positive(12, 3).as_nanos(), 12_000_000_003);
804    /// ```
805    #[inline]
806    pub const fn as_nanos(&self) -> i128 {
807        self.as_seconds() * NANOS_PER_SEC + self.subsec_nanos() as i128
808    }
809
810    /// Return the fractional part of the `Duration` in *whole* milliseconds
811    ///
812    /// # Examples
813    ///
814    /// ```rust
815    /// use fundu_core::time::Duration;
816    ///
817    /// assert_eq!(Duration::positive(0, 123_456_789).subsec_millis(), 123);
818    /// assert_eq!(Duration::negative(0, 123_456_789).subsec_millis(), -123);
819    /// assert_eq!(Duration::positive(1, 0).subsec_millis(), 0);
820    /// assert_eq!(Duration::positive(0, 1).subsec_millis(), 0);
821    /// ```
822    #[inline]
823    pub const fn subsec_millis(&self) -> i32 {
824        self.subsec_nanos() / NANOS_PER_MILLI
825    }
826
827    /// Return the fractional part of the `Duration` in *whole* microseconds
828    ///
829    /// # Examples
830    ///
831    /// ```rust
832    /// use fundu_core::time::Duration;
833    ///
834    /// assert_eq!(Duration::positive(0, 123_456_789).subsec_micros(), 123_456);
835    /// assert_eq!(Duration::negative(0, 123_456_789).subsec_micros(), -123_456);
836    /// assert_eq!(Duration::positive(1, 0).subsec_micros(), 0);
837    /// assert_eq!(Duration::positive(0, 1).subsec_micros(), 0);
838    #[inline]
839    pub const fn subsec_micros(&self) -> i32 {
840        self.subsec_nanos() / NANOS_PER_MICRO
841    }
842
843    /// Return the fractional part of the `Duration` in nanoseconds
844    ///
845    /// # Examples
846    ///
847    /// ```rust
848    /// use fundu_core::time::Duration;
849    ///
850    /// assert_eq!(Duration::positive(0, 123_456_789).subsec_nanos(), 123_456_789);
851    /// assert_eq!(Duration::negative(0, 123_456_789).subsec_nanos(), -123_456_789);
852    /// assert_eq!(Duration::positive(1, 0).subsec_nanos(), 0);
853    /// assert_eq!(Duration::positive(0, 1).subsec_nanos(), 1);
854    #[inline]
855    pub const fn subsec_nanos(&self) -> i32 {
856        #[allow(clippy::cast_possible_wrap)]
857        let nanos = self.inner.subsec_nanos() as i32;
858        if self.is_negative { -nanos } else { nanos }
859    }
860
861    fn extract_i64(&mut self, factor: u64) -> i64 {
862        debug_assert!(factor > 1);
863
864        let secs = self.inner.as_secs();
865        let extracted = i64::try_from(secs / factor).unwrap();
866        if extracted > 0 {
867            self.inner = std::time::Duration::new(secs % factor, self.inner.subsec_nanos());
868            if self.is_negative {
869                extracted.neg()
870            } else {
871                extracted
872            }
873        } else {
874            0
875        }
876    }
877
878    /// Return the number of *whole* weeks in this `Duration` and reduce it by that amount
879    ///
880    /// # Examples
881    ///
882    /// ```rust
883    /// use fundu_core::time::Duration;
884    ///
885    /// let mut duration = Duration::positive(86400 * 7, 0);
886    /// assert_eq!(duration.extract_weeks(), 1);
887    /// assert_eq!(duration, Duration::ZERO);
888    ///
889    /// let mut duration = Duration::positive(123, 456);
890    /// assert_eq!(duration.extract_weeks(), 0);
891    /// assert_eq!(duration, Duration::positive(123, 456));
892    /// ```
893    #[inline]
894    pub fn extract_weeks(&mut self) -> i64 {
895        self.extract_i64(SECS_PER_WEEK)
896    }
897
898    /// Return the number of *whole* days in this `Duration` and reduce it by that amount
899    ///
900    /// # Examples
901    ///
902    /// ```rust
903    /// use fundu_core::time::Duration;
904    ///
905    /// let mut duration = Duration::positive(86400, 0);
906    /// assert_eq!(duration.extract_days(), 1);
907    /// assert_eq!(duration, Duration::ZERO);
908    ///
909    /// let mut duration = Duration::positive(123, 456);
910    /// assert_eq!(duration.extract_days(), 0);
911    /// assert_eq!(duration, Duration::positive(123, 456));
912    /// ```
913    #[inline]
914    pub fn extract_days(&mut self) -> i64 {
915        self.extract_i64(SECS_PER_DAY)
916    }
917
918    /// Return the number of *whole* hours in this `Duration` and reduce it by that amount
919    ///
920    /// # Examples
921    ///
922    /// ```rust
923    /// use fundu_core::time::Duration;
924    ///
925    /// let mut duration = Duration::positive(3600, 0);
926    /// assert_eq!(duration.extract_hours(), 1);
927    /// assert_eq!(duration, Duration::ZERO);
928    ///
929    /// let mut duration = Duration::positive(123, 456);
930    /// assert_eq!(duration.extract_hours(), 0);
931    /// assert_eq!(duration, Duration::positive(123, 456));
932    /// ```
933    #[inline]
934    pub fn extract_hours(&mut self) -> i64 {
935        self.extract_i64(SECS_PER_HOUR)
936    }
937
938    /// Return the number of *whole* minutes in this `Duration` and reduce it by that amount
939    ///
940    /// # Examples
941    ///
942    /// ```rust
943    /// use fundu_core::time::Duration;
944    ///
945    /// let mut duration = Duration::positive(60, 0);
946    /// assert_eq!(duration.extract_minutes(), 1);
947    /// assert_eq!(duration, Duration::ZERO);
948    ///
949    /// let mut duration = Duration::positive(12, 456);
950    /// assert_eq!(duration.extract_minutes(), 0);
951    /// assert_eq!(duration, Duration::positive(12, 456));
952    /// ```
953    #[inline]
954    pub fn extract_minutes(&mut self) -> i64 {
955        self.extract_i64(SECS_PER_MINUTE)
956    }
957
958    /// Return the number of seconds in this `Duration` and reduce it by that amount
959    ///
960    /// # Examples
961    ///
962    /// ```rust
963    /// use fundu_core::time::Duration;
964    ///
965    /// let mut duration = Duration::positive(1, 0);
966    /// assert_eq!(duration.extract_seconds(), 1);
967    /// assert_eq!(duration, Duration::ZERO);
968    ///
969    /// let mut duration = Duration::positive(12, 456);
970    /// assert_eq!(duration.extract_seconds(), 12);
971    /// assert_eq!(duration, Duration::positive(0, 456));
972    /// ```
973    #[inline]
974    pub fn extract_seconds(&mut self) -> i128 {
975        let extracted = self.as_seconds();
976        if extracted == 0 {
977            0
978        } else {
979            self.inner = std::time::Duration::new(0, self.inner.subsec_nanos());
980            extracted
981        }
982    }
983
984    /// Returns true if the `Duration` is negative
985    ///
986    /// # Examples
987    ///
988    /// ```rust
989    /// use fundu_core::time::Duration;
990    ///
991    /// let duration = Duration::MIN;
992    /// assert!(duration.is_negative());
993    ///
994    /// let duration = Duration::negative(0, 1);
995    /// assert!(duration.is_negative());
996    /// ```
997    #[inline]
998    pub const fn is_negative(&self) -> bool {
999        self.is_negative
1000    }
1001
1002    /// Returns true if the `Duration` is positive
1003    ///
1004    /// # Examples
1005    ///
1006    /// ```rust
1007    /// use fundu_core::time::Duration;
1008    ///
1009    /// let duration = Duration::ZERO;
1010    /// assert!(duration.is_positive());
1011    ///
1012    /// let duration = Duration::positive(0, 1);
1013    /// assert!(duration.is_positive());
1014    /// ```
1015    #[inline]
1016    pub const fn is_positive(&self) -> bool {
1017        !self.is_negative
1018    }
1019
1020    /// Returns true if the `Duration` is zero
1021    ///
1022    /// # Examples
1023    ///
1024    /// ```rust
1025    /// use fundu_core::time::Duration;
1026    ///
1027    /// let duration = Duration::ZERO;
1028    /// assert!(duration.is_zero());
1029    ///
1030    /// let duration = Duration::positive(0, 0);
1031    /// assert!(duration.is_zero());
1032    ///
1033    /// let duration = Duration::negative(0, 0);
1034    /// assert!(duration.is_zero());
1035    /// ```
1036    #[inline]
1037    pub const fn is_zero(&self) -> bool {
1038        self.inner.is_zero()
1039    }
1040
1041    /// Returns the absolute value of the duration
1042    ///
1043    /// This operation is lossless.
1044    ///
1045    /// # Examples
1046    ///
1047    /// ```rust
1048    /// use fundu_core::time::Duration;
1049    ///
1050    /// let duration = Duration::MIN;
1051    /// assert_eq!(duration.abs(), Duration::MAX);
1052    ///
1053    /// let duration = Duration::negative(1, 0);
1054    /// assert_eq!(duration.abs(), Duration::positive(1, 0));
1055    ///
1056    /// let duration = Duration::positive(1, 0);
1057    /// assert_eq!(duration.abs(), Duration::positive(1, 0));
1058    /// ```
1059    #[inline]
1060    pub const fn abs(&self) -> Self {
1061        Self::from_std(false, self.inner)
1062    }
1063
1064    /// Sums this duration with the `other` duration, returning None if an overflow occurred
1065    ///
1066    /// # Examples
1067    ///
1068    /// ```rust
1069    /// use fundu_core::time::Duration;
1070    ///
1071    /// assert_eq!(
1072    ///     Duration::positive(1, 0).checked_add(Duration::positive(1, 0)),
1073    ///     Some(Duration::positive(2, 0))
1074    /// );
1075    /// assert_eq!(
1076    ///     Duration::positive(u64::MAX, 0).checked_add(Duration::positive(1, 0)),
1077    ///     None
1078    /// );
1079    /// assert_eq!(
1080    ///     Duration::negative(u64::MAX, 0).checked_add(Duration::negative(1, 0)),
1081    ///     None
1082    /// );
1083    /// ```
1084    pub fn checked_add(&self, other: Self) -> Option<Self> {
1085        match (
1086            self.is_negative,
1087            other.is_negative,
1088            self.inner.cmp(&other.inner),
1089        ) {
1090            (true, true, _) => self
1091                .inner
1092                .checked_add(other.inner)
1093                .map(|d| Self::from_std(true, d)),
1094            (true, false, Ordering::Equal) | (false, true, Ordering::Equal) => Some(Self::ZERO),
1095            (true, false, Ordering::Greater) => {
1096                Some(Self::from_std(true, self.inner.checked_sub(other.inner)?))
1097            }
1098            (true, false, Ordering::Less) => {
1099                Some(Self::from_std(false, other.inner.checked_sub(self.inner)?))
1100            }
1101            (false, true, Ordering::Greater) => {
1102                Some(Self::from_std(false, self.inner.checked_sub(other.inner)?))
1103            }
1104            (false, true, Ordering::Less) => {
1105                Some(Self::from_std(true, other.inner.checked_sub(self.inner)?))
1106            }
1107            (false, false, _) => self
1108                .inner
1109                .checked_add(other.inner)
1110                .map(|d| Self::from_std(false, d)),
1111        }
1112    }
1113
1114    /// Subtracts this duration with the `other` duration, returning None if an overflow occurred
1115    ///
1116    /// # Examples
1117    ///
1118    /// ```rust
1119    /// use fundu_core::time::Duration;
1120    ///
1121    /// assert_eq!(
1122    ///     Duration::positive(1, 0).checked_sub(Duration::positive(1, 0)),
1123    ///     Some(Duration::ZERO)
1124    /// );
1125    /// assert_eq!(
1126    ///     Duration::negative(u64::MAX, 0).checked_sub(Duration::positive(1, 0)),
1127    ///     None
1128    /// );
1129    /// ```
1130    #[inline]
1131    pub fn checked_sub(&self, other: Self) -> Option<Self> {
1132        self.checked_add(other.neg())
1133    }
1134
1135    /// Saturating [`Duration`] addition. Computes `self + other`, returning [`Duration::MAX`] or
1136    /// [`Duration::MIN`] if an overflow occurred.
1137    ///
1138    /// # Examples
1139    ///
1140    /// ```rust
1141    /// use fundu_core::time::Duration;
1142    ///
1143    /// assert_eq!(
1144    ///     Duration::positive(1, 0).saturating_add(Duration::positive(0, 1)),
1145    ///     Duration::positive(1, 1)
1146    /// );
1147    /// assert_eq!(
1148    ///     Duration::positive(u64::MAX, 0).saturating_add(Duration::positive(1, 0)),
1149    ///     Duration::MAX
1150    /// );
1151    /// ```
1152    pub fn saturating_add(&self, other: Self) -> Self {
1153        match self.checked_add(other) {
1154            Some(d) => d,
1155            // checked_add only returns None if both durations are either negative or positive so it
1156            // is enough to check one of the durations for negativity
1157            None if self.is_negative => Self::MIN,
1158            None => Self::MAX,
1159        }
1160    }
1161
1162    /// Saturating [`Duration`] subtraction. Computes `self - other`, returning [`Duration::MAX`] or
1163    /// [`Duration::MIN`] if an overflow occurred.
1164    ///
1165    /// # Examples
1166    ///
1167    /// ```rust
1168    /// use fundu_core::time::Duration;
1169    ///
1170    /// assert_eq!(
1171    ///     Duration::positive(1, 0).saturating_sub(Duration::positive(1, 0)),
1172    ///     Duration::ZERO
1173    /// );
1174    /// assert_eq!(
1175    ///     Duration::negative(u64::MAX, 0).saturating_sub(Duration::positive(1, 0)),
1176    ///     Duration::MIN
1177    /// );
1178    /// ```
1179    #[inline]
1180    pub fn saturating_sub(&self, other: Self) -> Self {
1181        self.saturating_add(other.neg())
1182    }
1183}
1184
1185impl Display for Duration {
1186    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1187        const YEAR: u64 = Year.multiplier().0.unsigned_abs();
1188        const MONTH: u64 = Month.multiplier().0.unsigned_abs();
1189        const WEEK: u64 = Week.multiplier().0.unsigned_abs();
1190        const DAY: u64 = Day.multiplier().0.unsigned_abs();
1191        const HOUR: u64 = Hour.multiplier().0.unsigned_abs();
1192        const MINUTE: u64 = Minute.multiplier().0.unsigned_abs();
1193        const MILLIS_PER_NANO: u32 = 1_000_000;
1194        const MICROS_PER_NANO: u32 = 1_000;
1195
1196        if self.is_zero() {
1197            return f.write_str("0ns");
1198        }
1199
1200        let mut result = Vec::with_capacity(10);
1201        let mut secs = self.inner.as_secs();
1202        if secs > 0 {
1203            if secs >= YEAR {
1204                result.push(format!("{}y", secs / YEAR));
1205                secs %= YEAR;
1206            }
1207            if secs >= MONTH {
1208                result.push(format!("{}M", secs / MONTH));
1209                secs %= MONTH;
1210            }
1211            if secs >= WEEK {
1212                result.push(format!("{}w", secs / WEEK));
1213                secs %= WEEK;
1214            }
1215            if secs >= DAY {
1216                result.push(format!("{}d", secs / DAY));
1217                secs %= DAY;
1218            }
1219            if secs >= HOUR {
1220                result.push(format!("{}h", secs / HOUR));
1221                secs %= HOUR;
1222            }
1223            if secs >= MINUTE {
1224                result.push(format!("{}m", secs / MINUTE));
1225                secs %= MINUTE;
1226            }
1227            if secs >= 1 {
1228                result.push(format!("{secs}s"));
1229            }
1230        }
1231
1232        let mut nanos = self.inner.subsec_nanos();
1233        if nanos > 0 {
1234            if nanos >= MILLIS_PER_NANO {
1235                result.push(format!("{}ms", nanos / MILLIS_PER_NANO));
1236                nanos %= MILLIS_PER_NANO;
1237            }
1238            if nanos >= MICROS_PER_NANO {
1239                result.push(format!("{}Ms", nanos / MICROS_PER_NANO));
1240                nanos %= MICROS_PER_NANO;
1241            }
1242            if nanos >= 1 {
1243                result.push(format!("{nanos}ns"));
1244            }
1245        }
1246
1247        if self.is_negative() {
1248            f.write_str(&format!("-{}", result.join(" -")))
1249        } else {
1250            f.write_str(&result.join(" "))
1251        }
1252    }
1253}
1254
1255impl Add for Duration {
1256    type Output = Self;
1257
1258    fn add(self, rhs: Self) -> Self::Output {
1259        self.checked_add(rhs)
1260            .expect("Overflow when adding duration")
1261    }
1262}
1263
1264impl AddAssign for Duration {
1265    fn add_assign(&mut self, rhs: Self) {
1266        *self = *self + rhs;
1267    }
1268}
1269
1270impl Sub for Duration {
1271    type Output = Self;
1272
1273    fn sub(self, rhs: Self) -> Self::Output {
1274        self.checked_sub(rhs)
1275            .expect("Overflow when subtracting duration")
1276    }
1277}
1278
1279impl SubAssign for Duration {
1280    fn sub_assign(&mut self, rhs: Self) {
1281        *self = *self - rhs;
1282    }
1283}
1284
1285impl Neg for Duration {
1286    type Output = Self;
1287
1288    fn neg(self) -> Self::Output {
1289        Self {
1290            is_negative: self.is_negative ^ true,
1291            inner: self.inner,
1292        }
1293    }
1294}
1295
1296impl PartialEq for Duration {
1297    fn eq(&self, other: &Self) -> bool {
1298        (self.is_zero() && other.is_zero())
1299            || (self.is_negative == other.is_negative && self.inner == other.inner)
1300    }
1301}
1302
1303impl Hash for Duration {
1304    fn hash<H: Hasher>(&self, state: &mut H) {
1305        if self.is_zero() {
1306            false.hash(state);
1307        } else {
1308            self.is_negative.hash(state);
1309        }
1310        self.inner.hash(state);
1311    }
1312}
1313
1314impl PartialOrd for Duration {
1315    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
1316        Some(self.cmp(other))
1317    }
1318}
1319
1320impl Ord for Duration {
1321    fn cmp(&self, other: &Self) -> Ordering {
1322        match (self.is_negative, other.is_negative) {
1323            (true, true) => other.inner.cmp(&self.inner),
1324            (true, false) | (false, true) if self.is_zero() && other.is_zero() => Ordering::Equal,
1325            (true, false) => Ordering::Less,
1326            (false, true) => Ordering::Greater,
1327            (false, false) => self.inner.cmp(&other.inner),
1328        }
1329    }
1330}
1331
1332/// Convert a [`std::time::Duration`] into a [`Duration`]
1333impl From<std::time::Duration> for Duration {
1334    fn from(duration: std::time::Duration) -> Self {
1335        Self {
1336            is_negative: false,
1337            inner: duration,
1338        }
1339    }
1340}
1341
1342impl SaturatingInto<std::time::Duration> for Duration {
1343    fn saturating_into(self) -> std::time::Duration {
1344        self.try_into().unwrap_or_else(|error| match error {
1345            TryFromDurationError::NegativeDuration => std::time::Duration::ZERO,
1346            _ => unreachable!(), // cov:excl-line
1347        })
1348    }
1349}
1350
1351/// Convert a [`Duration`] into a [`std::time::Duration`]
1352impl TryFrom<Duration> for std::time::Duration {
1353    type Error = TryFromDurationError;
1354
1355    fn try_from(duration: Duration) -> Result<Self, Self::Error> {
1356        if !duration.is_zero() && duration.is_negative {
1357            Err(TryFromDurationError::NegativeDuration)
1358        } else {
1359            Ok(duration.inner)
1360        }
1361    }
1362}
1363
1364#[cfg(feature = "time")]
1365/// Convert a [`time::Duration`] into a [`Duration`]
1366///
1367/// [`time::Duration`]: <https://docs.rs/time/latest/time/struct.Duration.html>
1368impl From<time::Duration> for Duration {
1369    fn from(duration: time::Duration) -> Self {
1370        Self {
1371            is_negative: duration.is_negative(),
1372            inner: std::time::Duration::new(
1373                duration.whole_seconds().unsigned_abs(),
1374                duration.subsec_nanoseconds().unsigned_abs(),
1375            ),
1376        }
1377    }
1378}
1379
1380#[cfg(feature = "time")]
1381/// Convert a [`Duration`] into a [`time::Duration`]
1382///
1383/// [`time::Duration`]: <https://docs.rs/time/latest/time/struct.Duration.html>
1384impl TryFrom<Duration> for time::Duration {
1385    type Error = TryFromDurationError;
1386
1387    fn try_from(duration: Duration) -> Result<Self, Self::Error> {
1388        (&duration).try_into()
1389    }
1390}
1391
1392#[cfg(feature = "time")]
1393/// Convert a [`Duration`] into a [`time::Duration`]
1394///
1395/// [`time::Duration`]: <https://docs.rs/time/latest/time/struct.Duration.html>
1396impl TryFrom<&Duration> for time::Duration {
1397    type Error = TryFromDurationError;
1398
1399    fn try_from(duration: &Duration) -> Result<Self, Self::Error> {
1400        #[allow(clippy::cast_possible_wrap)]
1401        match (duration.is_negative, duration.inner.as_secs()) {
1402            (true, secs) if secs > i64::MIN.unsigned_abs() => {
1403                Err(TryFromDurationError::NegativeOverflow)
1404            }
1405            (true, secs) => Ok(Self::new(
1406                secs.wrapping_neg() as i64,
1407                -(duration.inner.subsec_nanos() as i32),
1408            )),
1409            (false, secs) if secs > i64::MAX as u64 => Err(TryFromDurationError::PositiveOverflow),
1410            (false, secs) => Ok(Self::new(secs as i64, duration.inner.subsec_nanos() as i32)),
1411        }
1412    }
1413}
1414
1415#[cfg(feature = "time")]
1416impl SaturatingInto<time::Duration> for Duration {
1417    fn saturating_into(self) -> time::Duration {
1418        self.try_into().unwrap_or_else(|error| match error {
1419            TryFromDurationError::PositiveOverflow => time::Duration::MAX,
1420            TryFromDurationError::NegativeOverflow => time::Duration::MIN,
1421            TryFromDurationError::NegativeDuration => unreachable!(), // cov:excl-line
1422        })
1423    }
1424}
1425
1426#[cfg(feature = "chrono")]
1427/// Convert a [`Duration`] into a [`chrono::Duration`]
1428impl TryFrom<Duration> for chrono::Duration {
1429    type Error = TryFromDurationError;
1430
1431    fn try_from(duration: Duration) -> Result<Self, Self::Error> {
1432        (&duration).try_into()
1433    }
1434}
1435
1436#[cfg(feature = "chrono")]
1437/// Convert a [`Duration`] into a [`chrono::Duration`]
1438impl TryFrom<&Duration> for chrono::Duration {
1439    type Error = TryFromDurationError;
1440
1441    fn try_from(duration: &Duration) -> Result<Self, Self::Error> {
1442        const MAX: chrono::Duration = chrono::TimeDelta::MAX;
1443        const MIN: chrono::Duration = chrono::TimeDelta::MIN;
1444
1445        #[allow(clippy::cast_possible_wrap)]
1446        match (duration.is_negative, duration.inner.as_secs()) {
1447            (true, secs) if secs > MIN.num_seconds().unsigned_abs() => {
1448                Err(TryFromDurationError::NegativeOverflow)
1449            }
1450            (true, secs) => {
1451                let nanos = Self::nanoseconds((i64::from(duration.inner.subsec_nanos())).neg());
1452                Self::seconds((secs as i64).neg())
1453                    .checked_add(&nanos)
1454                    .ok_or(TryFromDurationError::NegativeOverflow)
1455            }
1456            (false, secs) if secs > MAX.num_seconds().unsigned_abs() => {
1457                Err(TryFromDurationError::PositiveOverflow)
1458            }
1459            (false, secs) => {
1460                let nanos = Self::nanoseconds(i64::from(duration.inner.subsec_nanos()));
1461                Self::seconds(secs as i64)
1462                    .checked_add(&nanos)
1463                    .ok_or(TryFromDurationError::PositiveOverflow)
1464            }
1465        }
1466    }
1467}
1468
1469#[cfg(feature = "chrono")]
1470impl SaturatingInto<chrono::Duration> for Duration {
1471    fn saturating_into(self) -> chrono::Duration {
1472        self.try_into().unwrap_or_else(|error| match error {
1473            TryFromDurationError::PositiveOverflow => chrono::TimeDelta::MAX,
1474            TryFromDurationError::NegativeOverflow => chrono::TimeDelta::MIN,
1475            TryFromDurationError::NegativeDuration => unreachable!(), // cov:excl-line
1476        })
1477    }
1478}
1479
1480#[cfg(feature = "chrono")]
1481/// Convert a [`chrono::Duration`] into a [`Duration`]
1482impl From<chrono::Duration> for Duration {
1483    fn from(duration: chrono::Duration) -> Self {
1484        duration.to_std().map_or_else(
1485            |_| Self {
1486                is_negative: true,
1487                inner: duration.abs().to_std().unwrap(),
1488            },
1489            |inner| Self {
1490                is_negative: false,
1491                inner,
1492            },
1493        )
1494    }
1495}
1496
1497#[cfg(test)]
1498mod tests {
1499    use std::collections::hash_map::DefaultHasher;
1500    use std::time::Duration as StdDuration;
1501
1502    #[cfg(feature = "chrono")]
1503    use chrono::Duration as ChronoDuration;
1504    use rstest::rstest;
1505    use rstest_reuse::{apply, template};
1506    #[cfg(feature = "serde")]
1507    use serde_test::{Token, assert_tokens};
1508
1509    use super::*;
1510
1511    #[cfg(feature = "chrono")]
1512    const CHRONO_MIN_DURATION: Duration =
1513        Duration::negative(i64::MIN.unsigned_abs() / 1000, 807_000_000);
1514    #[cfg(feature = "chrono")]
1515    const CHRONO_MAX_DURATION: Duration = Duration::positive(i64::MAX as u64 / 1000, 807_000_000);
1516
1517    const YEAR_AS_SECS: u64 = 60 * 60 * 24 * 365 + 60 * 60 * 24 / 4; // 365 days + day/4
1518    const MONTH_AS_SECS: u64 = YEAR_AS_SECS / 12;
1519
1520    #[cfg(feature = "serde")]
1521    #[test]
1522    fn test_time_unit_serde() {
1523        let time_unit = TimeUnit::Day;
1524
1525        assert_tokens(
1526            &time_unit,
1527            &[
1528                Token::Enum { name: "TimeUnit" },
1529                Token::Str("Day"),
1530                Token::Unit,
1531            ],
1532        );
1533    }
1534
1535    #[cfg(feature = "serde")]
1536    #[test]
1537    fn test_serde_multiplier() {
1538        let multiplier = Multiplier(1, 2);
1539
1540        assert_tokens(
1541            &multiplier,
1542            &[
1543                Token::TupleStruct {
1544                    name: "Multiplier",
1545                    len: 2,
1546                },
1547                Token::I64(1),
1548                Token::I16(2),
1549                Token::TupleStructEnd,
1550            ],
1551        );
1552    }
1553
1554    #[cfg(feature = "serde")]
1555    #[test]
1556    fn test_serde_duration() {
1557        let duration = Duration::positive(1, 2);
1558
1559        assert_tokens(
1560            &duration,
1561            &[
1562                Token::Struct {
1563                    name: "Duration",
1564                    len: 2,
1565                },
1566                Token::Str("is_negative"),
1567                Token::Bool(false),
1568                Token::Str("inner"),
1569                Token::Struct {
1570                    name: "Duration",
1571                    len: 2,
1572                },
1573                Token::Str("secs"),
1574                Token::U64(1),
1575                Token::Str("nanos"),
1576                Token::U32(2),
1577                Token::StructEnd,
1578                Token::StructEnd,
1579            ],
1580        );
1581    }
1582
1583    #[rstest]
1584    #[case::nano_second(NanoSecond, "ns")]
1585    #[case::micro_second(MicroSecond, "Ms")]
1586    #[case::milli_second(MilliSecond, "ms")]
1587    #[case::second(Second, "s")]
1588    #[case::minute(Minute, "m")]
1589    #[case::hour(Hour, "h")]
1590    #[case::day(Day, "d")]
1591    #[case::week(Week, "w")]
1592    #[case::month(Month, "M")]
1593    #[case::year(Year, "y")]
1594    fn test_time_unit_default_identifier(#[case] time_unit: TimeUnit, #[case] expected: &str) {
1595        assert_eq!(time_unit.default_identifier(), expected);
1596    }
1597
1598    #[rstest]
1599    #[case::nano_second(NanoSecond, Multiplier(1, -9))]
1600    #[case::micro_second(MicroSecond, Multiplier(1, -6))]
1601    #[case::milli_second(MilliSecond, Multiplier(1, -3))]
1602    #[case::second(Second, Multiplier(1, 0))]
1603    #[case::minute(Minute, Multiplier(60, 0))]
1604    #[case::hour(Hour, Multiplier(60 * 60, 0))]
1605    #[case::day(Day, Multiplier(60 * 60 * 24, 0))]
1606    #[case::week(Week, Multiplier(60 * 60 * 24 * 7, 0))]
1607    #[case::month(Month, Multiplier((60 * 60 * 24 * 365 + 60 * 60 * 24 / 4) / 12, 0))] // (365 days + day/4) / 12
1608    #[case::year(Year, Multiplier(60 * 60 * 24 * 365 + 60 * 60 * 24 / 4, 0))] // 365 days + day/4
1609    fn test_time_unit_multiplier(#[case] time_unit: TimeUnit, #[case] expected: Multiplier) {
1610        assert_eq!(time_unit.multiplier(), expected);
1611    }
1612
1613    #[test]
1614    fn test_multiplier_get_coefficient() {
1615        let multi = Multiplier(1234, 0);
1616        assert_eq!(multi.coefficient(), 1234);
1617    }
1618
1619    #[test]
1620    fn test_multiplier_get_exponent() {
1621        let multi = Multiplier(0, 1234);
1622        assert_eq!(multi.exponent(), 1234);
1623    }
1624
1625    #[rstest]
1626    #[case::zero(Multiplier(0, 0), false)]
1627    #[case::negative(Multiplier(-1, 0), true)]
1628    #[case::positive(Multiplier(1, 0), false)]
1629    #[case::negative_exponent(Multiplier(1, -1), false)]
1630    #[case::positive_exponent(Multiplier(-1, 1), true)]
1631    fn test_multiplier_is_negative_and_is_positive(
1632        #[case] multi: Multiplier,
1633        #[case] expected: bool,
1634    ) {
1635        assert_eq!(multi.is_negative(), expected);
1636        assert_eq!(multi.is_positive(), !expected);
1637    }
1638
1639    #[rstest]
1640    #[case::nano_second(NanoSecond, Multiplier(i64::MAX, i16::MIN + 9))]
1641    #[case::micro_second(MicroSecond, Multiplier(i64::MAX, i16::MIN + 6))]
1642    #[case::milli_second(MilliSecond, Multiplier(i64::MAX, i16::MIN + 3))]
1643    #[case::second(Second, Multiplier(i64::MAX, i16::MIN))]
1644    #[case::minute(Minute, Multiplier(i64::MAX / 60, i16::MIN))]
1645    #[case::hour(Hour, Multiplier(i64::MAX / (60 * 60), i16::MIN))]
1646    #[case::day(Day, Multiplier(i64::MAX / (60 * 60 * 24), i16::MIN))]
1647    #[case::week(Week, Multiplier(i64::MAX / (60 * 60 * 24 * 7), i16::MIN))]
1648    #[case::month(Month, Multiplier(3_507_252_276_543, i16::MIN))]
1649    #[case::year(Year, Multiplier(292_271_023_045, i16::MIN))]
1650    fn test_multiplier_multiplication_barely_no_panic(
1651        #[case] time_unit: TimeUnit,
1652        #[case] multiplier: Multiplier,
1653    ) {
1654        _ = time_unit.multiplier() * multiplier;
1655    }
1656
1657    #[rstest]
1658    #[case::nano_second(NanoSecond, Multiplier(i64::MAX, i16::MIN + 8))]
1659    #[case::micro_second(MicroSecond, Multiplier(i64::MAX, i16::MIN + 4))]
1660    #[case::milli_second(MilliSecond, Multiplier(i64::MAX, i16::MIN + 2))]
1661    #[case::minute(Minute, Multiplier(i64::MAX / 60 + 1, i16::MIN))]
1662    #[case::hour(Hour, Multiplier(i64::MAX / (60 * 60) + 1, i16::MIN))]
1663    #[case::day(Day, Multiplier(i64::MAX / (60 * 60 * 24) + 1, i16::MIN))]
1664    #[case::week(Week, Multiplier(i64::MAX / (60 * 60 * 24 * 7) + 1, i16::MIN))]
1665    #[case::month(Month, Multiplier(3_507_252_276_543 + 1, i16::MIN))]
1666    #[case::year(Year, Multiplier(292_271_023_045 + 1, i16::MIN))]
1667    #[should_panic(expected = "Overflow")]
1668    fn test_multiplier_multiplication_then_panic(
1669        #[case] time_unit: TimeUnit,
1670        #[case] multiplier: Multiplier,
1671    ) {
1672        _ = time_unit.multiplier() * multiplier;
1673    }
1674
1675    #[rstest]
1676    #[case::positive_zero(Duration::positive(0, 0), true)]
1677    #[case::negative_zero(Duration::negative(0, 0), false)] // FIXME: This should return true
1678    #[case::positive_one(Duration::positive(1, 0), true)]
1679    #[case::negative_one(Duration::negative(1, 0), false)]
1680    fn test_fundu_duration_is_positive_and_is_negative(
1681        #[case] duration: Duration,
1682        #[case] expected: bool,
1683    ) {
1684        assert_eq!(duration.is_positive(), expected);
1685        assert_eq!(duration.is_negative(), !expected);
1686    }
1687
1688    #[rstest]
1689    #[case::positive_zero(Duration::positive(0, 0), Duration::positive(0, 0))]
1690    #[case::negative_zero(Duration::negative(0, 0), Duration::positive(0, 0))]
1691    #[case::positive_one(Duration::positive(1, 0), Duration::positive(1, 0))]
1692    #[case::positive_one_nano(Duration::positive(0, 1), Duration::positive(0, 1))]
1693    #[case::negative_one(Duration::negative(1, 0), Duration::positive(1, 0))]
1694    #[case::negative_one_nano(Duration::negative(0, 1), Duration::positive(0, 1))]
1695    #[case::negative_one_one(Duration::negative(1, 1), Duration::positive(1, 1))]
1696    fn test_fundu_duration_abs(#[case] duration: Duration, #[case] expected: Duration) {
1697        assert_eq!(duration.abs(), expected);
1698    }
1699
1700    #[rstest]
1701    #[case::zero(Duration::ZERO, "0ns")]
1702    #[case::nano_second(Duration::positive(0, 1), "1ns")]
1703    #[case::micro_second(Duration::positive(0, 1_000), "1Ms")]
1704    #[case::milli_second(Duration::positive(0, 1_000_000), "1ms")]
1705    #[case::second(Duration::positive(1, 0), "1s")]
1706    #[case::minute(Duration::positive(60, 0), "1m")]
1707    #[case::hour(Duration::positive(60 * 60, 0), "1h")]
1708    #[case::day(Duration::positive(60 * 60 * 24, 0), "1d")]
1709    #[case::week(Duration::positive(60 * 60 * 24 * 7, 0), "1w")]
1710    #[case::month(Duration::positive(MONTH_AS_SECS, 0), "1M")]
1711    #[case::year(Duration::positive(YEAR_AS_SECS, 0), "1y")]
1712    #[case::all_one(
1713        Duration::positive(
1714            YEAR_AS_SECS + MONTH_AS_SECS + 60 * 60 * 24 * 8 + 60 * 60 + 60 + 1,
1715            1_001_001
1716        ),
1717        "1y 1M 1w 1d 1h 1m 1s 1ms 1Ms 1ns"
1718    )]
1719    #[case::max(Duration::MAX, "584542046090y 7M 2w 1d 17h 30m 15s 999ms 999Ms 999ns")]
1720    fn test_fundu_display(#[case] duration: Duration, #[case] expected: &str) {
1721        assert_eq!(duration.to_string(), expected.to_owned());
1722        if duration.is_zero() {
1723            assert_eq!(duration.neg().to_string(), expected.to_owned());
1724        } else {
1725            assert_eq!(
1726                duration.neg().to_string(),
1727                format!("-{}", expected.replace(' ', " -"))
1728            );
1729        }
1730    }
1731
1732    #[template]
1733    #[rstest]
1734    #[case::both_standard_zero(Duration::ZERO, Duration::ZERO, Duration::ZERO)]
1735    #[case::both_positive_zero(
1736        Duration::positive(0, 0),
1737        Duration::positive(0, 0),
1738        Duration::positive(0, 0)
1739    )]
1740    #[case::both_negative_zero(Duration::negative(0, 0), Duration::negative(0, 0), Duration::ZERO)]
1741    #[case::one_add_zero(Duration::positive(1, 0), Duration::ZERO, Duration::positive(1, 0))]
1742    #[case::minus_one_add_zero(Duration::negative(1, 0), Duration::ZERO, Duration::negative(1, 0))]
1743    #[case::minus_one_add_plus_one(
1744        Duration::negative(1, 0),
1745        Duration::positive(1, 0),
1746        Duration::ZERO
1747    )]
1748    #[case::minus_one_add_plus_two_then_carry(
1749        Duration::negative(1, 0),
1750        Duration::positive(2, 0),
1751        Duration::positive(1, 0)
1752    )]
1753    #[case::minus_one_nano_add_one_then_carry(
1754        Duration::negative(0, 1),
1755        Duration::positive(1, 0),
1756        Duration::positive(0, 999_999_999)
1757    )]
1758    #[case::plus_one_nano_add_minus_one_then_carry(
1759        Duration::positive(0, 1),
1760        Duration::negative(1, 0),
1761        Duration::negative(0, 999_999_999)
1762    )]
1763    #[case::plus_one_add_minus_two_then_carry(
1764        Duration::positive(1, 0),
1765        Duration::negative(2, 0),
1766        Duration::negative(1, 0)
1767    )]
1768    #[case::one_sec_below_min_add_max(
1769        Duration::negative(u64::MAX - 1, 999_999_999),
1770        Duration::MAX,
1771        Duration::positive(1, 0),
1772    )]
1773    #[case::one_nano_below_min_add_max(
1774        Duration::negative(u64::MAX, 999_999_998),
1775        Duration::MAX,
1776        Duration::positive(0, 1)
1777    )]
1778    #[case::one_sec_below_max_add_min(
1779        Duration::positive(u64::MAX - 1, 999_999_999),
1780        Duration::MIN,
1781        Duration::negative(1, 0)
1782    )]
1783    #[case::one_nano_below_max_add_min(
1784        Duration::positive(u64::MAX, 999_999_998),
1785        Duration::MIN,
1786        Duration::negative(0, 1)
1787    )]
1788    #[case::min_and_max(Duration::MIN, Duration::MAX, Duration::ZERO)]
1789    fn test_fundu_duration_add_no_overflow_template(
1790        #[case] lhs: Duration,
1791        #[case] rhs: Duration,
1792        #[case] expected: Duration,
1793    ) {
1794    }
1795
1796    #[apply(test_fundu_duration_add_no_overflow_template)]
1797    fn test_fundu_duration_add(lhs: Duration, rhs: Duration, expected: Duration) {
1798        assert_eq!(lhs + rhs, expected);
1799        assert_eq!(rhs + lhs, expected);
1800    }
1801
1802    #[apply(test_fundu_duration_add_no_overflow_template)]
1803    fn test_fundu_duration_add_assign(lhs: Duration, rhs: Duration, expected: Duration) {
1804        let mut res = lhs;
1805        res += rhs;
1806        assert_eq!(res, expected);
1807        let mut res = rhs;
1808        res += lhs;
1809        assert_eq!(res, expected);
1810    }
1811
1812    #[apply(test_fundu_duration_add_no_overflow_template)]
1813    fn test_fundu_duration_checked_add(lhs: Duration, rhs: Duration, expected: Duration) {
1814        assert_eq!(lhs.checked_add(rhs), Some(expected));
1815        assert_eq!(rhs.checked_add(lhs), Some(expected));
1816    }
1817
1818    #[apply(test_fundu_duration_add_no_overflow_template)]
1819    fn test_fundu_duration_sub(lhs: Duration, rhs: Duration, expected: Duration) {
1820        assert_eq!(lhs - rhs.neg(), expected);
1821        assert_eq!(rhs - lhs.neg(), expected);
1822    }
1823
1824    #[apply(test_fundu_duration_add_no_overflow_template)]
1825    fn test_fundu_duration_sub_assign(lhs: Duration, rhs: Duration, expected: Duration) {
1826        let mut res = lhs;
1827        res -= rhs.neg();
1828        assert_eq!(res, expected);
1829        let mut res = rhs;
1830        res -= lhs.neg();
1831        assert_eq!(res, expected);
1832    }
1833
1834    #[apply(test_fundu_duration_add_no_overflow_template)]
1835    fn test_fundu_duration_checked_sub(lhs: Duration, rhs: Duration, expected: Duration) {
1836        assert_eq!(lhs.checked_sub(rhs.neg()), Some(expected));
1837        assert_eq!(rhs.checked_sub(lhs.neg()), Some(expected));
1838    }
1839
1840    #[template]
1841    #[rstest]
1842    #[case::min(Duration::MIN, Duration::MIN)]
1843    #[case::min_minus_one(Duration::MIN, Duration::negative(1, 0))]
1844    #[case::min_minus_one_nano(Duration::MIN, Duration::negative(0, 1))]
1845    #[case::max(Duration::MAX, Duration::MAX)]
1846    #[case::max_plus_one(Duration::MAX, Duration::positive(1, 0))]
1847    #[case::max_plus_one_nano(Duration::MAX, Duration::positive(0, 1))]
1848    #[case::positive_middle_nano_overflow(
1849        Duration::positive(u64::MAX / 2 + 1, 999_999_999 / 2 + 1),
1850        Duration::positive(u64::MAX / 2, 999_999_999 / 2 + 1)
1851    )]
1852    #[case::positive_middle_secs_overflow(
1853        Duration::positive(u64::MAX / 2 + 1, 999_999_999 / 2 + 1),
1854        Duration::positive(u64::MAX / 2 + 1, 999_999_999 / 2)
1855    )]
1856    #[case::negative_middle_nano_overflow(
1857        Duration::negative(u64::MAX / 2 + 1, 999_999_999 / 2 + 1),
1858        Duration::negative(u64::MAX / 2, 999_999_999 / 2 + 1)
1859    )]
1860    #[case::negative_middle_secs_overflow(
1861        Duration::negative(u64::MAX / 2 + 1, 999_999_999 / 2 + 1),
1862         Duration::negative(u64::MAX / 2 + 1, 999_999_999 / 2)
1863    )]
1864    fn test_fundu_duration_add_overflow_template(#[case] lhs: Duration, #[case] rhs: Duration) {}
1865
1866    #[apply(test_fundu_duration_add_overflow_template)]
1867    #[should_panic = "Overflow when adding duration"]
1868    fn test_fundu_duration_add_and_add_assign_then_overflow(mut lhs: Duration, rhs: Duration) {
1869        lhs += rhs;
1870    }
1871
1872    #[apply(test_fundu_duration_add_overflow_template)]
1873    #[should_panic = "Overflow when subtracting duration"]
1874    #[allow(clippy::no_effect)]
1875    fn test_fundu_duration_sub_and_sub_assign_then_overflow(mut lhs: Duration, rhs: Duration) {
1876        lhs -= rhs.neg();
1877    }
1878
1879    #[apply(test_fundu_duration_add_overflow_template)]
1880    fn test_fundu_duration_checked_add_then_overflow(lhs: Duration, rhs: Duration) {
1881        assert_eq!(lhs.checked_add(rhs), None);
1882        assert_eq!(rhs.checked_add(lhs), None);
1883    }
1884
1885    #[apply(test_fundu_duration_add_overflow_template)]
1886    fn test_fundu_duration_checked_sub_then_overflow(lhs: Duration, rhs: Duration) {
1887        assert_eq!(lhs.checked_sub(rhs.neg()), None);
1888    }
1889
1890    #[apply(test_fundu_duration_add_overflow_template)]
1891    fn test_fundu_duration_saturating_add_then_saturate(lhs: Duration, rhs: Duration) {
1892        let expected = if lhs.checked_add(rhs).is_none() && lhs.is_positive() && rhs.is_positive() {
1893            Duration::MAX
1894        } else {
1895            Duration::MIN
1896        };
1897        assert_eq!(lhs.saturating_add(rhs), expected);
1898        assert_eq!(rhs.saturating_add(lhs), expected);
1899    }
1900
1901    #[apply(test_fundu_duration_add_overflow_template)]
1902    fn test_fundu_duration_saturating_sub_then_saturate(lhs: Duration, rhs: Duration) {
1903        let expected =
1904            if lhs.checked_sub(rhs.neg()).is_none() && lhs.is_negative() && rhs.neg().is_positive()
1905            {
1906                Duration::MIN
1907            } else {
1908                Duration::MAX
1909            };
1910        assert_eq!(lhs.saturating_sub(rhs.neg()), expected);
1911    }
1912
1913    #[rstest]
1914    #[case::zero(Duration::ZERO, 0)]
1915    #[case::positive_one(Duration::positive(1, 0), 1)]
1916    #[case::positive_one_nano(Duration::positive(0, 1), 0)]
1917    #[case::positive_one_sec_and_nano(Duration::positive(1, 1), 1)]
1918    #[case::negative_one(Duration::negative(1, 0), -1)]
1919    #[case::negative_one_nano(Duration::negative(0, 1), 0)]
1920    #[case::negative_one_sec_and_nano(Duration::negative(1, 1), -1)]
1921    #[case::some_positive_value(Duration::positive(123_456_789, 987_654_321), 123_456_789)]
1922    #[case::some_negative_value(Duration::negative(123_456_789, 987_654_321), -123_456_789)]
1923    #[case::min(Duration::MIN, i128::from(u64::MAX).neg())]
1924    #[case::max(Duration::MAX, i128::from(u64::MAX))]
1925    fn test_fundu_duration_as_seconds(#[case] duration: Duration, #[case] expected: i128) {
1926        assert_eq!(duration.as_seconds(), expected);
1927    }
1928
1929    #[rstest]
1930    #[case::zero(Duration::ZERO)]
1931    #[case::one(Duration::positive(1, 0))]
1932    #[case::one_nano(Duration::positive(0, 1))]
1933    #[case::one_sec_and_nano(Duration::positive(1, 1))]
1934    #[case::one_minute(Duration::positive(60, 0))]
1935    #[case::one_minute_plus_one_sec(Duration::positive(61, 0))]
1936    #[case::one_minute_minus_one_sec(Duration::positive(59, 0))]
1937    #[case::two_minutes(Duration::positive(120, 0))]
1938    #[case::some_value(Duration::positive(123_456_789, 987_654_321))]
1939    #[case::min(Duration::MIN)]
1940    #[case::max(Duration::MAX)]
1941    fn test_fundu_duration_as_whole(
1942        #[case] duration: Duration,
1943        #[values(2, 3, 4, 60, u64::MAX / 2, u64::MAX)] factor: u64,
1944    ) {
1945        let mut expected = i64::try_from(duration.inner.as_secs() / factor).unwrap();
1946        if duration.is_negative() {
1947            expected = expected.neg();
1948        }
1949        assert_eq!(duration.as_whole(factor), expected);
1950        assert_eq!(duration.neg().as_whole(factor), expected.neg());
1951    }
1952
1953    #[rstest]
1954    #[case::one_week(86400 * 7, 1)]
1955    #[case::one_week_plus_sec(86400 * 7 + 1, 1)]
1956    #[case::one_week_minus_sec(86400 * 7 - 1, 0)]
1957    fn test_fundu_duration_as_weeks(#[case] seconds: u64, #[case] expected: i64) {
1958        let duration = Duration::positive(seconds, 0);
1959
1960        assert_eq!(duration.as_weeks(), expected);
1961        assert_eq!(duration.neg().as_weeks(), expected.neg());
1962    }
1963
1964    #[rstest]
1965    #[case::one_day(86400, 1)]
1966    #[case::one_day_plus_sec(86400 + 1, 1)]
1967    #[case::one_day_minus_sec(86400 - 1, 0)]
1968    fn test_fundu_duration_as_days(#[case] seconds: u64, #[case] expected: i64) {
1969        let duration = Duration::positive(seconds, 0);
1970
1971        assert_eq!(duration.as_days(), expected);
1972        assert_eq!(duration.neg().as_days(), expected.neg());
1973    }
1974
1975    #[rstest]
1976    #[case::one_hour(3600, 1)]
1977    #[case::one_hour_plus_sec(3600 + 1, 1)]
1978    #[case::one_hour_minus_sec(3600 - 1, 0)]
1979    fn test_fundu_duration_as_hours(#[case] seconds: u64, #[case] expected: i64) {
1980        let duration = Duration::positive(seconds, 0);
1981
1982        assert_eq!(duration.as_hours(), expected);
1983        assert_eq!(duration.neg().as_hours(), expected.neg());
1984    }
1985
1986    #[rstest]
1987    #[case::one_minute(60, 1)]
1988    #[case::one_minute_plus_sec(60 + 1, 1)]
1989    #[case::one_minute_minus_sec(60 - 1, 0)]
1990    fn test_fundu_duration_as_minutes(#[case] seconds: u64, #[case] expected: i64) {
1991        let duration = Duration::positive(seconds, 0);
1992
1993        assert_eq!(duration.as_minutes(), expected);
1994        assert_eq!(duration.neg().as_minutes(), expected.neg());
1995    }
1996
1997    #[template]
1998    #[rstest]
1999    #[case::zero(Duration::ZERO, 0)]
2000    #[case::one_second(Duration::positive(1, 0), 1_000_000_000)]
2001    #[case::one_second_and_one_nano(Duration::positive(1, 1), 1_000_000_001)]
2002    #[case::one_second_and_one_nano(Duration::positive(1, 1), 1_000_000_001)]
2003    #[case::two_second(Duration::positive(2, 0), 2_000_000_000)]
2004    #[case::two_second_and_one_nano(Duration::positive(2, 1), 2_000_000_001)]
2005    #[case::one_sec_and_one_micro(Duration::positive(2, 1_000), 2_000_001_000)]
2006    #[case::one_sec_and_one_milli(Duration::positive(2, 1_000_000), 2_001_000_000)]
2007    #[case::some_value(Duration::positive(123_456_789, 987_654_321), 123_456_789_987_654_321)]
2008    #[case::max(Duration::MAX, i128::from(u64::MAX) * 1_000_000_000 + 999_999_999)]
2009    #[case::min(Duration::MIN, i128::from(u64::MAX).neg() * 1_000_000_000 - 999_999_999)]
2010    fn test_fundu_duration_as_sub_sec_template(#[case] duration: Duration, #[case] expected: i128) {
2011    }
2012
2013    #[apply(test_fundu_duration_as_sub_sec_template)]
2014    fn test_fundu_duration_as_nanos(duration: Duration, expected: i128) {
2015        assert_eq!(duration.as_nanos(), expected);
2016        assert_eq!(duration.neg().as_nanos(), expected.neg());
2017    }
2018
2019    #[apply(test_fundu_duration_as_sub_sec_template)]
2020    fn test_fundu_duration_as_micros(duration: Duration, expected: i128) {
2021        assert_eq!(duration.as_micros(), expected / 1_000);
2022        assert_eq!(duration.neg().as_micros(), expected.neg() / 1_000);
2023    }
2024
2025    #[apply(test_fundu_duration_as_sub_sec_template)]
2026    fn test_fundu_duration_as_millis(duration: Duration, expected: i128) {
2027        assert_eq!(duration.as_millis(), expected / 1_000_000);
2028        assert_eq!(duration.neg().as_millis(), expected.neg() / 1_000_000);
2029    }
2030
2031    #[template]
2032    #[rstest]
2033    #[case::zero(Duration::ZERO, 0i32)]
2034    #[case::one_sec(Duration::positive(1, 0), 0i32)]
2035    #[case::one_sec_and_one_nano(Duration::positive(1, 1), 1i32)]
2036    #[case::two_nano(Duration::positive(0, 2), 2i32)]
2037    #[case::one_micro(Duration::positive(0, 1_000), 1_000i32)]
2038    #[case::one_milli(Duration::positive(0, 1_000_000), 1_000_000i32)]
2039    #[case::some_value(Duration::positive(0, 123_456_789), 123_456_789i32)]
2040    #[case::max(Duration::MAX, 999_999_999i32)]
2041    #[case::min(Duration::MIN, -999_999_999i32)]
2042    fn test_fundu_duration_subsec_template(#[case] duration: Duration, #[case] expected: i32) {}
2043
2044    #[apply(test_fundu_duration_subsec_template)]
2045    fn test_fundu_duration_subsec_nanos(duration: Duration, expected: i32) {
2046        assert_eq!(duration.subsec_nanos(), expected);
2047        assert_eq!(duration.neg().subsec_nanos(), expected.neg());
2048    }
2049
2050    #[apply(test_fundu_duration_subsec_template)]
2051    fn test_fundu_duration_subsec_micros(duration: Duration, expected: i32) {
2052        let expected = expected / 1000i32;
2053        assert_eq!(duration.subsec_micros(), expected);
2054        assert_eq!(duration.neg().subsec_micros(), expected.neg());
2055    }
2056
2057    #[apply(test_fundu_duration_subsec_template)]
2058    fn test_fundu_duration_subsec_millis(duration: Duration, expected: i32) {
2059        let expected = expected / 1_000_000i32;
2060        assert_eq!(duration.subsec_millis(), expected);
2061        assert_eq!(duration.neg().subsec_millis(), expected.neg());
2062    }
2063
2064    #[rstest]
2065    #[case::zero(Duration::ZERO)]
2066    #[case::one(Duration::positive(1, 0))]
2067    #[case::one_plus_one_nano(Duration::positive(1, 1))]
2068    #[case::two(Duration::positive(2, 0))]
2069    #[case::two_plus_one_nano(Duration::positive(2, 1))]
2070    #[case::sixty(Duration::positive(60, 0))]
2071    #[case::sixty_plus_one(Duration::positive(61, 0))]
2072    #[case::sixty_minus_one(Duration::positive(59, 0))]
2073    #[case::some_value(Duration::positive(123_456_789, 987_654_321))]
2074    #[case::max(Duration::MAX)]
2075    #[case::min(Duration::MIN)]
2076    fn test_fundu_duration_extract_i64(
2077        #[case] mut duration: Duration,
2078        #[values(2, 3, 4, 5, 60, u64::MAX / 2, u64::MAX)] factor: u64,
2079    ) {
2080        let mut expected_number = i64::try_from(duration.inner.as_secs() / factor).unwrap();
2081        if duration.is_negative() {
2082            expected_number = expected_number.neg();
2083        }
2084        let expected_duration = Duration::from_std(
2085            duration.is_negative(),
2086            StdDuration::new(
2087                duration.inner.as_secs() % factor,
2088                duration.inner.subsec_nanos(),
2089            ),
2090        );
2091
2092        let actual_number = duration.extract_i64(factor);
2093        assert_eq!(actual_number, expected_number);
2094        assert_eq!(duration, expected_duration);
2095    }
2096
2097    #[rstest]
2098    #[case::one(Duration::positive(86400 * 7, 123), 1, Duration::positive(0, 123))]
2099    #[case::one_plus_sec(
2100        Duration::positive(86400 * 7 + 1, 123),
2101        1,
2102        Duration::positive(1, 123)
2103    )]
2104    #[case::one_minus_sec(
2105        Duration::positive(86400 * 7 - 1, 123),
2106        0,
2107        Duration::positive(86400 * 7 - 1, 123)
2108    )]
2109    fn test_fundu_duration_extract_weeks(
2110        #[case] duration: Duration,
2111        #[case] expected_number: i64,
2112        #[case] expected_duration: Duration,
2113    ) {
2114        let mut duration = duration;
2115        let number = duration.extract_weeks();
2116        assert_eq!(number, expected_number);
2117        assert_eq!(duration, expected_duration);
2118    }
2119
2120    #[rstest]
2121    #[case::one(Duration::positive(86400, 123), 1, Duration::positive(0, 123))]
2122    #[case::one_plus_sec(
2123        Duration::positive(86400 + 1, 123),
2124        1,
2125        Duration::positive(1, 123)
2126    )]
2127    #[case::one_minus_sec(
2128        Duration::positive(86400 - 1, 123),
2129        0,
2130        Duration::positive(86400 - 1, 123)
2131    )]
2132    fn test_fundu_duration_extract_days(
2133        #[case] duration: Duration,
2134        #[case] expected_number: i64,
2135        #[case] expected_duration: Duration,
2136    ) {
2137        let mut duration = duration;
2138        let number = duration.extract_days();
2139        assert_eq!(number, expected_number);
2140        assert_eq!(duration, expected_duration);
2141    }
2142
2143    #[rstest]
2144    #[case::one(Duration::positive(3600, 123), 1, Duration::positive(0, 123))]
2145    #[case::one_plus_sec(Duration::positive(3600 + 1, 123), 1, Duration::positive(1, 123))]
2146    #[case::one_minus_sec(Duration::positive(3600 - 1, 123), 0, Duration::positive(3599, 123))]
2147    fn test_fundu_duration_extract_hours(
2148        #[case] duration: Duration,
2149        #[case] expected_number: i64,
2150        #[case] expected_duration: Duration,
2151    ) {
2152        let mut duration = duration;
2153        let number = duration.extract_hours();
2154        assert_eq!(number, expected_number);
2155        assert_eq!(duration, expected_duration);
2156    }
2157
2158    #[rstest]
2159    #[case::one(Duration::positive(60, 123), 1, Duration::positive(0, 123))]
2160    #[case::one_plus_sec(Duration::positive(60 + 1, 123), 1, Duration::positive(1, 123))]
2161    #[case::one_minus_sec(Duration::positive(60 - 1, 123), 0, Duration::positive(59, 123))]
2162    fn test_fundu_duration_extract_minutes(
2163        #[case] duration: Duration,
2164        #[case] expected_number: i64,
2165        #[case] expected_duration: Duration,
2166    ) {
2167        let mut duration = duration;
2168        let number = duration.extract_minutes();
2169        assert_eq!(number, expected_number);
2170        assert_eq!(duration, expected_duration);
2171    }
2172
2173    #[rstest]
2174    #[case::zero(Duration::ZERO, 0, Duration::ZERO)]
2175    #[case::one(Duration::positive(1, 123), 1, Duration::positive(0, 123))]
2176    #[case::two(Duration::positive(2, 123), 2, Duration::positive(0, 123))]
2177    #[case::zero_sec(Duration::positive(0, 123), 0, Duration::positive(0, 123))]
2178    #[case::max(
2179        Duration::MAX,
2180        i128::from(u64::MAX),
2181        Duration::positive(0, 999_999_999)
2182    )]
2183    #[case::min(Duration::MIN, i128::from(u64::MAX).neg(), Duration::negative(0, 999_999_999))]
2184    fn test_fundu_duration_extract_seconds(
2185        #[case] duration: Duration,
2186        #[case] expected_number: i128,
2187        #[case] expected_duration: Duration,
2188    ) {
2189        let mut duration_copy = duration;
2190        let number = duration_copy.extract_seconds();
2191        assert_eq!(number, expected_number);
2192        assert_eq!(duration_copy, expected_duration);
2193
2194        let mut duration = duration.neg();
2195        let number = duration.extract_seconds();
2196        assert_eq!(number, expected_number.neg());
2197        assert_eq!(duration, expected_duration.neg());
2198    }
2199
2200    #[rstest]
2201    #[case::positive_zero(Duration::ZERO, Duration::ZERO, Ordering::Equal)]
2202    #[case::negative_zero(Duration::negative(0, 0), Duration::negative(0, 0), Ordering::Equal)]
2203    #[case::negative_zero_and_positive_zero(
2204        Duration::negative(0, 0),
2205        Duration::ZERO,
2206        Ordering::Equal
2207    )]
2208    #[case::both_positive_one_sec(
2209        Duration::positive(1, 0),
2210        Duration::positive(1, 0),
2211        Ordering::Equal
2212    )]
2213    #[case::both_negative_one_sec(
2214        Duration::negative(1, 0),
2215        Duration::negative(1, 0),
2216        Ordering::Equal
2217    )]
2218    #[case::negative_and_positive_one_sec(
2219        Duration::negative(1, 0),
2220        Duration::positive(1, 0),
2221        Ordering::Less
2222    )]
2223    #[case::one_nano_second_difference_positive(
2224        Duration::ZERO,
2225        Duration::positive(0, 1),
2226        Ordering::Less
2227    )]
2228    #[case::one_nano_second_difference_negative(
2229        Duration::ZERO,
2230        Duration::negative(0, 1),
2231        Ordering::Greater
2232    )]
2233    #[case::max(Duration::MAX, Duration::MAX, Ordering::Equal)]
2234    #[case::one_nano_below_max(
2235        Duration::MAX,
2236        Duration::positive(u64::MAX, 999_999_998),
2237        Ordering::Greater
2238    )]
2239    #[case::min(Duration::MIN, Duration::MIN, Ordering::Equal)]
2240    #[case::one_nano_above_min(
2241        Duration::MIN,
2242        Duration::negative(u64::MAX, 999_999_998),
2243        Ordering::Less
2244    )]
2245    #[case::min_max(Duration::MIN, Duration::MAX, Ordering::Less)]
2246    #[case::mixed_positive(
2247        Duration::positive(123, 987),
2248        Duration::positive(987, 123),
2249        Ordering::Less
2250    )]
2251    #[case::mixed_negative(
2252        Duration::negative(123, 987),
2253        Duration::negative(987, 123),
2254        Ordering::Greater
2255    )]
2256    #[case::mixed_positive_and_negative(
2257        Duration::positive(123, 987),
2258        Duration::negative(987, 123),
2259        Ordering::Greater
2260    )]
2261    fn test_duration_partial_and_total_ordering(
2262        #[case] lhs: Duration,
2263        #[case] rhs: Duration,
2264        #[case] expected: Ordering,
2265    ) {
2266        assert_eq!(lhs.partial_cmp(&rhs), Some(expected));
2267        assert_eq!(rhs.partial_cmp(&lhs), Some(expected.reverse()));
2268    }
2269
2270    #[rstest]
2271    #[case::positive_zero(Duration::ZERO, Duration::ZERO)]
2272    #[case::negative_zero(Duration::negative(0, 0), Duration::negative(0, 0))]
2273    #[case::negative_zero_and_positive_zero(Duration::negative(0, 0), Duration::ZERO)]
2274    #[case::positive_one_sec(Duration::positive(1, 0), Duration::positive(1, 0))]
2275    #[case::negative_one_sec(Duration::negative(1, 0), Duration::negative(1, 0))]
2276    #[case::max(Duration::MAX, Duration::MAX)]
2277    #[case::min(Duration::MIN, Duration::MIN)]
2278    fn test_duration_hash_and_eq_property_when_equal(
2279        #[case] duration: Duration,
2280        #[case] other: Duration,
2281    ) {
2282        assert_eq!(duration, other);
2283        assert_eq!(other, duration);
2284
2285        let mut hasher = DefaultHasher::new();
2286        duration.hash(&mut hasher);
2287
2288        let mut other_hasher = DefaultHasher::new();
2289        other.hash(&mut other_hasher);
2290
2291        assert_eq!(hasher.finish(), other_hasher.finish());
2292    }
2293
2294    #[rstest]
2295    #[case::zero_and_positive_one(Duration::ZERO, Duration::positive(1, 0))]
2296    #[case::zero_and_negative_one(Duration::ZERO, Duration::negative(1, 0))]
2297    #[case::positive_sec(Duration::positive(1, 0), Duration::negative(2, 0))]
2298    #[case::positive_and_negative_one_sec(Duration::positive(1, 0), Duration::negative(1, 0))]
2299    #[case::min_and_max(Duration::MIN, Duration::MAX)]
2300    fn test_duration_hash_and_eq_property_when_not_equal(
2301        #[case] duration: Duration,
2302        #[case] other: Duration,
2303    ) {
2304        assert_ne!(duration, other);
2305        assert_ne!(other, duration);
2306
2307        let mut hasher = DefaultHasher::new();
2308        duration.hash(&mut hasher);
2309
2310        let mut other_hasher = DefaultHasher::new();
2311        other.hash(&mut other_hasher);
2312
2313        assert_ne!(hasher.finish(), other_hasher.finish());
2314    }
2315
2316    #[rstest]
2317    #[case::positive_zero(Duration::ZERO, StdDuration::ZERO)]
2318    #[case::negative_zero(Duration::negative(0, 0), StdDuration::ZERO)]
2319    #[case::positive_one(Duration::positive(1, 0), StdDuration::new(1, 0))]
2320    #[case::positive_one_nano(Duration::positive(0, 1), StdDuration::new(0, 1))]
2321    #[case::negative_one(Duration::negative(1, 0), StdDuration::ZERO)]
2322    #[case::negative_one_nano(Duration::negative(0, 1), StdDuration::ZERO)]
2323    #[case::max(Duration::MAX, StdDuration::MAX)]
2324    fn test_fundu_duration_saturating_into_std_duration(
2325        #[case] duration: Duration,
2326        #[case] expected: StdDuration,
2327    ) {
2328        assert_eq!(
2329            SaturatingInto::<std::time::Duration>::saturating_into(duration),
2330            expected
2331        );
2332    }
2333
2334    #[cfg(feature = "time")]
2335    #[rstest]
2336    #[case::positive_zero(Duration::ZERO, time::Duration::ZERO)]
2337    #[case::negative_zero(Duration::negative(0, 0), time::Duration::ZERO)]
2338    #[case::positive_one(Duration::positive(1, 0), time::Duration::new(1, 0))]
2339    #[case::negative_one(Duration::negative(1, 0), time::Duration::new(-1, 0))]
2340    #[case::negative_barely_no_overflow(
2341        Duration::negative(i64::MIN.unsigned_abs(), 999_999_999),
2342        time::Duration::MIN
2343    )]
2344    #[case::negative_barely_overflow(
2345        Duration::negative(i64::MIN.unsigned_abs() + 1, 0),
2346        time::Duration::MIN
2347    )]
2348    #[case::negative_max_overflow(Duration::negative(u64::MAX, 999_999_999), time::Duration::MIN)]
2349    #[case::positive_barely_no_overflow(
2350        Duration::positive(i64::MAX as u64, 999_999_999),
2351        time::Duration::MAX
2352    )]
2353    #[case::positive_barely_overflow(
2354        Duration::positive(i64::MAX as u64 + 1, 999_999_999),
2355        time::Duration::MAX
2356    )]
2357    #[case::positive_max_overflow(Duration::positive(u64::MAX, 999_999_999), time::Duration::MAX)]
2358    fn test_fundu_duration_saturating_into_time_duration(
2359        #[case] duration: Duration,
2360        #[case] expected: time::Duration,
2361    ) {
2362        assert_eq!(
2363            SaturatingInto::<time::Duration>::saturating_into(duration),
2364            expected
2365        );
2366    }
2367
2368    #[cfg(feature = "chrono")]
2369    #[rstest]
2370    #[case::positive_zero(Duration::ZERO, chrono::Duration::zero())]
2371    #[case::negative_zero(Duration::negative(0, 0), chrono::Duration::zero())]
2372    #[case::positive_one(Duration::positive(1, 0), chrono::Duration::seconds(1))]
2373    #[case::negative_one(Duration::negative(1, 0), chrono::Duration::seconds(-1))]
2374    #[case::negative_barely_no_overflow(
2375        Duration::negative(i64::MIN.unsigned_abs() / 1000, 808_000_000),
2376        chrono::TimeDelta::MIN
2377    )]
2378    #[case::negative_barely_overflow(
2379        Duration::negative(i64::MIN.unsigned_abs() / 1000 + 1, 0),
2380        chrono::TimeDelta::MIN
2381    )]
2382    #[case::negative_max_overflow(
2383        Duration::negative(u64::MAX, 999_999_999),
2384        chrono::TimeDelta::MIN
2385    )]
2386    #[case::positive_barely_no_overflow(
2387        Duration::positive(i64::MAX as u64 / 1000, 807_000_000),
2388        chrono::TimeDelta::MAX
2389    )]
2390    #[case::positive_barely_overflow(
2391        Duration::positive(i64::MAX as u64 / 1000 + 1, 807_000_000),
2392        chrono::TimeDelta::MAX
2393    )]
2394    #[case::positive_max_overflow(
2395        Duration::positive(u64::MAX, 999_999_999),
2396        chrono::TimeDelta::MAX
2397    )]
2398    fn test_fundu_duration_saturating_into_chrono_duration(
2399        #[case] duration: Duration,
2400        #[case] expected: chrono::Duration,
2401    ) {
2402        assert_eq!(
2403            SaturatingInto::<chrono::Duration>::saturating_into(duration),
2404            expected
2405        );
2406    }
2407
2408    #[rstest]
2409    #[case::negative_one(Duration::negative(1, 0))]
2410    #[case::negative_one_nano(Duration::negative(0, 1))]
2411    #[case::one_one(Duration::negative(1, 1))]
2412    #[case::min(Duration::MIN)]
2413    fn test_std_duration_try_from_for_fundu_duration_then_error(#[case] duration: Duration) {
2414        assert_eq!(
2415            TryInto::<std::time::Duration>::try_into(duration).unwrap_err(),
2416            TryFromDurationError::NegativeDuration
2417        );
2418    }
2419
2420    #[cfg(feature = "chrono")]
2421    #[rstest]
2422    #[case::zero(Duration::ZERO, ChronoDuration::zero())]
2423    #[case::negative_zero(Duration::negative(0, 0), ChronoDuration::zero())]
2424    #[case::positive_one_sec(Duration::positive(1, 0), ChronoDuration::seconds(1))]
2425    #[case::positive_one_sec_and_nano(
2426        Duration::positive(1, 1),
2427        ChronoDuration::nanoseconds(1_000_000_001)
2428    )]
2429    #[case::negative_one_sec(Duration::negative(1, 0), ChronoDuration::seconds(-1))]
2430    #[case::negative_one_sec_and_nano(
2431        Duration::negative(1, 1),
2432        ChronoDuration::nanoseconds(-1_000_000_001)
2433    )]
2434    #[case::max_nanos(
2435        Duration::positive(0, 999_999_999),
2436        ChronoDuration::nanoseconds(999_999_999)
2437    )]
2438    #[case::min_nanos(
2439        Duration::negative(0, 999_999_999),
2440        ChronoDuration::nanoseconds(-999_999_999)
2441    )]
2442    #[case::max_secs(
2443        Duration::positive(i64::MAX as u64 / 1000, 0),
2444        ChronoDuration::seconds(i64::MAX / 1000))
2445    ]
2446    #[case::max_secs_and_nanos(
2447        Duration::positive(i64::MAX as u64 / 1000, 807_000_000),
2448        chrono::TimeDelta::MAX)
2449    ]
2450    #[case::secs_and_nanos_one_below_max(
2451        Duration::positive(i64::MAX as u64 / 1000, 807_000_000 - 1),
2452        chrono::TimeDelta::MAX.checked_sub(&ChronoDuration::nanoseconds(1)).unwrap())
2453    ]
2454    #[case::min_secs(
2455        Duration::negative(i64::MIN.unsigned_abs() / 1000, 0),
2456        ChronoDuration::seconds(i64::MIN / 1000))
2457    ]
2458    #[case::min_secs_and_nanos(
2459        Duration::negative(i64::MIN.unsigned_abs() / 1000, 807_000_000),
2460        chrono::TimeDelta::MIN)
2461    ]
2462    #[case::secs_and_nanos_one_above_min(
2463        Duration::negative(i64::MIN.unsigned_abs() / 1000, 807_000_000 - 1),
2464        chrono::TimeDelta::MIN.checked_add(&ChronoDuration::nanoseconds(1)).unwrap())
2465    ]
2466    fn test_chrono_duration_try_from_fundu_duration(
2467        #[case] duration: Duration,
2468        #[case] expected: ChronoDuration,
2469    ) {
2470        let chrono_duration: ChronoDuration = duration.try_into().unwrap();
2471        assert_eq!(chrono_duration, expected);
2472    }
2473
2474    #[cfg(feature = "chrono")]
2475    #[rstest]
2476    #[case::positive_overflow_secs(
2477        Duration::positive(i64::MAX as u64 / 1000 + 1, 0),
2478        TryFromDurationError::PositiveOverflow)
2479    ]
2480    #[case::positive_overflow_secs_and_nanos(
2481        Duration::positive(i64::MAX as u64 / 1000, 807_000_000 + 1),
2482        TryFromDurationError::PositiveOverflow)
2483    ]
2484    #[case::positive_overflow_max_fundu_duration(
2485        Duration::MAX,
2486        TryFromDurationError::PositiveOverflow
2487    )]
2488    #[case::negative_overflow_secs(
2489        Duration::negative(i64::MIN.unsigned_abs() / 1000 + 1, 0),
2490        TryFromDurationError::NegativeOverflow)
2491    ]
2492    #[case::negative_overflow_secs_and_nanos(
2493        Duration::negative(i64::MIN.unsigned_abs() / 1000, 808_000_001),
2494        TryFromDurationError::NegativeOverflow)
2495    ]
2496    #[case::negative_overflow_min_fundu_duration(
2497        Duration::MIN,
2498        TryFromDurationError::NegativeOverflow
2499    )]
2500    fn test_chrono_duration_try_from_fundu_duration_then_error(
2501        #[case] duration: Duration,
2502        #[case] expected: TryFromDurationError,
2503    ) {
2504        let result: Result<ChronoDuration, TryFromDurationError> = duration.try_into();
2505        assert_eq!(result.unwrap_err(), expected);
2506    }
2507
2508    #[cfg(feature = "time")]
2509    #[test]
2510    fn test_time_duration_try_from_fundu_duration() {
2511        let duration = Duration::from_std(false, std::time::Duration::new(1, 0));
2512        let time_duration: time::Duration = duration.try_into().unwrap();
2513        assert_eq!(time_duration, time::Duration::new(1, 0));
2514    }
2515
2516    #[rstest]
2517    #[case::zero(
2518        std::time::Duration::ZERO,
2519        Duration::from_std(false, std::time::Duration::ZERO)
2520    )]
2521    #[case::one(
2522        std::time::Duration::new(1, 0),
2523        Duration::from_std(false, std::time::Duration::new(1, 0))
2524    )]
2525    #[case::with_nano_seconds(
2526        std::time::Duration::new(1, 123_456_789),
2527        Duration::from_std(false, std::time::Duration::new(1, 123_456_789))
2528    )]
2529    #[case::max(
2530        std::time::Duration::MAX,
2531        Duration::from_std(false, std::time::Duration::MAX)
2532    )]
2533    fn test_fundu_duration_from_std_time_duration(
2534        #[case] std_duration: std::time::Duration,
2535        #[case] expected: Duration,
2536    ) {
2537        assert_eq!(Duration::from(std_duration), expected);
2538    }
2539
2540    #[cfg(feature = "time")]
2541    #[rstest]
2542    #[case::zero(time::Duration::ZERO, Duration::ZERO)]
2543    #[case::positive_one(time::Duration::new(1, 0), Duration::positive(1, 0))]
2544    #[case::positive_one_nano(time::Duration::new(0, 1), Duration::positive(0, 1))]
2545    #[case::negative_one(time::Duration::new(-1, 0), Duration::negative(1, 0))]
2546    #[case::negative_one_nano(time::Duration::new(0, -1), Duration::negative(0, 1))]
2547    #[case::positive_one_negative_one_nano(
2548        time::Duration::new(1, -1),
2549        Duration::positive(0, 999_999_999)
2550    )]
2551    #[case::negative_one_positive_one_nano(
2552        time::Duration::new(-1, 1),
2553        Duration::negative(0, 999_999_999)
2554    )]
2555    #[case::min(time::Duration::MIN, Duration::negative(i64::MIN.unsigned_abs(), 999_999_999))]
2556    #[case::max(time::Duration::MAX, Duration::positive(i64::MAX as u64, 999_999_999))]
2557    fn test_fundu_duration_from_time_duration(
2558        #[case] time_duration: time::Duration,
2559        #[case] expected: Duration,
2560    ) {
2561        assert_eq!(Duration::from(time_duration), expected);
2562    }
2563
2564    #[cfg(feature = "chrono")]
2565    #[rstest]
2566    #[case::zero(chrono::Duration::zero(), Duration::ZERO)]
2567    #[case::positive_one(chrono::Duration::seconds(1), Duration::positive(1, 0))]
2568    #[case::positive_one_nano(chrono::Duration::nanoseconds(1), Duration::positive(0, 1))]
2569    #[case::negative_one(chrono::Duration::seconds(-1), Duration::negative(1, 0))]
2570    #[case::negative_one_nano(chrono::Duration::nanoseconds(-1), Duration::negative(0, 1))]
2571    #[case::min(chrono::TimeDelta::MIN, CHRONO_MIN_DURATION)]
2572    #[case::max(chrono::TimeDelta::MAX, CHRONO_MAX_DURATION)]
2573    fn test_fundu_duration_from_chrono_duration(
2574        #[case] chrono_duration: chrono::Duration,
2575        #[case] expected: Duration,
2576    ) {
2577        assert_eq!(Duration::from(chrono_duration), expected);
2578    }
2579}