deep_time/dt/time.rs
1use crate::{Dt, Scale};
2use core::convert::From;
3use time::{Duration, OffsetDateTime, Timestamp, UtcDateTime, UtcOffset};
4
5impl Dt {
6 /// Converts this [`Dt`] to a [`time::Timestamp`].
7 ///
8 /// ## Time scale
9 ///
10 /// [`time::Timestamp`] is a **Unix / POSIX** instant: nanoseconds since
11 /// 1970-01-01 00:00:00 UTC. Like `chrono` and `jiff`, it does **not** count
12 /// leap seconds in the numeric value. Conversion therefore goes through
13 /// [`Scale::UTC`](../enum.Scale.html#variant.UTC) and the Unix epoch so deep-time's leap-second
14 /// tables are applied on the way in and out of TAI storage.
15 ///
16 /// This is **not** a TAI timestamp.
17 ///
18 /// ## Precision and range
19 ///
20 /// - Sub-nanosecond attoseconds are truncated toward zero.
21 /// - Saturates at [`Timestamp::MIN`] / [`Timestamp::MAX`] if out of range
22 /// (year range depends on the `time` crate's `large-dates` feature; without
23 /// it, roughly ±9999).
24 pub fn to_time_timestamp(&self) -> Timestamp {
25 let nanos = self.target(Scale::UTC).to_unix().to_ns().0;
26 match Timestamp::from_nanoseconds(nanos) {
27 Ok(ts) => ts,
28 Err(_) => {
29 if nanos >= 0 {
30 Timestamp::MAX
31 } else {
32 Timestamp::MIN
33 }
34 }
35 }
36 }
37
38 /// Creates a TAI [`Dt`] from a [`time::Timestamp`].
39 ///
40 /// Inverse of [`Dt::to_time_timestamp`]. The Unix nanosecond count is treated
41 /// as a POSIX/UTC elapsed time since the Unix epoch (not TAI), then converted
42 /// into deep-time's TAI storage via [`Dt::from_unix`].
43 #[inline]
44 pub fn from_time_timestamp(ts: Timestamp) -> Dt {
45 Self::from_unix_ns(ts.as_nanoseconds())
46 }
47
48 /// Converts this [`Dt`] to a [`time::OffsetDateTime`] with a UTC offset of zero.
49 ///
50 /// ## Time scale
51 ///
52 /// Same Unix/POSIX UTC semantics as [`Dt::to_time_timestamp`]. The returned value
53 /// always has [`UtcOffset::UTC`]; wall-clock fields are UTC civil time, not TAI.
54 ///
55 /// ## Precision and range
56 ///
57 /// - Sub-nanosecond attoseconds are truncated toward zero.
58 /// - Saturates at the minimum/maximum representable `OffsetDateTime` in UTC
59 /// (via [`Timestamp::MIN`] / [`Timestamp::MAX`]).
60 #[inline]
61 pub fn to_time_offset_datetime_utc(&self) -> OffsetDateTime {
62 self.to_time_timestamp().to_offset(UtcOffset::UTC)
63 }
64
65 /// Creates a TAI [`Dt`] from a [`time::OffsetDateTime`].
66 ///
67 /// Uses [`OffsetDateTime::unix_timestamp_nanos`], so the absolute instant is
68 /// taken correctly regardless of the value's fixed offset. The offset itself
69 /// is not preserved on the resulting [`Dt`] (deep-time stores scale, not
70 /// civil zone offset).
71 ///
72 /// Same POSIX/UTC Unix semantics as [`Dt::from_time_timestamp`].
73 #[inline]
74 pub fn from_time_offset_datetime(dt: OffsetDateTime) -> Dt {
75 Self::from_unix_ns(dt.unix_timestamp_nanos())
76 }
77
78 /// Converts this [`Dt`] to a [`time::UtcDateTime`].
79 ///
80 /// ## Time scale
81 ///
82 /// Same Unix/POSIX UTC semantics as [`Dt::to_time_timestamp`]. Fields are UTC
83 /// civil time, not TAI.
84 ///
85 /// ## Precision and range
86 ///
87 /// - Sub-nanosecond attoseconds are truncated toward zero.
88 /// - Saturates at [`UtcDateTime::MIN`] / [`UtcDateTime::MAX`] if out of range.
89 #[inline]
90 pub fn to_time_utc_datetime(&self) -> UtcDateTime {
91 self.to_time_timestamp().to_utc()
92 }
93
94 /// Creates a TAI [`Dt`] from a [`time::UtcDateTime`].
95 ///
96 /// Inverse of [`Dt::to_time_utc_datetime`]. Same POSIX/UTC Unix semantics as
97 /// [`Dt::from_time_timestamp`].
98 #[inline]
99 pub fn from_time_utc_datetime(dt: UtcDateTime) -> Dt {
100 Self::from_unix_ns(dt.unix_timestamp_nanos())
101 }
102
103 /// Converts this [`Dt`] to a [`time::Duration`] (nanosecond precision).
104 ///
105 /// ## Time scale
106 ///
107 /// A [`time::Duration`] is a pure elapsed span (SI seconds + nanoseconds). It is
108 /// **not** tied to UTC, TAI, or any other time scale. The conversion uses this
109 /// [`Dt`]'s raw attosecond count (same convention as chrono/jiff duration interop).
110 ///
111 /// ## Precision and range
112 ///
113 /// - Sub-nanosecond attoseconds are truncated toward zero.
114 /// - Saturates at [`Duration::MIN`] / [`Duration::MAX`]
115 /// (roughly ±292 billion years) if out of range.
116 pub fn to_time_duration(&self) -> Duration {
117 let total_nanos = self.to_ns().0;
118 let max_ns = Duration::MAX.whole_nanoseconds();
119 let min_ns = Duration::MIN.whole_nanoseconds();
120 if total_nanos >= max_ns {
121 Duration::MAX
122 } else if total_nanos <= min_ns {
123 Duration::MIN
124 } else {
125 Duration::nanoseconds_i128(total_nanos)
126 }
127 }
128
129 /// Creates a [`Dt`] from a [`time::Duration`] (nanosecond precision).
130 ///
131 /// Inverse of [`Dt::to_time_duration`]. The result is a span stored on TAI
132 /// (no leap-second adjustment of the duration itself), matching chrono/jiff
133 /// duration interop.
134 #[inline]
135 pub fn from_time_duration(dur: Duration) -> Dt {
136 Self::from_ns(dur.whole_nanoseconds(), 0, Scale::TAI, Scale::TAI)
137 }
138}
139
140impl From<Timestamp> for Dt {
141 #[inline]
142 fn from(ts: Timestamp) -> Self {
143 Self::from_time_timestamp(ts)
144 }
145}
146
147impl From<Dt> for Timestamp {
148 #[inline]
149 fn from(dt: Dt) -> Self {
150 dt.to_time_timestamp()
151 }
152}
153
154impl From<OffsetDateTime> for Dt {
155 #[inline]
156 fn from(dt: OffsetDateTime) -> Self {
157 Self::from_time_offset_datetime(dt)
158 }
159}
160
161impl From<Dt> for OffsetDateTime {
162 #[inline]
163 fn from(dt: Dt) -> Self {
164 dt.to_time_offset_datetime_utc()
165 }
166}
167
168impl From<UtcDateTime> for Dt {
169 #[inline]
170 fn from(dt: UtcDateTime) -> Self {
171 Self::from_time_utc_datetime(dt)
172 }
173}
174
175impl From<Dt> for UtcDateTime {
176 #[inline]
177 fn from(dt: Dt) -> Self {
178 dt.to_time_utc_datetime()
179 }
180}
181
182impl From<Duration> for Dt {
183 #[inline]
184 fn from(dur: Duration) -> Self {
185 Self::from_time_duration(dur)
186 }
187}
188
189impl From<Dt> for Duration {
190 #[inline]
191 fn from(dt: Dt) -> Self {
192 dt.to_time_duration()
193 }
194}