use crate::{period::Period, time_int::TimeInt, ConversionError};
use core::{convert::TryFrom, fmt, mem::size_of, prelude::v1::*};
use num::Bounded;
pub trait Duration: Sized + Copy + fmt::Display {
type Rep: TimeInt;
const PERIOD: Period;
fn new(value: Self::Rep) -> Self;
fn count(self) -> Self::Rep;
fn from_ticks<Rep: TimeInt>(ticks: Rep, period: Period) -> Result<Self, ConversionError>
where
Self::Rep: TryFrom<Rep>,
{
if size_of::<Self::Rep>() > size_of::<Rep>() {
let converted_ticks =
Self::Rep::try_from(ticks).map_err(|_| ConversionError::ConversionFailure)?;
if period > <Period>::new(1, 1) {
Ok(Self::new(TimeInt::checked_div_period(
&TimeInt::checked_mul_period(&converted_ticks, &period)?,
&Self::PERIOD,
)?))
} else {
Ok(Self::new(TimeInt::checked_mul_period(
&converted_ticks,
&period.checked_div(&Self::PERIOD)?,
)?))
}
} else {
let ticks = if period > <Period>::new(1, 1) {
TimeInt::checked_div_period(
&TimeInt::checked_mul_period(&ticks, &period)?,
&Self::PERIOD,
)?
} else if Self::PERIOD > <Period>::new(1, 1) {
TimeInt::checked_mul_period(
&TimeInt::checked_div_period(&ticks, &Self::PERIOD)?,
&period,
)?
} else {
TimeInt::checked_mul_period(&ticks, &period.checked_div(&Self::PERIOD)?)?
};
let converted_ticks =
Self::Rep::try_from(ticks).map_err(|_| ConversionError::ConversionFailure)?;
Ok(Self::new(converted_ticks))
}
}
fn into_ticks<Rep: TimeInt>(self, period: Period) -> Result<Rep, ConversionError>
where
Self::Rep: TimeInt,
Rep: TryFrom<Self::Rep>,
{
if size_of::<Rep>() > size_of::<Self::Rep>() {
let ticks =
Rep::try_from(self.count()).map_err(|_| ConversionError::ConversionFailure)?;
if period > <Period>::new(1, 1) {
TimeInt::checked_div_period(
&TimeInt::checked_mul_period(&ticks, &Self::PERIOD)?,
&period,
)
} else {
TimeInt::checked_mul_period(&ticks, &Self::PERIOD.checked_div(&period)?)
}
} else {
let ticks = if Self::PERIOD > <Period>::new(1, 1) {
TimeInt::checked_div_period(
&TimeInt::checked_mul_period(&self.count(), &Self::PERIOD)?,
&period,
)?
} else {
TimeInt::checked_mul_period(&self.count(), &Self::PERIOD.checked_div(&period)?)?
};
Rep::try_from(ticks).map_err(|_| ConversionError::ConversionFailure)
}
}
#[must_use]
fn min_value() -> Self::Rep {
Self::Rep::min_value()
}
#[must_use]
fn max_value() -> Self::Rep {
Self::Rep::max_value()
}
}
pub trait TryConvertFrom<Source>: Sized {
fn try_convert_from(other: Source) -> Result<Self, ConversionError>;
}
pub trait TryConvertInto<Dest> {
fn try_convert_into(self) -> Result<Dest, ConversionError>;
}
impl<Source: Duration, Dest: Duration> TryConvertFrom<Source> for Dest
where
Dest::Rep: TryFrom<Source::Rep>,
{
fn try_convert_from(source: Source) -> Result<Self, ConversionError> {
Self::from_ticks(source.count(), Source::PERIOD)
}
}
impl<Source, Dest> TryConvertInto<Dest> for Source
where
Source: Duration,
Dest: Duration + TryConvertFrom<Source>,
{
fn try_convert_into(self) -> Result<Dest, ConversionError> {
Dest::try_convert_from(self)
}
}
#[doc(hidden)]
pub mod units {
use crate::{
duration::{Duration, TryConvertFrom},
period::Period,
time_int::TimeInt,
ConversionError,
};
use core::{
cmp,
convert::{self, TryInto as _},
fmt::{self, Formatter},
ops,
};
macro_rules! impl_duration {
( $name:ident, ($numer:expr, $denom:expr) ) => {
#[derive(Copy, Clone, Debug, Eq, Ord)]
pub struct $name<T: TimeInt = u32>(pub T);
impl<Rep: TimeInt> Duration for $name<Rep> {
type Rep = Rep;
const PERIOD: Period = <Period>::new($numer, $denom);
fn new(value: Self::Rep) -> Self {
Self(value)
}
fn count(self) -> Self::Rep {
self.0
}
}
impl<T: TimeInt> fmt::Display for $name<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl<Rep, RhsDur> ops::Add<RhsDur> for $name<Rep>
where
RhsDur: Duration,
RhsDur::Rep: TimeInt,
Rep: TimeInt + convert::TryFrom<RhsDur::Rep>,
{
type Output = Self;
#[inline]
fn add(self, rhs: RhsDur) -> Self::Output {
Self(self.count() + Self::try_convert_from(rhs).unwrap().count())
}
}
impl<Rep, RhsDur> ops::Sub<RhsDur> for $name<Rep>
where
Rep: TimeInt + convert::TryFrom<RhsDur::Rep>,
RhsDur: Duration,
{
type Output = Self;
#[inline]
fn sub(self, rhs: RhsDur) -> Self::Output {
Self(self.count() - Self::try_convert_from(rhs).unwrap().count())
}
}
impl<Rep, Dur> ops::Rem<Dur> for $name<Rep>
where
Rep: TimeInt + convert::TryFrom<Dur::Rep>,
Dur: Duration,
{
type Output = Self;
fn rem(self, rhs: Dur) -> Self::Output {
let rhs = <Self as TryConvertFrom<Dur>>::try_convert_from(rhs)
.unwrap()
.count();
if rhs > Rep::from(0) {
Self(self.count() % rhs)
} else {
Self(Rep::from(0))
}
}
}
impl<Rep, OtherDur> cmp::PartialEq<OtherDur> for $name<Rep>
where
Rep: TimeInt + convert::TryFrom<OtherDur::Rep>,
OtherDur: Duration,
OtherDur::Rep: convert::TryFrom<Rep>,
{
fn eq(&self, other: &OtherDur) -> bool {
if Self::PERIOD < OtherDur::PERIOD {
self.count() == Self::try_convert_from(*other).unwrap().count()
} else {
OtherDur::try_convert_from(*self).unwrap().count() == other.count()
}
}
}
impl<Rep, OtherDur> PartialOrd<OtherDur> for $name<Rep>
where
Rep: TimeInt + convert::TryFrom<OtherDur::Rep>,
OtherDur: Duration,
OtherDur::Rep: convert::TryFrom<Rep>,
{
fn partial_cmp(&self, other: &OtherDur) -> Option<core::cmp::Ordering> {
if Self::PERIOD < OtherDur::PERIOD {
Some(
self.count()
.cmp(&Self::try_convert_from(*other).unwrap().count()),
)
} else {
Some(
OtherDur::try_convert_from(*self)
.unwrap()
.count()
.cmp(&other.count()),
)
}
}
}
};
( $name:ident, ($numer:expr, $denom:expr), ge_secs ) => {
impl_duration![$name, ($numer, $denom)];
impl<Rep: TimeInt> convert::TryFrom<$name<Rep>> for core::time::Duration {
type Error = ConversionError;
fn try_from(duration: $name<Rep>) -> Result<Self, Self::Error> {
let seconds = Seconds::<u64>::try_convert_from(duration)?;
Ok(Self::from_secs(seconds.count()))
}
}
impl<Rep: TimeInt> convert::TryFrom<core::time::Duration> for $name<Rep> {
type Error = ConversionError;
fn try_from(core_duration: core::time::Duration) -> Result<Self, Self::Error> {
let seconds = Seconds(core_duration.as_secs());
Self::try_convert_from(seconds)
}
}
};
( $name:ident, ($numer:expr, $denom:expr), $from_core_dur:ident, $as_core_dur:ident ) => {
impl_duration![$name, ($numer, $denom)];
impl<Rep: TimeInt> convert::TryFrom<$name<Rep>> for core::time::Duration {
type Error = ConversionError;
fn try_from(duration: $name<Rep>) -> Result<Self, Self::Error> {
Ok(Self::$from_core_dur(duration.count().into()))
}
}
impl<Rep: TimeInt> convert::TryFrom<core::time::Duration> for $name<Rep> {
type Error = ConversionError;
fn try_from(core_duration: core::time::Duration) -> Result<Self, Self::Error> {
Ok(Self(
core_duration
.$as_core_dur()
.try_into()
.map_err(|_| ConversionError::ConversionFailure)?,
))
}
}
};
}
impl_duration![Hours, (3600, 1), ge_secs];
impl_duration![Minutes, (60, 1), ge_secs];
impl_duration![Seconds, (1, 1), ge_secs];
impl_duration![Milliseconds, (1, 1_000), from_millis, as_millis];
impl_duration![Microseconds, (1, 1_000_000), from_micros, as_micros];
impl_duration![Nanoseconds, (1, 1_000_000_000), from_nanos, as_nanos];
}
#[cfg(test)]
mod tests {
use super::*;
use core::convert::TryInto;
use units::*;
#[test]
fn check_for_overflows() {
let mut time = 1_u64;
time *= 60;
assert_eq!(Minutes(time), Hours(1_u32));
time *= 60;
assert_eq!(Seconds(time), Hours(1_u32));
time *= 1000;
assert_eq!(Milliseconds(time), Hours(1_u32));
time *= 1000;
assert_eq!(Microseconds(time), Hours(1_u32));
time *= 1000;
assert_eq!(Nanoseconds(time), Hours(1_u32));
}
#[test]
fn remainder() {
assert_eq!(Minutes(62_u32) % Hours(1_u32), Minutes(2_u32));
assert_eq!(Minutes(62_u32) % Milliseconds(1_u32), Minutes(0_u32));
assert_eq!(Minutes(62_u32) % Minutes(60_u32), Minutes(2_u32));
}
#[test]
fn convert_from_core_duration() {
let core_duration = core::time::Duration::from_nanos(5_025_678_901_234);
assert_eq!(
core_duration.try_into(),
Ok(Nanoseconds::<u64>(5_025_678_901_234))
);
assert_eq!(
core_duration.try_into(),
Ok(Microseconds::<u64>(5_025_678_901))
);
assert_eq!(core_duration.try_into(), Ok(Milliseconds::<u32>(5_025_678)));
assert_eq!(core_duration.try_into(), Ok(Seconds::<u32>(5_025)));
assert_eq!(core_duration.try_into(), Ok(Minutes::<u32>(83)));
assert_eq!(core_duration.try_into(), Ok(Hours::<u32>(1)));
}
#[test]
fn convert_to_core_duration() {
assert_eq!(
Nanoseconds(123_u32).try_into(),
Ok(core::time::Duration::from_nanos(123))
);
assert_eq!(
Microseconds(123_u32).try_into(),
Ok(core::time::Duration::from_micros(123))
);
assert_eq!(
Milliseconds(123_u32).try_into(),
Ok(core::time::Duration::from_millis(123))
);
assert_eq!(
Seconds(123_u32).try_into(),
Ok(core::time::Duration::from_secs(123))
);
assert_eq!(
Minutes(123_u32).try_into(),
Ok(core::time::Duration::from_secs(123 * 60))
);
assert_eq!(
Hours(123_u32).try_into(),
Ok(core::time::Duration::from_secs(123 * 3600))
);
}
}