Coverage Report

Created: 2025-11-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/jiff-0.2.16/src/duration.rs
Line
Count
Source
1
use core::time::Duration as UnsignedDuration;
2
3
use 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)]
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.39k
    pub(crate) fn to_signed(self) -> Result<SDuration, Error> {
23
4.39k
        match self {
24
4.39k
            Duration::Span(span) => Ok(SDuration::Span(span)),
25
0
            Duration::Signed(sdur) => Ok(SDuration::Absolute(sdur)),
26
0
            Duration::Unsigned(udur) => {
27
0
                let sdur =
28
0
                    SignedDuration::try_from(udur).with_context(|| {
29
0
                        err!(
30
                            "unsigned duration {udur:?} exceeds Jiff's limits"
31
                        )
32
0
                    })?;
Unexecuted instantiation: <jiff::duration::Duration>::to_signed::{closure#0}
Unexecuted instantiation: <jiff::duration::Duration>::to_signed::{closure#0}
Unexecuted instantiation: <jiff::duration::Duration>::to_signed::{closure#0}
Unexecuted instantiation: <jiff::duration::Duration>::to_signed::{closure#0}
Unexecuted instantiation: <jiff::duration::Duration>::to_signed::{closure#0}
33
0
                Ok(SDuration::Absolute(sdur))
34
            }
35
        }
36
4.39k
    }
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
1.69k
    pub(crate) fn checked_neg(self) -> Result<Duration, Error> {
60
1.69k
        match self {
61
1.69k
            Duration::Span(span) => Ok(Duration::Span(span.negate())),
62
0
            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
0
                if let Some(sdur) = sdur.checked_neg() {
67
0
                    Ok(Duration::Signed(sdur))
68
                } else {
69
0
                    let udur = UnsignedDuration::new(
70
0
                        i64::MIN.unsigned_abs(),
71
0
                        sdur.subsec_nanos().unsigned_abs(),
72
                    );
73
0
                    Ok(Duration::Unsigned(udur))
74
                }
75
            }
76
0
            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
0
                let sdur = if udur.as_secs() == i64::MIN.unsigned_abs() {
81
0
                    SignedDuration::new_without_nano_overflow(
82
                        i64::MIN,
83
                        // OK because `udur.subsec_nanos()` < 999_999_999.
84
0
                        -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
0
                    -SignedDuration::try_from(udur).with_context(|| {
95
0
                        err!("failed to negate unsigned duration {udur:?}")
96
0
                    })?
Unexecuted instantiation: <jiff::duration::Duration>::checked_neg::{closure#0}
Unexecuted instantiation: <jiff::duration::Duration>::checked_neg::{closure#0}
Unexecuted instantiation: <jiff::duration::Duration>::checked_neg::{closure#0}
Unexecuted instantiation: <jiff::duration::Duration>::checked_neg::{closure#0}
Unexecuted instantiation: <jiff::duration::Duration>::checked_neg::{closure#0}
97
                };
98
0
                Ok(Duration::Signed(sdur))
99
            }
100
        }
101
1.69k
    }
102
103
    /// Returns true if and only if this duration is negative.
104
    #[cfg_attr(feature = "perf-inline", inline(always))]
105
0
    pub(crate) fn is_negative(&self) -> bool {
106
0
        match *self {
107
0
            Duration::Span(ref span) => span.is_negative(),
108
0
            Duration::Signed(ref sdur) => sdur.is_negative(),
109
0
            Duration::Unsigned(_) => false,
110
        }
111
0
    }
112
}
113
114
impl From<Span> for Duration {
115
    #[inline]
116
4.39k
    fn from(span: Span) -> Duration {
117
4.39k
        Duration::Span(span)
118
4.39k
    }
119
}
120
121
impl From<SignedDuration> for Duration {
122
    #[inline]
123
0
    fn from(sdur: SignedDuration) -> Duration {
124
0
        Duration::Signed(sdur)
125
0
    }
126
}
127
128
impl From<UnsignedDuration> for Duration {
129
    #[inline]
130
0
    fn from(udur: UnsignedDuration) -> Duration {
131
0
        Duration::Unsigned(udur)
132
0
    }
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)]
148
pub(crate) enum SDuration {
149
    Span(Span),
150
    Absolute(SignedDuration),
151
}