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