vault_audit_tools/utils/
time.rs

1use anyhow::{Context, Result};
2use chrono::{DateTime, Utc};
3
4/// Parse a timestamp string from Vault audit logs
5#[allow(dead_code)]
6pub fn parse_timestamp(ts: &str) -> Result<DateTime<Utc>> {
7    DateTime::parse_from_rfc3339(ts)
8        .context("Failed to parse timestamp")
9        .map(|dt| dt.with_timezone(&Utc))
10}
11
12/// Format a timestamp for display
13#[allow(dead_code)]
14pub fn format_timestamp(dt: &DateTime<Utc>) -> String {
15    dt.format("%Y-%m-%d %H:%M:%S UTC").to_string()
16}
17
18/// Calculate duration between two timestamps in human-readable format
19#[allow(dead_code)]
20pub fn duration_human(start: &DateTime<Utc>, end: &DateTime<Utc>) -> String {
21    let duration = end.signed_duration_since(*start);
22    let seconds = duration.num_seconds();
23
24    if seconds < 60 {
25        format!("{} seconds", seconds)
26    } else if seconds < 3600 {
27        format!("{} minutes", seconds / 60)
28    } else if seconds < 86400 {
29        format!("{:.1} hours", seconds as f64 / 3600.0)
30    } else {
31        format!("{:.1} days", seconds as f64 / 86400.0)
32    }
33}
34
35#[cfg(test)]
36mod tests {
37    use super::*;
38    use chrono::Datelike;
39
40    #[test]
41    fn test_parse_timestamp() {
42        let ts = "2025-10-06T07:26:03.801191678Z";
43        let dt = parse_timestamp(ts).unwrap();
44        assert_eq!(dt.year(), 2025);
45        assert_eq!(dt.month(), 10);
46        assert_eq!(dt.day(), 6);
47    }
48
49    #[test]
50    fn test_duration_human() {
51        let start = parse_timestamp("2025-10-06T07:26:03Z").unwrap();
52        let end = parse_timestamp("2025-10-06T08:26:03Z").unwrap();
53        let duration = duration_human(&start, &end);
54        assert!(duration.contains("1.0 hours"));
55    }
56}