use std::time::Duration;
pub fn parse_duration(s: &str) -> Result<Duration, String> {
parse_minutes(s)
.map(|m| Duration::from_secs(m * 60))
.ok_or_else(|| format!("invalid duration '{s}' (examples: 2h, 30m, 1h30m, 120)"))
}
pub fn parse_minutes(s: &str) -> Option<u64> {
if s.is_empty() {
return None;
}
if let Ok(n) = s.parse::<u64>() {
return if n > 0 { Some(n) } else { None };
}
let mut total: u64 = 0;
let mut current = String::new();
for c in s.chars() {
if c.is_ascii_digit() {
current.push(c);
} else if c == 'h' || c == 'H' {
let hours: u64 = current.parse().ok()?;
total += hours * 60;
current.clear();
} else if c == 'm' || c == 'M' {
let mins: u64 = current.parse().ok()?;
total += mins;
current.clear();
} else {
return None;
}
}
if total > 0 { Some(total) } else { None }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_minutes_various_formats() {
assert_eq!(parse_minutes("30m"), Some(30));
assert_eq!(parse_minutes("2h"), Some(120));
assert_eq!(parse_minutes("1h30m"), Some(90));
assert_eq!(parse_minutes("90"), Some(90));
assert_eq!(parse_minutes("0"), None);
assert_eq!(parse_minutes(""), None);
assert_eq!(parse_minutes("abc"), None);
}
#[test]
fn parse_duration_returns_correct_duration() {
assert_eq!(parse_duration("2h").unwrap(), Duration::from_secs(7200));
assert_eq!(parse_duration("30m").unwrap(), Duration::from_secs(1800));
assert!(parse_duration("").is_err());
assert!(parse_duration("abc").is_err());
}
}