Skip to main content

deser_core/ext/bridges/
chrono.rs

1use ::chrono::{
2    DateTime, Datelike, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeDelta, Timelike, Utc,
3};
4
5use crate::error::Error;
6use crate::ext::known::{Bridge, impl_bridge, invalid, out_of_range};
7use crate::ext::{Date, Datetime, Duration, Offset, Time, Timestamp};
8
9fn date_to_known(value: NaiveDate) -> Result<Date, Error> {
10    match u16::try_from(value.year()) {
11        Ok(year) if year <= 9999 => Ok(Date {
12            year,
13            month: value.month() as u8,
14            day: value.day() as u8,
15        }),
16        _ => Err(out_of_range("year out of range")),
17    }
18}
19
20fn time_to_known(value: NaiveTime) -> Time {
21    // chrono represents leap seconds with nanoseconds beyond one second
22    let (second, nanosecond) = if value.nanosecond() >= 1_000_000_000 {
23        (60, value.nanosecond() - 1_000_000_000)
24    } else {
25        (value.second() as u8, value.nanosecond())
26    };
27    Time {
28        hour: value.hour() as u8,
29        minute: value.minute() as u8,
30        second,
31        nanosecond,
32    }
33}
34
35fn date_from_known(value: Date) -> Result<NaiveDate, Error> {
36    NaiveDate::from_ymd_opt(i32::from(value.year), value.month.into(), value.day.into())
37        .ok_or_else(|| invalid("invalid date"))
38}
39
40fn time_from_known(value: Time) -> Result<NaiveTime, Error> {
41    let (second, nanosecond) = if value.second == 60 {
42        (59, value.nanosecond + 1_000_000_000)
43    } else {
44        (value.second, value.nanosecond)
45    };
46    NaiveTime::from_hms_nano_opt(
47        value.hour.into(),
48        value.minute.into(),
49        second.into(),
50        nanosecond,
51    )
52    .ok_or_else(|| invalid("invalid time"))
53}
54
55fn datetime_to_known(value: NaiveDateTime) -> Result<Datetime, Error> {
56    Ok(Datetime {
57        date: Some(date_to_known(value.date())?),
58        time: Some(time_to_known(value.time())),
59        offset: None,
60    })
61}
62
63impl Bridge for NaiveDate {
64    type Known = Datetime;
65
66    const EXPECTING: &'static str = "local date";
67
68    fn to_known(&self) -> Result<Datetime, Error> {
69        date_to_known(*self).map(Datetime::from)
70    }
71
72    fn from_known(value: Datetime) -> Result<NaiveDate, Error> {
73        date_from_known(value.expect_local_date()?)
74    }
75}
76
77impl Bridge for NaiveTime {
78    type Known = Datetime;
79
80    const EXPECTING: &'static str = "local time";
81
82    fn to_known(&self) -> Result<Datetime, Error> {
83        Ok(Datetime::from(time_to_known(*self)))
84    }
85
86    fn from_known(value: Datetime) -> Result<NaiveTime, Error> {
87        time_from_known(value.expect_local_time()?)
88    }
89}
90
91impl Bridge for NaiveDateTime {
92    type Known = Datetime;
93
94    const EXPECTING: &'static str = "local date-time";
95
96    fn to_known(&self) -> Result<Datetime, Error> {
97        datetime_to_known(*self)
98    }
99
100    fn from_known(value: Datetime) -> Result<NaiveDateTime, Error> {
101        let (date, time) = value.expect_local_datetime()?;
102        Ok(NaiveDateTime::new(
103            date_from_known(date)?,
104            time_from_known(time)?,
105        ))
106    }
107}
108
109impl Bridge for DateTime<FixedOffset> {
110    type Known = Datetime;
111
112    const EXPECTING: &'static str = "offset date-time";
113
114    fn to_known(&self) -> Result<Datetime, Error> {
115        let seconds = self.offset().local_minus_utc();
116        if seconds % 60 != 0 {
117            return Err(out_of_range("offsets with seconds are not supported"));
118        }
119        let mut rv = datetime_to_known(self.naive_local())?;
120        rv.offset = Some(match seconds / 60 {
121            0 => Offset::Z,
122            minutes => Offset::Custom {
123                minutes: minutes as i16,
124            },
125        });
126        Ok(rv)
127    }
128
129    fn from_known(value: Datetime) -> Result<DateTime<FixedOffset>, Error> {
130        let (date, time, offset) = value.expect_offset_datetime()?;
131        let offset = FixedOffset::east_opt(i32::from(offset.minutes()) * 60)
132            .ok_or_else(|| invalid("invalid offset"))?;
133        NaiveDateTime::new(date_from_known(date)?, time_from_known(time)?)
134            .and_local_timezone(offset)
135            .single()
136            .ok_or_else(|| out_of_range("date-time out of range"))
137    }
138}
139
140impl Bridge for DateTime<Utc> {
141    type Known = Timestamp;
142
143    fn to_known(&self) -> Result<Timestamp, Error> {
144        Ok(Timestamp {
145            seconds: self.timestamp(),
146            // leap seconds are clamped
147            nanosecond: self.timestamp_subsec_nanos().min(999_999_999),
148        })
149    }
150
151    fn from_known(value: Timestamp) -> Result<DateTime<Utc>, Error> {
152        DateTime::from_timestamp(value.seconds, value.nanosecond)
153            .ok_or_else(|| out_of_range("timestamp out of range"))
154    }
155}
156
157impl Bridge for TimeDelta {
158    type Known = Duration;
159
160    fn to_known(&self) -> Result<Duration, Error> {
161        Ok(Duration {
162            seconds: self.num_seconds(),
163            nanosecond: self.subsec_nanos(),
164        })
165    }
166
167    fn from_known(value: Duration) -> Result<TimeDelta, Error> {
168        TimeDelta::try_seconds(value.seconds)
169            .and_then(|x| x.checked_add(&TimeDelta::nanoseconds(value.nanosecond.into())))
170            .ok_or_else(|| out_of_range("duration out of range"))
171    }
172}
173
174impl_bridge!(
175    NaiveDate,
176    NaiveTime,
177    NaiveDateTime,
178    DateTime<FixedOffset>,
179    DateTime<Utc>,
180    TimeDelta,
181);