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