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}