Skip to main content

provenant/utils/
time.rs

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