Skip to main content

deep_time/dt/
icu.rs

1use crate::{ATTOS_PER_NS, Dt, DtErr, DtErrKind, Scale, an_err};
2use core::convert::{From, TryFrom};
3use icu_calendar::{Date, Iso};
4use icu_time::{DateTime, Time};
5
6impl Dt {
7    /// Converts this [`Dt`] to an ICU4X [`DateTime`]`<`[`Iso`]`>` (civil ISO date + time).
8    ///
9    /// ## Time scale
10    ///
11    /// Fields are **UTC civil** wall time: the instant is converted to
12    /// [`Scale::UTC`](../enum.Scale.html#variant.UTC) (applying leap-second tables as
13    /// usual) before the calendar date and clock fields are read. This matches the
14    /// UTC convention used by [`Dt::to_chrono_datetime_utc`] and the Unix-based
15    /// jiff/`time` interop paths.
16    ///
17    /// ## Precision and range
18    ///
19    /// - Sub-nanosecond attoseconds are truncated toward zero into
20    ///   [`icu_time::Nanosecond`] (`0..=999_999_999`).
21    /// - Leap seconds are preserved when UTC civil time shows `sec == 60`
22    ///   (`Time` allows seconds `0..=60`).
23    /// - Year must fit ICU's ISO constructor range (`-9999..=9999`); otherwise
24    ///   returns [`DtErrKind::YearOutOfRange`].
25    /// - Other out-of-range civil fields map to [`DtErrKind::InvalidDate`] or
26    ///   [`DtErrKind::InvalidTime`].
27    ///
28    /// ## Example
29    ///
30    /// ```
31    /// use deep_time::{Dt, Scale};
32    ///
33    /// let dt = Dt::from_ymd(2024, 4, 15, Scale::UTC, 14, 30, 45, 123_456_789_000_000_000);
34    /// let icu = dt.to_icu_datetime_iso().unwrap();
35    /// assert_eq!(icu.date.era_year().extended_year, 2024);
36    /// assert_eq!(icu.date.month().ordinal, 4);
37    /// assert_eq!(icu.date.day_of_month().0, 15);
38    /// assert_eq!(icu.time.hour.number(), 14);
39    /// assert_eq!(icu.time.minute.number(), 30);
40    /// assert_eq!(icu.time.second.number(), 45);
41    /// assert_eq!(icu.time.subsecond.number(), 123_456_789);
42    /// ```
43    pub fn to_icu_datetime_iso(&self) -> Result<DateTime<Iso>, DtErr> {
44        let ymd = self.target(Scale::UTC).to_ymd();
45        let year_i32: i32 = ymd
46            .yr()
47            .try_into()
48            .map_err(|_| an_err!(DtErrKind::YearOutOfRange, "year={}", ymd.yr()))?;
49
50        let date = Date::try_new_iso(year_i32, ymd.mo(), ymd.day())
51            .map_err(|_| an_err!(DtErrKind::InvalidDate, "ymd"))?;
52
53        let nanos = (ymd.attos() / ATTOS_PER_NS) as u32;
54        let time = Time::try_new(ymd.hr(), ymd.min(), ymd.sec(), nanos)
55            .map_err(|_| an_err!(DtErrKind::InvalidTime, "hms.ns"))?;
56
57        Ok(DateTime { date, time })
58    }
59
60    /// Creates a TAI [`Dt`] from an ICU4X [`DateTime`]`<`[`Iso`]`>`.
61    ///
62    /// Inverse of [`Dt::to_icu_datetime_iso`]. Civil fields are interpreted as
63    /// **UTC** wall time (same convention as [`Dt::from_chrono_datetime_utc`]),
64    /// then stored on TAI via [`Dt::from_ymd`].
65    ///
66    /// ## Precision
67    ///
68    /// Nanoseconds are expanded to attoseconds (`ns * 10⁹`). Values below one
69    /// nanosecond that were truncated on the way out are not recovered.
70    ///
71    /// ## Example
72    ///
73    /// ```
74    /// use deep_time::{Dt, Scale};
75    /// use icu_calendar::Date;
76    /// use icu_time::{DateTime, Time};
77    ///
78    /// let icu = DateTime {
79    ///     date: Date::try_new_iso(2024, 4, 15).unwrap(),
80    ///     time: Time::try_new(14, 30, 45, 123_456_789).unwrap(),
81    /// };
82    /// let dt = Dt::from_icu_datetime_iso(icu);
83    /// let ymd = dt.target(Scale::UTC).to_ymd();
84    /// assert_eq!((ymd.yr(), ymd.mo(), ymd.day()), (2024, 4, 15));
85    /// assert_eq!((ymd.hr(), ymd.min(), ymd.sec()), (14, 30, 45));
86    /// assert_eq!(ymd.attos(), 123_456_789_000_000_000);
87    /// ```
88    pub fn from_icu_datetime_iso(dt: DateTime<Iso>) -> Dt {
89        let yr = i64::from(dt.date.era_year().extended_year);
90        let mo = dt.date.month().ordinal;
91        let day = dt.date.day_of_month().0;
92        let hr = dt.time.hour.number();
93        let min = dt.time.minute.number();
94        let sec = dt.time.second.number();
95        let attos = u64::from(dt.time.subsecond.number()).saturating_mul(ATTOS_PER_NS);
96
97        Dt::from_ymd(yr, mo, day, Scale::UTC, hr, min, sec, attos)
98    }
99}
100
101impl TryFrom<Dt> for DateTime<Iso> {
102    type Error = DtErr;
103
104    #[inline]
105    fn try_from(dt: Dt) -> Result<Self, Self::Error> {
106        dt.to_icu_datetime_iso()
107    }
108}
109
110impl From<DateTime<Iso>> for Dt {
111    #[inline]
112    fn from(dt: DateTime<Iso>) -> Self {
113        Self::from_icu_datetime_iso(dt)
114    }
115}