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