1use std::{
2 fmt,
3 time::{Duration, SystemTime, UNIX_EPOCH},
4};
5use time::{format_description::well_known::Rfc3339, OffsetDateTime, UtcOffset};
6
7#[derive(Clone, Copy, Eq, Hash, PartialEq)]
12pub struct Date {
13 inner: SystemTime,
14}
15
16#[derive(Debug)]
18#[non_exhaustive]
19pub struct InvalidXmlDate;
20
21pub(crate) struct InfiniteOrNanDate;
22
23impl Date {
24 const PLIST_EPOCH_UNIX_TIMESTAMP: Duration = Duration::from_secs(978_307_200);
26
27 pub fn from_xml_format(date: &str) -> Result<Self, InvalidXmlDate> {
29 let offset: OffsetDateTime = OffsetDateTime::parse(date, &Rfc3339)
30 .map_err(|_| InvalidXmlDate)?
31 .to_offset(UtcOffset::UTC);
32 Ok(Date {
33 inner: offset.into(),
34 })
35 }
36
37 pub fn to_xml_format(&self) -> String {
39 let datetime: OffsetDateTime = self.inner.into();
40 datetime.format(&Rfc3339).unwrap()
41 }
42
43 pub(crate) fn from_seconds_since_plist_epoch(
44 timestamp: f64,
45 ) -> Result<Date, InfiniteOrNanDate> {
46 let plist_epoch = UNIX_EPOCH + Date::PLIST_EPOCH_UNIX_TIMESTAMP;
48
49 if !timestamp.is_finite() {
50 return Err(InfiniteOrNanDate);
51 }
52
53 let is_negative = timestamp < 0.0;
54 let timestamp = timestamp.abs();
55 let seconds = timestamp.floor() as u64;
56 let subsec_nanos = (timestamp.fract() * 1e9) as u32;
57
58 let dur_since_plist_epoch = Duration::new(seconds, subsec_nanos);
59
60 let inner = if is_negative {
61 plist_epoch.checked_sub(dur_since_plist_epoch)
62 } else {
63 plist_epoch.checked_add(dur_since_plist_epoch)
64 };
65
66 let inner = inner.ok_or(InfiniteOrNanDate)?;
67
68 Ok(Date { inner })
69 }
70
71 pub(crate) fn as_seconds_since_plist_epoch(&self) -> f64 {
72 fn as_secs_f64(d: Duration) -> f64 {
74 const NANOS_PER_SEC: f64 = 1_000_000_000.00;
75 (d.as_secs() as f64) + f64::from(d.subsec_nanos()) / NANOS_PER_SEC
76 }
77
78 let plist_epoch = UNIX_EPOCH + Date::PLIST_EPOCH_UNIX_TIMESTAMP;
79 match self.inner.duration_since(plist_epoch) {
80 Ok(dur_since_plist_epoch) => as_secs_f64(dur_since_plist_epoch),
81 Err(err) => -as_secs_f64(err.duration()),
82 }
83 }
84}
85
86impl fmt::Debug for Date {
87 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
88 write!(f, "{}", self.to_xml_format())
89 }
90}
91
92impl From<SystemTime> for Date {
93 fn from(date: SystemTime) -> Self {
94 Date { inner: date }
95 }
96}
97
98impl From<Date> for SystemTime {
99 fn from(val: Date) -> Self {
100 val.inner
101 }
102}
103
104impl fmt::Display for InvalidXmlDate {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 f.write_str("String was not a valid XML plist date")
107 }
108}
109
110impl std::error::Error for InvalidXmlDate {}
111
112#[cfg(feature = "serde")]
113pub mod serde_impls {
114 use serde::{
115 de::{Deserialize, Deserializer, Error, Unexpected, Visitor},
116 ser::{Serialize, Serializer},
117 };
118 use std::fmt;
119
120 use crate::Date;
121
122 pub const DATE_NEWTYPE_STRUCT_NAME: &str = "PLIST-DATE";
123
124 impl Serialize for Date {
125 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
126 where
127 S: Serializer,
128 {
129 let date_str = self.to_xml_format();
130 serializer.serialize_newtype_struct(DATE_NEWTYPE_STRUCT_NAME, &date_str)
131 }
132 }
133
134 struct DateNewtypeVisitor;
135
136 impl<'de> Visitor<'de> for DateNewtypeVisitor {
137 type Value = Date;
138
139 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
140 formatter.write_str("a plist date newtype")
141 }
142
143 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
144 where
145 E: Error,
146 {
147 DateStrVisitor.visit_str(v)
148 }
149
150 fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
151 where
152 D: Deserializer<'de>,
153 {
154 deserializer.deserialize_str(DateStrVisitor)
155 }
156 }
157
158 struct DateStrVisitor;
159
160 impl<'de> Visitor<'de> for DateStrVisitor {
161 type Value = Date;
162
163 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
164 formatter.write_str("a plist date string")
165 }
166
167 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
168 where
169 E: Error,
170 {
171 Date::from_xml_format(v).map_err(|_| E::invalid_value(Unexpected::Str(v), &self))
172 }
173 }
174
175 impl<'de> Deserialize<'de> for Date {
176 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
177 where
178 D: Deserializer<'de>,
179 {
180 deserializer.deserialize_newtype_struct(DATE_NEWTYPE_STRUCT_NAME, DateNewtypeVisitor)
181 }
182 }
183}
184
185#[cfg(test)]
186mod testing {
187 use super::*;
188
189 #[test]
190 fn date_roundtrip() {
191 let date_str = "1981-05-16T11:32:06Z";
192
193 let date = Date::from_xml_format(date_str).expect("should parse");
194
195 let generated_str = date.to_xml_format();
196
197 assert_eq!(date_str, generated_str);
198 }
199
200 #[test]
201 fn far_past_date() {
202 let date_str = "1920-01-01T00:00:00Z";
203 Date::from_xml_format(date_str).expect("should parse");
204 }
205}