jiff/
duration.rs

1use core::time::Duration as UnsignedDuration;
2
3use crate::{
4    error::{err, ErrorContext},
5    Error, SignedDuration, Span,
6};
7
8/// An internal type for abstracting over different duration types.
9#[derive(Clone, Copy, Debug)]
10pub(crate) enum Duration {
11    Span(Span),
12    Signed(SignedDuration),
13    Unsigned(UnsignedDuration),
14}
15
16impl Duration {
17    /// Convert this to a signed duration.
18    ///
19    /// This returns an error only in the case where this is an unsigned
20    /// duration with a number of whole seconds that exceeds `|i64::MIN|`.
21    #[cfg_attr(feature = "perf-inline", inline(always))]
22    pub(crate) fn to_signed(self) -> Result<SDuration, Error> {
23        match self {
24            Duration::Span(span) => Ok(SDuration::Span(span)),
25            Duration::Signed(sdur) => Ok(SDuration::Absolute(sdur)),
26            Duration::Unsigned(udur) => {
27                let sdur =
28                    SignedDuration::try_from(udur).with_context(|| {
29                        err!(
30                            "unsigned duration {udur:?} exceeds Jiff's limits"
31                        )
32                    })?;
33                Ok(SDuration::Absolute(sdur))
34            }
35        }
36    }
37
38    /// Negates this duration.
39    ///
40    /// When the duration is a span, this can never fail because a span defines
41    /// its min and max values such that negation is always possible.
42    ///
43    /// When the duration is signed, then this attempts to return a signed
44    /// duration and only falling back to an unsigned duration when the number
45    /// of seconds corresponds to `i64::MIN`.
46    ///
47    /// When the duration is unsigned, then this fails when the whole seconds
48    /// exceed the absolute value of `i64::MIN`. Otherwise, a signed duration
49    /// is returned.
50    ///
51    /// The failures for large unsigned durations here are okay because the
52    /// point at which absolute durations overflow on negation, they would also
53    /// cause overflow when adding or subtracting to *any* valid datetime value
54    /// for *any* datetime type in this crate. So while the error message may
55    /// be different, the actual end result is the same (failure).
56    ///
57    /// TODO: Write unit tests for this.
58    #[cfg_attr(feature = "perf-inline", inline(always))]
59    pub(crate) fn checked_neg(self) -> Result<Duration, Error> {
60        match self {
61            Duration::Span(span) => Ok(Duration::Span(span.negate())),
62            Duration::Signed(sdur) => {
63                // We try to stick with signed durations, but in the case
64                // where negation fails, we can represent its negation using
65                // an unsigned duration.
66                if let Some(sdur) = sdur.checked_neg() {
67                    Ok(Duration::Signed(sdur))
68                } else {
69                    let udur = UnsignedDuration::new(
70                        i64::MIN.unsigned_abs(),
71                        sdur.subsec_nanos().unsigned_abs(),
72                    );
73                    Ok(Duration::Unsigned(udur))
74                }
75            }
76            Duration::Unsigned(udur) => {
77                // We can permit negating i64::MIN.unsigned_abs() to
78                // i64::MIN, but we need to handle it specially since
79                // i64::MIN.unsigned_abs() exceeds i64::MAX.
80                let sdur = if udur.as_secs() == i64::MIN.unsigned_abs() {
81                    SignedDuration::new_without_nano_overflow(
82                        i64::MIN,
83                        // OK because `udur.subsec_nanos()` < 999_999_999.
84                        -i32::try_from(udur.subsec_nanos()).unwrap(),
85                    )
86                } else {
87                    // The negation here is always correct because it can only
88                    // panic with `sdur.as_secs() == i64::MIN`, which is
89                    // impossible because it must be positive.
90                    //
91                    // Otherwise, this is the only failure point in this entire
92                    // routine. And specifically, we fail here in precisely
93                    // the cases where `udur.as_secs() > |i64::MIN|`.
94                    -SignedDuration::try_from(udur).with_context(|| {
95                        err!("failed to negate unsigned duration {udur:?}")
96                    })?
97                };
98                Ok(Duration::Signed(sdur))
99            }
100        }
101    }
102
103    /// Returns true if and only if this duration is negative.
104    #[cfg_attr(feature = "perf-inline", inline(always))]
105    pub(crate) fn is_negative(&self) -> bool {
106        match *self {
107            Duration::Span(ref span) => span.is_negative(),
108            Duration::Signed(ref sdur) => sdur.is_negative(),
109            Duration::Unsigned(_) => false,
110        }
111    }
112}
113
114impl From<Span> for Duration {
115    #[inline]
116    fn from(span: Span) -> Duration {
117        Duration::Span(span)
118    }
119}
120
121impl From<SignedDuration> for Duration {
122    #[inline]
123    fn from(sdur: SignedDuration) -> Duration {
124        Duration::Signed(sdur)
125    }
126}
127
128impl From<UnsignedDuration> for Duration {
129    #[inline]
130    fn from(udur: UnsignedDuration) -> Duration {
131        Duration::Unsigned(udur)
132    }
133}
134
135/// An internal type for abstracting over signed durations.
136///
137/// This is typically converted to from a `Duration`. It enables callers
138/// downstream to implement datetime arithmetic on only two duration types
139/// instead of doing it for three duration types (including
140/// `std::time::Duration`).
141///
142/// The main thing making this idea work is that if an unsigned duration cannot
143/// fit into a signed duration, then it would overflow any calculation on any
144/// datetime type in Jiff anyway. If this weren't true, then we'd need to
145/// support doing actual arithmetic with unsigned durations separately from
146/// signed durations.
147#[derive(Clone, Copy, Debug)]
148pub(crate) enum SDuration {
149    Span(Span),
150    Absolute(SignedDuration),
151}