#![allow(
clippy::arithmetic_side_effects,
reason = "tests are allowed have arithmetic_side_effects"
)]
use chrono::TimeDelta;
use serde::Deserialize;
use crate::test::ApproxEq;
use super::{Hms, Seconds};
impl From<TimeDelta> for Seconds {
fn from(value: TimeDelta) -> Self {
Seconds(value)
}
}
impl ApproxEq for TimeDelta {
type Tolerance = i64;
fn default_tolerance() -> Self::Tolerance {
3
}
fn approx_eq_tolerance(&self, other: &Self, tolerance: i64) -> bool {
let diff = self.num_seconds() - other.num_seconds();
diff.abs() <= tolerance
}
}
pub(crate) trait FromHms {
#[track_caller]
fn from_hms(hms: &str) -> Self;
}
impl FromHms for TimeDelta {
fn from_hms(hms: &str) -> Self {
const SEPARATOR: char = ':';
const SECS_IN_HOUR: i64 = 3600;
let (hour, min, sec) = {
let (hour, rest) = hms.split_once(SEPARATOR).unwrap_or_else(|| {
panic!("Unable to parse time in HMS format. Hour separator is not present. `{hms}`")
});
let (min, sec) = rest.split_once(SEPARATOR).unwrap_or_else(|| {
panic!(
"Unable to parse time in HMS format. Minute separator is not present. `{hms}`"
)
});
(hour, min, sec)
};
let hour = hour.parse::<i64>().unwrap_or_else(|_| {
panic!("Unable to parse time in HMS format. The hour is not an integer. `{hms}`")
});
let min = min.parse::<i64>().unwrap_or_else(|_| {
panic!("Unable to parse time in HMS format. The minute is not an integer. `{hms}`")
});
assert!(
(0..60).contains(&min),
"Unable to parse time in HMS format. The minute is not in range `[0-60)`. `{hms}`"
);
let sec = sec.parse::<i64>().unwrap_or_else(|_| {
panic!("Unable to parse time in HMS format. The second is not an integer. `{hms}`")
});
assert!(
(0..60).contains(&sec),
"Unable to parse time in HMS format. The second is not in range `[0-60)`. `{hms}`"
);
let secs_total = {
let hours_as_secs = hour.checked_mul(SECS_IN_HOUR).unwrap_or_else(|| {
panic!("Unable to parse time in HMS format. The hour range is too large. `{hms}`")
});
hours_as_secs
.checked_add(min * super::SECS_IN_MIN + sec)
.unwrap_or_else(|| {
panic!(
"Unable to parse time in HMS format. The hour range is too large. `{hms}`"
)
})
};
TimeDelta::seconds(secs_total)
}
}
impl ApproxEq for Hms {
type Tolerance = i64;
fn default_tolerance() -> Self::Tolerance {
10
}
fn approx_eq_tolerance(&self, other: &Self, tolerance: Self::Tolerance) -> bool {
self.0.approx_eq_tolerance(&other.0, tolerance)
}
}
impl<'de> Deserialize<'de> for Hms {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(Hms(TimeDelta::from_hms(&s)))
}
}
#[test]
#[should_panic(expected = "Unable to parse time in HMS format. Hour separator is not present. ``")]
fn should_panic_on_empty() {
TimeDelta::from_hms("");
}
#[test]
#[should_panic(
expected = "Unable to parse time in HMS format. Minute separator is not present. `:`"
)]
fn should_panic_on_single_separator() {
TimeDelta::from_hms(":");
}
#[test]
#[should_panic(expected = "Unable to parse time in HMS format. The hour is not an integer. `::`")]
fn should_panic_on_empty_separator() {
TimeDelta::from_hms("::");
}
#[test]
#[should_panic(
expected = "Unable to parse time in HMS format. Minute separator is not present. `01:02`"
)]
fn should_panic_on_missing_component() {
TimeDelta::from_hms("01:02");
}
#[test]
#[should_panic(
expected = "Unable to parse time in HMS format. The hour is not an integer. ` 01:02:03 `"
)]
fn should_panic_on_extraneous_whitespace() {
TimeDelta::from_hms(" 01:02:03 ");
}
#[test]
#[should_panic(
expected = "Unable to parse time in HMS format. The hour is not an integer. ` 01: 02:03`"
)]
fn should_panic_malformed_whitespace() {
TimeDelta::from_hms(" 01: 02:03");
}
#[test]
#[should_panic(
expected = "Unable to parse time in HMS format. The minute is not in range `[0-60)`. `01:60:03`"
)]
fn should_panic_minutes_out_of_range() {
TimeDelta::from_hms("01:60:03");
}
#[test]
#[should_panic(
expected = "Unable to parse time in HMS format. The second is not in range `[0-60)`. `01:02:60`"
)]
fn should_panic_seconds_out_of_range() {
TimeDelta::from_hms("01:02:60");
}
#[test]
fn should_parse_hms() {
assert_eq!(TimeDelta::from_hms("01:02:03"), TimeDelta::seconds(3723));
assert_eq!(TimeDelta::from_hms("1:2:3"), TimeDelta::seconds(3723));
assert_eq!(
TimeDelta::from_hms("36:52:33"),
TimeDelta::seconds(36 * 3600 + 52 * 60 + 33)
);
assert_eq!(
TimeDelta::from_hms("120:52:33"),
TimeDelta::seconds(120 * 3600 + 52 * 60 + 33)
);
}