Skip to main content

linsight_core/
time.rs

1// SPDX-FileCopyrightText: 2026 VisorCraft LLC
2// SPDX-License-Identifier: GPL-3.0-only
3
4//! Shared time/duration utilities.
5
6use std::time::Duration;
7
8/// Parse a duration string with a `d` (days), `h` (hours), or `m` (minutes)
9/// integer suffix.
10///
11/// - Input is trimmed before parsing.
12/// - The numeric value must be a positive integer (> 0).
13/// - Arithmetic uses `checked_mul` so overflow returns `None`.
14/// - Returns `None` for any input that doesn't match the grammar.
15pub fn parse_duration_dhm(s: &str) -> Option<Duration> {
16    let s = s.trim();
17    let split_at = s.find(|c: char| !c.is_ascii_digit()).unwrap_or(s.len());
18    let (digits, unit) = s.split_at(split_at);
19    let n: u64 = digits.parse().ok().filter(|&v| v > 0)?;
20    let secs = match unit {
21        "d" => n.checked_mul(86_400)?,
22        "h" => n.checked_mul(3_600)?,
23        "m" => n.checked_mul(60)?,
24        _ => return None,
25    };
26    Some(Duration::from_secs(secs))
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32
33    #[test]
34    fn parse_duration_dhm_suffixes() {
35        assert_eq!(parse_duration_dhm("30d"), Some(Duration::from_secs(30 * 86_400)));
36        assert_eq!(parse_duration_dhm("12h"), Some(Duration::from_secs(12 * 3_600)));
37        assert_eq!(parse_duration_dhm("45m"), Some(Duration::from_secs(45 * 60)));
38        assert_eq!(parse_duration_dhm("1d"), Some(Duration::from_secs(86_400)));
39        assert_eq!(parse_duration_dhm("2h"), Some(Duration::from_secs(7_200)));
40        assert_eq!(parse_duration_dhm("30m"), Some(Duration::from_secs(1_800)));
41    }
42
43    #[test]
44    fn parse_duration_dhm_rejects_garbage() {
45        assert_eq!(parse_duration_dhm("garbage"), None);
46        assert_eq!(parse_duration_dhm("5x"), None);
47        assert_eq!(parse_duration_dhm(""), None);
48    }
49
50    #[test]
51    fn parse_duration_dhm_rejects_zero() {
52        assert_eq!(parse_duration_dhm("0d"), None);
53        assert_eq!(parse_duration_dhm("0h"), None);
54        assert_eq!(parse_duration_dhm("0m"), None);
55    }
56
57    #[test]
58    fn parse_duration_dhm_rejects_overflow() {
59        assert_eq!(parse_duration_dhm("99999999999999999999d"), None);
60        assert_eq!(parse_duration_dhm("999999999999999999h"), None);
61    }
62
63    #[test]
64    fn parse_duration_dhm_trims_whitespace() {
65        assert_eq!(parse_duration_dhm("  7d  "), Some(Duration::from_secs(7 * 86_400)));
66        assert_eq!(parse_duration_dhm(" 3h "), Some(Duration::from_secs(3 * 3_600)));
67    }
68}