jiff/
duration.rs

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