use std::{error::Error, fmt, num::ParseIntError, str::FromStr, time::Duration};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Time {
pub hours: u64,
pub minutes: u64,
pub seconds: u64,
pub milliseconds: u64,
}
impl From<Time> for Duration {
fn from(value: Time) -> Self {
let minutes = value.minutes + (value.hours * 60);
let seconds = value.seconds + (minutes * 60);
let milliseconds = value.milliseconds + (seconds * 1000);
Duration::from_millis(milliseconds)
}
}
impl fmt::Display for Time {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
write!(
out,
"{:02}:{:02}:{:02},{}",
self.hours, self.minutes, self.seconds, self.milliseconds
)
}
}
impl FromStr for Time {
type Err = ParseTimeError;
fn from_str(raw: &str) -> Result<Self, Self::Err> {
let mut raw = raw.trim().split(',');
let (hours, minutes, seconds) = match raw.next() {
Some(raw_time) => {
let mut raw_time = raw_time.split(':');
let hours = match raw_time.next() {
Some(hours) => hours.parse::<u64>().map_err(ParseTimeError::ParseHours)?,
None => return Err(ParseTimeError::MissingHours),
};
let minutes = match raw_time.next() {
Some(minutes) => minutes.parse::<u64>().map_err(ParseTimeError::ParseMinutes)?,
None => return Err(ParseTimeError::MissingMinutes),
};
let seconds = match raw_time.next() {
Some(seconds) => seconds.parse::<u64>().map_err(ParseTimeError::ParseSeconds)?,
None => return Err(ParseTimeError::MissingSeconds),
};
if let Some(part) = raw_time.next() {
return Err(ParseTimeError::UnexpectedTimePart(String::from(part)));
}
(hours, minutes, seconds)
}
None => return Err(ParseTimeError::MissingTime),
};
let milliseconds = match raw.next() {
Some(value) => value.parse::<u64>().map_err(ParseTimeError::ParseMilliseconds)?,
None => return Err(ParseTimeError::MissingMilliseconds),
};
if let Some(part) = raw.next() {
return Err(ParseTimeError::UnexpectedTimePart(String::from(part)));
}
Ok(Self {
hours,
minutes,
seconds,
milliseconds,
})
}
}
#[derive(Debug)]
pub enum ParseTimeError {
ParseHours(ParseIntError),
ParseMilliseconds(ParseIntError),
ParseMinutes(ParseIntError),
ParseSeconds(ParseIntError),
MissingHours,
MissingMilliseconds,
MissingMinutes,
MissingSeconds,
MissingTime,
UnexpectedTimePart(String),
}
impl fmt::Display for ParseTimeError {
fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result {
use self::ParseTimeError::*;
match self {
ParseHours(err) => write!(out, "could not parse hours: {err}"),
ParseMinutes(err) => write!(out, "could not parse minutes: {err}"),
ParseSeconds(err) => write!(out, "could not parse seconds: {err}"),
ParseMilliseconds(err) => write!(out, "could not parse milliseconds: {err}"),
MissingHours => write!(out, "hours not found"),
MissingMinutes => write!(out, "minutes not found"),
MissingSeconds => write!(out, "seconds not found"),
MissingMilliseconds => write!(out, "milliseconds not found"),
MissingTime => write!(out, "time not found"),
UnexpectedTimePart(part) => write!(out, "unexpected time part: '{part}'"),
}
}
}
impl Error for ParseTimeError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
use self::ParseTimeError::*;
if let ParseHours(err) | ParseMinutes(err) | ParseSeconds(err) | ParseMilliseconds(err) = self {
Some(err)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse() {
assert_eq!(
"".parse::<Time>().unwrap_err().to_string(),
"could not parse hours: cannot parse integer from empty string"
);
assert_eq!(
"x".parse::<Time>().unwrap_err().to_string(),
"could not parse hours: invalid digit found in string"
);
assert_eq!(
"x,x".parse::<Time>().unwrap_err().to_string(),
"could not parse hours: invalid digit found in string"
);
assert_eq!("1,x".parse::<Time>().unwrap_err().to_string(), "minutes not found");
assert_eq!(
"00:01:02,200".parse::<Time>().unwrap(),
Time {
hours: 0,
minutes: 1,
seconds: 2,
milliseconds: 200
}
);
}
#[test]
fn display() {
let time = Time {
hours: 0,
minutes: 1,
seconds: 2,
milliseconds: 200,
};
assert_eq!(time.to_string(), "00:01:02,200");
}
#[test]
fn into_duration() {
let time = Time {
hours: 0,
minutes: 1,
seconds: 2,
milliseconds: 200,
};
assert_eq!(Duration::from(time), Duration::from_millis(62200));
}
}