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        if let Some(h) = self.hr {
64            bdt.set_hour(Some(h as i8))
65                .map_err(|e| an_err!(DtErrKind::InvalidItem, "hour: {}: {}", h, e))?;
66        }
67        if let Some(m) = self.min {
68            bdt.set_minute(Some(m as i8))
69                .map_err(|e| an_err!(DtErrKind::InvalidItem, "minute: {}: {}", m, e))?;
70        }
71        if let Some(s) = self.sec {
72            let non_ls_s = if s == 60 { 59 } else { s };
73            bdt.set_second(Some(non_ls_s as i8))
74                .map_err(|e| an_err!(DtErrKind::InvalidItem, "second: {}: {}", non_ls_s, e))?;
75        }
76
77        // Subsecond precision (attoseconds → nanoseconds)
78        if let Some(attos) = self.attos {
79            let ns_u64 = attos / ATTOS_PER_NS;
80            let ns: i32 = if ns_u64 >= 1_000_000_000 {
81                999_999_999
82            } else {
83                ns_u64 as i32
84            };
85            bdt.set_subsec_nanosecond(Some(ns))
86                .map_err(|e| an_err!(DtErrKind::InvalidItem, "ns: {}: {}", ns, e))?;
87        }
88
89        // Infallible setters
90        if let Some(wd) = self.wkday {
91            let jwd = match wd {
92                Weekday::Sunday => jiff::civil::Weekday::Sunday,
93                Weekday::Monday => jiff::civil::Weekday::Monday,
94                Weekday::Tuesday => jiff::civil::Weekday::Tuesday,
95                Weekday::Wednesday => jiff::civil::Weekday::Wednesday,
96                Weekday::Thursday => jiff::civil::Weekday::Thursday,
97                Weekday::Friday => jiff::civil::Weekday::Friday,
98                Weekday::Saturday => jiff::civil::Weekday::Saturday,
99            };
100            bdt.set_weekday(Some(jwd));
101        }
102        if let Some(mer) = self.meridiem {
103            let jmer = match mer {
104                Meridiem::AM => JiffMeridiem::AM,
105                Meridiem::PM => JiffMeridiem::PM,
106            };
107            bdt.set_meridiem(Some(jmer));
108        }
109
110        // Explicit Unix timestamp (highest priority)
111        if let Some(secs) = self.unix_timestamp_seconds {
112            let ts = Timestamp::from_second(secs)
113                .map_err(|e| an_err!(DtErrKind::InvalidInput, "timestamp: {}: {}", secs, e))?;
114            bdt.set_timestamp(Some(ts));
115        }
116
117        // Prefer IANA name if present; otherwise fall back to the custom TimeZone enum.
118        if let Some(name) = &self.iana_name {
119            match name.as_str() {
120                Ok(s) if !s.is_empty() => bdt.set_iana_time_zone(Some(String::from(s))),
121                Ok(_) => {} // empty name — do nothing
122                Err(e) => {
123                    return Err(an_err!(
124                        DtErrKind::InvalidBytes,
125                        "invalid iana ascii: {:?}: {}",
126                        name,
127                        e
128                    ));
129                }
130            }
131        } else if let Some(Offset::Fixed(secs)) = self.offset {
132            if let Ok(jiff_offset) = JiffOffset::from_seconds(secs) {
133                bdt.set_offset(Some(jiff_offset));
134            } else {
135                return Err(an_err!(
136                    DtErrKind::InvalidTimezoneOffset,
137                    "offset secs: {}",
138                    secs
139                ));
140            }
141        } else {
142            // Utc / None → treat as UTC
143            bdt.set_offset(Some(JiffOffset::UTC));
144        }
145
146        Ok(bdt)
147    }
148
149    /// Converts [`TimeParts`] → [`jiff::Zoned`].
150    pub fn to_jiff_zoned(&self) -> Result<Zoned, DtErr> {
151        let bdt = self.to_jiff_broken_down_time()?;
152        if let Ok(zoned) = bdt.to_zoned() {
153            return Ok(zoned);
154        }
155        if let Ok(ts) = bdt.to_timestamp()
156            && let Ok(zoned) = ts.in_tz("UTC")
157        {
158            return Ok(zoned);
159        }
160        if let Ok(dt) = bdt.to_datetime()
161            && let Ok(zoned) = dt.in_tz("UTC")
162        {
163            return Ok(zoned);
164        }
165        if let Ok(date) = bdt.to_date()
166            && let Ok(dt) = date.at(0, 0, 0, 0).in_tz("UTC")
167        {
168            return Ok(dt);
169        }
170
171        Err(an_err!(
172            DtErrKind::InvalidInput,
173            "could not convert to jiff zoned"
174        ))
175    }
176
177    /// Converts [`TimeParts`] → [`jiff::Timestamp`].
178    #[inline]
179    pub fn to_jiff_timestamp(&self) -> Result<Timestamp, DtErr> {
180        self.to_jiff_zoned().map(|z| z.timestamp())
181    }
182}