1use 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(×tamp))
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}