Skip to main content

deep_time/dt/
chrono.rs

1use crate::{Dt, Scale};
2use chrono::{DateTime, Datelike, Duration, TimeDelta, Timelike, Utc};
3
4/// Clamps an `i128` to the representable range of `u64`.
5#[inline]
6fn clamp_i128_to_u64(x: i128) -> u64 {
7    if x > u64::MAX as i128 {
8        u64::MAX
9    } else if x < u64::MIN as i128 {
10        u64::MIN
11    } else {
12        x as u64
13    }
14}
15
16impl Dt {
17    /// Converts this [`Dt`] to a [`chrono::DateTime`].
18    ///
19    /// - Sub-nanosecond attoseconds are truncated toward zero.
20    /// - Saturates at the minimum/maximum representable `DateTime<Utc>`
21    ///   (roughly years 1678–2262) if the instant is out of range.
22    #[inline]
23    pub fn to_chrono_datetime_utc(&self, current: Scale) -> DateTime<Utc> {
24        DateTime::<Utc>::from_timestamp_nanos(Dt::clamp_i128_to_i64(
25            self.to_unix(current, Scale::UTC).to_ns(),
26        ))
27    }
28
29    /// Creates a TAI [`Dt`] from a [`chrono::DateTime`].
30    ///
31    /// This is the inverse of [`Dt::to_chrono_datetime_utc`].
32    pub fn from_chrono_datetime_utc(dt: DateTime<Utc>) -> Self {
33        let yr = dt.year() as i64;
34        let mo = dt.month().clamp(1, 12) as u8;
35        let day = dt.day().clamp(1, 31) as u8;
36        let hr = dt.hour().clamp(0, 23) as u8;
37        let min = dt.minute().clamp(0, 59) as u8;
38        let sec = dt.second().clamp(0, 60) as u8;
39        let subsec_nanos = dt.nanosecond();
40        let attos = Dt::from_ns(subsec_nanos as i128, Scale::TAI).to_attos();
41
42        Dt::from_ymdhms_on(
43            yr,
44            mo,
45            day,
46            hr,
47            min,
48            sec,
49            clamp_i128_to_u64(attos),
50            Scale::UTC,
51        )
52    }
53
54    /// Creates a [`Dt`] from a [`chrono::Duration`] (nanosecond precision).
55    pub fn from_chrono_duration(dur: Duration) -> Self {
56        match dur.num_nanoseconds() {
57            Some(ns) => Self::from_ns(ns as i128, Scale::TAI),
58            None => {
59                let ns = if dur > Duration::zero() {
60                    i64::MAX
61                } else {
62                    i64::MIN
63                };
64                Self::from_ns(ns as i128, Scale::TAI)
65            }
66        }
67    }
68
69    /// Converts this [`Dt`] to a `chrono::Duration` (nanosecond precision).
70    ///
71    /// - Sub-nanosecond attoseconds are **truncated toward zero**.
72    /// - The conversion is fully exact up to the nanosecond (128-bit integer arithmetic).
73    /// - **Saturates** at `chrono::Duration::MIN` / `chrono::Duration::MAX`
74    ///   (roughly ±292 million years) if the value is out of range.
75    #[inline]
76    pub fn to_chrono_duration(&self) -> Duration {
77        TimeDelta::nanoseconds(Dt::clamp_i128_to_i64(self.to_ns()))
78    }
79}