use std::{
fmt::{Display, Formatter},
str::FromStr,
};
use anyhow::{Error, Result};
use crate::{
error::Error::InvalidTime,
realtime::hms::{hour::Hour, minute::Minute, second::Second},
};
pub(crate) mod hour;
pub(crate) mod minute;
pub(crate) mod second;
pub(crate) type HourMinuteSecondTuple = (Hour, Minute, Second);
#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(crate) struct HourMinuteSecond(pub(crate) Hour, pub(crate) Minute, pub(crate) Second);
impl HourMinuteSecond {
pub(crate) fn take(self) -> HourMinuteSecondTuple {
(self.0, self.1, self.2)
}
pub(crate) fn minutely() -> Self {
HourMinuteSecond(Hour::default(), Minute::default(), Second::zero())
}
pub(crate) fn hourly() -> Self {
HourMinuteSecond(Hour::default(), Minute::zero(), Second::zero())
}
pub(crate) fn daily() -> Self {
HourMinuteSecond(Hour::zero(), Minute::zero(), Second::zero())
}
}
impl TryFrom<&str> for HourMinuteSecond {
type Error = Error;
fn try_from(hms: &str) -> Result<Self> {
let hms_split = hms.split(':').collect::<Vec<&str>>();
if hms_split.len() == 3 {
let hour = hms_split[0].parse::<Hour>()?;
let minute = hms_split[1].parse::<Minute>()?;
let second = hms_split[2].parse::<Second>()?;
Ok(HourMinuteSecond(hour, minute, second))
} else {
Err(InvalidTime(hms.to_string()).into())
}
}
}
impl FromStr for HourMinuteSecond {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
HourMinuteSecond::try_from(s)
}
}
impl Display for HourMinuteSecond {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}:{}", self.0, self.1, self.2)
}
}
#[cfg(test)]
pub(crate) mod test {
use proptest::{prelude::proptest, prop_assume, prop_compose};
use crate::realtime::{
cv::ConstrainedValueParser as _,
hms::{
HourMinuteSecond,
hour::{
HOUR_RANGE_RE, HOUR_REPETITION_RE, Hour,
test::{VALID_HOUR_RE, hour_strategy},
},
minute::{
MINUTE_RANGE_RE, MINUTE_REPETITION_RE, Minute,
test::{VALID_MINUTE_RE, minute_strategy},
},
second::{
SECOND_RANGE_RE, SECOND_REPETITION_RE, Second,
test::{VALID_SECOND_RE, second_strategy},
},
},
};
prop_compose! {
pub(crate) fn arb_hms() (hour in hour_strategy(), minute in minute_strategy(), second in second_strategy()) -> (String, u8, u8, u8) {
let (hour_str, hour_val) = hour;
let (minute_str, minute_val) = minute;
let (second_str, second_val) = second;
let hms_str = format!("{hour_str}:{minute_str}:{second_str}");
(hms_str, hour_val, minute_val, second_val)
}
}
proptest! {
#[test]
fn arb_hms_works(s in arb_hms()) {
let (hms_str, _, _, _) = s;
assert!(HourMinuteSecond::try_from(hms_str.as_str()).is_ok());
}
}
proptest! {
#[test]
fn random_input_errors(hour in "\\PC*", minute in "\\PC*", second in "\\PC*") {
prop_assume!(!VALID_HOUR_RE.is_match(hour.as_str()));
prop_assume!(!HOUR_REPETITION_RE.is_match(hour.as_str()));
prop_assume!(!HOUR_RANGE_RE.is_match(hour.as_str()));
prop_assume!(hour.as_str() != "*");
prop_assume!(!VALID_MINUTE_RE.is_match(minute.as_str()));
prop_assume!(!MINUTE_REPETITION_RE.is_match(minute.as_str()));
prop_assume!(!MINUTE_RANGE_RE.is_match(minute.as_str()));
prop_assume!(minute.as_str() != "*");
prop_assume!(minute.as_str() != "R");
prop_assume!(!VALID_SECOND_RE.is_match(second.as_str()));
prop_assume!(!SECOND_REPETITION_RE.is_match(second.as_str()));
prop_assume!(!SECOND_RANGE_RE.is_match(second.as_str()));
prop_assume!(second.as_str() != "*");
prop_assume!(second.as_str() != "R");
let hms = format!("{hour}:{minute}:{second}");
assert!(HourMinuteSecond::try_from(hms.as_str()).is_err());
assert!(hms.as_str().parse::<HourMinuteSecond>().is_err());
}
}
#[test]
fn take_works() {
let hms = HourMinuteSecond(Hour::all(), Minute::all(), Second::all());
let (hour, minute, second) = hms.take();
assert_eq!(hour, Hour::all());
assert_eq!(minute, Minute::all());
assert_eq!(second, Second::all());
}
#[test]
fn minutely_works() {
let hms = HourMinuteSecond::minutely();
let (hour, minute, second) = hms.take();
assert_eq!(hour, Hour::default());
assert_eq!(minute, Minute::default());
assert_eq!(second, Second::zero());
}
#[test]
fn hourly_works() {
let hms = HourMinuteSecond::hourly();
let (hour, minute, second) = hms.take();
assert_eq!(hour, Hour::default());
assert_eq!(minute, Minute::zero());
assert_eq!(second, Second::zero());
}
#[test]
fn daily_works() {
let hms = HourMinuteSecond::daily();
let (hour, minute, second) = hms.take();
assert_eq!(hour, Hour::zero());
assert_eq!(minute, Minute::zero());
assert_eq!(second, Second::zero());
}
}