hl7_parser/datetime/
time_crate.rs

1//! All implementations here are implemented as `TryFrom` and `From` traits
2//! between the `TimeStamp` struct and various `time` types. This allows for
3//! easy conversion between the two types. The `TryFrom` implementations will
4//! return an error if the conversion is not possible, such as if the date or
5//! time components are invalid. The `From` implementations will always succeed
6//! and will set missing components to zero or the epoch if necessary.
7//!
8//! View the `TimeStamp` struct's documentation for more information on exactly
9//! which traits are implemented.
10//!
11//! # Examples
12//!
13//! ```
14//! use hl7_parser::datetime::{TimeStamp, TimeStampOffset};
15//! use time::{PrimitiveDateTime, OffsetDateTime};
16//!
17//! let ts = TimeStamp {
18//!    year: 2023,
19//!    month: Some(3),
20//!    day: Some(12),
21//!    hour: Some(19),
22//!    minute: Some(59),
23//!    second: Some(5),
24//!    microsecond: Some(1234),
25//!    offset: Some(TimeStampOffset {
26//!        hours: -7,
27//!        minutes: 0,
28//!     })
29//! };
30//!
31//! let datetime: OffsetDateTime = ts.try_into().unwrap();
32//! assert_eq!(datetime.year(), 2023);
33//! assert_eq!(datetime.month(), time::Month::March);
34//! assert_eq!(datetime.day(), 12);
35//! assert_eq!(datetime.hour(), 19);
36//! assert_eq!(datetime.minute(), 59);
37//! assert_eq!(datetime.second(), 5);
38//! assert_eq!(datetime.microsecond(), 1234);
39//! assert_eq!(datetime.offset().whole_hours(), -7);
40//! ```
41
42use time::{Date, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset};
43
44use super::{DateTimeParseError, ErroredDateTimeComponent, TimeStamp, TimeStampOffset};
45
46impl TryFrom<TimeStamp> for Date {
47    type Error = DateTimeParseError;
48
49    fn try_from(value: TimeStamp) -> Result<Self, Self::Error> {
50        let TimeStamp {
51            year, month, day, ..
52        } = value;
53
54        match (year, month, day) {
55            (year, Some(month), Some(day)) => {
56                let month = Month::try_from(month).map_err(|_| {
57                    DateTimeParseError::InvalidComponentRange(ErroredDateTimeComponent::Month)
58                })?;
59
60                Ok(
61                    Date::from_calendar_date(year.into(), month, day).map_err(|_| {
62                        DateTimeParseError::InvalidComponentRange(ErroredDateTimeComponent::Date)
63                    })?,
64                )
65            }
66            (_year, Some(_), None) => Err(DateTimeParseError::MissingComponent(
67                ErroredDateTimeComponent::Day,
68            )),
69            (_year, None, _) => Err(DateTimeParseError::MissingComponent(
70                ErroredDateTimeComponent::Month,
71            )),
72        }
73    }
74}
75
76impl TryFrom<super::Date> for Date {
77    type Error = DateTimeParseError;
78
79    fn try_from(value: super::Date) -> Result<Self, Self::Error> {
80        let super::Date { year, month, day } = value;
81
82        match (year, month, day) {
83            (year, Some(month), Some(day)) => {
84                let month = Month::try_from(month).map_err(|_| {
85                    DateTimeParseError::InvalidComponentRange(ErroredDateTimeComponent::Month)
86                })?;
87
88                Ok(
89                    Date::from_calendar_date(year.into(), month, day).map_err(|_| {
90                        DateTimeParseError::InvalidComponentRange(ErroredDateTimeComponent::Date)
91                    })?,
92                )
93            }
94            (_year, Some(_), None) => Err(DateTimeParseError::MissingComponent(
95                ErroredDateTimeComponent::Day,
96            )),
97            (_year, None, _) => Err(DateTimeParseError::MissingComponent(
98                ErroredDateTimeComponent::Month,
99            )),
100        }
101    }
102}
103
104impl From<Date> for TimeStamp {
105    fn from(value: Date) -> Self {
106        let (year, month, day) = value.to_calendar_date();
107
108        TimeStamp {
109            year: year as u16,
110            month: Some(month as u8),
111            day: Some(day),
112            hour: None,
113            minute: None,
114            second: None,
115            microsecond: None,
116            offset: None,
117        }
118    }
119}
120
121impl From<Date> for super::Date {
122    fn from(value: Date) -> Self {
123        let (year, month, day) = value.to_calendar_date();
124
125        super::Date {
126            year: year as u16,
127            month: Some(month as u8),
128            day: Some(day),
129        }
130    }
131}
132
133impl TryFrom<TimeStamp> for PrimitiveDateTime {
134    type Error = DateTimeParseError;
135
136    fn try_from(value: TimeStamp) -> Result<Self, Self::Error> {
137        let date = Date::try_from(value)?;
138
139        if value.hour.is_none() {
140            return Err(DateTimeParseError::MissingComponent(
141                ErroredDateTimeComponent::Hour,
142            ));
143        }
144        if value.minute.is_none() {
145            return Err(DateTimeParseError::MissingComponent(
146                ErroredDateTimeComponent::Minute,
147            ));
148        }
149        if value.second.is_none() {
150            return Err(DateTimeParseError::MissingComponent(
151                ErroredDateTimeComponent::Second,
152            ));
153        }
154
155        let TimeStamp {
156            hour,
157            minute,
158            second,
159            microsecond,
160            ..
161        } = value;
162
163        let time = Time::from_hms_micro(
164            hour.unwrap(),
165            minute.unwrap(),
166            second.unwrap(),
167            microsecond.unwrap_or(0),
168        )
169        .map_err(|_| DateTimeParseError::InvalidComponentRange(ErroredDateTimeComponent::Time))?;
170
171        Ok(PrimitiveDateTime::new(date, time))
172    }
173}
174
175impl TryFrom<super::Time> for Time {
176    type Error = DateTimeParseError;
177
178    fn try_from(value: super::Time) -> Result<Self, Self::Error> {
179        if value.minute.is_none() {
180            return Err(DateTimeParseError::MissingComponent(
181                ErroredDateTimeComponent::Minute,
182            ));
183        }
184        if value.second.is_none() {
185            return Err(DateTimeParseError::MissingComponent(
186                ErroredDateTimeComponent::Second,
187            ));
188        }
189
190        let super::Time {
191            hour,
192            minute,
193            second,
194            microsecond,
195            ..
196        } = value;
197
198        Time::from_hms_micro(
199            hour,
200            minute.unwrap(),
201            second.unwrap(),
202            microsecond.unwrap_or(0),
203        )
204        .map_err(|_| DateTimeParseError::InvalidComponentRange(ErroredDateTimeComponent::Time))
205    }
206}
207
208impl From<PrimitiveDateTime> for TimeStamp {
209    fn from(value: PrimitiveDateTime) -> Self {
210        let date = value.date();
211        let time = value.time();
212
213        TimeStamp {
214            year: date.year() as u16,
215            month: Some(date.month().into()),
216            day: Some(date.day()),
217            hour: Some(time.hour()),
218            minute: Some(time.minute()),
219            second: Some(time.second()),
220            microsecond: Some(time.microsecond()),
221            offset: None,
222        }
223    }
224}
225
226impl From<Time> for super::Time {
227    fn from(value: Time) -> Self {
228        super::Time {
229            hour: value.hour(),
230            minute: Some(value.minute()),
231            second: Some(value.second()),
232            microsecond: Some(value.microsecond()),
233            offset: None,
234        }
235    }
236}
237
238impl TryFrom<TimeStamp> for OffsetDateTime {
239    type Error = DateTimeParseError;
240
241    fn try_from(value: TimeStamp) -> Result<Self, Self::Error> {
242        if value.offset.is_none() {
243            return Err(DateTimeParseError::MissingComponent(
244                ErroredDateTimeComponent::Offset,
245            ));
246        }
247
248        let datetime = PrimitiveDateTime::try_from(value)?;
249        let offset = value.offset.unwrap();
250        let offset = UtcOffset::from_hms(offset.hours, offset.minutes as i8, 0).map_err(|_| {
251            DateTimeParseError::InvalidComponentRange(ErroredDateTimeComponent::Offset)
252        })?;
253
254        let date = datetime.date();
255        let time = datetime.time();
256
257        Ok(OffsetDateTime::new_in_offset(date, time, offset))
258    }
259}
260
261impl From<OffsetDateTime> for TimeStamp {
262    fn from(value: OffsetDateTime) -> Self {
263        let date = value.date();
264        let time = value.time();
265        let offset = value.offset();
266
267        TimeStamp {
268            year: date.year() as u16,
269            month: Some(date.month().into()),
270            day: Some(date.day()),
271            hour: Some(time.hour()),
272            minute: Some(time.minute()),
273            second: Some(time.second()),
274            microsecond: Some(time.microsecond()),
275            offset: Some(TimeStampOffset {
276                hours: offset.whole_hours(),
277                minutes: (offset.whole_minutes() % 60).unsigned_abs() as u8,
278            }),
279        }
280    }
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    #[test]
288    fn can_convert_timestamp_to_date() {
289        let ts = TimeStamp {
290            year: 2023,
291            month: Some(3),
292            day: Some(12),
293            hour: Some(19),
294            minute: Some(59),
295            second: None,
296            microsecond: None,
297            offset: None,
298        };
299        let actual = Date::try_from(ts).unwrap();
300        assert_eq!(
301            actual,
302            Date::from_calendar_date(2023, Month::March, 12).unwrap()
303        );
304    }
305
306    #[test]
307    fn can_convert_timestamp_to_offsetdateime() {
308        let ts = TimeStamp {
309            year: 2023,
310            month: Some(3),
311            day: Some(12),
312            hour: Some(19),
313            minute: Some(59),
314            second: Some(5),
315            microsecond: Some(1234),
316            offset: Some(TimeStampOffset {
317                hours: -7,
318                minutes: 0,
319            }),
320        };
321        let actual = OffsetDateTime::try_from(ts).unwrap();
322        assert_eq!(actual.year(), 2023);
323        assert_eq!(actual.month(), Month::March);
324        assert_eq!(actual.day(), 12);
325        assert_eq!(actual.hour(), 19);
326        assert_eq!(actual.minute(), 59);
327        assert_eq!(actual.second(), 5);
328        assert_eq!(actual.microsecond(), 1234);
329        assert_eq!(actual.offset(), UtcOffset::from_hms(-7, 0, 0).unwrap());
330    }
331}