1use ::serde::{Deserialize, Serialize};
4use ::std::{cmp::Ordering, str::FromStr};
5use ::time::{OffsetDateTime, error::Parse, format_description::well_known::Rfc3339};
6
7use crate::error::DateFormatError;
8
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
11#[serde(transparent)]
12pub struct Instant(#[serde(with = "time::serde::rfc3339")] pub OffsetDateTime);
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub enum Date {
17 Year(i32),
19 YearMonth(i32, time::Month),
21 Date(time::Date),
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
27#[serde(untagged)]
28pub enum DateTime {
29 Date(Date),
31 DateTime(Instant),
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
37#[serde(transparent)]
38pub struct Time(#[serde(with = "serde_time")] pub time::Time);
39
40mod serde_time {
42 use serde::{Deserialize, Serialize};
43 use time::{format_description::FormatItem, macros::format_description};
44
45 const TIME_FORMAT: &[FormatItem<'_>] = format_description!("[hour]:[minute]:[second]");
47 const TIME_FORMAT_SUBSEC: &[FormatItem<'_>] = fhir_time_format();
49
50 const fn fhir_time_format() -> &'static [FormatItem<'static>] {
52 const OPTIONAL_SUB_SECONDS: FormatItem<'_> =
54 FormatItem::Optional(&FormatItem::Compound(format_description!(".[subsecond]")));
55 &[FormatItem::Compound(TIME_FORMAT), OPTIONAL_SUB_SECONDS]
56 }
57
58 #[allow(clippy::trivially_copy_pass_by_ref, reason = "Parameter types are set by serde")]
60 pub fn serialize<S>(time: &time::Time, serializer: S) -> Result<S::Ok, S::Error>
61 where
62 S: serde::Serializer,
63 {
64 let format = if time.nanosecond() == 0 { TIME_FORMAT } else { TIME_FORMAT_SUBSEC };
65 time.format(format).map_err(serde::ser::Error::custom)?.serialize(serializer)
66 }
67
68 pub fn deserialize<'de, D>(deserializer: D) -> Result<time::Time, D::Error>
70 where
71 D: serde::Deserializer<'de>,
72 {
73 let string = String::deserialize(deserializer)?;
74 time::Time::parse(&string, TIME_FORMAT_SUBSEC).map_err(serde::de::Error::custom)
75 }
76}
77
78impl Serialize for Date {
79 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
81 where
82 S: serde::Serializer,
83 {
84 match &self {
85 Date::Year(year) => {
87 if (1000 .. 10000).contains(year) {
88 year.to_string().serialize(serializer)
89 } else {
90 Err(serde::ser::Error::custom("Year is not 4 digits long"))
91 }
92 }
93 Date::YearMonth(year, month) => {
95 if (1000 .. 10000).contains(year) {
96 serializer.serialize_str(&format!("{year}-{:02}", *month as u8))
97 } else {
98 Err(serde::ser::Error::custom("Year is not 4 digits long"))
99 }
100 }
101 Date::Date(date) => {
103 const FORMAT: &[time::format_description::FormatItem<'_>] =
105 time::macros::format_description!("[year]-[month]-[day]");
106 date.format(FORMAT).map_err(serde::ser::Error::custom)?.serialize(serializer)
107 }
108 }
109 }
110}
111
112impl FromStr for Date {
113 type Err = DateFormatError;
114
115 fn from_str(s: &str) -> Result<Self, Self::Err> {
116 match s.split('-').count() {
119 1 => Ok(Date::Year(s.parse::<i32>()?)),
120 2 => {
121 let (year, month) = s.split_once('-').ok_or(DateFormatError::StringSplit)?;
122 let year = year.parse::<i32>()?;
124 let month = month.parse::<u8>()?;
125
126 Ok(Date::YearMonth(year, month.try_into()?))
127 }
128 3 => {
129 const FORMAT: &[time::format_description::FormatItem<'_>] =
131 time::macros::format_description!("[year]-[month]-[day]");
132 Ok(Date::Date(time::Date::parse(s, FORMAT)?))
133 }
134 _ => Err(DateFormatError::InvalidDate),
135 }
136 }
137}
138
139impl<'de> Deserialize<'de> for Date {
140 fn deserialize<D>(deserializer: D) -> Result<Date, D::Error>
142 where
143 D: serde::Deserializer<'de>,
144 {
145 let string = String::deserialize(deserializer)?;
146 Date::from_str(&string).map_err(serde::de::Error::custom)
147 }
148}
149
150impl FromStr for DateTime {
151 type Err = DateFormatError;
152
153 fn from_str(s: &str) -> Result<Self, Self::Err> {
154 if s.contains('T') {
155 let instant = Instant::from_str(s)?;
156 Ok(DateTime::DateTime(instant))
157 } else {
158 let date = Date::from_str(s)?;
159 Ok(DateTime::Date(date))
160 }
161 }
162}
163
164impl<'de> Deserialize<'de> for DateTime {
165 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
167 where
168 D: serde::Deserializer<'de>,
169 {
170 let string = String::deserialize(deserializer)?;
171 Self::from_str(&string).map_err(serde::de::Error::custom)
172 }
173}
174
175impl FromStr for Instant {
176 type Err = Parse;
177
178 fn from_str(s: &str) -> Result<Self, Self::Err> {
179 Ok(Instant(OffsetDateTime::parse(s, &Rfc3339)?))
180 }
181}
182
183impl PartialOrd for Date {
184 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
185 match (self, other) {
186 (Date::Date(ld), r) => ld.partial_cmp(r),
187 (l, Date::Date(rd)) => l.partial_cmp(rd),
188 (Date::Year(ly), Date::Year(ry)) => ly.partial_cmp(ry),
189 (Date::Year(ly), Date::YearMonth(ry, _rm)) => ly.partial_cmp(ry),
190 (Date::YearMonth(ly, _lm), Date::Year(ry)) => ly.partial_cmp(ry),
191 (Date::YearMonth(ly, lm), Date::YearMonth(ry, rm)) => match ly.partial_cmp(ry)? {
192 Ordering::Less => Some(Ordering::Less),
193 Ordering::Greater => Some(Ordering::Greater),
194 Ordering::Equal => (*lm as u8).partial_cmp(&(*rm as u8)),
195 },
196 }
197 }
198}
199
200impl PartialOrd for DateTime {
201 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
202 match (self, other) {
203 (DateTime::Date(ld), DateTime::Date(rd)) => ld.partial_cmp(rd),
204 (DateTime::Date(ld), DateTime::DateTime(Instant(rdtm))) => ld.partial_cmp(&rdtm.date()),
205 (DateTime::DateTime(Instant(ldtm)), DateTime::Date(rd)) => ldtm.date().partial_cmp(rd),
206 (DateTime::DateTime(ldtm), DateTime::DateTime(rdtm)) => ldtm.partial_cmp(rdtm),
207 }
208 }
209}
210
211impl PartialEq<time::Date> for Date {
212 fn eq(&self, other: &time::Date) -> bool {
213 match self {
214 Self::Year(year) => *year == other.year(),
215 Self::YearMonth(year, month) => *year == other.year() && *month == other.month(),
216 Self::Date(date) => date == other,
217 }
218 }
219}
220
221impl PartialEq<Date> for time::Date {
222 fn eq(&self, other: &Date) -> bool {
223 match other {
224 Date::Year(year) => self.year() == *year,
225 Date::YearMonth(year, month) => self.year() == *year && self.month() == *month,
226 Date::Date(date) => self == date,
227 }
228 }
229}
230
231impl PartialOrd<time::Date> for Date {
232 fn partial_cmp(&self, other: &time::Date) -> Option<Ordering> {
233 match self {
234 Date::Year(year) => year.partial_cmp(&other.year()),
235 Date::YearMonth(year, month) => match year.partial_cmp(&other.year())? {
236 Ordering::Less => Some(Ordering::Less),
237 Ordering::Greater => Some(Ordering::Greater),
238 Ordering::Equal => (*month as u8).partial_cmp(&(other.month() as u8)),
239 },
240 Date::Date(date) => date.partial_cmp(other),
241 }
242 }
243}
244
245impl PartialOrd<Date> for time::Date {
246 fn partial_cmp(&self, other: &Date) -> Option<Ordering> {
247 match other {
248 Date::Year(year) => self.year().partial_cmp(year),
249 Date::YearMonth(year, month) => match self.year().partial_cmp(year)? {
250 Ordering::Less => Some(Ordering::Less),
251 Ordering::Greater => Some(Ordering::Greater),
252 Ordering::Equal => (self.month() as u8).partial_cmp(&(*month as u8)),
253 },
254 Date::Date(date) => self.partial_cmp(date),
255 }
256 }
257}
258
259impl PartialEq<OffsetDateTime> for DateTime {
260 fn eq(&self, other: &OffsetDateTime) -> bool {
261 match self {
262 Self::Date(date) => *date == other.date(),
263 Self::DateTime(Instant(datetime)) => datetime == other,
264 }
265 }
266}
267
268impl PartialEq<DateTime> for OffsetDateTime {
269 fn eq(&self, other: &DateTime) -> bool {
270 match other {
271 DateTime::Date(date) => self.date() == *date,
272 DateTime::DateTime(Instant(datetime)) => self == datetime,
273 }
274 }
275}
276
277impl PartialOrd<OffsetDateTime> for DateTime {
278 fn partial_cmp(&self, other: &OffsetDateTime) -> Option<Ordering> {
279 match self {
280 DateTime::Date(date) => date.partial_cmp(&other.date()),
281 DateTime::DateTime(Instant(datetime)) => datetime.partial_cmp(other),
282 }
283 }
284}
285
286impl PartialOrd<DateTime> for OffsetDateTime {
287 fn partial_cmp(&self, other: &DateTime) -> Option<Ordering> {
288 match other {
289 DateTime::Date(date) => self.date().partial_cmp(date),
290 DateTime::DateTime(Instant(datetime)) => self.partial_cmp(datetime),
291 }
292 }
293}