1use time::{OffsetDateTime, UtcDateTime, UtcOffset, format_description::well_known::Rfc3339};
2
3use crate::{Error, Result, Value};
4
5fn offset_date_time_from_str(s: &str) -> Result<OffsetDateTime> {
6 OffsetDateTime::parse(s, &Rfc3339).or(Err(Error::InvalidFormat))
7}
8
9fn utc_date_time_from_float(f: f64) -> Result<UtcDateTime> {
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 i64;
13 Ok(UtcDateTime::from_unix_timestamp(secs).or(Err(Error::Overflow))? + time::Duration::nanoseconds(nanos))
14 } else {
15 Err(Error::InvalidValue)
16 }
17}
18
19fn utc_date_time_from_u64(secs: u64) -> Result<UtcDateTime> {
20 let secs = secs.try_into().or(Err(Error::Overflow))?;
21 UtcDateTime::from_unix_timestamp(secs).or(Err(Error::Overflow))
22}
23
24impl TryFrom<OffsetDateTime> for crate::DateTime {
29 type Error = Error;
30
31 fn try_from(dt: OffsetDateTime) -> Result<Self> {
36 crate::DateTime::try_from(dt.format(&Rfc3339).or(Err(Error::Overflow))?)
37 }
38}
39
40impl TryFrom<&OffsetDateTime> for crate::DateTime {
41 type Error = Error;
42
43 fn try_from(dt: &OffsetDateTime) -> Result<Self> {
44 crate::DateTime::try_from(dt.format(&Rfc3339).or(Err(Error::Overflow))?)
45 }
46}
47
48impl TryFrom<UtcDateTime> for crate::DateTime {
53 type Error = Error;
54
55 fn try_from(dt: UtcDateTime) -> Result<Self> {
60 crate::DateTime::try_from(dt.format(&Rfc3339).or(Err(Error::Overflow))?)
61 }
62}
63
64impl TryFrom<&UtcDateTime> for crate::DateTime {
65 type Error = Error;
66
67 fn try_from(dt: &UtcDateTime) -> Result<Self> {
68 crate::DateTime::try_from(dt.format(&Rfc3339).or(Err(Error::Overflow))?)
69 }
70}
71
72impl TryFrom<OffsetDateTime> for crate::EpochTime {
77 type Error = Error;
78
79 fn try_from(value: OffsetDateTime) -> Result<Self> {
84 offset_date_time_to_epoch(&value)
85 }
86}
87
88impl TryFrom<&OffsetDateTime> for crate::EpochTime {
89 type Error = Error;
90
91 fn try_from(value: &OffsetDateTime) -> Result<Self> {
92 offset_date_time_to_epoch(value)
93 }
94}
95
96fn offset_date_time_to_epoch(value: &OffsetDateTime) -> Result<crate::EpochTime> {
97 let secs = value.unix_timestamp();
98 let nanos = value.unix_timestamp_nanos();
99
100 if nanos % 1_000_000_000 == 0 {
101 crate::EpochTime::try_from(secs)
102 } else {
103 let f = nanos as f64 / 1_000_000_000.0;
104 crate::EpochTime::try_from(f)
105 }
106}
107
108impl TryFrom<UtcDateTime> for crate::EpochTime {
113 type Error = Error;
114
115 fn try_from(value: UtcDateTime) -> Result<Self> {
120 utc_date_time_to_epoch(&value)
121 }
122}
123
124impl TryFrom<&UtcDateTime> for crate::EpochTime {
125 type Error = Error;
126
127 fn try_from(value: &UtcDateTime) -> Result<Self> {
128 utc_date_time_to_epoch(value)
129 }
130}
131
132fn utc_date_time_to_epoch(value: &UtcDateTime) -> Result<crate::EpochTime> {
133 let secs = value.unix_timestamp();
134 let nanos = value.unix_timestamp_nanos();
135
136 if nanos % 1_000_000_000 == 0 {
137 crate::EpochTime::try_from(secs)
138 } else {
139 let f = nanos as f64 / 1_000_000_000.0;
140 crate::EpochTime::try_from(f)
141 }
142}
143
144impl TryFrom<Value> for OffsetDateTime {
149 type Error = Error;
150
151 fn try_from(value: Value) -> Result<Self> {
156 value_to_time_offset_data_time(&value)
157 }
158}
159
160impl TryFrom<&Value> for OffsetDateTime {
161 type Error = Error;
162
163 fn try_from(value: &Value) -> Result<Self> {
164 value_to_time_offset_data_time(value)
165 }
166}
167
168fn value_to_time_offset_data_time(value: &Value) -> Result<OffsetDateTime> {
169 if let Ok(s) = value.as_str() {
170 offset_date_time_from_str(s)
171 } else if let Ok(f) = value.to_f64() {
172 utc_date_time_from_float(f).map(|dt| dt.to_offset(UtcOffset::UTC))
173 } else {
174 match value.to_u64() {
175 Ok(secs) => utc_date_time_from_u64(secs).map(|dt| dt.to_offset(UtcOffset::UTC)),
176 Err(Error::NegativeUnsigned) => Err(Error::InvalidValue),
177 Err(other_error) => Err(other_error),
178 }
179 }
180}
181
182impl TryFrom<Value> for UtcDateTime {
187 type Error = Error;
188
189 fn try_from(value: Value) -> Result<Self> {
194 value_to_time_utc_data_time(&value)
195 }
196}
197
198impl TryFrom<&Value> for UtcDateTime {
199 type Error = Error;
200
201 fn try_from(value: &Value) -> Result<Self> {
202 value_to_time_utc_data_time(value)
203 }
204}
205
206fn value_to_time_utc_data_time(value: &Value) -> Result<UtcDateTime> {
207 if let Ok(s) = value.as_str() {
208 offset_date_time_from_str(s).map(|dt| dt.to_utc())
209 } else if let Ok(f) = value.to_f64() {
210 utc_date_time_from_float(f)
211 } else {
212 match value.to_u64() {
213 Ok(secs) => utc_date_time_from_u64(secs),
214 Err(Error::NegativeUnsigned) => Err(Error::InvalidValue),
215 Err(other_error) => Err(other_error),
216 }
217 }
218}
219
220#[cfg(test)]
225mod tests {
226
227 use std::time::{Duration, SystemTime};
228
229 use time::{Date, Month, OffsetDateTime, Time, UtcDateTime, UtcOffset, format_description::well_known::Rfc3339};
230
231 use crate::{DataType, Error, Float, Value};
232
233 #[test]
236 fn time_to_date_time_utc() {
237 let d = Date::from_calendar_date(2000, Month::January, 1).unwrap();
238 let t = Time::from_hms(0, 0, 0).unwrap();
239 let dt = UtcDateTime::new(d, t);
240 let v = Value::date_time(dt);
241 assert_eq!(v.as_str(), Ok("2000-01-01T00:00:00Z"));
242 }
243
244 #[test]
245 fn time_to_date_time_utc_subsec() {
246 let d = Date::from_calendar_date(2000, Month::January, 1).unwrap();
247 let t = Time::from_hms_nano(12, 30, 45, 123456789).unwrap();
248 let dt = UtcDateTime::new(d, t);
249 let v = Value::date_time(dt);
250 assert_eq!(v.as_str(), Ok("2000-01-01T12:30:45.123456789Z"));
251 }
252
253 #[test]
254 fn time_to_date_time_utc_millis_only() {
255 let d = Date::from_calendar_date(2024, Month::June, 15).unwrap();
256 let t = Time::from_hms_milli(8, 0, 0, 123).unwrap();
257 let dt = UtcDateTime::new(d, t);
258 let v = Value::date_time(dt);
259 assert_eq!(v.as_str(), Ok("2024-06-15T08:00:00.123Z"));
260 }
261
262 #[test]
265 fn time_to_epoch_time_whole_second() {
266 let dt = UtcDateTime::from_unix_timestamp(1_000_000).unwrap();
267 let v = Value::epoch_time(dt);
268 assert_eq!(v.into_untagged(), Value::Unsigned(1_000_000));
270 }
271
272 #[test]
273 fn time_to_epoch_time_subsec() {
274 let dt = UtcDateTime::from_unix_timestamp_nanos(1_000_000_500_000_000).unwrap();
275 let v = Value::epoch_time(dt);
276 assert_eq!(v.into_untagged(), Value::Float(Float::from(1000000.5)));
278 }
279
280 #[test]
281 fn time_to_epoch_time_negative() {
282 let dt = UtcDateTime::from_unix_timestamp(-1).unwrap();
284 assert_eq!(crate::EpochTime::try_from(dt), Err(Error::InvalidValue));
285 }
286
287 #[test]
290 fn value_date_time_string_to_time_utc() {
291 let v = Value::date_time("2000-01-01T00:00:00Z");
292 let dt = UtcDateTime::try_from(v).unwrap();
293 assert_eq!(dt.format(&Rfc3339).unwrap(), "2000-01-01T00:00:00Z");
294 }
295
296 #[test]
297 fn value_date_time_string_with_offset_to_time_utc() {
298 let v = Value::date_time("2000-01-01T01:00:00+01:00");
299 let dt = UtcDateTime::try_from(v).unwrap();
300 assert_eq!(dt.format(&Rfc3339).unwrap(), "2000-01-01T00:00:00Z");
302 }
303
304 #[test]
305 fn value_epoch_int_to_time_utc() {
306 let v = Value::epoch_time(946684800_u64); let dt = UtcDateTime::try_from(v).unwrap();
308 assert_eq!(dt.format(&Rfc3339).unwrap(), "2000-01-01T00:00:00Z");
309 }
310
311 #[test]
312 fn value_epoch_float_to_time_utc() {
313 let v = Value::epoch_time(946684800.5_f64);
314 let dt = UtcDateTime::try_from(v).unwrap();
315 assert_eq!(dt.unix_timestamp_nanos(), 946_684_800_500_000_000);
316 }
317
318 #[test]
321 fn value_date_time_string_to_time_fixed_preserves_offset() {
322 let v = Value::date_time("2000-01-01T01:00:00+01:00");
323 let dt = OffsetDateTime::try_from(&v).unwrap();
324 assert_eq!(dt.format(&Rfc3339).unwrap(), "2000-01-01T01:00:00+01:00".to_string());
325 assert_eq!(
326 v.to_system_time(),
327 Value::date_time("2000-01-01T00:00:00Z").to_system_time()
328 );
329 }
330
331 #[test]
332 fn value_epoch_to_time_fixed_is_utc() {
333 let v = Value::epoch_time(0_u64);
334 let dt = OffsetDateTime::try_from(&v).unwrap();
335 assert_eq!(dt.offset(), UtcOffset::UTC);
336 }
337
338 #[test]
341 fn value_non_time_to_time_errors() {
342 assert_eq!(
343 UtcDateTime::try_from(Value::from("not a date")),
344 Err(Error::InvalidFormat)
345 );
346 assert_eq!(
347 UtcDateTime::try_from(Value::null()),
348 Err(Error::IncompatibleType(DataType::Null))
349 );
350 }
351
352 #[test]
353 fn value_negative_epoch_to_time_errors() {
354 let v = Value::from(-1);
355 assert_eq!(UtcDateTime::try_from(v), Err(Error::InvalidValue));
356 }
357
358 #[test]
361 fn time_roundtrip_unix_epoch() {
362 let st = SystemTime::UNIX_EPOCH;
363 let v = Value::date_time(st);
364 let dt = UtcDateTime::try_from(&v).unwrap();
365 assert_eq!(dt.unix_timestamp(), 0);
366 assert_eq!(dt.unix_timestamp_nanos(), 0);
367 let st2 = v.to_system_time().unwrap();
369 assert_eq!(st, st2);
370 }
371
372 #[test]
373 fn time_roundtrip_y2k() {
374 let st = SystemTime::UNIX_EPOCH + Duration::from_secs(946684800);
375 let v = Value::date_time(st);
376 let dt = UtcDateTime::try_from(&v).unwrap();
377 assert_eq!(dt.format(&Rfc3339).unwrap(), "2000-01-01T00:00:00Z".to_string());
378 let st2 = v.to_system_time().unwrap();
379 assert_eq!(st, st2);
380 }
381
382 #[test]
383 fn time_roundtrip_y2k38() {
384 let st = SystemTime::UNIX_EPOCH + Duration::from_secs(2147483647);
386 let v = Value::date_time(st);
387 let dt = UtcDateTime::try_from(&v).unwrap();
388 assert_eq!(dt.format(&Rfc3339).unwrap(), "2038-01-19T03:14:07Z".to_string());
389 let st2 = v.to_system_time().unwrap();
390 assert_eq!(st, st2);
391 }
392
393 #[test]
394 fn time_roundtrip_subsec_via_epoch() {
395 let st = SystemTime::UNIX_EPOCH + Duration::new(1_000_000, 123_000_000);
396 let v = Value::epoch_time(st);
397 let dt = UtcDateTime::try_from(&v).unwrap();
398 assert_eq!(dt.unix_timestamp(), 1_000_000);
399 assert_eq!(dt.unix_timestamp_nanos(), 1_000_000_123_000_000);
400 }
401
402 #[test]
403 fn time_roundtrip_time_to_value_to_time() {
404 let d = Date::from_calendar_date(2024, Month::July, 4).unwrap();
405 let t = Time::from_hms(12, 0, 0).unwrap();
406 let original = UtcDateTime::new(d, t);
407 let v = Value::date_time(original);
408 let back = UtcDateTime::try_from(&v).unwrap();
409 assert_eq!(original, back);
410 }
411}