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}