Skip to main content

deep_time/dt/
jiff.rs

1use crate::{Dt, DtErr, DtErrKind, Scale, an_err};
2use core::convert::{From, TryFrom};
3use jiff::{SignedDuration, Span, Timestamp};
4
5impl Dt {
6    /// Converts this [`Dt`] to a [`jiff::Timestamp`].
7    ///
8    /// ## Time scale
9    ///
10    /// [`jiff::Timestamp`] is a **Unix / POSIX** instant: nanoseconds since
11    /// `1970-01-01 00:00:00Z`. Jiff documents this as “the Unix timescale with a
12    /// UTC offset of zero” and **does not support leap seconds** (it behaves as if
13    /// they do not exist in the numeric count). Conversion therefore goes through
14    /// [`Scale::UTC`](../enum.Scale.html#variant.UTC) and the Unix epoch so deep-time's
15    /// leap-second tables are applied on the way out of TAI storage.
16    ///
17    /// This is **not** a TAI timestamp. A [`Dt`] stored on TAI (or any other scale)
18    /// is converted to the equivalent UTC civil instant before the Unix count is
19    /// taken.
20    ///
21    /// ## Precision and range
22    ///
23    /// - Sub-nanosecond attoseconds are truncated toward zero.
24    /// - Saturates at [`Timestamp::MIN`] / [`Timestamp::MAX`] if out of range
25    ///   (jiff's supported range is roughly years −9999…9999).
26    pub fn to_jiff_timestamp(&self) -> Timestamp {
27        let nanos = self.target(Scale::UTC).to_unix().to_ns().0;
28        match Timestamp::from_nanosecond(nanos) {
29            Ok(ts) => ts,
30            Err(_) => {
31                if nanos >= 0 {
32                    Timestamp::MAX
33                } else {
34                    Timestamp::MIN
35                }
36            }
37        }
38    }
39
40    /// Creates a TAI [`Dt`] from a [`jiff::Timestamp`].
41    ///
42    /// Inverse of [`Dt::to_jiff_timestamp`]. The Unix nanosecond count is treated
43    /// as a POSIX/UTC elapsed time since the Unix epoch (not TAI, and not as a
44    /// count since J2000), then converted into deep-time's TAI storage via
45    /// [`Dt::from_unix`].
46    #[inline]
47    pub fn from_jiff_timestamp(ts: Timestamp) -> Dt {
48        Dt::from_unix_ns(ts.as_nanosecond())
49    }
50
51    /// Converts this [`Dt`] to a [`jiff::Span`] (seconds + nanoseconds only).
52    ///
53    /// ## Time scale
54    ///
55    /// A [`Span`] built this way is a pure elapsed span of SI nanoseconds taken
56    /// from this [`Dt`]'s raw attosecond count. It is **not** tied to UTC leap
57    /// seconds or calendar units (years/months/days are left at zero).
58    ///
59    /// ## Precision and range
60    ///
61    /// - Sub-nanosecond attoseconds are truncated toward zero.
62    /// - Saturates at jiff's `Span` second/nanosecond limits
63    ///   (`±631_107_417_600` s and `±999_999_999` ns) if out of range.
64    pub fn to_jiff_span(&self) -> Span {
65        let (total_nanos, _) = self.to_ns();
66        let seconds = Dt::to_i64(total_nanos / 1_000_000_000);
67        let nanoseconds = Dt::to_i64(total_nanos % 1_000_000_000);
68
69        if let Ok(base) = Span::new().try_seconds(seconds)
70            && let Ok(span) = base.try_nanoseconds(nanoseconds)
71        {
72            return span;
73        }
74        // Saturate to Jiff's Span limits
75        if total_nanos >= 0 {
76            Span::new()
77                .seconds(631_107_417_600i64)
78                .nanoseconds(999_999_999i64)
79        } else {
80            Span::new()
81                .seconds(-631_107_417_600i64)
82                .nanoseconds(-999_999_999i64)
83        }
84    }
85
86    /// Converts this [`Dt`] to a [`jiff::SignedDuration`] (nanosecond precision).
87    ///
88    /// ## Time scale
89    ///
90    /// A [`SignedDuration`] is a pure elapsed span (SI seconds + nanoseconds). It
91    /// is **not** tied to UTC, TAI, or any other time scale. The conversion uses
92    /// this [`Dt`]'s raw attosecond count (same convention as chrono/`time` duration
93    /// interop).
94    ///
95    /// ## Precision and range
96    ///
97    /// - Sub-nanosecond attoseconds are **truncated toward zero**.
98    /// - Supports the **entire** range of [`Dt`]'s nanosecond projection
99    ///   (`SignedDuration::from_nanos_i128`; never saturates here).
100    #[inline]
101    pub fn to_jiff_signed_duration(&self) -> SignedDuration {
102        SignedDuration::from_nanos_i128(self.to_ns().0)
103    }
104
105    /// Creates a [`Dt`] from a [`jiff::SignedDuration`] (nanosecond precision).
106    ///
107    /// Inverse of [`Dt::to_jiff_signed_duration`]. The result is a span stored on
108    /// TAI (no leap-second adjustment of the duration itself), matching
109    /// chrono/`time` duration interop.
110    #[inline]
111    pub fn from_jiff_signed_duration(dur: SignedDuration) -> Dt {
112        Self::from_ns(dur.as_nanos(), 0, Scale::TAI, Scale::TAI)
113    }
114
115    /// Creates a [`Dt`] from a [`jiff::Span`] (nanosecond precision).
116    ///
117    /// Inverse of [`Dt::to_jiff_span`]. Converts the span to a
118    /// [`SignedDuration`] (seconds + nanoseconds only; calendar units must already
119    /// be zero or convertible without a relative datetime) and then uses
120    /// [`Dt::from_jiff_signed_duration`].
121    pub fn from_jiff_span(span: Span) -> Result<Self, DtErr> {
122        let dur = SignedDuration::try_from(span)
123            .map_err(|e| an_err!(DtErrKind::InvalidInput, "{}", e))?;
124        Ok(Self::from_jiff_signed_duration(dur))
125    }
126}
127
128impl From<Timestamp> for Dt {
129    #[inline]
130    fn from(ts: Timestamp) -> Self {
131        Self::from_jiff_timestamp(ts)
132    }
133}
134
135impl From<Dt> for Timestamp {
136    #[inline]
137    fn from(dt: Dt) -> Self {
138        dt.to_jiff_timestamp()
139    }
140}
141
142impl From<SignedDuration> for Dt {
143    #[inline]
144    fn from(dur: SignedDuration) -> Self {
145        Self::from_jiff_signed_duration(dur)
146    }
147}
148
149impl From<Dt> for SignedDuration {
150    #[inline]
151    fn from(dt: Dt) -> Self {
152        dt.to_jiff_signed_duration()
153    }
154}
155
156impl TryFrom<Span> for Dt {
157    type Error = DtErr;
158
159    fn try_from(span: Span) -> Result<Self, Self::Error> {
160        Self::from_jiff_span(span)
161    }
162}