use std::ops;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Millis(pub u32);
impl Millis {
pub const ZERO: Millis = Millis(0);
pub const ONE_SEC: Millis = Millis(1_000);
#[inline]
pub const fn from_secs(secs: u32) -> Millis {
Millis(secs.saturating_mul(1000))
}
#[inline]
pub const fn is_zero(&self) -> bool {
self.0 == 0
}
#[inline]
pub const fn non_zero(self) -> bool {
self.0 != 0
}
#[inline]
pub fn map<F, R>(&self, f: F) -> Option<R>
where
F: FnOnce(Millis) -> R,
{
if self.0 == 0 { None } else { Some(f(*self)) }
}
}
impl Default for Millis {
#[inline]
fn default() -> Millis {
Millis::ZERO
}
}
impl ops::Add<Millis> for Millis {
type Output = Millis;
#[inline]
fn add(self, other: Millis) -> Millis {
Millis(self.0.saturating_add(other.0))
}
}
impl ops::Add<Seconds> for Millis {
type Output = Millis;
#[inline]
#[allow(clippy::suspicious_arithmetic_impl)]
fn add(self, other: Seconds) -> Millis {
Millis(
self.0
.saturating_add((u32::from(other.0)).saturating_mul(1000)),
)
}
}
impl ops::Add<std::time::Duration> for Millis {
type Output = Millis;
#[inline]
fn add(self, other: std::time::Duration) -> Millis {
self + Millis::from(other)
}
}
impl ops::Add<Millis> for std::time::Duration {
type Output = std::time::Duration;
#[inline]
fn add(self, other: Millis) -> std::time::Duration {
self + Self::from(other)
}
}
impl From<u32> for Millis {
#[inline]
fn from(s: u32) -> Millis {
Millis(s)
}
}
impl From<i32> for Millis {
#[inline]
fn from(i: i32) -> Millis {
Millis(u32::try_from(i).unwrap_or_else(|_| negative("Millis", i)))
}
}
impl From<usize> for Millis {
#[inline]
fn from(s: usize) -> Millis {
Self(s.try_into().unwrap_or_else(|_| {
log::error!("Values is too large {s:?}");
u32::MAX
}))
}
}
impl From<Seconds> for Millis {
#[inline]
fn from(s: Seconds) -> Millis {
Millis((u32::from(s.0)).saturating_mul(1000))
}
}
impl From<std::time::Duration> for Millis {
#[inline]
fn from(d: std::time::Duration) -> Millis {
let millis = d.as_millis() + u128::from(!d.subsec_nanos().is_multiple_of(1_000_000));
Self(millis.try_into().unwrap_or_else(|_| {
log::error!("Duration is too large for Millis: {d:?}");
u32::MAX
}))
}
}
impl From<Millis> for std::time::Duration {
#[inline]
fn from(d: Millis) -> std::time::Duration {
std::time::Duration::from_millis(u64::from(d.0))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Seconds(pub u16);
impl Seconds {
pub const ZERO: Seconds = Seconds(0);
pub const ONE: Seconds = Seconds(1);
#[inline]
pub const fn new(secs: u16) -> Seconds {
Seconds(secs)
}
#[inline]
pub const fn checked_new(secs: usize) -> Seconds {
let secs = if (u16::MAX as usize) < secs {
u16::MAX
} else {
secs as u16
};
Seconds(secs)
}
#[inline]
pub const fn is_zero(self) -> bool {
self.0 == 0
}
#[inline]
pub const fn non_zero(self) -> bool {
self.0 != 0
}
#[inline]
pub const fn seconds(self) -> u64 {
self.0 as u64
}
#[inline]
pub fn map<F, R>(&self, f: F) -> Option<R>
where
F: FnOnce(Millis) -> R,
{
if self.0 == 0 {
None
} else {
Some(f(Millis::from(*self)))
}
}
}
impl Default for Seconds {
#[inline]
fn default() -> Seconds {
Seconds::ZERO
}
}
impl ops::Add<Seconds> for Seconds {
type Output = Seconds;
#[inline]
fn add(self, other: Seconds) -> Seconds {
Seconds(self.0.saturating_add(other.0))
}
}
impl From<Seconds> for std::time::Duration {
#[inline]
fn from(d: Seconds) -> std::time::Duration {
std::time::Duration::from_secs(u64::from(d.0))
}
}
impl From<u16> for Seconds {
#[inline]
fn from(secs: u16) -> Seconds {
Self(secs)
}
}
impl From<i32> for Seconds {
#[inline]
fn from(i: i32) -> Seconds {
Seconds(if i < 0 {
negative("Seconds", i)
} else {
(i as u32).min(u32::from(u16::MAX)) as u16
})
}
}
#[cold]
#[track_caller]
fn negative<T: Default>(kind: &str, val: i32) -> T {
log::error!("Negative {kind} value {val}, using zero");
debug_assert!(val >= 0, "Negative {kind} value {val}");
T::default()
}
impl From<usize> for Seconds {
#[inline]
fn from(secs: usize) -> Seconds {
Self(secs.try_into().unwrap_or_else(|_| {
log::error!("Seconds value is too large {secs:?}");
u16::MAX
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn time_types() {
let m = Millis::default();
assert_eq!(m.0, 0);
let m = Millis::from(10u32);
assert_eq!(m.0, 10);
let m = Millis::from(10usize);
assert_eq!(m.0, 10);
let m = Millis(10) + Millis(20);
assert_eq!(m.0, 30);
let m = Millis(10) + Millis(u32::MAX);
assert_eq!(m.0, u32::MAX);
let m = Millis(10) + Seconds(1);
assert_eq!(m.0, 1010);
let m = Millis(u32::MAX) + Seconds(1);
assert_eq!(m.0, u32::MAX);
let m = Millis(10) + Duration::from_millis(100);
assert_eq!(m.0, 110);
let m = Duration::from_millis(100) + Millis(10);
assert_eq!(m, Duration::from_millis(110));
let m = Millis::from(Seconds(1));
assert_eq!(m.0, 1000);
let m = Millis::from(Duration::from_secs(1));
assert_eq!(m.0, 1000);
let m = Millis::from(Duration::from_secs(u64::MAX));
assert_eq!(m.0, u32::MAX);
let m = Millis::from_secs(1);
assert_eq!(m.0, 1000);
assert_eq!(Millis::from_secs(u32::MAX).0, u32::MAX);
let m = Millis(0);
assert_eq!(m.map(|m| m + Millis(1)), None);
let m = Millis(1);
assert_eq!(m.map(|m| m + Millis(1)), Some(Millis(2)));
let s = Seconds::new(10);
assert_eq!(s.0, 10);
let s = Seconds::from(10u16);
assert_eq!(s.0, 10);
let s = Seconds::from(10usize);
assert_eq!(s.0, 10);
let s = Seconds::from(10i32);
assert_eq!(s.0, 10);
assert_eq!(Seconds::from(i32::MAX).0, u16::MAX);
let s = Seconds::checked_new(10);
assert_eq!(s.0, 10);
let s = Seconds::checked_new(u16::MAX as usize + 10);
assert_eq!(s.0, u16::MAX);
assert!(Seconds::ZERO.is_zero());
assert!(!Seconds::ZERO.non_zero());
let s = Seconds::new(10);
assert_eq!(s.seconds(), 10);
let s = Seconds::default();
assert_eq!(s.0, 0);
let s = Seconds::new(10) + Seconds::new(10);
assert_eq!(s.seconds(), 20);
assert_eq!(Seconds(0).map(|_| 1usize), None);
assert_eq!(Seconds(2).map(|_| 1usize), Some(1));
let d = Duration::from(Seconds(100));
assert_eq!(d.as_secs(), 100);
}
#[test]
fn time_types_conversions() {
assert!(Millis(1).non_zero());
assert!(!Millis::ZERO.non_zero());
assert_eq!(Millis::from(0i32), Millis::ZERO);
assert_eq!(Seconds::from(0i32), Seconds::ZERO);
assert_eq!(Millis::from(5i32), Millis(5));
assert_eq!(Millis::from(i32::MAX), Millis(i32::MAX as u32));
assert_eq!(Millis::from(7usize), Millis(7));
assert_eq!(Seconds::from(7usize), Seconds(7));
#[cfg(target_pointer_width = "64")]
{
assert_eq!(Millis::from(usize::MAX), Millis(u32::MAX));
assert_eq!(Seconds::from(usize::MAX), Seconds(u16::MAX));
}
}
#[test]
fn negative_integer_conversions() {
fn check<T: PartialEq + std::fmt::Debug>(f: impl FnOnce() -> T, zero: &T) {
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f));
if cfg!(debug_assertions) {
assert!(res.is_err());
} else {
assert_eq!(&res.unwrap(), zero);
}
}
for val in [-1, i32::MIN] {
check(|| Millis::from(val), &Millis::ZERO);
check(|| Seconds::from(val), &Seconds::ZERO);
}
}
#[test]
fn duration_conversion_rounds_up() {
assert_eq!(Millis::from(Duration::ZERO), Millis::ZERO);
assert_eq!(Millis::from(Duration::from_nanos(1)), Millis(1));
assert_eq!(Millis::from(Duration::from_micros(500)), Millis(1));
assert_eq!(Millis::from(Duration::from_millis(1)), Millis(1));
assert_eq!(Millis::from(Duration::from_secs(1)), Millis(1_000));
assert_eq!(Millis::from(Duration::from_micros(1_500)), Millis(2));
assert_eq!(Millis::from(Duration::from_nanos(1_000_001)), Millis(2));
let max = Duration::from_millis(u64::from(u32::MAX));
assert_eq!(Millis::from(max), Millis(u32::MAX));
assert_eq!(
Millis::from(max + Duration::from_nanos(1)),
Millis(u32::MAX)
);
}
}