use {
core::{
cmp::Ordering,
fmt::{self, Display, Formatter},
ops::Deref,
str::FromStr,
time::Duration,
},
};
use crate::Error;
pub const MINUTE: u64 = 60;
pub const HOUR: u64 = 60 * MINUTE;
pub const DAY: u64 = 24 * HOUR;
pub const WEEK: u64 = 7 * DAY;
const NANOSECOND_UNIT: &str = concat!('n', 's');
const MICROSECOND_UNIT: &str = concat!('m', 'c');
const MILLISECOND_UNIT: &str = concat!('m', 's');
const SECOND_UNIT: &str = concat!('s');
const MINUTE_UNIT: &str = concat!('m');
const HOUR_UNIT: &str = concat!('h');
const DAY_UNIT: &str = concat!('d');
const WEEK_UNIT: &str = concat!('w');
#[test]
fn test_constants() {
assert_eq!(MINUTE, 60);
assert_eq!(HOUR, 60 * 60);
assert_eq!(DAY, 24 * 60 * 60);
assert_eq!(WEEK, 7 * 24 * 60 * 60);
assert_eq!(NANOSECOND_UNIT, "ns");
assert_eq!(MICROSECOND_UNIT, "mc");
assert_eq!(MILLISECOND_UNIT, "ms");
assert_eq!(SECOND_UNIT, "s");
assert_eq!(MINUTE_UNIT, "m");
assert_eq!(HOUR_UNIT, "h");
assert_eq!(DAY_UNIT, "d");
assert_eq!(WEEK_UNIT, "w");
}
#[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct ClDuration {
duration: Duration,
}
impl ClDuration {
pub const fn new(duration: Duration) -> Self {
Self {
duration,
}
}
}
impl From<ClDuration> for Duration {
fn from(cld: ClDuration) -> Self {
cld.duration
}
}
impl From<Duration> for ClDuration {
fn from(duration: Duration) -> Self {
Self::new(duration)
}
}
impl Display for ClDuration {
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
let (d, h, m, s) = duration_to_dhms(&self.duration);
let hms = format!("{h:02}:{m:02}:{s:02}", h=h, m=m, s=s);
match d {
0 => f.write_str(&hms),
_ => {
let days = format!("{d}{unit}", d=d, unit=DAY_UNIT);
match (h, m, s) {
(0, 0, 0) => f.write_str(&days),
_ => write!(f, "{days}, {hms}", days=days, hms=hms),
}
},
}
}
}
fn duration_to_dhms(duration: &Duration) -> (u64, u64, u64, u64) {
let seconds = duration.as_secs();
let (minutes, seconds) = {
let minutes = seconds / 60;
(minutes, seconds - minutes * 60)
};
let (hours, minutes) = {
let hours = minutes / 60;
(hours, minutes - hours * 60)
};
let (days, hours) = {
let days = hours / 24;
(days, hours - days * 24)
};
(days, hours, minutes, seconds)
}
impl FromStr for ClDuration {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (number, unit) = match s.chars().position(|c| c < '0' || c > '9') {
Some(pos) => (&s[..pos], &s[pos..]),
None => return Err(e!("Missing unit")),
};
let number = u64::from_str(number).map_err(|_| e!("Invalid duration: {}", s))?;
let factor = match unit {
SECOND_UNIT => 1,
MINUTE_UNIT => MINUTE,
HOUR_UNIT => HOUR,
DAY_UNIT => DAY,
WEEK_UNIT => WEEK,
_ => {
let f = match unit {
NANOSECOND_UNIT => Duration::from_nanos,
MICROSECOND_UNIT => Duration::from_micros,
MILLISECOND_UNIT => Duration::from_millis,
_ => return Err(e!("Invalid duration unit: {}", unit)),
};
return Ok(Self {
duration: f(number),
});
},
};
return Ok(Self {
duration: Duration::from_secs(number.checked_mul(factor).ok_or_else(|| e!("Invalid duration: {}", s))?),
});
}
}
impl PartialEq<ClDuration> for Duration {
fn eq(&self, cld: &ClDuration) -> bool {
self == &cld.duration
}
}
impl PartialEq<Duration> for ClDuration {
fn eq(&self, duration: &Duration) -> bool {
&self.duration == duration
}
}
impl Deref for ClDuration {
type Target = Duration;
fn deref(&self) -> &Self::Target {
&self.duration
}
}
impl PartialOrd<Duration> for ClDuration {
fn partial_cmp(&self, duration: &Duration) -> Option<Ordering> {
self.duration.partial_cmp(duration)
}
}
impl PartialOrd<ClDuration> for Duration {
fn partial_cmp(&self, cld: &ClDuration) -> Option<Ordering> {
self.partial_cmp(&cld.duration)
}
}
#[test]
fn test_from_str_of_duration() -> crate::Result<()> {
assert_eq!(ClDuration::from_str("2s")?, Duration::from_secs(2));
assert_eq!(ClDuration::from_str("3ns")?, Duration::from_nanos(3));
assert_eq!(ClDuration::from_str("4mc")?, Duration::from_micros(4));
assert_eq!(ClDuration::from_str("5ms")?, Duration::from_millis(5));
assert_eq!(ClDuration::from_str("6m")?, Duration::from_secs(MINUTE * 6));
assert_eq!(ClDuration::from_str("7h")?, Duration::from_secs(HOUR * 7));
assert_eq!(ClDuration::from_str("8d")?, Duration::from_secs(DAY * 8));
assert_eq!(ClDuration::from_str("9w")?, Duration::from_secs(WEEK * 9));
[concat!(), concat!('s'), "0", "1s", "2ns", "3mc", "4ms", "5m", "6h", "7d", "8w"].iter().for_each(|s|
assert!(ClDuration::from_str(&s.to_uppercase()).is_err())
);
Ok(())
}
pub const fn from_nanos(nanos: u64) -> ClDuration {
ClDuration::new(Duration::from_nanos(nanos))
}
pub const fn from_micros(micros: u64) -> ClDuration {
ClDuration::new(Duration::from_micros(micros))
}
pub const fn from_millis(millis: u64) -> ClDuration {
ClDuration::new(Duration::from_millis(millis))
}
pub const fn from_secs(secs: u64) -> ClDuration {
ClDuration::new(Duration::from_secs(secs))
}
#[test]
fn test_from_x() {
assert_eq!(from_nanos(1), Duration::from_nanos(1));
assert_eq!(from_micros(2), Duration::from_micros(2));
assert_eq!(from_millis(3), Duration::from_millis(3));
assert_eq!(from_secs(4), Duration::from_secs(4));
}