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 mut nanos = self.target(Scale::UTC).to_unix().to_ns().0;
28 // required to due jiff panic in v0.2.33
29 if nanos > Timestamp::MAX.as_nanosecond() {
30 nanos = Timestamp::MAX.as_nanosecond();
31 } else if nanos < Timestamp::MIN.as_nanosecond() {
32 nanos = Timestamp::MIN.as_nanosecond();
33 }
34 match Timestamp::from_nanosecond(nanos) {
35 Ok(ts) => ts,
36 Err(_) => {
37 if nanos >= 0 {
38 Timestamp::MAX
39 } else {
40 Timestamp::MIN
41 }
42 }
43 }
44 }
45
46 /// Creates a TAI [`Dt`] from a [`jiff::Timestamp`].
47 ///
48 /// Inverse of [`Dt::to_jiff_timestamp`]. The Unix nanosecond count is treated
49 /// as a POSIX/UTC elapsed time since the Unix epoch (not TAI, and not as a
50 /// count since J2000), then converted into deep-time's TAI storage via
51 /// [`Dt::from_unix`].
52 #[inline]
53 pub fn from_jiff_timestamp(ts: Timestamp) -> Dt {
54 Dt::from_unix_ns(ts.as_nanosecond())
55 }
56
57 /// Converts this [`Dt`] to a [`jiff::Span`] (seconds + nanoseconds only).
58 ///
59 /// ## Time scale
60 ///
61 /// A [`Span`] built this way is a pure elapsed span of SI nanoseconds taken
62 /// from this [`Dt`]'s raw attosecond count. It is **not** tied to UTC leap
63 /// seconds or calendar units (years/months/days are left at zero).
64 ///
65 /// ## Precision and range
66 ///
67 /// - Sub-nanosecond attoseconds are truncated toward zero via
68 /// [`Dt::to_jiff_signed_duration`](../struct.Dt.html#method.to_jiff_signed_duration).
69 /// - Converts that duration with jiff's `Span::try_from` (`TryFrom<SignedDuration>`).
70 /// Returns [`Err`] when the duration is wider than a `Span` can represent.
71 /// Never panics.
72 #[inline]
73 pub fn to_jiff_span(&self) -> Result<Span, DtErr> {
74 Span::try_from(self.to_jiff_signed_duration())
75 .map_err(|e| an_err!(DtErrKind::InvalidInput, "{}", e))
76 }
77
78 /// Converts this [`Dt`] to a [`jiff::SignedDuration`] (nanosecond precision).
79 ///
80 /// ## Time scale
81 ///
82 /// A [`SignedDuration`] is a pure elapsed span (SI seconds + nanoseconds). It
83 /// is **not** tied to UTC, TAI, or any other time scale. The conversion uses
84 /// this [`Dt`]'s raw attosecond count (same convention as chrono/`time` duration
85 /// interop).
86 ///
87 /// ## Precision and range
88 ///
89 /// - Sub-nanosecond attoseconds are **truncated toward zero**.
90 /// - Saturates at [`SignedDuration::MIN`] / [`SignedDuration::MAX`] when the
91 /// nanosecond count requires more than an `i64` whole-second field
92 /// (jiff's `from_nanos_i128` would otherwise panic). Never panics.
93 pub fn to_jiff_signed_duration(&self) -> SignedDuration {
94 let nanos = self.to_ns().0;
95 match SignedDuration::try_from_nanos_i128(nanos) {
96 Some(dur) => dur,
97 None => {
98 if nanos >= 0 {
99 SignedDuration::MAX
100 } else {
101 SignedDuration::MIN
102 }
103 }
104 }
105 }
106
107 /// Creates a [`Dt`] from a [`jiff::SignedDuration`] (nanosecond precision).
108 ///
109 /// Inverse of [`Dt::to_jiff_signed_duration`]. The result is a span stored on
110 /// TAI (no leap-second adjustment of the duration itself), matching
111 /// chrono/`time` duration interop.
112 #[inline(always)]
113 pub fn from_jiff_signed_duration(dur: SignedDuration) -> Dt {
114 Self::from_ns(dur.as_nanos(), 0, Scale::TAI, Scale::TAI)
115 }
116
117 /// Creates a [`Dt`] from a [`jiff::Span`] (nanosecond precision).
118 ///
119 /// Inverse of [`Dt::to_jiff_span`](../struct.Dt.html#method.to_jiff_span).
120 /// Converts the span to a `SignedDuration` (seconds + nanoseconds only;
121 /// calendar units must already be zero or convertible without a relative
122 /// datetime) and then uses
123 /// [`Dt::from_jiff_signed_duration`](../struct.Dt.html#method.from_jiff_signed_duration).
124 pub fn from_jiff_span(span: Span) -> Result<Self, DtErr> {
125 let dur = SignedDuration::try_from(span)
126 .map_err(|e| an_err!(DtErrKind::InvalidInput, "{}", e))?;
127 Ok(Self::from_jiff_signed_duration(dur))
128 }
129}
130
131impl From<Timestamp> for Dt {
132 #[inline]
133 fn from(ts: Timestamp) -> Self {
134 Self::from_jiff_timestamp(ts)
135 }
136}
137
138impl From<Dt> for Timestamp {
139 #[inline]
140 fn from(dt: Dt) -> Self {
141 dt.to_jiff_timestamp()
142 }
143}
144
145impl From<SignedDuration> for Dt {
146 #[inline]
147 fn from(dur: SignedDuration) -> Self {
148 Self::from_jiff_signed_duration(dur)
149 }
150}
151
152impl From<Dt> for SignedDuration {
153 #[inline]
154 fn from(dt: Dt) -> Self {
155 dt.to_jiff_signed_duration()
156 }
157}
158
159impl TryFrom<Span> for Dt {
160 type Error = DtErr;
161
162 fn try_from(span: Span) -> Result<Self, Self::Error> {
163 Self::from_jiff_span(span)
164 }
165}