Skip to main content

t2t_core/interop/
jiff.rs

1//! [`Timestamp`] and [`Timedelta`] to and from jiff's `Timestamp` and `SignedDuration`.
2
3use jiff::{SignedDuration, Timestamp as JiffTimestamp};
4
5use crate::errors::narrow;
6use crate::{OutOfRangeError, Timedelta, Timestamp};
7
8/// Every timestamp, since jiff's reach years -9999 to 9999.
9impl From<Timestamp> for JiffTimestamp {
10    #[inline]
11    fn from(instant: Timestamp) -> Self {
12        // jiff refuses only an instant past its years, which no timestamp reaches; were one to,
13        // the conversion would saturate, as t2t's operators do.
14        let saturated = if instant.0 < 0 { Self::MIN } else { Self::MAX };
15        Self::from_nanosecond(i128::from(instant.0)).unwrap_or(saturated)
16    }
17}
18
19/// Refuses an instant outside [`Timestamp::MIN`] to [`Timestamp::MAX`].
20impl TryFrom<JiffTimestamp> for Timestamp {
21    type Error = OutOfRangeError;
22
23    #[inline]
24    fn try_from(instant: JiffTimestamp) -> Result<Self, Self::Error> {
25        narrow(instant.as_nanosecond()).map(Self)
26    }
27}
28
29/// Every span, since a `SignedDuration` holds every `i64` of nanoseconds.
30impl From<Timedelta> for SignedDuration {
31    #[inline]
32    fn from(span: Timedelta) -> Self {
33        Self::from_nanos(span.0)
34    }
35}
36
37/// Refuses a duration past [`Timedelta::MIN`] or [`Timedelta::MAX`].
38impl TryFrom<SignedDuration> for Timedelta {
39    type Error = OutOfRangeError;
40
41    #[inline]
42    fn try_from(duration: SignedDuration) -> Result<Self, Self::Error> {
43        narrow(duration.as_nanos()).map(Self)
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use jiff::{SignedDuration, Timestamp as JiffTimestamp};
50    use rstest::rstest;
51
52    use crate::{OutOfRangeError, Timedelta, Timestamp};
53
54    #[rstest]
55    #[case::the_earliest(Timestamp::MIN)]
56    #[case::before_the_epoch(Timestamp::from_nanos(-1))]
57    #[case::a_capture(Timestamp::from_nanos(1_789_544_735_123_456_789))]
58    #[case::the_latest(Timestamp::MAX)]
59    fn an_instant_crosses_both_ways(#[case] instant: Timestamp) {
60        let theirs = JiffTimestamp::from(instant);
61        assert_eq!(theirs.as_nanosecond(), i128::from(instant.as_nanos()), "the same instant");
62        assert_eq!(Timestamp::try_from(theirs), Ok(instant), "and back");
63    }
64
65    #[test]
66    fn an_instant_past_the_range_is_refused() {
67        assert_eq!(Timestamp::try_from(JiffTimestamp::MAX), Err(OutOfRangeError), "after 2262");
68        assert_eq!(Timestamp::try_from(JiffTimestamp::MIN), Err(OutOfRangeError), "before 1677");
69    }
70
71    #[rstest]
72    #[case::the_longest_backwards(Timedelta::MIN)]
73    #[case::a_budget(Timedelta::from_millis(-250))]
74    #[case::the_longest(Timedelta::MAX)]
75    fn a_span_crosses_both_ways(#[case] span: Timedelta) {
76        let theirs = SignedDuration::from(span);
77        assert_eq!(theirs.as_nanos(), i128::from(span.as_nanos()), "the same span");
78        assert_eq!(Timedelta::try_from(theirs), Ok(span), "and back");
79    }
80
81    #[test]
82    fn a_span_past_the_range_is_refused() {
83        assert_eq!(Timedelta::try_from(SignedDuration::MAX), Err(OutOfRangeError), "forwards");
84        assert_eq!(Timedelta::try_from(SignedDuration::MIN), Err(OutOfRangeError), "and backwards");
85    }
86}