nv_redfish_core/
edm_date_time_offset.rs1use core::str::FromStr;
51use serde::{Deserialize, Serialize};
52use std::fmt::Display;
53use std::fmt::Error as FmtError;
54use std::fmt::Formatter;
55use std::fmt::Result as FmtResult;
56use std::time::Duration;
57use std::time::SystemTime;
58use time::format_description::well_known::Rfc3339;
59use time::OffsetDateTime;
60
61#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
63#[serde(transparent)]
64pub struct EdmDateTimeOffset(#[serde(with = "time::serde::rfc3339")] OffsetDateTime);
65
66impl From<OffsetDateTime> for EdmDateTimeOffset {
67 fn from(dt: OffsetDateTime) -> Self {
68 Self(dt)
69 }
70}
71
72impl From<EdmDateTimeOffset> for OffsetDateTime {
73 fn from(w: EdmDateTimeOffset) -> Self {
74 w.0
75 }
76}
77
78impl From<EdmDateTimeOffset> for SystemTime {
79 fn from(w: EdmDateTimeOffset) -> Self {
80 let unix_timestamp = w.0.unix_timestamp();
81 let nanos = w.0.nanosecond();
82
83 let duration = Duration::new(unix_timestamp.unsigned_abs(), nanos);
84 if unix_timestamp >= 0 {
85 Self::UNIX_EPOCH + duration
86 } else {
87 Self::UNIX_EPOCH - duration
88 }
89 }
90}
91
92impl Display for EdmDateTimeOffset {
93 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
94 let s = self.0.format(&Rfc3339).map_err(|_| FmtError)?;
95 f.write_str(&s)
96 }
97}
98
99#[allow(clippy::absolute_paths)]
100impl FromStr for EdmDateTimeOffset {
101 type Err = time::error::Parse;
102
103 fn from_str(s: &str) -> Result<Self, Self::Err> {
104 let dt = OffsetDateTime::parse(s, &Rfc3339)?;
105 Ok(Self(dt))
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use time::UtcOffset;
113
114 #[test]
115 fn parses_and_displays_utc_z() {
116 let s = "2021-03-04T05:06:07Z";
117 let w: EdmDateTimeOffset = s.parse().unwrap();
118 assert_eq!(w.to_string(), s);
119
120 let dt: OffsetDateTime = w.into();
121 assert_eq!(dt.offset(), UtcOffset::UTC);
122 }
123
124 #[test]
125 fn parses_utc_plus00_canonicalizes_to_z_on_display() {
126 let s = "2021-03-04T05:06:07+00:00";
127 let w: EdmDateTimeOffset = s.parse().unwrap();
128 let displayed = w.to_string();
129 assert!(displayed.ends_with('Z'));
130 }
131
132 #[test]
133 fn parses_and_displays_positive_offset() {
134 let s = "2021-03-04T10:36:07+05:30"; let w: EdmDateTimeOffset = s.parse().unwrap();
136 assert_eq!(w.to_string(), s);
137
138 let dt: OffsetDateTime = w.into();
139 assert_eq!(dt.offset(), UtcOffset::from_hms(5, 30, 0).unwrap());
140 }
141
142 #[test]
143 fn parses_and_displays_fractional_seconds() {
144 let s = "2021-03-04T05:06:07.123456789Z";
145 let w: EdmDateTimeOffset = s.parse().unwrap();
146 assert_eq!(w.to_string(), s);
147 }
148
149 #[test]
150 fn rejects_invalid_inputs() {
151 assert!("not-a-date".parse::<EdmDateTimeOffset>().is_err());
152 assert!("2021-03-04T05:06:07".parse::<EdmDateTimeOffset>().is_err());
154 }
155
156 #[test]
157 fn serde_serializes_conformant_strings() {
158 let w_z: EdmDateTimeOffset = "2021-03-04T05:06:07Z".parse().unwrap();
160 let json_z = serde_json::to_string(&w_z).unwrap();
161 assert_eq!(json_z, r#""2021-03-04T05:06:07Z""#);
162
163 let w_pos: EdmDateTimeOffset = "2021-03-04T10:36:07+05:30".parse().unwrap();
165 let json_pos = serde_json::to_string(&w_pos).unwrap();
166 assert_eq!(json_pos, r#""2021-03-04T10:36:07+05:30""#);
167
168 let w_frac: EdmDateTimeOffset = "2021-03-04T05:06:07.123456789Z".parse().unwrap();
170 let json_frac = serde_json::to_string(&w_frac).unwrap();
171 assert_eq!(json_frac, r#""2021-03-04T05:06:07.123456789Z""#);
172
173 let w_plus00: EdmDateTimeOffset = "2021-03-04T05:06:07+00:00".parse().unwrap();
175 let json_plus00 = serde_json::to_string(&w_plus00).unwrap();
176 assert_eq!(json_plus00, r#""2021-03-04T05:06:07Z""#);
177 }
178
179 #[test]
180 fn serde_deserializes_from_conformant_strings() {
181 let s_z = r#""2021-03-04T05:06:07Z""#;
183 let w_z: EdmDateTimeOffset = serde_json::from_str(s_z).unwrap();
184 assert_eq!(w_z.to_string(), "2021-03-04T05:06:07Z");
185 let dt_z: OffsetDateTime = w_z.into();
186 assert_eq!(dt_z.offset(), UtcOffset::UTC);
187
188 let s_pos = r#""2021-03-04T10:36:07+05:30""#;
190 let w_pos: EdmDateTimeOffset = serde_json::from_str(s_pos).unwrap();
191 assert_eq!(w_pos.to_string(), "2021-03-04T10:36:07+05:30");
192 let dt_pos: OffsetDateTime = w_pos.into();
193 assert_eq!(dt_pos.offset(), UtcOffset::from_hms(5, 30, 0).unwrap());
194
195 let s_frac = r#""2021-03-04T05:06:07.123456789Z""#;
197 let w_frac: EdmDateTimeOffset = serde_json::from_str(s_frac).unwrap();
198 assert_eq!(w_frac.to_string(), "2021-03-04T05:06:07.123456789Z");
199 }
200
201 #[test]
202 fn parses_and_displays_negative_offset() {
203 let s = "2021-03-04T00:06:07-05:00";
204 let w: EdmDateTimeOffset = s.parse().unwrap();
205 assert_eq!(w.to_string(), s);
206
207 let dt: OffsetDateTime = w.into();
208 assert_eq!(dt.offset(), UtcOffset::from_hms(-5, 0, 0).unwrap());
209 }
210
211 #[test]
212 fn parses_fractional_with_non_utc_offset() {
213 let s = "2021-03-04T05:06:07.5+01:00";
214 let w: EdmDateTimeOffset = s.parse().unwrap();
215 assert_eq!(w.to_string(), s);
216 }
217
218 #[test]
219 fn parses_boundary_offsets() {
220 let s_plus = "2021-03-04T12:00:00+14:00";
222 let w_plus: EdmDateTimeOffset = s_plus.parse().unwrap();
223 assert_eq!(w_plus.to_string(), s_plus);
224
225 let s_minus = "2021-03-04T12:00:00-12:00";
226 let w_minus: EdmDateTimeOffset = s_minus.parse().unwrap();
227 assert_eq!(w_minus.to_string(), s_minus);
228 }
229
230 #[test]
231 fn rejects_leap_second() {
232 assert!("2021-03-04T23:59:60Z".parse::<EdmDateTimeOffset>().is_err());
233 }
234
235 #[test]
236 fn canonicalizes_negative_zero_offset_to_z() {
237 let s = "2021-03-04T05:06:07-00:00";
238 let w: EdmDateTimeOffset = s.parse().unwrap();
239 assert_eq!("2021-03-04T05:06:07Z", w.to_string());
240 }
241
242 #[test]
243 fn converts_to_system_time() {
244 let normal: EdmDateTimeOffset = "2021-03-04T05:06:07-00:00".parse().unwrap();
245 let time: SystemTime = normal.into();
246 assert_eq!(
247 time.duration_since(SystemTime::UNIX_EPOCH)
248 .unwrap()
249 .as_secs(),
250 1614834367
251 );
252
253 let before_epoch: EdmDateTimeOffset = "1960-01-01T00:00:00-00:00".parse().unwrap();
254 let time: SystemTime = before_epoch.into();
255 assert_eq!(
256 SystemTime::UNIX_EPOCH
257 .duration_since(time)
258 .unwrap()
259 .as_secs(),
260 315619200
261 );
262
263 let very_old: EdmDateTimeOffset = "0001-01-01T00:00:00-00:00".parse().unwrap();
264 let time: SystemTime = very_old.into();
265 assert_eq!(
266 SystemTime::UNIX_EPOCH
267 .duration_since(time)
268 .unwrap()
269 .as_secs(),
270 62135596800
271 );
272
273 let far_future: EdmDateTimeOffset = "9999-12-31T23:59:59-00:00".parse().unwrap();
274 let time: SystemTime = far_future.into();
275 assert_eq!(
276 time.duration_since(SystemTime::UNIX_EPOCH)
277 .unwrap()
278 .as_secs(),
279 253402300799
280 );
281 }
282}