1use crate::calendar::{fixed_from_gregorian, gregorian_from_fixed};
9use crate::util::u32;
10use chrono::{DateTime, Datelike, TimeZone, Timelike, Utc};
11
12pub(crate) fn fixed_from_chrono(t: DateTime<Utc>) -> f64 {
13 let rd_sec = f64::from(t.hour() * 3600 + t.minute() * 60 + t.second())
14 + (f64::from(t.nanosecond()) / 1_000_000_000.0);
15 f64::from(fixed_from_gregorian(t.year(), t.month(), t.day())) + (rd_sec / 86400.0)
16}
17
18pub(crate) fn chrono_from_fixed(t: f64) -> Option<DateTime<Utc>> {
19 let (year, month, day) = gregorian_from_fixed(t);
20 let fract = t.rem_euclid(1.0) * 86400.0;
21 let hour = u32(fract / 3600.0);
22 let min = u32(fract % 3600.0 / 60.0);
23 let sec = u32(fract % 60.0);
24 let nano = u32((fract % 1.0) * 1_000_000_000.0);
25 Utc.ymd_opt(year, month, day).and_hms_nano_opt(hour, min, sec, nano).single()
26}
27
28#[cfg(test)]
29#[allow(clippy::float_cmp)]
30#[test]
31fn test_chrono_conversions() {
32 let input = [
33 (730_120.5, Utc.ymd(2000, 1, 1).and_hms(12, 0, 0)),
34 (-61_387.0 + 256_f64.recip(), Utc.ymd(-168, 12, 5).and_hms_milli(0, 5, 37, 500)),
35 (-95_746_495.0, chrono::MIN_DATE.and_hms(0, 0, 0)),
36 (95_745_764.0, chrono::MAX_DATE.and_hms(0, 0, 0)),
37 ];
38 let iter = crate::data::TEST_DATA
39 .iter()
40 .copied()
41 .map(|(rd, (y, m, d), _, _, _, _)| (rd, Utc.ymd(y, m, d).and_hms(0, 0, 0)))
42 .chain(input.iter().copied());
43 for (rd, datetime) in iter {
44 assert_eq!(rd, fixed_from_chrono(datetime));
45 assert_eq!(chrono_from_fixed(rd).unwrap(), datetime);
46 }
47
48 assert!(chrono_from_fixed(-95_746_496.0).is_none());
49 assert!(chrono_from_fixed(95_745_765.0).is_none());
50}