opencord 0.2.1

Open-source ephemeral chat servers with automatic shutdown
Documentation
use std::time::Duration;

/// Parse a human-readable duration string into a [`Duration`].
///
/// Supports: `"30m"`, `"2h"`, `"1h30m"`, or plain minutes like `"120"`.
/// Returns an error string suitable for display if the input is invalid.
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)"))
}

/// Parse a human-readable duration string into minutes.
///
/// Supports: `"30m"`, `"2h"`, `"1h30m"`, or plain minutes like `"90"`.
/// Returns `None` for empty, zero, or malformed input.
pub fn parse_minutes(s: &str) -> Option<u64> {
    if s.is_empty() {
        return None;
    }

    // Plain number → treat as minutes.
    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());
    }
}