1use std::time::Duration;
7
8pub 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}