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