use std::time::Duration;
use serde::{
Deserialize,
Serialize,
};
use super::{
DurationOverflowError,
DurationParseError,
};
#[must_use]
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize,
)]
#[serde(rename_all = "snake_case")]
pub enum DurationUnit {
Nanoseconds,
Microseconds,
#[default]
Milliseconds,
Seconds,
Minutes,
Hours,
Days,
}
impl DurationUnit {
#[inline]
pub fn parse_strict(symbol: &str) -> Result<Self, DurationParseError> {
if let Some(unit) = Self::strict_symbol(symbol) {
return Ok(unit);
}
if let Some((_, canonical)) = Self::lenient_alias(symbol) {
return Err(DurationParseError::NonCanonicalUnit { canonical });
}
Err(DurationParseError::UnsupportedUnit)
}
#[inline]
pub fn parse_lenient(symbol: &str) -> Result<Self, DurationParseError> {
Self::strict_symbol(symbol)
.or_else(|| Self::lenient_alias(symbol).map(|(unit, _)| unit))
.ok_or(DurationParseError::UnsupportedUnit)
}
fn strict_symbol(symbol: &str) -> Option<Self> {
match symbol {
"ns" => Some(Self::Nanoseconds),
"us" | "µs" | "μs" => Some(Self::Microseconds),
"ms" => Some(Self::Milliseconds),
"s" => Some(Self::Seconds),
"min" => Some(Self::Minutes),
"h" => Some(Self::Hours),
"d" => Some(Self::Days),
_ => None,
}
}
fn lenient_alias(symbol: &str) -> Option<(Self, &'static str)> {
match symbol {
"m" => Some((Self::Minutes, "min")),
_ => None,
}
}
#[must_use]
#[inline(always)]
pub const fn symbol(self) -> &'static str {
match self {
Self::Nanoseconds => "ns",
Self::Microseconds => "µs",
Self::Milliseconds => "ms",
Self::Seconds => "s",
Self::Minutes => "min",
Self::Hours => "h",
Self::Days => "d",
}
}
#[inline(always)]
pub fn duration_from_u64(
self,
value: u64,
) -> Result<Duration, DurationOverflowError> {
self.duration_from_u128(u128::from(value))
}
pub fn duration_from_u128(
self,
value: u128,
) -> Result<Duration, DurationOverflowError> {
match self {
Self::Nanoseconds => {
duration_from_subseconds(value, 1_000_000_000, 1)
}
Self::Microseconds => {
duration_from_subseconds(value, 1_000_000, 1_000)
}
Self::Milliseconds => {
duration_from_subseconds(value, 1_000, 1_000_000)
}
Self::Seconds => checked_seconds(value, 1),
Self::Minutes => checked_seconds(value, 60),
Self::Hours => checked_seconds(value, 60 * 60),
Self::Days => checked_seconds(value, 24 * 60 * 60),
}
}
#[must_use]
#[inline]
pub fn exact_units(self, duration: Duration) -> Option<u128> {
let total_nanos = duration.as_nanos();
let nanos_per_unit = self.nanos_per_unit();
if total_nanos.is_multiple_of(nanos_per_unit) {
Some(total_nanos / nanos_per_unit)
} else {
None
}
}
#[must_use]
pub fn rounded_units(self, duration: Duration) -> u128 {
let total_nanos = duration.as_nanos();
let unit_nanos = self.nanos_per_unit();
let quotient = total_nanos / unit_nanos;
let remainder = total_nanos % unit_nanos;
if remainder >= unit_nanos.div_ceil(2) {
quotient + 1
} else {
quotient
}
}
#[must_use]
#[inline(always)]
pub(crate) const fn nanos_per_unit(self) -> u128 {
match self {
Self::Nanoseconds => 1,
Self::Microseconds => 1_000,
Self::Milliseconds => 1_000_000,
Self::Seconds => 1_000_000_000,
Self::Minutes => 60 * 1_000_000_000,
Self::Hours => 60 * 60 * 1_000_000_000,
Self::Days => 24 * 60 * 60 * 1_000_000_000,
}
}
}
#[inline]
fn checked_seconds(
value: u128,
seconds_per_unit: u128,
) -> Result<Duration, DurationOverflowError> {
let seconds = value
.checked_mul(seconds_per_unit)
.filter(|seconds| *seconds <= u128::from(u64::MAX))
.ok_or(DurationOverflowError)?;
Ok(Duration::from_secs(seconds as u64))
}
#[inline]
fn duration_from_subseconds(
value: u128,
units_per_second: u128,
nanos_per_unit: u32,
) -> Result<Duration, DurationOverflowError> {
let seconds = value / units_per_second;
if seconds > u128::from(u64::MAX) {
return Err(DurationOverflowError);
}
let nanos = (value % units_per_second) as u32 * nanos_per_unit;
Ok(Duration::new(seconds as u64, nanos))
}