pub use crate::fraction::Fraction;
use crate::{
fixed_point::{self, FixedPoint},
rate,
time_int::TimeInt,
ConversionError,
};
use core::{
convert::TryFrom,
hash::{Hash, Hasher},
mem::size_of,
prelude::v1::*,
};
#[doc(hidden)]
pub use fixed_point::FixedPoint as _;
use num::{CheckedDiv, CheckedMul};
#[doc(inline)]
pub use units::*;
pub trait Duration: Sized + Copy {
fn to_generic<DestInt: TimeInt>(
self,
scaling_factor: Fraction,
) -> Result<Generic<DestInt>, ConversionError>
where
Self: FixedPoint,
DestInt: TryFrom<Self::T>,
{
Ok(Generic::<DestInt>::new(
self.into_ticks(scaling_factor)?,
scaling_factor,
))
}
fn to_rate<Rate: rate::Rate>(&self) -> Result<Rate, ConversionError>
where
Rate: FixedPoint,
Self: FixedPoint,
Rate::T: TryFrom<Self::T>,
{
let conversion_factor = Self::SCALING_FACTOR
.checked_mul(&Rate::SCALING_FACTOR)
.ok_or(ConversionError::Unspecified)?
.recip();
if size_of::<Self::T>() >= size_of::<Rate::T>() {
fixed_point::FixedPoint::from_ticks(
Self::T::from(*conversion_factor.numerator())
.checked_div(
&self
.integer()
.checked_mul(&Self::T::from(*conversion_factor.denominator()))
.ok_or(ConversionError::Overflow)?,
)
.ok_or(ConversionError::DivByZero)?,
Rate::SCALING_FACTOR,
)
} else {
fixed_point::FixedPoint::from_ticks(
Rate::T::from(*conversion_factor.numerator())
.checked_div(
&Rate::T::try_from(self.integer())
.map_err(|_| ConversionError::Overflow)?
.checked_mul(&Rate::T::from(*conversion_factor.denominator()))
.ok_or(ConversionError::Overflow)?,
)
.ok_or(ConversionError::DivByZero)?,
Rate::SCALING_FACTOR,
)
}
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct Generic<T> {
integer: T,
scaling_factor: Fraction,
}
impl<T: TimeInt> PartialOrd<Generic<T>> for Generic<T> {
fn partial_cmp(&self, rhs: &Generic<T>) -> Option<core::cmp::Ordering> {
Some(
self.integer
.checked_mul_fraction(&self.scaling_factor)?
.cmp(&rhs.integer.checked_mul_fraction(&rhs.scaling_factor)?),
)
}
}
impl<T: TimeInt> Ord for Generic<T> {
fn cmp(&self, rhs: &Generic<T>) -> core::cmp::Ordering {
if let Some(v) = self.partial_cmp(rhs) {
v
} else {
panic!("Cmp failed")
}
}
}
impl<T: TimeInt> PartialEq<Generic<T>> for Generic<T> {
fn eq(&self, rhs: &Generic<T>) -> bool {
self.partial_cmp(rhs) == Some(core::cmp::Ordering::Equal)
}
}
impl<T: TimeInt> Eq for Generic<T> {}
impl<T: TimeInt + Hash> Hash for Generic<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
if let Some(v) = self.integer.checked_mul_fraction(&self.scaling_factor) {
v.hash(state);
}
}
}
impl<T: TimeInt> Generic<T> {
pub fn new(integer: T, scaling_factor: Fraction) -> Self {
Self {
integer,
scaling_factor,
}
}
pub fn integer(&self) -> T {
self.integer
}
pub fn scaling_factor(&self) -> &Fraction {
&self.scaling_factor
}
}
impl<T: TimeInt> Duration for Generic<T> {}
#[doc(hidden)]
pub mod units {
use super::*;
use crate::{
fixed_point::{self, FixedPoint},
fraction::Fraction,
time_int::TimeInt,
ConversionError,
};
use core::{
cmp,
convert::{TryFrom, TryInto},
fmt::{self, Formatter},
ops,
};
#[doc(hidden)]
pub use Extensions as _;
macro_rules! impl_duration {
( $name:ident, ($numer:expr, $denom:expr) ) => {
#[derive(Copy, Clone, Eq, Ord, Hash, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct $name<T: TimeInt = u32>(pub T);
impl<T: TimeInt> $name<T> {
pub fn new(value: T) -> Self {
Self(value)
}
}
impl<T: TimeInt> Duration for $name<T> {}
impl<T: TimeInt> FixedPoint for $name<T> {
type T = T;
const SCALING_FACTOR: Fraction = Fraction::new($numer, $denom);
fn new(value: Self::T) -> Self {
Self(value)
}
fn integer(&self) -> Self::T {
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<T: TimeInt, Rhs: Duration> ops::Add<Rhs> for $name<T>
where
Rhs: FixedPoint,
Self: TryFrom<Rhs>,
{
type Output = Self;
fn add(self, rhs: Rhs) -> Self::Output {
<Self as FixedPoint>::add(self, rhs)
}
}
impl<T: TimeInt, Rhs: Duration> ops::Sub<Rhs> for $name<T>
where
Self: TryFrom<Rhs>,
Rhs: FixedPoint,
{
type Output = Self;
fn sub(self, rhs: Rhs) -> Self::Output {
<Self as FixedPoint>::sub(self, rhs)
}
}
impl<T: TimeInt, Clock: crate::Clock> ops::Add<crate::Instant<Clock>> for $name<T>
where
Clock::T: TryFrom<T>,
{
type Output = crate::Instant<Clock>;
fn add(self, rhs: crate::Instant<Clock>) -> Self::Output {
if let Some(v) = rhs.checked_add(self) {
v
} else {
panic!("Add failed")
}
}
}
impl<T: TimeInt> ops::Mul<T> for $name<T> {
type Output = Self;
fn mul(self, rhs: T) -> Self::Output {
<Self as FixedPoint>::mul(self, rhs)
}
}
impl<T: TimeInt> ops::Div<T> for $name<T> {
type Output = Self;
fn div(self, rhs: T) -> Self::Output {
<Self as FixedPoint>::div(self, rhs)
}
}
impl<T: TimeInt, Rhs: Duration> ops::Rem<Rhs> for $name<T>
where
Self: TryFrom<Rhs>,
Rhs: FixedPoint,
{
type Output = Self;
fn rem(self, rhs: Rhs) -> Self::Output {
<Self as FixedPoint>::rem(self, rhs)
}
}
impl<SourceInt: TimeInt, DestInt: TimeInt> TryFrom<Generic<SourceInt>>
for $name<DestInt>
where
DestInt: TryFrom<SourceInt>,
{
type Error = ConversionError;
fn try_from(generic_duration: Generic<SourceInt>) -> Result<Self, Self::Error> {
fixed_point::FixedPoint::from_ticks(
generic_duration.integer,
generic_duration.scaling_factor,
)
}
}
impl<T: TimeInt> From<$name<T>> for Generic<T> {
fn from(duration: $name<T>) -> Self {
Self::new(duration.integer(), $name::<T>::SCALING_FACTOR)
}
}
};
( $name:ident, ($numer:expr, $denom:expr), ge_secs ) => {
impl_duration![$name, ($numer, $denom)];
impl TryFrom<$name<u32>> for core::time::Duration {
type Error = ConversionError;
fn try_from(duration: $name<u32>) -> Result<Self, Self::Error> {
let seconds: Seconds<u64> = duration.into();
Ok(Self::from_secs(seconds.integer()))
}
}
impl TryFrom<core::time::Duration> for $name<u32> {
type Error = ConversionError;
fn try_from(core_duration: core::time::Duration) -> Result<Self, Self::Error> {
let seconds = Seconds(core_duration.as_secs());
seconds.try_into()
}
}
impl From<core::time::Duration> for $name<u64> {
fn from(core_duration: core::time::Duration) -> Self {
let seconds = Seconds(core_duration.as_secs());
seconds.into()
}
}
};
( $name:ident, ($numer:expr, $denom:expr), $from_core_dur:ident, $as_core_dur:ident ) => {
impl_duration![$name, ($numer, $denom)];
impl<T: TimeInt> TryFrom<$name<T>> for core::time::Duration
where
u64: From<T>,
{
type Error = ConversionError;
fn try_from(duration: $name<T>) -> Result<Self, Self::Error> {
Ok(Self::$from_core_dur(duration.integer().into()))
}
}
impl<T: TimeInt> TryFrom<core::time::Duration> for $name<T>
where
T: TryFrom<u128>,
{
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];
macro_rules! impl_partial_eq {
($name:ident) => {
impl<T: TimeInt, RhsInt: TimeInt> cmp::PartialEq<$name<RhsInt>> for $name<T>
where
T: TryFrom<RhsInt>,
{
fn eq(&self, rhs: &$name<RhsInt>) -> bool {
match T::try_from(rhs.integer()) {
Ok(rhs_integer) => self.integer() == rhs_integer,
Err(_) => false,
}
}
}
};
}
impl_partial_eq![Hours];
impl_partial_eq![Minutes];
impl_partial_eq![Seconds];
impl_partial_eq![Milliseconds];
impl_partial_eq![Microseconds];
impl_partial_eq![Nanoseconds];
macro_rules! impl_big_partial_eq_small {
($big:ident) => {};
($big:ident, $($small:ident),+) => {
$(
impl<T: TimeInt, RhsInt: TimeInt> cmp::PartialEq<$small<RhsInt>> for $big<T>
where
$small<RhsInt>: TryFrom<Self>,
{
fn eq(&self, rhs: &$small<RhsInt>) -> bool {
match $small::<RhsInt>::try_from(*self) {
Ok(lhs) => lhs.integer() == rhs.integer(),
Err(_) => false,
}
}
}
)+
impl_big_partial_eq_small![$($small),+];
};
}
impl_big_partial_eq_small![
Hours,
Minutes,
Seconds,
Milliseconds,
Microseconds,
Nanoseconds
];
macro_rules! impl_small_partial_eq_big {
($small:ident) => {};
($small:ident, $($big:ident),+) => {
$(
impl<T: TimeInt, RhsInt: TimeInt> cmp::PartialEq<$big<RhsInt>> for $small<T>
where
Self: TryFrom<$big<RhsInt>>,
{
fn eq(&self, rhs: &$big<RhsInt>) -> bool {
match Self::try_from(*rhs) {
Ok(rhs) => self.integer() == rhs.integer(),
Err(_) => false,
}
}
}
)+
impl_small_partial_eq_big![$($big),+];
};
}
impl_small_partial_eq_big![
Nanoseconds,
Microseconds,
Milliseconds,
Seconds,
Minutes,
Hours
];
macro_rules! impl_partial_ord {
($name:ident) => {
impl<T: TimeInt, RhsInt: TimeInt> PartialOrd<$name<RhsInt>> for $name<T>
where
T: TryFrom<RhsInt>,
{
fn partial_cmp(&self, rhs: &$name<RhsInt>) -> Option<core::cmp::Ordering> {
match T::try_from(rhs.integer()) {
Ok(rhs_integer) => Some(self.integer().cmp(&rhs_integer)),
Err(_) => Some(core::cmp::Ordering::Less),
}
}
}
};
}
impl_partial_ord![Hours];
impl_partial_ord![Minutes];
impl_partial_ord![Seconds];
impl_partial_ord![Milliseconds];
impl_partial_ord![Microseconds];
impl_partial_ord![Nanoseconds];
macro_rules! impl_big_partial_ord_small {
($big:ident) => {};
($big:ident, $($small:ident),+) => {
$(
impl<T: TimeInt, RhsInt: TimeInt> PartialOrd<$small<RhsInt>> for $big<T>
where
$small<RhsInt>: TryFrom<Self>,
{
fn partial_cmp(&self, rhs: &$small<RhsInt>) -> Option<core::cmp::Ordering> {
match $small::<RhsInt>::try_from(*self) {
Ok(lhs) => Some(lhs.integer().cmp(&rhs.integer())),
Err(_) => Some(core::cmp::Ordering::Greater),
}
}
}
)+
impl_big_partial_ord_small![$($small),+];
};
}
impl_big_partial_ord_small![
Hours,
Minutes,
Seconds,
Milliseconds,
Microseconds,
Nanoseconds
];
macro_rules! impl_small_partial_ord_big {
($small:ident) => {};
($small:ident, $($big:ident),+) => {
$(
impl<T: TimeInt, RhsInt: TimeInt> PartialOrd<$big<RhsInt>> for $small<T>
where
Self: TryFrom<$big<RhsInt>>,
{
fn partial_cmp(&self, rhs: &$big<RhsInt>) -> Option<core::cmp::Ordering> {
match Self::try_from(*rhs) {
Ok(rhs) => Some(self.integer().cmp(&rhs.integer())),
Err(_) => Some(core::cmp::Ordering::Less),
}
}
}
)+
impl_small_partial_ord_big![$($big),+];
};
}
impl_small_partial_ord_big![
Nanoseconds,
Microseconds,
Milliseconds,
Seconds,
Minutes,
Hours
];
macro_rules! impl_from {
($name:ident) => {
impl From<$name<u32>> for $name<u64> {
fn from(source: $name<u32>) -> Self {
Self::new(u64::from(source.integer()))
}
}
impl TryFrom<$name<u64>> for $name<u32> {
type Error = ConversionError;
fn try_from(source: $name<u64>) -> Result<Self, Self::Error> {
fixed_point::FixedPoint::from_ticks(
source.integer(),
$name::<u64>::SCALING_FACTOR,
)
}
}
};
}
impl_from![Hours];
impl_from![Minutes];
impl_from![Seconds];
impl_from![Milliseconds];
impl_from![Microseconds];
impl_from![Nanoseconds];
macro_rules! impl_from_smaller {
($name:ident) => {};
($big:ident, $($small:ident),+) => {
$(
impl<T: TimeInt> From<$small<T>> for $big<T>
{
fn from(small: $small<T>) -> Self {
if let Ok(v) = fixed_point::FixedPoint::from_ticks(small.integer(), $small::<T>::SCALING_FACTOR) {
v
} else {
panic!("From failed")
}
}
}
impl From<$small<u32>> for $big<u64>
{
fn from(small: $small<u32>) -> Self {
if let Ok(v) = fixed_point::FixedPoint::from_ticks(small.integer(), $small::<u32>::SCALING_FACTOR) {
v
} else {
panic!("From failed")
}
}
}
impl TryFrom<$small<u64>> for $big<u32>
{
type Error = ConversionError;
fn try_from(small: $small<u64>) -> Result<Self, Self::Error> {
fixed_point::FixedPoint::from_ticks(
small.integer(),
$small::<u64>::SCALING_FACTOR,
)
}
}
)+
impl_from_smaller![$($small),+];
};
}
impl_from_smaller![
Hours,
Minutes,
Seconds,
Milliseconds,
Microseconds,
Nanoseconds
];
macro_rules! impl_from_bigger {
($small:ident) => {};
($small:ident, $($big:ident),+) => {
$(
impl From<$big<u32>> for $small<u64>
{
fn from(big: $big<u32>) -> Self {
if let Ok(v) = fixed_point::FixedPoint::from_ticks(big.integer(), $big::<u32>::SCALING_FACTOR) {
v
} else {
panic!("From failed")
}
}
}
impl<T: TimeInt> TryFrom<$big<T>> for $small<T>
{
type Error = ConversionError;
fn try_from(big: $big<T>) -> Result<Self, Self::Error> {
fixed_point::FixedPoint::from_ticks(
big.integer(),
$big::<T>::SCALING_FACTOR,
)
}
}
impl TryFrom<$big<u64>> for $small<u32>
{
type Error = ConversionError;
fn try_from(big: $big<u64>) -> Result<Self, Self::Error> {
fixed_point::FixedPoint::from_ticks(
big.integer(),
$big::<u64>::SCALING_FACTOR,
)
}
}
)+
impl_from_bigger![$($big),+];
};
}
impl_from_bigger![
Nanoseconds,
Microseconds,
Milliseconds,
Seconds,
Minutes,
Hours
];
pub trait Extensions: TimeInt {
fn nanoseconds(self) -> Nanoseconds<Self> {
Nanoseconds::new(self)
}
fn microseconds(self) -> Microseconds<Self> {
Microseconds::new(self)
}
fn milliseconds(self) -> Milliseconds<Self> {
Milliseconds::new(self)
}
fn seconds(self) -> Seconds<Self> {
Seconds::new(self)
}
fn minutes(self) -> Minutes<Self> {
Minutes::new(self)
}
fn hours(self) -> Hours<Self> {
Hours::new(self)
}
}
impl Extensions for u32 {}
}
#[cfg(test)]
mod tests {
use super::*;
}