1use chrono::{DateTime, FixedOffset, SecondsFormat, TimeZone, Utc};
2
3use crate::{Error, Result, Value};
4
5fn date_time_from_str(s: &str) -> Result<DateTime<FixedOffset>> {
6 DateTime::<FixedOffset>::parse_from_rfc3339(s).or(Err(Error::InvalidFormat))
7}
8
9fn date_time_from_float(f: f64) -> Result<DateTime<Utc>> {
10 if f.is_finite() && f >= 0.0 {
11 let secs = f.trunc() as i64;
12 let nanos = ((f - f.trunc()) * 1_000_000_000.0).round() as u32;
13 DateTime::from_timestamp(secs, nanos).ok_or(Error::InvalidValue)
14 } else {
15 Err(Error::InvalidValue)
16 }
17}
18
19fn date_time_from_u64(secs: u64) -> Result<DateTime<Utc>> {
20 let secs = secs.try_into().or(Err(Error::InvalidValue))?;
21 DateTime::from_timestamp(secs, 0).ok_or(Error::InvalidValue)
22}
23
24impl<Tz: TimeZone> TryFrom<DateTime<Tz>> for crate::DateTime
29where
30 Tz::Offset: std::fmt::Display,
31{
32 type Error = Error;
33
34 fn try_from(value: DateTime<Tz>) -> Result<Self> {
39 crate::DateTime::try_from(value.to_rfc3339_opts(SecondsFormat::AutoSi, true))
40 }
41}
42
43impl<Tz: TimeZone> TryFrom<&DateTime<Tz>> for crate::DateTime
44where
45 Tz::Offset: std::fmt::Display,
46{
47 type Error = Error;
48
49 fn try_from(value: &DateTime<Tz>) -> Result<Self> {
50 crate::DateTime::try_from(value.to_rfc3339_opts(SecondsFormat::AutoSi, true))
51 }
52}
53
54impl<Tz: TimeZone> TryFrom<DateTime<Tz>> for crate::EpochTime {
59 type Error = Error;
60
61 fn try_from(value: DateTime<Tz>) -> Result<Self> {
66 chrono_to_epoch(&value)
67 }
68}
69
70impl<Tz: TimeZone> TryFrom<&DateTime<Tz>> for crate::EpochTime {
71 type Error = Error;
72
73 fn try_from(value: &DateTime<Tz>) -> Result<Self> {
74 chrono_to_epoch(value)
75 }
76}
77
78fn chrono_to_epoch<Tz: TimeZone>(value: &DateTime<Tz>) -> Result<crate::EpochTime> {
79 let secs = value.timestamp();
80 let nanos = value.timestamp_subsec_nanos();
81
82 if nanos == 0 {
83 crate::EpochTime::try_from(secs)
84 } else {
85 let f = secs as f64 + nanos as f64 / 1_000_000_000.0;
86 crate::EpochTime::try_from(f)
87 }
88}
89
90impl TryFrom<Value> for DateTime<Utc> {
95 type Error = Error;
96
97 fn try_from(value: Value) -> Result<Self> {
102 value_to_chrono_utc(&value)
103 }
104}
105
106impl TryFrom<&Value> for DateTime<Utc> {
107 type Error = Error;
108
109 fn try_from(value: &Value) -> Result<Self> {
110 value_to_chrono_utc(value)
111 }
112}
113
114fn value_to_chrono_utc(value: &Value) -> Result<DateTime<Utc>> {
115 if let Ok(s) = value.as_str() {
116 date_time_from_str(s).map(|dt| dt.to_utc())
117 } else if let Ok(f) = value.to_f64() {
118 date_time_from_float(f)
119 } else {
120 match value.to_u64() {
121 Ok(secs) => date_time_from_u64(secs),
122 Err(Error::NegativeUnsigned) => Err(Error::InvalidValue),
123 Err(other_error) => Err(other_error),
124 }
125 }
126}
127
128impl TryFrom<Value> for DateTime<FixedOffset> {
133 type Error = Error;
134
135 fn try_from(value: Value) -> Result<Self> {
140 value_to_chrono_fixed(&value)
141 }
142}
143
144impl TryFrom<&Value> for DateTime<FixedOffset> {
145 type Error = Error;
146
147 fn try_from(value: &Value) -> Result<Self> {
148 value_to_chrono_fixed(value)
149 }
150}
151
152fn value_to_chrono_fixed(value: &Value) -> Result<DateTime<FixedOffset>> {
153 if let Ok(s) = value.as_str() {
154 date_time_from_str(s)
155 } else if let Ok(f) = value.to_f64() {
156 date_time_from_float(f).map(|dt| dt.into())
157 } else {
158 match value.to_u64() {
159 Ok(secs) => date_time_from_u64(secs).map(|dt| dt.into()),
160 Err(Error::NegativeUnsigned) => Err(Error::InvalidValue),
161 Err(other_error) => Err(other_error),
162 }
163 }
164}
165
166#[cfg(test)]
171mod tests {
172 use std::time::{Duration, UNIX_EPOCH};
173
174 use chrono::{DateTime, FixedOffset, NaiveDate, SecondsFormat, Utc};
175
176 use crate::{DataType, Error, Float, Value};
177
178 #[test]
181 fn chrono_to_date_time_utc() {
182 let chrono_dt = NaiveDate::from_ymd_opt(2000, 1, 1)
183 .unwrap()
184 .and_hms_opt(0, 0, 0)
185 .unwrap()
186 .and_utc();
187 let v = Value::date_time(chrono_dt);
188 assert_eq!(v.as_str(), Ok("2000-01-01T00:00:00Z"));
189 }
190
191 #[test]
192 fn chrono_to_date_time_subsec() {
193 let chrono_dt = NaiveDate::from_ymd_opt(2000, 1, 1)
194 .unwrap()
195 .and_hms_nano_opt(12, 30, 45, 123_456_789)
196 .unwrap()
197 .and_utc();
198 let v = Value::date_time(chrono_dt);
199 assert_eq!(v.as_str(), Ok("2000-01-01T12:30:45.123456789Z"));
200 }
201
202 #[test]
203 fn chrono_to_date_time_millis_only() {
204 let chrono_dt = NaiveDate::from_ymd_opt(2024, 6, 15)
205 .unwrap()
206 .and_hms_milli_opt(8, 0, 0, 500) .unwrap()
208 .and_utc();
209 let v = Value::date_time(chrono_dt);
210 assert_eq!(v.as_str(), Ok("2024-06-15T08:00:00.500Z"));
211 }
212
213 #[test]
216 fn chrono_to_epoch_time_whole_second() {
217 let chrono_dt = DateTime::from_timestamp(1_000_000, 0).unwrap();
218 let v = Value::epoch_time(chrono_dt);
219 assert_eq!(v.into_untagged(), Value::Unsigned(1_000_000));
221 }
222
223 #[test]
224 fn chrono_to_epoch_time_subsec() {
225 let chrono_dt = DateTime::from_timestamp(1_000_000, 500_000_000).unwrap();
226 let v = Value::epoch_time(chrono_dt);
227 assert_eq!(v.into_untagged(), Value::Float(Float::from(1000000.5)));
229 }
230
231 #[test]
232 fn chrono_to_epoch_time_negative() {
233 let chrono_dt = DateTime::from_timestamp(-1, 0).unwrap();
235 assert_eq!(crate::EpochTime::try_from(chrono_dt), Err(Error::InvalidValue));
236 }
237
238 #[test]
241 fn value_date_time_string_to_chrono_utc() {
242 let v = Value::date_time("2000-01-01T00:00:00Z");
243 let dt: DateTime<Utc> = DateTime::try_from(&v).unwrap();
244 assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), "2000-01-01T00:00:00Z");
245 }
246
247 #[test]
248 fn value_date_time_string_with_offset_to_chrono_utc() {
249 let v = Value::date_time("2000-01-01T01:00:00+01:00");
250 let dt: DateTime<Utc> = DateTime::try_from(&v).unwrap();
251 assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), "2000-01-01T00:00:00Z");
253 }
254
255 #[test]
256 fn value_epoch_int_to_chrono_utc() {
257 let v = Value::epoch_time(946684800_u64); let dt: DateTime<Utc> = DateTime::try_from(&v).unwrap();
259 assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), "2000-01-01T00:00:00Z");
260 }
261
262 #[test]
263 fn value_epoch_float_to_chrono_utc() {
264 let v = Value::epoch_time(946684800.5_f64);
265 let dt: DateTime<Utc> = DateTime::try_from(&v).unwrap();
266 assert_eq!(dt.timestamp(), 946684800);
267 assert_eq!(dt.timestamp_subsec_nanos(), 500_000_000);
268 }
269
270 #[test]
273 fn value_date_time_string_to_chrono_fixed_preserves_offset() {
274 let v = Value::date_time("2000-01-01T01:00:00+01:00");
275 let dt: DateTime<FixedOffset> = DateTime::try_from(&v).unwrap();
276 assert_eq!(dt.to_rfc3339(), "2000-01-01T01:00:00+01:00");
277 assert_eq!(
278 v.to_system_time(),
279 Value::date_time("2000-01-01T00:00:00Z").to_system_time()
280 );
281 }
282
283 #[test]
284 fn value_epoch_to_chrono_fixed_is_utc() {
285 let v = Value::epoch_time(0_u64);
286 let dt: DateTime<FixedOffset> = DateTime::try_from(&v).unwrap();
287 assert_eq!(dt.offset().local_minus_utc(), 0);
288 }
289
290 #[test]
293 fn value_non_time_to_chrono_errors() {
294 assert_eq!(
295 DateTime::<Utc>::try_from(Value::from("not a date")),
296 Err(Error::InvalidFormat)
297 );
298 assert_eq!(
299 DateTime::<Utc>::try_from(Value::null()),
300 Err(Error::IncompatibleType(DataType::Null))
301 );
302 }
303
304 #[test]
305 fn value_negative_epoch_to_chrono_errors() {
306 let v = Value::from(-1);
307 assert_eq!(DateTime::<Utc>::try_from(v), Err(Error::InvalidValue));
308 }
309
310 #[test]
313 fn chrono_roundtrip_unix_epoch() {
314 let st = UNIX_EPOCH;
315 let v = Value::date_time(st);
316 let dt: DateTime<Utc> = DateTime::try_from(&v).unwrap();
317 assert_eq!(dt.timestamp(), 0);
318 assert_eq!(dt.timestamp_subsec_nanos(), 0);
319 let st2 = v.to_system_time().unwrap();
321 assert_eq!(st, st2);
322 }
323
324 #[test]
325 fn chrono_roundtrip_y2k() {
326 let st = UNIX_EPOCH + Duration::from_secs(946684800);
327 let v = Value::date_time(st);
328 let dt: DateTime<Utc> = DateTime::try_from(&v).unwrap();
329 assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), "2000-01-01T00:00:00Z");
330 let st2 = v.to_system_time().unwrap();
331 assert_eq!(st, st2);
332 }
333
334 #[test]
335 fn chrono_roundtrip_y2k38() {
336 let st = UNIX_EPOCH + Duration::from_secs(2147483647);
338 let v = Value::date_time(st);
339 let dt: DateTime<Utc> = DateTime::try_from(&v).unwrap();
340 assert_eq!(dt.to_rfc3339_opts(SecondsFormat::Secs, true), "2038-01-19T03:14:07Z");
341 let st2 = v.to_system_time().unwrap();
342 assert_eq!(st, st2);
343 }
344
345 #[test]
346 fn chrono_roundtrip_subsec_via_epoch() {
347 let st = UNIX_EPOCH + Duration::new(1_000_000, 123_000_000);
348 let v = Value::epoch_time(st);
349 let dt: DateTime<Utc> = DateTime::try_from(&v).unwrap();
350 assert_eq!(dt.timestamp(), 1_000_000);
351 assert_eq!(dt.timestamp_subsec_millis(), 123);
352 }
353
354 #[test]
355 fn chrono_roundtrip_chrono_to_value_to_chrono() {
356 let original = NaiveDate::from_ymd_opt(2024, 7, 4)
357 .unwrap()
358 .and_hms_opt(12, 0, 0)
359 .unwrap()
360 .and_utc();
361 let cbor_dt = crate::DateTime::try_from(original).unwrap();
362 let v = Value::date_time(cbor_dt);
363 let back: DateTime<Utc> = DateTime::try_from(&v).unwrap();
364 assert_eq!(original, back);
365 }
366}