1use chrono::{DateTime, Utc};
4
5pub fn format_time(t: f64) -> String {
9 const MAX_MS: f64 = 253_402_300_799_999.0; let ms = if t.is_finite() {
11 (t * 1000.0).round().clamp(0.0, MAX_MS)
12 } else if t > 0.0 {
13 MAX_MS
14 } else {
15 0.0
16 };
17 DateTime::<Utc>::from_timestamp_millis(ms as i64)
18 .unwrap_or_default()
19 .format("%Y-%m-%dT%H:%M:%S%.3fZ")
20 .to_string()
21}
22
23pub fn parse_time(s: &str) -> Option<f64> {
26 let t = DateTime::parse_from_rfc3339(s.trim()).ok()?;
27 Some(t.timestamp() as f64 + f64::from(t.timestamp_subsec_nanos()) * 1e-9)
28}
29
30#[cfg(test)]
31mod tests {
32 use super::*;
33
34 #[test]
35 fn formats_utc_millis() {
36 assert_eq!(format_time(0.0), "1970-01-01T00:00:00.000Z");
37 assert_eq!(format_time(1_704_067_200.0), "2024-01-01T00:00:00.000Z");
38 assert_eq!(format_time(1_704_067_200.123), "2024-01-01T00:00:00.123Z");
39 assert_eq!(format_time(1_704_067_200.999_6), "2024-01-01T00:00:01.000Z");
40 assert_eq!(format_time(f64::NAN), "1970-01-01T00:00:00.000Z");
41 assert_eq!(format_time(f64::INFINITY), "9999-12-31T23:59:59.999Z");
42 assert_eq!(format_time(-5.0), "1970-01-01T00:00:00.000Z");
43 }
44
45 #[test]
46 fn parses_instant_and_offset_forms() {
47 assert_eq!(parse_time("2024-01-01T00:00:00Z"), Some(1_704_067_200.0));
48 let t = parse_time("2024-01-01T00:00:00.123456789Z").unwrap();
49 assert!((t - 1_704_067_200.123_457).abs() < 1e-6);
50 assert_eq!(
51 parse_time("2023-12-31T17:00:00-07:00"),
52 Some(1_704_067_200.0)
53 );
54 assert_eq!(parse_time("yesterday"), None);
55 assert_eq!(parse_time("2024-01-01T00:00:00"), None);
56 }
57
58 #[test]
59 fn round_trips() {
60 for t in [1_700_000_000.5, 1_234_567_890.001, 86_400.25] {
61 assert!((parse_time(&format_time(t)).unwrap() - t).abs() < 1e-3);
62 }
63 }
64}