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