Skip to main content

deser_core/ext/bridges/
jiff.rs

1use ::jiff::civil;
2use ::jiff::tz::{self, TimeZone};
3use ::jiff::{SignedDuration, Zoned};
4use alloc::string::ToString;
5
6use crate::error::Error;
7use crate::ext::known::{Bridge, impl_bridge, invalid, out_of_range};
8use crate::ext::{Date, Datetime, Duration, Offset, Time, Timestamp};
9
10#[cold]
11fn jiff_error(err: ::jiff::Error) -> Error {
12    #[allow(unused_mut)]
13    let mut rv = invalid(err.to_string());
14    // the errors of jiff only implement `Error` with `std`
15    #[cfg(feature = "std")]
16    rv.set_source(err);
17    rv
18}
19
20fn date_to_known(value: civil::Date) -> Result<Date, Error> {
21    match u16::try_from(value.year()) {
22        Ok(year) => Ok(Date {
23            year,
24            month: value.month() as u8,
25            day: value.day() as u8,
26        }),
27        Err(_) => Err(out_of_range("negative years are not supported")),
28    }
29}
30
31fn time_to_known(value: civil::Time) -> Time {
32    Time {
33        hour: value.hour() as u8,
34        minute: value.minute() as u8,
35        second: value.second() as u8,
36        nanosecond: value.subsec_nanosecond() as u32,
37    }
38}
39
40fn date_from_known(value: Date) -> Result<civil::Date, Error> {
41    let year = i16::try_from(value.year).map_err(|_| out_of_range("year out of range"))?;
42    civil::Date::new(year, value.month as i8, value.day as i8).map_err(jiff_error)
43}
44
45fn time_from_known(value: Time) -> Result<civil::Time, Error> {
46    civil::Time::new(
47        value.hour as i8,
48        value.minute as i8,
49        // jiff does not support leap seconds, they are clamped
50        value.second.min(59) as i8,
51        value.nanosecond as i32,
52    )
53    .map_err(jiff_error)
54}
55
56impl Bridge for civil::Date {
57    type Known = Datetime;
58
59    const EXPECTING: &'static str = "local date";
60
61    fn to_known(&self) -> Result<Datetime, Error> {
62        date_to_known(*self).map(Datetime::from)
63    }
64
65    fn from_known(value: Datetime) -> Result<civil::Date, Error> {
66        date_from_known(value.expect_local_date()?)
67    }
68
69    fn parse_fallback(value: &str) -> Option<civil::Date> {
70        value.parse().ok()
71    }
72}
73
74impl Bridge for civil::Time {
75    type Known = Datetime;
76
77    const EXPECTING: &'static str = "local time";
78
79    fn to_known(&self) -> Result<Datetime, Error> {
80        Ok(Datetime::from(time_to_known(*self)))
81    }
82
83    fn from_known(value: Datetime) -> Result<civil::Time, Error> {
84        time_from_known(value.expect_local_time()?)
85    }
86
87    fn parse_fallback(value: &str) -> Option<civil::Time> {
88        value.parse().ok()
89    }
90}
91
92impl Bridge for civil::DateTime {
93    type Known = Datetime;
94
95    const EXPECTING: &'static str = "local date-time";
96
97    fn to_known(&self) -> Result<Datetime, Error> {
98        Ok(Datetime {
99            date: Some(date_to_known(self.date())?),
100            time: Some(time_to_known(self.time())),
101            offset: None,
102        })
103    }
104
105    fn from_known(value: Datetime) -> Result<civil::DateTime, Error> {
106        let (date, time) = value.expect_local_datetime()?;
107        Ok(civil::DateTime::from_parts(
108            date_from_known(date)?,
109            time_from_known(time)?,
110        ))
111    }
112
113    fn parse_fallback(value: &str) -> Option<civil::DateTime> {
114        value.parse().ok()
115    }
116}
117
118impl Bridge for Zoned {
119    type Known = Datetime;
120
121    const EXPECTING: &'static str = "offset date-time";
122
123    /// Converts into an offset date-time, the time zone is lost.
124    fn to_known(&self) -> Result<Datetime, Error> {
125        let seconds = self.offset().seconds();
126        if seconds % 60 != 0 {
127            return Err(out_of_range("offsets with seconds are not supported"));
128        }
129        Ok(Datetime {
130            date: Some(date_to_known(self.date())?),
131            time: Some(time_to_known(self.time())),
132            offset: Some(match seconds / 60 {
133                0 => Offset::Z,
134                minutes => Offset::Custom {
135                    minutes: minutes as i16,
136                },
137            }),
138        })
139    }
140
141    /// Converts an offset date-time into a zoned date-time with a fixed
142    /// offset.
143    fn from_known(value: Datetime) -> Result<Zoned, Error> {
144        let (date, time, offset) = value.expect_offset_datetime()?;
145        let datetime = civil::DateTime::from_parts(date_from_known(date)?, time_from_known(time)?);
146        let offset =
147            tz::Offset::from_seconds(i32::from(offset.minutes()) * 60).map_err(jiff_error)?;
148        TimeZone::fixed(offset)
149            .to_zoned(datetime)
150            .map_err(jiff_error)
151    }
152
153    fn parse_fallback(value: &str) -> Option<Zoned> {
154        value.parse().ok()
155    }
156}
157
158impl Bridge for ::jiff::Timestamp {
159    type Known = Timestamp;
160
161    fn to_known(&self) -> Result<Timestamp, Error> {
162        let nanos = self.as_nanosecond();
163        Ok(Timestamp {
164            seconds: nanos.div_euclid(1_000_000_000) as i64,
165            nanosecond: nanos.rem_euclid(1_000_000_000) as u32,
166        })
167    }
168
169    fn from_known(value: Timestamp) -> Result<::jiff::Timestamp, Error> {
170        let nanos = i128::from(value.seconds) * 1_000_000_000 + i128::from(value.nanosecond);
171        ::jiff::Timestamp::from_nanosecond(nanos).map_err(jiff_error)
172    }
173
174    fn parse_fallback(value: &str) -> Option<::jiff::Timestamp> {
175        value.parse().ok()
176    }
177}
178
179impl Bridge for SignedDuration {
180    type Known = Duration;
181
182    fn to_known(&self) -> Result<Duration, Error> {
183        Ok(Duration {
184            seconds: self.as_secs(),
185            nanosecond: self.subsec_nanos(),
186        })
187    }
188
189    fn from_known(value: Duration) -> Result<SignedDuration, Error> {
190        if !value.is_valid() {
191            return Err(invalid("invalid duration"));
192        }
193        Ok(SignedDuration::new(value.seconds, value.nanosecond))
194    }
195
196    fn parse_fallback(value: &str) -> Option<SignedDuration> {
197        value.parse().ok()
198    }
199}
200
201impl_bridge!(
202    civil::Date,
203    civil::Time,
204    civil::DateTime,
205    Zoned,
206    ::jiff::Timestamp,
207    SignedDuration,
208);