Skip to main content

deep_time/time_parts/
to_jiff.rs

1use {
2    crate::{
3        ATTOS_PER_NS, Meridiem, Offset, TimeParts, Weekday, an_err,
4        error::{DtErr, DtErrKind},
5    },
6    alloc::string::String,
7    core::result::Result,
8    jiff::{
9        Timestamp, Zoned,
10        fmt::strtime::{BrokenDownTime, Meridiem as JiffMeridiem},
11        tz::Offset as JiffOffset,
12    },
13};
14
15impl TimeParts {
16    /// Converts [`TimeParts`] → [`jiff::fmt::strtime::BrokenDownTime`].
17    pub fn to_jiff_broken_down_time(&self) -> Result<BrokenDownTime, DtErr> {
18        let mut bdt = BrokenDownTime::default();
19
20        // Date fields
21        if let Some(year) = self.yr {
22            let y: i16 = year
23                .try_into()
24                .map_err(|e| an_err!(DtErrKind::InvalidInput, "year: {}: {}", year, e))?;
25            bdt.set_year(Some(y))
26                .map_err(|e| an_err!(DtErrKind::InvalidItem, "year: {}: {}", y, e))?;
27        }
28        if let Some(m) = self.mo {
29            bdt.set_month(Some(m as i8))
30                .map_err(|e| an_err!(DtErrKind::InvalidItem, "month: {}: {}", m, e))?;
31        }
32        if let Some(d) = self.day {
33            bdt.set_day(Some(d as i8))
34                .map_err(|e| an_err!(DtErrKind::InvalidItem, "day: {}: {}", d, e))?;
35        }
36
37        // Week / day-of-year fields
38        if let Some(doy) = self.day_of_yr {
39            bdt.set_day_of_year(Some(doy as i16))
40                .map_err(|e| an_err!(DtErrKind::InvalidItem, "doy: {}: {}", doy, e))?;
41        }
42        if let Some(y) = self.iso_wk_yr {
43            let y: i16 = y
44                .try_into()
45                .map_err(|e| an_err!(DtErrKind::InvalidInput, "iso wk yr: {}: {}", y, e))?;
46            bdt.set_iso_week_year(Some(y))
47                .map_err(|e| an_err!(DtErrKind::InvalidItem, "iso wk yr: {}: {}", y, e))?;
48        }
49        if let Some(w) = self.iso_wk {
50            bdt.set_iso_week(Some(w as i8))
51                .map_err(|e| an_err!(DtErrKind::InvalidItem, "iso wk: {}: {}", w, e))?;
52        }
53        if let Some(w) = self.wk_sun {
54            bdt.set_sunday_based_week(Some(w as i8))
55                .map_err(|e| an_err!(DtErrKind::InvalidItem, "sun based wk: {}: {}", w, e))?;
56        }
57        if let Some(w) = self.wk_mon {
58            bdt.set_monday_based_week(Some(w as i8))
59                .map_err(|e| an_err!(DtErrKind::InvalidItem, "mon based wk: {}: {}", w, e))?;
60        }
61
62        // Time of day
63        bdt.set_hour(Some(self.hr as i8))
64            .map_err(|e| an_err!(DtErrKind::InvalidItem, "hour: {}: {}", self.hr, e))?;
65        bdt.set_minute(Some(self.min as i8))
66            .map_err(|e| an_err!(DtErrKind::InvalidItem, "minute: {}: {}", self.min, e))?;
67        let non_ls_s = if self.sec == 60 { 59 } else { self.sec };
68        bdt.set_second(Some(non_ls_s as i8))
69            .map_err(|e| an_err!(DtErrKind::InvalidItem, "second: {}: {}", non_ls_s, e))?;
70
71        // Subsecond precision (attoseconds → nanoseconds)
72        if self.attos != 0 {
73            let ns_u64 = self.attos / ATTOS_PER_NS;
74            let ns: i32 = if ns_u64 >= 1_000_000_000 {
75                999_999_999
76            } else {
77                ns_u64 as i32
78            };
79            bdt.set_subsec_nanosecond(Some(ns))
80                .map_err(|e| an_err!(DtErrKind::InvalidItem, "ns: {}: {}", ns, e))?;
81        }
82
83        // Infallible setters
84        if let Some(wd) = self.wkday {
85            let jwd = match wd {
86                Weekday::Sunday => jiff::civil::Weekday::Sunday,
87                Weekday::Monday => jiff::civil::Weekday::Monday,
88                Weekday::Tuesday => jiff::civil::Weekday::Tuesday,
89                Weekday::Wednesday => jiff::civil::Weekday::Wednesday,
90                Weekday::Thursday => jiff::civil::Weekday::Thursday,
91                Weekday::Friday => jiff::civil::Weekday::Friday,
92                Weekday::Saturday => jiff::civil::Weekday::Saturday,
93            };
94            bdt.set_weekday(Some(jwd));
95        }
96        if let Some(mer) = self.meridiem {
97            let jmer = match mer {
98                Meridiem::AM => JiffMeridiem::AM,
99                Meridiem::PM => JiffMeridiem::PM,
100            };
101            bdt.set_meridiem(Some(jmer));
102        }
103
104        // Explicit Unix timestamp (highest priority)
105        if let Some(secs) = self.unix_timestamp_seconds {
106            let ts = Timestamp::from_second(secs)
107                .map_err(|e| an_err!(DtErrKind::InvalidInput, "timestamp: {}: {}", secs, e))?;
108            bdt.set_timestamp(Some(ts));
109        }
110
111        // Prefer IANA name if present; otherwise fall back to the custom TimeZone enum.
112        if let Some(name) = &self.iana_name {
113            let name_str = name.as_str();
114            if !name_str.is_empty() {
115                bdt.set_iana_time_zone(Some(String::from(name_str)));
116            }
117        } else if let Some(Offset::Fixed(secs)) = self.offset {
118            if let Ok(jiff_offset) = JiffOffset::from_seconds(secs) {
119                bdt.set_offset(Some(jiff_offset));
120            } else {
121                return Err(an_err!(
122                    DtErrKind::InvalidTimezoneOffset,
123                    "offset secs: {}",
124                    secs
125                ));
126            }
127        } else {
128            // Utc / None → treat as UTC
129            bdt.set_offset(Some(JiffOffset::UTC));
130        }
131
132        Ok(bdt)
133    }
134
135    /// Converts [`TimeParts`] → [`jiff::Zoned`].
136    pub fn to_jiff_zoned(&self) -> Result<Zoned, DtErr> {
137        let bdt = self.to_jiff_broken_down_time()?;
138        if let Ok(zoned) = bdt.to_zoned() {
139            return Ok(zoned);
140        }
141        if let Ok(ts) = bdt.to_timestamp()
142            && let Ok(zoned) = ts.in_tz("UTC")
143        {
144            return Ok(zoned);
145        }
146        if let Ok(dt) = bdt.to_datetime()
147            && let Ok(zoned) = dt.in_tz("UTC")
148        {
149            return Ok(zoned);
150        }
151        if let Ok(date) = bdt.to_date()
152            && let Ok(dt) = date.at(0, 0, 0, 0).in_tz("UTC")
153        {
154            return Ok(dt);
155        }
156
157        Err(an_err!(
158            DtErrKind::InvalidInput,
159            "could not convert to jiff zoned"
160        ))
161    }
162
163    /// Converts [`TimeParts`] → [`jiff::Timestamp`].
164    #[inline(always)]
165    pub fn to_jiff_timestamp(&self) -> Result<Timestamp, DtErr> {
166        self.to_jiff_zoned().map(|z| z.timestamp())
167    }
168}