Skip to main content

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}