Coverage Report

Created: 2026-02-14 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/jiff-0.2.20/src/duration.rs
Line
Count
Source
1
use core::time::Duration as UnsignedDuration;
2
3
use 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)]
10
pub(crate) enum Duration {
11
    Span(Span),
12
    Signed(SignedDuration),
13
    Unsigned(UnsignedDuration),
14
}
15
16
impl 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
4.83k
    pub(crate) fn to_signed(&self) -> Result<SDuration<'_>, Error> {
23
4.83k
        match *self {
24
4.83k
            Duration::Span(ref span) => Ok(SDuration::Span(span)),
25
0
            Duration::Signed(sdur) => Ok(SDuration::Absolute(sdur)),
26
0
            Duration::Unsigned(udur) => {
27
0
                let sdur = SignedDuration::try_from(udur)
28
0
                    .context(E::RangeUnsignedDuration)?;
29
0
                Ok(SDuration::Absolute(sdur))
30
            }
31
        }
32
4.83k
    }
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
1.90k
    pub(crate) fn checked_neg(self) -> Result<Duration, Error> {
56
1.90k
        match self {
57
1.90k
            Duration::Span(span) => Ok(Duration::Span(span.negate())),
58
0
            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
0
                if let Some(sdur) = sdur.checked_neg() {
63
0
                    Ok(Duration::Signed(sdur))
64
                } else {
65
0
                    let udur = UnsignedDuration::new(
66
0
                        i64::MIN.unsigned_abs(),
67
0
                        sdur.subsec_nanos().unsigned_abs(),
68
                    );
69
0
                    Ok(Duration::Unsigned(udur))
70
                }
71
            }
72
0
            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
0
                let sdur = if udur.as_secs() == i64::MIN.unsigned_abs() {
77
0
                    SignedDuration::new_without_nano_overflow(
78
                        i64::MIN,
79
                        // OK because `udur.subsec_nanos()` < 999_999_999.
80
0
                        -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
0
                    -SignedDuration::try_from(udur)
91
0
                        .context(E::FailedNegateUnsignedDuration)?
92
                };
93
0
                Ok(Duration::Signed(sdur))
94
            }
95
        }
96
1.90k
    }
97
98
    /// Returns true if and only if this duration is negative.
99
    #[cfg_attr(feature = "perf-inline", inline(always))]
100
0
    pub(crate) fn is_negative(&self) -> bool {
101
0
        match *self {
102
0
            Duration::Span(ref span) => span.is_negative(),
103
0
            Duration::Signed(ref sdur) => sdur.is_negative(),
104
0
            Duration::Unsigned(_) => false,
105
        }
106
0
    }
107
}
108
109
impl From<Span> for Duration {
110
    #[inline]
111
4.83k
    fn from(span: Span) -> Duration {
112
4.83k
        Duration::Span(span)
113
4.83k
    }
114
}
115
116
impl From<SignedDuration> for Duration {
117
    #[inline]
118
0
    fn from(sdur: SignedDuration) -> Duration {
119
0
        Duration::Signed(sdur)
120
0
    }
121
}
122
123
impl From<UnsignedDuration> for Duration {
124
    #[inline]
125
0
    fn from(udur: UnsignedDuration) -> Duration {
126
0
        Duration::Unsigned(udur)
127
0
    }
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)]
143
pub(crate) enum SDuration<'a> {
144
    Span(&'a Span),
145
    Absolute(SignedDuration),
146
}