hl7_parser/datetime/
jiff.rs

1//! All implementations here are implemented as `TryFrom` and `From` traits
2//! between the `TimeStamp` struct and various `chrono` 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 jiff::civil::{date, time, datetime};
16//!
17//! let timestamp = TimeStamp {
18//!    year: 2021,
19//!    month: Some(1),
20//!    day: Some(1),
21//!    hour: Some(12),
22//!    minute: Some(0),
23//!    second: Some(0),
24//!    microsecond: Some(0),
25//!    offset: None,
26//! };
27//!
28//! let datetime = datetime(2021, 1, 1, 12, 0, 0, 0);
29//! assert_eq!(TimeStamp::from(datetime), timestamp);
30//! ```
31use jiff::{
32    civil::{date, Date, DateTime, Time},
33    Zoned,
34};
35
36use super::{DateTimeParseError, TimeStamp as HL7TimeStamp, Time as HL7Time, Date as HL7Date};
37
38impl TryFrom<HL7TimeStamp> for Date {
39    type Error = DateTimeParseError;
40
41    fn try_from(value: HL7TimeStamp) -> Result<Self, Self::Error> {
42        let HL7TimeStamp {
43            year, month, day, ..
44        } = value;
45
46        let month = month.unwrap_or(1);
47        let day = day.unwrap_or(1);
48
49        Ok(date(year as i16, month as i8, day as i8))
50    }
51}
52
53impl From<Date> for HL7TimeStamp {
54    fn from(value: Date) -> Self {
55        let year = value.year();
56        let month = value.month();
57        let day = value.day();
58
59        HL7TimeStamp {
60            year: year as u16,
61            month: Some(month as u8),
62            day: Some(day as u8),
63            ..Default::default()
64        }
65    }
66}
67
68impl TryFrom<HL7Date> for Date {
69    type Error = DateTimeParseError;
70
71    fn try_from(value: super::Date) -> Result<Self, Self::Error> {
72        let super::Date { year, month, day } = value;
73
74        let month = month.unwrap_or(1);
75        let day = day.unwrap_or(1);
76
77        Ok(date(year as i16, month as i8, day as i8))
78    }
79}
80
81impl From<Date> for HL7Date {
82    fn from(value: Date) -> Self {
83        let year = value.year();
84        let month = value.month();
85        let day = value.day();
86
87        super::Date {
88            year: year as u16,
89            month: Some(month as u8),
90            day: Some(day as u8),
91        }
92    }
93}
94
95impl TryFrom<HL7Time> for Time {
96    type Error = DateTimeParseError;
97
98    fn try_from(value: super::Time) -> Result<Self, Self::Error> {
99        let super::Time {
100            hour,
101            minute,
102            second,
103            microsecond,
104            ..
105        } = value;
106
107        let minute = minute.unwrap_or(0);
108        let second = second.unwrap_or(0);
109        let microsecond = microsecond.unwrap_or(0);
110        Ok(jiff::civil::time(
111            hour as i8,
112            minute as i8,
113            second as i8,
114            microsecond as i32,
115        ))
116    }
117}
118
119impl From<Time> for HL7Time {
120    fn from(value: Time) -> Self {
121        let hour = value.hour();
122        let minute = value.minute();
123        let second = value.second();
124        let microsecond = value.microsecond();
125
126        super::Time {
127            hour: hour as u8,
128            minute: Some(minute as u8),
129            second: Some(second as u8),
130            microsecond: Some(microsecond as u32),
131            offset: None,
132        }
133    }
134}
135
136impl TryFrom<HL7TimeStamp> for Time {
137    type Error = DateTimeParseError;
138
139    fn try_from(value: HL7TimeStamp) -> Result<Self, Self::Error> {
140        let HL7TimeStamp {
141            hour,
142            minute,
143            second,
144            microsecond,
145            ..
146        } = value;
147
148        let hour = hour.unwrap_or(0);
149        let minute = minute.unwrap_or(0);
150        let second = second.unwrap_or(0);
151        let microsecond = microsecond.unwrap_or(0);
152        Ok(jiff::civil::time(
153            hour as i8,
154            minute as i8,
155            second as i8,
156            microsecond as i32,
157        ))
158    }
159}
160
161impl TryFrom<HL7TimeStamp> for DateTime {
162    type Error = DateTimeParseError;
163
164    fn try_from(value: HL7TimeStamp) -> Result<Self, Self::Error> {
165        let date = Date::try_from(value)?;
166        let time = Time::try_from(value)?;
167
168        Ok(jiff::civil::datetime(
169            date.year(),
170            date.month(),
171            date.day(),
172            time.hour(),
173            time.minute(),
174            time.second(),
175            time.microsecond().into(),
176        ))
177    }
178}
179
180impl From<DateTime> for HL7TimeStamp {
181    fn from(value: DateTime) -> Self {
182        let date = value.date();
183        let time = value.time();
184
185        let year = date.year();
186        let month = date.month();
187        let day = date.day();
188
189        let hour = time.hour();
190        let minute = time.minute();
191        let second = time.second();
192        let microsecond = time.microsecond();
193
194        HL7TimeStamp {
195            year: year as u16,
196            month: Some(month as u8),
197            day: Some(day as u8),
198            hour: Some(hour as u8),
199            minute: Some(minute as u8),
200            second: Some(second as u8),
201            microsecond: Some(microsecond as u32),
202            offset: None,
203        }
204    }
205}
206
207impl TryFrom<Zoned> for HL7TimeStamp {
208    type Error = DateTimeParseError;
209
210    fn try_from(value: Zoned) -> Result<Self, Self::Error> {
211        let date = value.date();
212        let time = value.time();
213        let offset = value.offset();
214
215        let year = date.year();
216        let month = date.month();
217        let day = date.day();
218
219        let hour = time.hour();
220        let minute = time.minute();
221        let second = time.second();
222        let microsecond = time.microsecond();
223
224        let offset_seconds = offset.seconds();
225        let offset_hours: i8 = (offset_seconds / 3600) as i8;
226        let offset_minutes: u8 = ((offset_seconds.abs() % 3600) / 60) as u8;
227
228        Ok(HL7TimeStamp {
229            year: year as u16,
230            month: Some(month as u8),
231            day: Some(day as u8),
232            hour: Some(hour as u8),
233            minute: Some(minute as u8),
234            second: Some(second as u8),
235            microsecond: Some(microsecond as u32),
236            offset: Some(super::TimeStampOffset {
237                hours: offset_hours,
238                minutes: offset_minutes,
239            }),
240        })
241    }
242}
243
244impl TryFrom<HL7TimeStamp> for Zoned {
245    type Error = jiff::Error;
246
247    fn try_from(value: HL7TimeStamp) -> Result<Self, Self::Error> {
248        let date = Date::try_from(value).unwrap();
249        let time = Time::try_from(value).unwrap();
250        let offset = value.offset.unwrap_or_default();
251
252        let year = date.year();
253        let month = date.month();
254        let day = date.day();
255
256        let hour = time.hour();
257        let minute = time.minute();
258        let second = time.second();
259        let microsecond = time.microsecond();
260
261        let offset_seconds = (offset.hours as i32 * 3600) + (offset.minutes as i32 * 60);
262        let offset = jiff::tz::Offset::from_seconds(offset_seconds)?;
263        let timezone = jiff::tz::TimeZone::fixed(offset);
264
265        let datetime =
266            jiff::civil::datetime(year, month, day, hour, minute, second, microsecond.into());
267
268        datetime.to_zoned(timezone)
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275
276    #[test]
277    fn can_roundtrip_date() {
278        let date = date(2021, 1, 1);
279        let hl7_date = HL7Date::from(date);
280        let date2 = Date::try_from(hl7_date).unwrap();
281        assert_eq!(date, date2);
282    }
283
284    #[test]
285    fn can_roundtrip_time() {
286        let time = jiff::civil::time(12, 0, 0, 0);
287        let hl7_time = HL7Time::from(time);
288        let time2 = Time::try_from(hl7_time).unwrap();
289        assert_eq!(time, time2);
290    }
291
292    #[test]
293    fn can_roundtrip_timestamp() {
294        let timestamp = jiff::civil::datetime(2021, 1, 1, 12, 0, 0, 0);
295        let hl7_timestamp = HL7TimeStamp::from(timestamp);
296        let timestamp2 = DateTime::try_from(hl7_timestamp).unwrap();
297        assert_eq!(timestamp, timestamp2);
298    }
299
300    #[test]
301    fn can_convert_timestamp_to_zoned() {
302        let timestamp = jiff::civil::datetime(2021, 1, 1, 12, 0, 0, 0);
303        let hl7_timestamp = HL7TimeStamp::from(timestamp);
304        let zoned = Zoned::try_from(hl7_timestamp).unwrap();
305        assert_eq!(zoned.date(), timestamp.date());
306        assert_eq!(zoned.time(), timestamp.time());
307    }
308
309    #[test]
310    fn can_convert_zoned_to_timestamp() {
311        let timestamp = jiff::civil::datetime(2021, 1, 1, 12, 0, 0, 0);
312        let zoned = timestamp.to_zoned(jiff::tz::TimeZone::UTC).unwrap();
313        let hl7_timestamp = HL7TimeStamp::try_from(zoned).unwrap();
314
315        assert_eq!(hl7_timestamp.year, 2021);
316        assert_eq!(hl7_timestamp.month, Some(1));
317        assert_eq!(hl7_timestamp.day, Some(1));
318        assert_eq!(hl7_timestamp.hour, Some(12));
319        assert_eq!(hl7_timestamp.minute, Some(0));
320        assert_eq!(hl7_timestamp.second, Some(0));
321        assert_eq!(hl7_timestamp.microsecond, Some(0));
322        assert_eq!(hl7_timestamp.offset, Some(crate::datetime::TimeStampOffset { hours: 0, minutes: 0 }));
323    }
324}