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}