use std::error::Error;
use std::fmt;
const SECONDS_PER_MINUTE: u64 = 60;
const SECONDS_PER_HOUR: u64 = 60 * SECONDS_PER_MINUTE;
#[derive(Debug)]
pub struct ParseError(String);
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Parse error: {}", self.0)
}
}
impl Error for ParseError {}
#[derive(Debug)]
enum Token {
Number(u64),
Unit(String),
}
fn parse_number_word(word: &str) -> Option<u64> {
match word {
"zero" => Some(0),
"one" => Some(1),
"oen" => Some(1),
"two" => Some(2),
"tow" => Some(2),
"three" => Some(3),
"thre" => Some(3),
"four" => Some(4),
"foru" => Some(4),
"five" => Some(5),
"fiev" => Some(5),
"six" => Some(6),
"seven" => Some(7),
"sevne" => Some(7),
"eight" => Some(8),
"nine" => Some(9),
"nien" => Some(9),
"ten" => Some(10),
"eleven" => Some(11),
"elevne" => Some(11),
"twelve" => Some(12),
"thirteen" => Some(13),
"fourteen" => Some(14),
"fifteen" => Some(15),
"sixteen" => Some(16),
"seventeen" => Some(17),
"eighteen" => Some(18),
"nineteen" => Some(19),
"twenty" => Some(20),
"thirty" => Some(30),
"forty" => Some(40),
"fourty" => Some(40),
"fifty" => Some(50),
"sixty" => Some(60),
"twentyone" => Some(21),
"twentytwo" => Some(22),
"twentythree" => Some(23),
"twentyfour" => Some(24),
"twentyfive" => Some(25),
"twentysix" => Some(26),
"twentyseven" => Some(27),
"twentyeight" => Some(28),
"twentynine" => Some(29),
"thirtyone" => Some(31),
"thirtytwo" => Some(32),
"thirtythree" => Some(33),
"thirtyfour" => Some(34),
"thirtyfive" => Some(35),
"thirtysix" => Some(36),
"thirtyseven" => Some(37),
"thirtyeight" => Some(38),
"thirtynine" => Some(39),
"fortyone" => Some(41),
"fortytwo" => Some(42),
"fortythree" => Some(43),
"fortyfour" => Some(44),
"fortyfive" => Some(45),
"fortysix" => Some(46),
"fortyseven" => Some(47),
"fortyeight" => Some(48),
"fortynine" => Some(49),
"fourtyone" => Some(41),
"fourtytwo" => Some(42),
"fourtythree" => Some(43),
"fourtyfour" => Some(44),
"fourtyfive" => Some(45),
"fourtysix" => Some(46),
"fourtyseven" => Some(47),
"fourtyeight" => Some(48),
"fourtynine" => Some(49),
"fiftyone" => Some(51),
"fiftytwo" => Some(52),
"fiftythree" => Some(53),
"fiftyfour" => Some(54),
"fiftyfive" => Some(55),
"fiftysix" => Some(56),
"fiftyseven" => Some(57),
"fiftyeight" => Some(58),
"fiftynine" => Some(59),
"twenty-one" => Some(21),
"twenty-two" => Some(22),
"twenty-three" => Some(23),
"twenty-four" => Some(24),
"twenty-five" => Some(25),
"twenty-six" => Some(26),
"twenty-seven" => Some(27),
"twenty-eight" => Some(28),
"twenty-nine" => Some(29),
"thirty-one" => Some(31),
"thirty-two" => Some(32),
"thirty-three" => Some(33),
"thirty-four" => Some(34),
"thirty-five" => Some(35),
"thirty-six" => Some(36),
"thirty-seven" => Some(37),
"thirty-eight" => Some(38),
"thirty-nine" => Some(39),
"forty-one" => Some(41),
"forty-two" => Some(42),
"forty-three" => Some(43),
"forty-four" => Some(44),
"forty-five" => Some(45),
"forty-six" => Some(46),
"forty-seven" => Some(47),
"forty-eight" => Some(48),
"forty-nine" => Some(49),
"fourty-one" => Some(41),
"fourty-two" => Some(42),
"fourty-three" => Some(43),
"fourty-four" => Some(44),
"fourty-five" => Some(45),
"fourty-six" => Some(46),
"fourty-seven" => Some(47),
"fourty-eight" => Some(48),
"fourty-nine" => Some(49),
"fifty-one" => Some(51),
"fifty-two" => Some(52),
"fifty-three" => Some(53),
"fifty-four" => Some(54),
"fifty-five" => Some(55),
"fifty-six" => Some(56),
"fifty-seven" => Some(57),
"fifty-eight" => Some(58),
"fifty-nine" => Some(59),
_ => None,
}
}
fn tokenize(input: &str) -> Result<Vec<Token>, ParseError> {
let input = input.trim().to_lowercase();
let mut tokens = Vec::new();
let mut current = String::new();
let mut in_number = false;
for ch in input.chars() {
if ch.is_ascii_digit() {
if !in_number && !current.is_empty() {
tokens.push(Token::Unit(current.clone()));
current.clear();
}
in_number = true;
current.push(ch);
} else if ch.is_ascii_alphabetic() {
if in_number && !current.is_empty() {
let num: u64 = current
.parse()
.map_err(|_| ParseError(format!("Invalid number: {}", current)))?;
tokens.push(Token::Number(num));
current.clear();
}
in_number = false;
current.push(ch);
} else if ch.is_whitespace() {
if !current.is_empty() {
if in_number {
let num: u64 = current
.parse()
.map_err(|_| ParseError(format!("Invalid number: {}", current)))?;
tokens.push(Token::Number(num));
} else {
if let Some(num) = parse_number_word(¤t) {
tokens.push(Token::Number(num));
} else {
tokens.push(Token::Unit(current.clone()));
}
}
current.clear();
in_number = false;
}
} else {
if in_number && !current.is_empty() {
let num: u64 = current
.parse()
.map_err(|_| ParseError(format!("Invalid number: {}", current)))?;
tokens.push(Token::Number(num));
current.clear();
in_number = false;
}
current.push(ch);
}
}
if !current.is_empty() {
if in_number {
let num: u64 = current
.parse()
.map_err(|_| ParseError(format!("Invalid number: {}", current)))?;
tokens.push(Token::Number(num));
} else {
if let Some(num) = parse_number_word(¤t) {
tokens.push(Token::Number(num));
} else {
tokens.push(Token::Unit(current));
}
}
}
Ok(tokens)
}
fn parse_unit(unit: &str) -> Result<u64, ParseError> {
match unit {
"h" | "hr" | "hrs" | "hour" | "hours" | "horus" | "housr" => Ok(SECONDS_PER_HOUR),
"m" | "min" | "mins" | "minute" | "minutes" | "mintues" => Ok(SECONDS_PER_MINUTE),
"s" | "sec" | "secs" | "second" | "seconds" | "secodns" => Ok(1),
_ => Err(ParseError(format!("Unknown time unit: '{}'", unit))),
}
}
fn parse_colon_time(s: &str) -> Result<u64, ParseError> {
let parts: Vec<&str> = s.split(':').collect();
match parts.len() {
1 => {
let secs: u64 = parts[0]
.parse()
.map_err(|_| ParseError(format!("Invalid seconds: {}", parts[0])))?;
Ok(secs)
}
2 => {
let mins: u64 = parts[0]
.parse()
.map_err(|_| ParseError(format!("Invalid minutes: {}", parts[0])))?;
let secs: u64 = parts[1]
.parse()
.map_err(|_| ParseError(format!("Invalid seconds: {}", parts[1])))?;
Ok(mins * SECONDS_PER_MINUTE + secs)
}
3 => {
let hours: u64 = parts[0]
.parse()
.map_err(|_| ParseError(format!("Invalid hours: {}", parts[0])))?;
let mins: u64 = parts[1]
.parse()
.map_err(|_| ParseError(format!("Invalid minutes: {}", parts[1])))?;
let secs: u64 = parts[2]
.parse()
.map_err(|_| ParseError(format!("Invalid seconds: {}", parts[2])))?;
Ok(hours * SECONDS_PER_HOUR + mins * SECONDS_PER_MINUTE + secs)
}
_ => Err(ParseError(format!("Invalid time format: {}", s))),
}
}
fn is_colon_time(s: &str) -> bool {
if !s.contains(':') {
return false;
}
s.chars().all(|c| c.is_ascii_digit() || c == ':')
}
pub fn parse_input(input: &str) -> Result<(u64, String), ParseError> {
let words: Vec<&str> = input.split_whitespace().collect();
let mut colon_duration = 0u64;
let mut remaining_input = Vec::new();
for word in words {
if is_colon_time(word) {
colon_duration += parse_colon_time(word)?;
} else {
remaining_input.push(word);
}
}
if remaining_input.is_empty() && colon_duration > 0 {
return Err(ParseError("No message found in input".to_string()));
}
let remaining_str = remaining_input.join(" ");
let tokens = tokenize(&remaining_str)?;
if tokens.is_empty() && colon_duration == 0 {
return Err(ParseError("Empty input".to_string()));
}
let mut total_seconds = colon_duration; let mut message_parts = Vec::new();
let mut i = 0;
while i < tokens.len() {
match &tokens[i] {
Token::Number(num) => {
if i + 1 < tokens.len()
&& let Token::Unit(unit) = &tokens[i + 1]
{
if let Ok(multiplier) = parse_unit(unit) {
total_seconds += num * multiplier;
i += 2;
continue;
}
message_parts.push(num.to_string());
message_parts.push(unit.clone());
i += 2;
continue;
}
message_parts.push(num.to_string());
i += 1;
}
Token::Unit(unit) => {
message_parts.push(unit.clone());
i += 1;
}
}
}
if total_seconds == 0 {
return Err(ParseError("No valid duration found in input".to_string()));
}
let message = message_parts.join(" ");
if message.is_empty() {
return Err(ParseError("No message found in input".to_string()));
}
Ok((total_seconds, message))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_short_units() {
let (duration, message) = parse_input("5m break").unwrap();
assert_eq!(duration, 300);
assert_eq!(message, "break");
let (duration, message) = parse_input("timer 1h").unwrap();
assert_eq!(duration, 3600);
assert_eq!(message, "timer");
let (duration, message) = parse_input("30s reminder").unwrap();
assert_eq!(duration, 30);
assert_eq!(message, "reminder");
}
#[test]
fn test_simple_long_units() {
let (duration, _) = parse_input("5minutes break").unwrap();
assert_eq!(duration, 300);
let (duration, _) = parse_input("1hour timer").unwrap();
assert_eq!(duration, 3600);
let (duration, _) = parse_input("30seconds go").unwrap();
assert_eq!(duration, 30);
let (duration, _) = parse_input("2hrs meeting").unwrap();
assert_eq!(duration, 7200);
let (duration, _) = parse_input("45mins lunch").unwrap();
assert_eq!(duration, 2700);
}
#[test]
fn test_combined_short_units() {
let (duration, _) = parse_input("1h30m break").unwrap();
assert_eq!(duration, 5400);
let (duration, _) = parse_input("2h15m30s meeting").unwrap();
assert_eq!(duration, 8130);
}
#[test]
fn test_combined_long_units() {
let (duration, _) = parse_input("1hour30minutes break").unwrap();
assert_eq!(duration, 5400);
let (duration, _) = parse_input("msg 2hours 15minutes 30seconds").unwrap();
assert_eq!(duration, 8130);
let (duration, _) = parse_input("1 hour 30 minutes break").unwrap();
assert_eq!(duration, 5400);
}
#[test]
fn test_mixed_units() {
let (duration, _) = parse_input("1h 30min break").unwrap();
assert_eq!(duration, 5400);
let (duration, _) = parse_input("5 hours 30m timer").unwrap();
assert_eq!(duration, 19800);
let (duration, _) = parse_input("1hour30m break").unwrap();
assert_eq!(duration, 5400);
let (duration, _) = parse_input("msg 1second 5h 30min").unwrap();
assert_eq!(duration, 19801);
}
#[test]
fn test_case_insensitive() {
let (duration, _) = parse_input("5M break").unwrap();
assert_eq!(duration, 300);
let (duration, _) = parse_input("1H timer").unwrap();
assert_eq!(duration, 3600);
let (duration, _) = parse_input("30S go").unwrap();
assert_eq!(duration, 30);
let (duration, _) = parse_input("5Minutes break").unwrap();
assert_eq!(duration, 300);
let (duration, _) = parse_input("1HOUR timer").unwrap();
assert_eq!(duration, 3600);
}
#[test]
fn test_parse_input_mixed() {
let (duration, message) = parse_input("15mins 1 hour 20s take a break").unwrap();
assert_eq!(duration, 15 * 60 + 3600 + 20); assert_eq!(message, "take a break");
}
#[test]
fn test_parse_input_duration_first() {
let (duration, message) = parse_input("5m coffee time").unwrap();
assert_eq!(duration, 300);
assert_eq!(message, "coffee time");
}
#[test]
fn test_parse_input_duration_last() {
let (duration, message) = parse_input("get coffee 5m").unwrap();
assert_eq!(duration, 300);
assert_eq!(message, "get coffee");
}
#[test]
fn test_parse_input_multiple_durations() {
let (duration, message) = parse_input("wait 5m and then 10s more for tea").unwrap();
assert_eq!(duration, 5 * 60 + 10); assert_eq!(message, "wait and then more for tea");
}
#[test]
fn test_parse_input_message_with_numbers() {
let (duration, message) = parse_input("5m call 123 people").unwrap();
assert_eq!(duration, 300);
assert_eq!(message, "call 123 people");
}
#[test]
fn test_parse_input_complex() {
let (duration, message) = parse_input("1h 30m break for lunch at 12").unwrap();
assert_eq!(duration, 3600 + 1800); assert_eq!(message, "break for lunch at 12");
}
#[test]
fn test_parse_input_errors() {
assert!(parse_input("just a message").is_err());
assert!(parse_input("5m").is_err());
assert!(parse_input("1h 30m").is_err());
assert!(parse_input("").is_err());
assert!(parse_input("5x message").is_err());
}
#[test]
fn test_colon_format_minutes_seconds() {
let (duration, message) = parse_input("5:30 tea is ready").unwrap();
assert_eq!(duration, 5 * 60 + 30); assert_eq!(message, "tea is ready");
}
#[test]
fn test_colon_format_hours_minutes_seconds() {
let (duration, message) = parse_input("1:30:45 coffee break").unwrap();
assert_eq!(duration, 3600 + 30 * 60 + 45); assert_eq!(message, "coffee break");
}
#[test]
fn test_colon_format_with_leading_zeros() {
let (duration, message) = parse_input("05:50:55 timer").unwrap();
assert_eq!(duration, 5 * 3600 + 50 * 60 + 55); assert_eq!(message, "timer");
}
#[test]
fn test_colon_format_message_first() {
let (duration, message) = parse_input("reminder 0:30").unwrap();
assert_eq!(duration, 30); assert_eq!(message, "reminder");
}
#[test]
fn test_colon_format_mixed_with_standard() {
let (duration, message) = parse_input("1:30 5m reminder").unwrap();
assert_eq!(duration, 90 + 300); assert_eq!(message, "reminder");
}
#[test]
fn test_colon_format_multiple() {
let (duration, message) = parse_input("1:00 2:30 break").unwrap();
assert_eq!(duration, 60 + 150); assert_eq!(message, "break");
}
#[test]
fn test_colon_format_errors() {
assert!(parse_input("5:30").is_err());
assert!(parse_input("5:30:45:10 message").is_err());
assert!(parse_input("5:3a message").is_err());
}
#[test]
fn test_number_words_basic() {
let (duration, message) = parse_input("one minute reminder").unwrap();
assert_eq!(duration, 60);
assert_eq!(message, "reminder");
let (duration, message) = parse_input("five minutes test").unwrap();
assert_eq!(duration, 300);
assert_eq!(message, "test");
let (duration, message) = parse_input("ten seconds go").unwrap();
assert_eq!(duration, 10);
assert_eq!(message, "go");
}
#[test]
fn test_number_words_teens() {
let (duration, message) = parse_input("fifteen minutes break").unwrap();
assert_eq!(duration, 900);
assert_eq!(message, "break");
let (duration, message) = parse_input("thirteen seconds timer").unwrap();
assert_eq!(duration, 13);
assert_eq!(message, "timer");
}
#[test]
fn test_number_words_tens() {
let (duration, message) = parse_input("twenty minutes reminder").unwrap();
assert_eq!(duration, 1200);
assert_eq!(message, "reminder");
let (duration, message) = parse_input("thirty seconds go").unwrap();
assert_eq!(duration, 30);
assert_eq!(message, "go");
let (duration, message) = parse_input("fifty minutes lunch").unwrap();
assert_eq!(duration, 3000);
assert_eq!(message, "lunch");
}
#[test]
fn test_number_words_compounds() {
let (duration, message) = parse_input("twentyfive minutes break").unwrap();
assert_eq!(duration, 1500);
assert_eq!(message, "break");
let (duration, message) = parse_input("fortyfive seconds timer").unwrap();
assert_eq!(duration, 45);
assert_eq!(message, "timer");
}
#[test]
fn test_number_words_mixed_with_digits() {
let (duration, message) = parse_input("one hour 30 minutes break").unwrap();
assert_eq!(duration, 5400);
assert_eq!(message, "break");
let (duration, message) = parse_input("5 minutes thirty seconds go").unwrap();
assert_eq!(duration, 330);
assert_eq!(message, "go");
}
#[test]
fn test_number_words_multiple() {
let (duration, message) = parse_input("two hours five minutes reminder").unwrap();
assert_eq!(duration, 2 * 3600 + 5 * 60); assert_eq!(message, "reminder");
let (duration, message) = parse_input("one hour one minute one second test").unwrap();
assert_eq!(duration, 3661);
assert_eq!(message, "test");
}
#[test]
fn test_number_words_case_insensitive() {
let (duration, message) = parse_input("One Minute Test").unwrap();
assert_eq!(duration, 60);
assert_eq!(message, "test");
let (duration, message) = parse_input("FIVE SECONDS GO").unwrap();
assert_eq!(duration, 5);
assert_eq!(message, "go");
}
}