use crate::error::{Result, ToriiError};
pub fn parse_duration(s: &str) -> Result<u32> {
let s = s.trim();
if s.is_empty() {
return Err(ToriiError::InvalidConfig("Empty duration string".to_string()));
}
let mut total_minutes: u32 = 0;
let mut current_number = String::new();
let mut chars = s.chars().peekable();
while let Some(ch) = chars.next() {
if ch.is_ascii_digit() {
current_number.push(ch);
} else if ch.is_alphabetic() {
if current_number.is_empty() {
return Err(ToriiError::InvalidConfig(
format!("Invalid duration format: '{}'. Expected number before unit", s)
));
}
let number: u32 = current_number.parse()
.map_err(|_| ToriiError::InvalidConfig(
format!("Invalid number in duration: '{}'", current_number)
))?;
let mut unit = String::from(ch);
while let Some(&next_ch) = chars.peek() {
if next_ch.is_alphabetic() {
unit.push(chars.next().unwrap());
} else {
break;
}
}
let unit_lower = unit.to_lowercase();
let minutes = match unit_lower.as_str() {
"s" | "sec" | "second" | "seconds" => {
if number < 60 && total_minutes == 0 {
return Err(ToriiError::InvalidConfig(
"Duration must be at least 1 minute (60 seconds)".to_string()
));
}
number / 60
}
"m" | "min" | "minute" | "minutes" => number,
"h" | "hr" | "hour" | "hours" => number * 60,
"d" | "day" | "days" => number * 60 * 24,
_ => {
return Err(ToriiError::InvalidConfig(
format!("Unknown time unit: '{}'. Use: s (seconds), m (minutes), h (hours), d (days)", unit)
));
}
};
total_minutes += minutes;
current_number.clear();
} else if !ch.is_whitespace() {
return Err(ToriiError::InvalidConfig(
format!("Invalid character in duration: '{}'", ch)
));
}
}
if !current_number.is_empty() {
let number: u32 = current_number.parse()
.map_err(|_| ToriiError::InvalidConfig(
format!("Invalid number in duration: '{}'", current_number)
))?;
total_minutes += number;
}
if total_minutes == 0 {
return Err(ToriiError::InvalidConfig(
"Duration must be at least 1 minute".to_string()
));
}
Ok(total_minutes)
}
pub fn format_duration(minutes: u32) -> String {
if minutes < 60 {
format!("{} minute{}", minutes, if minutes == 1 { "" } else { "s" })
} else if minutes < 60 * 24 {
let hours = minutes / 60;
let mins = minutes % 60;
if mins == 0 {
format!("{} hour{}", hours, if hours == 1 { "" } else { "s" })
} else {
format!("{} hour{} {} minute{}",
hours, if hours == 1 { "" } else { "s" },
mins, if mins == 1 { "" } else { "s" })
}
} else {
let days = minutes / (60 * 24);
let hours = (minutes % (60 * 24)) / 60;
if hours == 0 {
format!("{} day{}", days, if days == 1 { "" } else { "s" })
} else {
format!("{} day{} {} hour{}",
days, if days == 1 { "" } else { "s" },
hours, if hours == 1 { "" } else { "s" })
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_duration() {
assert_eq!(parse_duration("10m").unwrap(), 10);
assert_eq!(parse_duration("120s").unwrap(), 2);
assert_eq!(parse_duration("2h").unwrap(), 120);
assert_eq!(parse_duration("1d").unwrap(), 1440);
assert_eq!(parse_duration("10").unwrap(), 10);
assert_eq!(parse_duration("1h30m").unwrap(), 90);
assert_eq!(parse_duration("2h15m").unwrap(), 135);
assert_eq!(parse_duration("1d12h").unwrap(), 2160); assert_eq!(parse_duration("1d12h30m").unwrap(), 2190); assert_eq!(parse_duration("2d6h30m").unwrap(), 3270);
assert_eq!(parse_duration("1h 30m").unwrap(), 90);
assert_eq!(parse_duration("2d12h30m").unwrap(), 3630);
}
#[test]
fn test_format_duration() {
assert_eq!(format_duration(10), "10 minutes");
assert_eq!(format_duration(1), "1 minute");
assert_eq!(format_duration(60), "1 hour");
assert_eq!(format_duration(90), "1 hour 30 minutes");
assert_eq!(format_duration(1440), "1 day");
assert_eq!(format_duration(1500), "1 day 1 hour");
}
}