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    /// Creates a `Dt` from a `chrono::DateTime<chrono::Utc>`.
18    ///
19    /// This is the inverse of [`Dt::to_chrono_datetime_utc`].
20    pub fn from_chrono_datetime_utc(dt: DateTime<Utc>) -> Self {
21        let yr = dt.year() as i64;
22        let mo = dt.month().clamp(1, 12) as u8;
23        let day = dt.day().clamp(1, 31) as u8;
24        let hr = dt.hour().clamp(0, 23) as u8;
25        let min = dt.minute().clamp(0, 59) as u8;
26        let sec = dt.second().clamp(0, 60) as u8;
27        let subsec_nanos = dt.nanosecond();
28        let attos = Dt::from_ns(subsec_nanos as i128, Scale::TAI).to_attos();
29
30        Dt::from_ymdhms_on(
31            yr,
32            mo,
33            day,
34            hr,
35            min,
36            sec,
37            clamp_i128_to_u64(attos),
38            Scale::UTC,
39        )
40    }
41
42    /// Creates a [`Dt`] from a [`chrono::Duration`] (nanosecond precision).
43    pub fn from_chrono_duration(dur: Duration) -> Self {
44        match dur.num_nanoseconds() {
45            Some(ns) => Self::from_ns(ns as i128, Scale::TAI),
46            None => {
47                let ns = if dur > Duration::zero() {
48                    i64::MAX
49                } else {
50                    i64::MIN
51                };
52                Self::from_ns(ns as i128, Scale::TAI)
53            }
54        }
55    }
56
57    /// Converts this [`Dt`] to a `chrono::DateTime<chrono::Utc>`.
58    ///
59    /// - Sub-nanosecond attoseconds are truncated toward zero.
60    /// - Saturates at the minimum/maximum representable `DateTime<Utc>`
61    ///   (roughly years 1678–2262) if the instant is out of range.
62    #[inline]
63    pub fn to_chrono_datetime_utc(&self, current: Scale) -> DateTime<Utc> {
64        DateTime::<Utc>::from_timestamp_nanos(Dt::clamp_i128_to_i64(
65            self.to_unix(current, Scale::UTC).to_ns(),
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}