haste_fhirpath/
partial_datetime.rs1use chrono::{FixedOffset, NaiveDate, NaiveDateTime, NaiveTime};
8use haste_fhir_model::r4::datetime::{
9 Date, DateTime, Time, parse_date, parse_datetime, parse_partial_time,
10};
11use haste_reflect::MetaValue;
12use regex::Regex;
13use std::{any::Any, fmt, sync::LazyLock};
14
15static TIME_AND_OFFSET_REGEX: LazyLock<Regex> = LazyLock::new(|| {
17 Regex::new(r"^(?<time>[0-9][0-9:.]*?)(?<offset>Z|(\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?$")
18 .unwrap()
19});
20
21#[derive(Debug, Clone, PartialEq)]
23pub struct PartialDateTime {
24 pub date: NaiveDate,
25 pub time: Time,
26 pub offset: Option<FixedOffset>,
28}
29
30pub enum AnyDateTime {
32 Fhir(DateTime),
33 Partial(PartialDateTime),
34}
35
36pub fn parse_any_datetime(text: &str) -> Option<AnyDateTime> {
38 match parse_datetime(text) {
39 Ok(value) => Some(AnyDateTime::Fhir(value)),
40 Err(_) => PartialDateTime::parse(text).map(AnyDateTime::Partial),
41 }
42}
43
44pub fn naive_time(time: &Time) -> NaiveTime {
46 match time {
47 Time::Hour(hour) => NaiveTime::from_hms_opt(u32::from(*hour), 0, 0),
48 Time::HourMinute(hour, minute) => {
49 NaiveTime::from_hms_opt(u32::from(*hour), u32::from(*minute), 0)
50 }
51 Time::HourMinuteSecond(time) => Some(*time),
52 }
53 .unwrap_or_default()
54}
55
56impl PartialDateTime {
57 fn parse(text: &str) -> Option<Self> {
59 let (date, time) = text.split_once('T')?;
60 let captures = TIME_AND_OFFSET_REGEX.captures(time)?;
61 let Date::YearMonthDay(year, month, day) = parse_date(date).ok()? else {
62 return None;
63 };
64 let offset = match captures.name("offset").map(|offset| offset.as_str()) {
66 None => None,
67 Some("Z") => Some(FixedOffset::east_opt(0)?),
68 Some(offset) => Some(offset.parse::<FixedOffset>().ok()?),
69 };
70 Some(PartialDateTime {
71 date: NaiveDate::from_ymd_opt(i32::from(year), u32::from(month), u32::from(day))?,
72 time: parse_partial_time(&captures["time"]).ok()?,
73 offset,
74 })
75 }
76
77 #[must_use]
79 pub fn local(&self) -> NaiveDateTime {
80 self.date.and_time(naive_time(&self.time))
81 }
82
83 #[must_use]
85 pub fn utc(&self) -> Option<NaiveDateTime> {
86 match self.offset {
87 Some(offset) => self.local().checked_sub_offset(offset),
88 None => Some(self.local()),
89 }
90 }
91}
92
93impl fmt::Display for PartialDateTime {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 write!(
96 f,
97 "{}T{}",
98 self.date.format("%Y-%m-%d"),
99 self.time.to_string()
100 )?;
101 match self.offset {
102 Some(offset) => write!(f, "{offset}"),
103 None => Ok(()),
104 }
105 }
106}
107
108impl MetaValue for PartialDateTime {
110 fn fields(&self) -> Vec<&'static str> {
111 vec![]
112 }
113
114 fn get_field<'a>(&'a self, _field: &str) -> Option<&'a dyn MetaValue> {
115 None
116 }
117
118 fn get_field_mut<'a>(&'a mut self, _field: &str) -> Option<&'a mut dyn MetaValue> {
119 None
120 }
121
122 fn get_index(&self, _index: usize) -> Option<&dyn MetaValue> {
123 None
124 }
125
126 fn get_index_mut(&mut self, _index: usize) -> Option<&mut dyn MetaValue> {
127 None
128 }
129
130 fn flatten(&self) -> Vec<&dyn MetaValue> {
131 vec![self]
132 }
133
134 fn as_any(&self) -> &dyn Any {
135 self
136 }
137
138 fn fhir_type(&self) -> &'static str {
139 "http://hl7.org/fhirpath/System.DateTime"
140 }
141
142 fn is_many(&self) -> bool {
143 false
144 }
145}