Skip to main content

provenant/utils/
time.rs

1use chrono::{DateTime, Datelike, NaiveDateTime, Timelike, Utc};
2
3const ISO_UTC_TIMESTAMP_FALLBACK: &str = "1970-01-01T00:00:00Z";
4
5pub(crate) fn fallback_iso_utc_timestamp() -> &'static str {
6    ISO_UTC_TIMESTAMP_FALLBACK
7}
8
9pub(crate) fn convert_header_timestamp_to_iso_utc(value: &str) -> Option<String> {
10    parse_header_timestamp(value).map(|timestamp| format_iso_utc_timestamp(&timestamp))
11}
12
13fn format_iso_utc_timestamp(timestamp: &DateTime<Utc>) -> String {
14    format!(
15        "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z",
16        timestamp.year(),
17        timestamp.month(),
18        timestamp.day(),
19        timestamp.hour(),
20        timestamp.minute(),
21        timestamp.second()
22    )
23}
24
25fn parse_header_timestamp(value: &str) -> Option<DateTime<Utc>> {
26    if let Ok(timestamp) = DateTime::parse_from_rfc3339(value) {
27        return Some(timestamp.with_timezone(&Utc));
28    }
29
30    NaiveDateTime::parse_from_str(value, "%Y-%m-%dT%H%M%S%.f")
31        .ok()
32        .map(|timestamp| DateTime::<Utc>::from_naive_utc_and_offset(timestamp, Utc))
33}
34
35#[cfg(test)]
36mod tests {
37    use super::convert_header_timestamp_to_iso_utc;
38
39    #[test]
40    fn convert_header_timestamp_to_iso_utc_accepts_scancode_and_rfc3339_inputs() {
41        assert_eq!(
42            convert_header_timestamp_to_iso_utc("2026-04-11T091828.024390"),
43            Some("2026-04-11T09:18:28Z".to_string())
44        );
45        assert_eq!(
46            convert_header_timestamp_to_iso_utc("2026-04-11T09:18:28.024390124+00:00"),
47            Some("2026-04-11T09:18:28Z".to_string())
48        );
49        assert_eq!(
50            convert_header_timestamp_to_iso_utc("2026-04-11T09:18:28.024390124Z"),
51            Some("2026-04-11T09:18:28Z".to_string())
52        );
53    }
54}