use std::fmt;
use core::num::NonZero;
#[repr(transparent)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Timestamp(i64);
#[repr(transparent)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Duration(u64);
#[repr(transparent)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Delta(i64);
impl Timestamp {
pub const MIN: Self = Self(i64::MIN);
pub const MAX: Self = Self(i64::MAX);
pub const ZERO: Self = Self(0);
#[inline]
pub const fn new(ts: i64) -> Self {
Self(ts)
}
#[inline]
pub const fn get(self) -> i64 {
self.0
}
#[inline]
pub const fn is_zero(self) -> bool {
self.0 == 0
}
#[inline]
pub const fn is_negative(self) -> bool {
self.0.is_negative()
}
#[inline]
pub const fn is_positive(self) -> bool {
self.0.is_positive()
}
#[inline]
pub const fn checked_add(self, dur: Duration) -> Option<Self> {
match self.0.checked_add_unsigned(dur.0) {
Some(value) => Some(Self(value)),
_ => None,
}
}
#[inline]
pub const fn checked_sub(self, dur: Duration) -> Option<Self> {
match self.0.checked_sub_unsigned(dur.0) {
Some(value) => Some(Self(value)),
_ => None,
}
}
#[inline]
pub const fn saturating_add(self, dur: Duration) -> Self {
Self(self.0.saturating_add_unsigned(dur.0))
}
#[inline]
pub const fn saturating_sub(self, dur: Duration) -> Self {
Self(self.0.saturating_sub_unsigned(dur.0))
}
#[inline]
pub const fn checked_delta(self, other: Self) -> Option<Delta> {
match self.0.checked_sub(other.0) {
Some(delta) => Some(Delta(delta)),
_ => None,
}
}
#[inline]
pub const fn saturating_delta(self, other: Self) -> Delta {
Delta(self.0.saturating_sub(other.0))
}
#[inline]
pub const fn checked_add_delta(self, delta: Delta) -> Option<Self> {
match self.0.checked_add(delta.0) {
Some(value) => Some(Self(value)),
_ => None,
}
}
#[inline]
pub const fn checked_sub_delta(self, delta: Delta) -> Option<Self> {
match self.0.checked_sub(delta.0) {
Some(value) => Some(Self(value)),
_ => None,
}
}
#[inline]
pub const fn saturating_add_delta(self, delta: Delta) -> Self {
Self(self.0.saturating_add(delta.0))
}
#[inline]
pub const fn saturating_sub_delta(self, delta: Delta) -> Self {
Self(self.0.saturating_sub(delta.0))
}
#[inline]
pub const fn abs_delta(self, other: Self) -> Duration {
Duration(self.0.abs_diff(other.0))
}
#[inline]
pub const fn duration_from(self, other: Self) -> Option<Duration> {
match self.0.checked_sub(other.0) {
Some(diff) if !diff.is_negative() => Some(Duration(diff as u64)),
_ => None,
}
}
#[inline]
pub const fn duration_to(self, other: Self) -> Option<Duration> {
match other.0.checked_sub(self.0) {
Some(diff) if !diff.is_negative() => Some(Duration(diff as u64)),
_ => None,
}
}
#[inline]
pub const fn align_towards_zero(self, align_to: Duration) -> Option<Self> {
if align_to.is_zero() || align_to.0 > i64::MAX as u64 {
return None;
}
Some(Self((self.0 / align_to.0 as i64) * align_to.0 as i64))
}
#[inline]
pub const fn align_down(self, align_to: Duration) -> Option<Self> {
if align_to.is_zero() || align_to.0 > i64::MAX as u64 {
return None;
}
let dur_i64 = align_to.0 as i64;
match self.0.div_euclid(dur_i64).checked_mul(dur_i64) {
Some(snapped) => Some(Self(snapped)),
_ => None,
}
}
}
impl From<i64> for Timestamp {
fn from(value: i64) -> Self {
Self(value)
}
}
impl TryFrom<u64> for Timestamp {
type Error = std::num::TryFromIntError;
fn try_from(value: u64) -> Result<Self, Self::Error> {
let ts: i64 = value.try_into()?;
Ok(Self(ts))
}
}
impl From<i32> for Timestamp {
fn from(value: i32) -> Self {
Self(i64::from(value))
}
}
impl From<u32> for Timestamp {
fn from(value: u32) -> Self {
Self(i64::from(value))
}
}
impl From<i16> for Timestamp {
fn from(value: i16) -> Self {
Self(i64::from(value))
}
}
impl From<u16> for Timestamp {
fn from(value: u16) -> Self {
Self(i64::from(value))
}
}
impl From<i8> for Timestamp {
fn from(value: i8) -> Self {
Self(i64::from(value))
}
}
impl From<u8> for Timestamp {
fn from(value: u8) -> Self {
Self(i64::from(value))
}
}
impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl Duration {
pub const MIN: Self = Self(u64::MIN);
pub const MAX: Self = Self(u64::MAX);
pub const ZERO: Self = Self(0);
#[inline]
pub const fn new(dur: u64) -> Self {
Self(dur)
}
#[inline]
pub const fn get(self) -> u64 {
self.0
}
#[inline]
pub const fn is_zero(self) -> bool {
self.0 == 0
}
#[inline]
pub const fn checked_add(self, other: Self) -> Option<Self> {
match self.0.checked_add(other.0) {
Some(value) => Some(Self(value)),
_ => None,
}
}
#[inline]
pub const fn checked_div(self, rhs: u64) -> Option<Self> {
match self.0.checked_div(rhs) {
Some(value) => Some(Self(value)),
_ => None,
}
}
#[inline]
pub const fn checked_div_euclid(self, rhs: u64) -> Option<Self> {
match self.0.checked_div_euclid(rhs) {
Some(value) => Some(Self(value)),
_ => None,
}
}
#[inline]
pub const fn checked_mul(self, rhs: u64) -> Option<Self> {
match self.0.checked_mul(rhs) {
Some(value) => Some(Self(value)),
_ => None,
}
}
#[inline]
pub const fn checked_sub(self, other: Self) -> Option<Self> {
match self.0.checked_sub(other.0) {
Some(value) => Some(Self(value)),
_ => None,
}
}
#[inline]
pub const fn saturating_add(self, other: Self) -> Self {
Self(self.0.saturating_add(other.0))
}
#[inline]
pub const fn saturating_div(self, rhs: u64) -> Self {
Self(self.0.saturating_div(rhs))
}
#[inline]
pub const fn saturating_mul(self, rhs: u64) -> Self {
Self(self.0.saturating_mul(rhs))
}
#[inline]
pub const fn saturating_sub(self, other: Self) -> Self {
Self(self.0.saturating_sub(other.0))
}
#[inline]
pub const fn align_towards_zero(self, align_to: Self) -> Option<Self> {
if align_to.is_zero() {
return None;
}
Some(Self((self.0 / align_to.0) * align_to.0))
}
#[inline]
pub const fn align_down(self, align_to: Self) -> Option<Self> {
self.align_towards_zero(align_to)
}
#[inline]
pub const fn timestamp_from(self, from: Timestamp) -> Option<Timestamp> {
from.checked_add(self)
}
}
impl From<u64> for Duration {
fn from(value: u64) -> Self {
Self(value)
}
}
impl TryFrom<i64> for Duration {
type Error = std::num::TryFromIntError;
fn try_from(value: i64) -> Result<Self, Self::Error> {
let dur: u64 = value.try_into()?;
Ok(Self(dur))
}
}
impl From<u32> for Duration {
fn from(value: u32) -> Self {
Self(u64::from(value))
}
}
impl TryFrom<i32> for Duration {
type Error = std::num::TryFromIntError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
let dur: u64 = value.try_into()?;
Ok(Self(dur))
}
}
impl From<u16> for Duration {
fn from(value: u16) -> Self {
Self(u64::from(value))
}
}
impl TryFrom<i16> for Duration {
type Error = std::num::TryFromIntError;
fn try_from(value: i16) -> Result<Self, Self::Error> {
let dur: u64 = value.try_into()?;
Ok(Self(dur))
}
}
impl From<u8> for Duration {
fn from(value: u8) -> Self {
Self(u64::from(value))
}
}
impl TryFrom<i8> for Duration {
type Error = std::num::TryFromIntError;
fn try_from(value: i8) -> Result<Self, Self::Error> {
let dur: u64 = value.try_into()?;
Ok(Self(dur))
}
}
impl fmt::Display for Duration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl Delta {
pub const MIN: Self = Self(i64::MIN);
pub const MAX: Self = Self(i64::MAX);
pub const ZERO: Self = Self(0);
#[inline]
pub const fn get(self) -> i64 {
self.0
}
#[inline]
pub const fn is_zero(self) -> bool {
self.0 == 0
}
#[inline]
pub const fn is_negative(self) -> bool {
self.0.is_negative()
}
#[inline]
pub const fn is_positive(self) -> bool {
self.0.is_positive()
}
#[inline]
pub const fn checked_abs(self) -> Option<Self> {
match self.0.checked_abs() {
Some(abs) => Some(Self(abs)),
None => None,
}
}
#[inline]
pub const fn unsigned_abs(self) -> u64 {
self.0.unsigned_abs()
}
}
impl fmt::Display for Delta {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Time {
seconds: i64,
nanos: u32,
}
impl Time {
pub const MAX: Self = Time { seconds: i64::MAX, nanos: 999_999_999 };
pub const MIN: Self = Time { seconds: i64::MIN, nanos: 0 };
pub const ZERO: Self = Time { seconds: 0, nanos: 0 };
const MS_PER_SEC: i64 = 1_000;
const US_PER_SEC: i64 = 1_000_000;
const NS_PER_SEC: i64 = 1_000_000_000;
const NS_PER_SEC_128: i128 = 1_000_000_000;
const SECS_PER_HR: i64 = 60 * 60;
const SECS_PER_MIN: i64 = 60;
pub const fn try_new(s: i64, ns: u32) -> Option<Self> {
match ns {
0..1_000_000_000 => Some(Time { seconds: s, nanos: ns }),
_ => None,
}
}
pub fn try_from_nanos_i128(total_ns: i128) -> Option<Time> {
let seconds = total_ns.div_euclid(Self::NS_PER_SEC_128);
let nanos = total_ns.rem_euclid(Self::NS_PER_SEC_128);
seconds.try_into().ok().map(|seconds| Time { seconds, nanos: nanos as u32 })
}
pub fn try_from_nanos_u128(total_ns: u128) -> Option<Time> {
let seconds = total_ns.div_euclid(Self::NS_PER_SEC_128 as u128);
let nanos = total_ns.rem_euclid(Self::NS_PER_SEC_128 as u128);
seconds.try_into().ok().map(|seconds| Time { seconds, nanos: nanos as u32 })
}
pub fn try_from_secs_f64(total_secs: f64) -> Option<Time> {
if !total_secs.is_finite() {
return None;
}
let seconds = total_secs.floor();
if seconds < i64::MIN as f64 || seconds > i64::MAX as f64 {
return None;
}
let nanos = ((total_secs - seconds) * 1_000_000_000.0).round();
Some(if nanos >= 1_000_000_000.0 {
Time { seconds: (seconds as i64).saturating_add(1), nanos: 0 }
}
else {
Time { seconds: seconds as i64, nanos: nanos as u32 }
})
}
pub const fn from_nanos(total_ns: i64) -> Time {
let seconds = total_ns.div_euclid(Self::NS_PER_SEC);
let nanos = total_ns.rem_euclid(Self::NS_PER_SEC);
Time { seconds, nanos: nanos as u32 }
}
pub const fn from_nanos_u64(total_ns: u64) -> Time {
let seconds = total_ns.div_euclid(Self::NS_PER_SEC as u64);
let nanos = total_ns.rem_euclid(Self::NS_PER_SEC as u64);
Time { seconds: seconds as i64, nanos: nanos as u32 }
}
pub const fn from_micros(total_us: i64) -> Time {
let seconds = total_us.div_euclid(Self::US_PER_SEC);
let micros = total_us.rem_euclid(Self::US_PER_SEC);
Time { seconds, nanos: 1_000 * micros as u32 }
}
pub const fn from_micros_u64(total_us: u64) -> Time {
let seconds = total_us.div_euclid(Self::US_PER_SEC as u64);
let micros = total_us.rem_euclid(Self::US_PER_SEC as u64);
Time { seconds: seconds as i64, nanos: 1_000 * micros as u32 }
}
pub const fn from_millis(total_ms: i64) -> Time {
let seconds = total_ms.div_euclid(Self::MS_PER_SEC);
let millis = total_ms.rem_euclid(Self::MS_PER_SEC);
Time { seconds, nanos: 1_000_000 * millis as u32 }
}
pub const fn from_millis_u64(total_ms: u64) -> Time {
let seconds = total_ms.div_euclid(Self::MS_PER_SEC as u64);
let millis = total_ms.rem_euclid(Self::MS_PER_SEC as u64);
Time { seconds: seconds as i64, nanos: 1_000_000 * millis as u32 }
}
pub fn from_ss(s: u8, ns: u32) -> Option<Time> {
if s > 59 || ns >= Time::NS_PER_SEC as u32 {
return None;
}
let seconds = i64::from(s);
Some(Time { seconds, nanos: ns })
}
pub fn from_mmss(m: u8, s: u8, ns: u32) -> Option<Time> {
if m > 59 || s > 59 || ns >= Time::NS_PER_SEC as u32 {
return None;
}
let seconds = (Time::SECS_PER_MIN * i64::from(m)) + i64::from(s);
Some(Time { seconds, nanos: ns })
}
pub fn from_hhmmss(h: u32, m: u8, s: u8, ns: u32) -> Option<Time> {
if m > 59 || s > 59 || ns >= Time::NS_PER_SEC as u32 {
return None;
}
let seconds =
(Time::SECS_PER_HR * i64::from(h)) + (Time::SECS_PER_MIN * i64::from(m)) + i64::from(s);
Some(Time { seconds, nanos: ns })
}
#[inline]
pub const fn is_zero(&self) -> bool {
self.seconds == 0 && self.nanos == 0
}
#[inline]
pub const fn is_positive(&self) -> bool {
self.seconds.is_positive()
}
#[inline]
pub const fn is_negative(&self) -> bool {
self.seconds.is_negative()
}
#[inline]
pub fn as_nanos(&self) -> i128 {
1_000_000_000 * i128::from(self.seconds) + i128::from(self.nanos)
}
#[inline]
pub fn as_micros(&self) -> i128 {
self.as_nanos() / 1_000
}
#[inline]
pub fn as_millis(&self) -> i128 {
self.as_nanos() / 1_000_000
}
#[inline]
pub fn as_secs(&self) -> i64 {
if self.seconds >= 0 || self.nanos == 0 { self.seconds } else { self.seconds + 1 }
}
#[inline]
pub fn as_mins(&self) -> i64 {
self.as_secs() / 60
}
#[inline]
pub fn as_hours(&self) -> i64 {
self.as_secs() / (60 * 60)
}
#[inline]
pub fn as_secs_f64(&self) -> f64 {
self.seconds as f64 + self.nanos as f64 * 1e-9
}
pub fn parts(&self) -> (i64, u32) {
if self.seconds >= 0 {
(self.seconds, self.nanos)
}
else if self.nanos == 0 {
(self.seconds, 0)
}
else {
(self.seconds + 1, 1_000_000_000 - self.nanos)
}
}
pub fn checked_neg(self) -> Option<Self> {
if self.nanos == 0 {
Some(Self { seconds: self.seconds.checked_neg()?, nanos: 0 })
}
else {
let seconds = self.seconds.checked_neg()?.checked_sub(1)?;
Some(Self { seconds, nanos: 1_000_000_000 - self.nanos })
}
}
fn checked_add(self, delta_secs: i64, delta_nanos: i64) -> Option<Self> {
let seconds = self.seconds.checked_add(delta_secs)?;
let nanos = i64::from(self.nanos) + delta_nanos;
let time = if nanos >= Self::NS_PER_SEC {
Self { seconds: seconds.checked_add(1)?, nanos: (nanos - Self::NS_PER_SEC) as u32 }
}
else if nanos < 0 {
Self { seconds: seconds.checked_sub(1)?, nanos: (nanos + Self::NS_PER_SEC) as u32 }
}
else {
Self { seconds, nanos: nanos as u32 }
};
Some(time)
}
pub fn checked_add_nanos(self, nanos: i64) -> Option<Self> {
let add_secs = nanos / Self::NS_PER_SEC;
let add_nanos = nanos % Self::NS_PER_SEC;
self.checked_add(add_secs, add_nanos)
}
pub fn checked_add_micros(self, micros: i64) -> Option<Self> {
let add_secs = micros / Self::US_PER_SEC;
let add_micros = micros % Self::US_PER_SEC;
self.checked_add(add_secs, 1_000 * add_micros)
}
pub fn checked_add_millis(self, millis: i64) -> Option<Self> {
let add_secs = millis / Self::MS_PER_SEC;
let add_millis = millis % Self::MS_PER_SEC;
self.checked_add(add_secs, 1_000_000 * add_millis)
}
pub fn checked_add_secs(self, secs: i64) -> Option<Self> {
self.checked_add(secs, 0)
}
pub fn checked_add_mins(self, mins: i64) -> Option<Self> {
let add_secs = mins.checked_mul(Self::SECS_PER_MIN)?;
self.checked_add(add_secs, 0)
}
pub fn checked_add_hours(self, hours: i64) -> Option<Self> {
let add_secs = hours.checked_mul(Self::SECS_PER_HR)?;
self.checked_add(add_secs, 0)
}
fn checked_sub(self, delta_secs: i64, delta_nanos: i64) -> Option<Self> {
let seconds = self.seconds.checked_sub(delta_secs)?;
let nanos = i64::from(self.nanos) - delta_nanos;
let time = if nanos >= Self::NS_PER_SEC {
Self { seconds: seconds.checked_add(1)?, nanos: (nanos - Self::NS_PER_SEC) as u32 }
}
else if nanos < 0 {
Self { seconds: seconds.checked_sub(1)?, nanos: (nanos + Self::NS_PER_SEC) as u32 }
}
else {
Self { seconds, nanos: nanos as u32 }
};
Some(time)
}
pub fn checked_sub_nanos(self, nanos: i64) -> Option<Self> {
let add_secs = nanos / Self::NS_PER_SEC;
let add_nanos = nanos % Self::NS_PER_SEC;
self.checked_sub(add_secs, add_nanos)
}
pub fn checked_sub_micros(self, micros: i64) -> Option<Self> {
let add_secs = micros / Self::US_PER_SEC;
let add_micros = micros % Self::US_PER_SEC;
self.checked_sub(add_secs, 1_000 * add_micros)
}
pub fn checked_sub_millis(self, millis: i64) -> Option<Self> {
let add_secs = millis / Self::MS_PER_SEC;
let add_millis = millis % Self::MS_PER_SEC;
self.checked_sub(add_secs, 1_000_000 * add_millis)
}
pub fn checked_sub_secs(self, secs: i64) -> Option<Self> {
self.checked_sub(secs, 0)
}
pub fn checked_sub_mins(self, mins: i64) -> Option<Self> {
let add_secs = mins.checked_mul(Self::SECS_PER_MIN)?;
self.checked_sub(add_secs, 0)
}
pub fn checked_sub_hours(self, hours: i64) -> Option<Self> {
let add_secs = hours.checked_mul(Self::SECS_PER_HR)?;
self.checked_sub(add_secs, 0)
}
}
impl From<u8> for Time {
fn from(seconds: u8) -> Self {
Time::try_new(i64::from(seconds), 0).unwrap()
}
}
impl From<u16> for Time {
fn from(seconds: u16) -> Self {
Time::try_new(i64::from(seconds), 0).unwrap()
}
}
impl From<u32> for Time {
fn from(seconds: u32) -> Self {
Time::try_new(i64::from(seconds), 0).unwrap()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct TimeBase {
pub numer: NonZero<u32>,
pub denom: NonZero<u32>,
}
impl Default for TimeBase {
fn default() -> Self {
Self { numer: NonZero::new(1).unwrap(), denom: NonZero::new(1).unwrap() }
}
}
impl TimeBase {
pub const fn new(numer: NonZero<u32>, denom: NonZero<u32>) -> Self {
TimeBase { numer, denom }
}
pub fn try_new(numer: u32, denom: u32) -> Option<Self> {
let numer = NonZero::new(numer)?;
let denom = NonZero::new(denom)?;
Some(TimeBase { numer, denom })
}
pub const fn from_recip(rate: NonZero<u32>) -> Self {
TimeBase { numer: NonZero::new(1).unwrap(), denom: rate }
}
pub fn try_from_recip(rate: u32) -> Option<Self> {
let denom = NonZero::new(rate)?;
Some(TimeBase { numer: NonZero::new(1).unwrap(), denom })
}
pub fn calc_time(&self, ts: Timestamp) -> Option<Time> {
const NS_PER_SEC_64: i64 = 1_000_000_000;
const NS_PER_SEC_128: i128 = 1_000_000_000;
let numer = i64::from(self.numer.get());
let denom = i64::from(self.denom.get());
if let Some(product) =
ts.get().checked_mul(numer).and_then(|x| x.checked_mul(NS_PER_SEC_64))
{
let total_nanos = product / denom;
Some(Time::from_nanos(total_nanos))
}
else {
let product = i128::from(ts.get()) * i128::from(numer) * NS_PER_SEC_128;
let total_nanos = product / i128::from(denom);
Time::try_from_nanos_i128(total_nanos)
}
}
pub fn calc_time_saturating(&self, ts: Timestamp) -> Time {
self.calc_time(ts).unwrap_or_else(|| if ts.is_negative() { Time::MIN } else { Time::MAX })
}
pub fn calc_timestamp(&self, time: Time) -> Option<Timestamp> {
const NS_PER_SEC: i64 = 1_000_000_000;
let numer = i64::from(self.numer.get());
let denom = i64::from(self.denom.get());
let frac = (i64::from(time.nanos) * denom) / NS_PER_SEC;
if let Some(ts) = time
.seconds
.checked_mul(denom)
.and_then(|whole| whole.checked_add(frac))
.map(|x| x / numer)
{
Some(Timestamp(ts))
}
else {
let whole = i128::from(time.seconds) * i128::from(denom);
let ts = (whole + i128::from(frac)) / i128::from(numer);
ts.try_into().ok().map(Timestamp)
}
}
pub fn scale(self, factor: f64) -> Option<Self> {
if factor == 1.0 {
return Some(self);
}
if !factor.is_finite() || factor <= 0.0 {
return None;
}
let numer = (self.numer.get() as f64 * factor).round();
if numer > f64::from(u32::MAX) {
return None;
}
Some(TimeBase { numer: NonZero::new(numer as u32)?, denom: self.denom })
}
pub fn reduce(self) -> Self {
let gcd = {
let mut a = self.numer.get();
let mut b = self.denom.get();
while b != 0 {
let r = a % b;
a = b;
b = r;
}
a
};
Self {
numer: NonZero::new(self.numer.get() / gcd).unwrap(),
denom: NonZero::new(self.denom.get() / gcd).unwrap(),
}
}
}
impl From<TimeBase> for f64 {
fn from(timebase: TimeBase) -> Self {
f64::from(timebase.numer.get()) / f64::from(timebase.denom.get())
}
}
impl fmt::Display for TimeBase {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.numer, self.denom)
}
}
#[cfg(test)]
mod tests {
use std::i64;
use super::{Time, TimeBase, Timestamp};
#[test]
fn verify_time() {
assert_eq!(Time::try_new(0, 0).unwrap(), Time::ZERO);
assert_eq!(Time::try_from_secs_f64(0.0).unwrap(), Time::ZERO);
assert_eq!(Time::try_from_nanos_i128(0).unwrap(), Time::ZERO);
assert_eq!(Time::from_nanos(0), Time::ZERO);
assert_eq!(Time::from_micros(0), Time::ZERO);
assert_eq!(Time::from_millis(0), Time::ZERO);
assert!(Time::try_new(0, 999_999_999).is_some());
assert!(Time::try_new(0, 1_000_000_000).is_none());
assert!(Time::from_ss(0, 1_000_000_000).is_none());
assert!(Time::from_mmss(0, 0, 1_000_000_000).is_none());
assert!(Time::from_hhmmss(0, 0, 0, 1_000_000_000).is_none());
assert!(Time::from_ss(60, 0).is_none());
assert!(Time::from_mmss(0, 60, 0).is_none());
assert!(Time::from_hhmmss(0, 0, 60, 0).is_none());
assert!(Time::from_mmss(60, 0, 0).is_none());
assert!(Time::from_hhmmss(0, 60, 0, 0).is_none());
assert!(Time::try_new(5, 1) == Time::try_new(5, 1));
assert!(Time::try_new(-5, 1) == Time::try_new(-5, 1));
assert!(Time::ZERO < Time::try_new(0, 1).unwrap()); assert!(Time::ZERO > Time::try_new(-1, 999_999_999).unwrap()); assert!(Time::try_new(5, 1) < Time::try_new(5, 2)); assert!(Time::try_new(-5, 1) < Time::try_new(-5, 2));
assert_eq!(Time::try_new(1, 750_000_000).unwrap().parts(), (1, 750_000_000)); assert_eq!(Time::try_new(0, 100_000_000).unwrap().parts(), (0, 100_000_000)); assert_eq!(Time::try_new(-2, 750_000_000).unwrap().parts(), (-1, 250_000_000));
assert_eq!(Time::try_new(100, 250_999_999).unwrap().as_nanos(), 100_250_999_999);
assert_eq!(Time::try_new(0, 100_999_999).unwrap().as_nanos(), 100_999_999);
assert_eq!(Time::try_new(-100, 250_999_999).unwrap().as_nanos(), -99_749_000_001);
assert_eq!(Time::try_new(100, 250_999_999).unwrap().as_micros(), 100_250_999);
assert_eq!(Time::try_new(0, 100_999_999).unwrap().as_micros(), 100_999);
assert_eq!(Time::try_new(-100, 250_999_999).unwrap().as_micros(), -99_749_000);
assert_eq!(Time::try_new(100, 250_999_999).unwrap().as_millis(), 100_250);
assert_eq!(Time::try_new(0, 100_999_999).unwrap().as_millis(), 100);
assert_eq!(Time::try_new(-100, 250_999_999).unwrap().as_millis(), -99_749);
assert_eq!(Time::try_new(100, 250_000_000).unwrap().as_secs(), 100); assert_eq!(Time::try_new(-100, 250_000_000).unwrap().as_secs(), -99); assert_eq!(Time::try_new(-100, 0).unwrap().as_secs(), -100);
assert_eq!(Time::try_new(60, 0).unwrap().as_mins(), 1); assert_eq!(Time::try_new(-60, 0).unwrap().as_mins(), -1); assert_eq!(Time::try_new(-60, 1).unwrap().as_mins(), 0);
assert_eq!(Time::try_new(3600, 0).unwrap().as_hours(), 1); assert_eq!(Time::try_new(-3600, 0).unwrap().as_hours(), -1); assert_eq!(Time::try_new(-3600, 1).unwrap().as_hours(), 0);
assert_eq!(Time::try_new(100, 250_000_000).unwrap().as_secs_f64(), 100.25);
assert_eq!(Time::try_new(0, 100_000_000).unwrap().as_secs_f64(), 0.1);
assert_eq!(Time::try_new(-100, 250_000_000).unwrap().as_secs_f64(), -99.75);
assert_eq!(Time::default().checked_neg().unwrap(), Time::default());
assert_eq!(Time::try_new(-1, 0).unwrap().checked_neg().unwrap().as_millis(), 1000);
assert_eq!(Time::try_new(1, 0).unwrap().checked_neg().unwrap().as_millis(), -1000);
assert_eq!(Time::try_new(-1, 250_000_000).unwrap().checked_neg().unwrap().as_millis(), 750);
assert_eq!(
Time::try_new(1, 250_000_000).unwrap().checked_neg().unwrap().as_millis(),
-1250
);
assert_eq!(Time::try_new(i64::MIN, 0).unwrap().checked_neg(), None);
assert_eq!(
Time::try_new(i64::MIN + 1, 0).unwrap().checked_neg(),
Time::try_new(i64::MAX, 0)
);
assert_eq!(Time::default().checked_add_nanos(1_250_000_000).unwrap().as_millis(), 1250);
assert_eq!(Time::default().checked_add_nanos(250_000_000).unwrap().as_millis(), 250);
assert_eq!(Time::default().checked_add_nanos(0).unwrap().as_millis(), 0);
assert_eq!(Time::default().checked_add_nanos(-250_000_000).unwrap().as_millis(), -250);
assert_eq!(Time::default().checked_add_nanos(-1_250_000_000).unwrap().as_millis(), -1250);
assert_eq!(Time::default().checked_sub_nanos(1_250_000_000).unwrap().as_millis(), -1250);
assert_eq!(Time::default().checked_sub_nanos(250_000_000).unwrap().as_millis(), -250);
assert_eq!(Time::default().checked_sub_nanos(0).unwrap().as_millis(), 0);
assert_eq!(Time::default().checked_sub_nanos(-250_000_000).unwrap().as_millis(), 250);
assert_eq!(Time::default().checked_sub_nanos(-1_250_000_000).unwrap().as_millis(), 1250);
assert_eq!(Time::default().checked_add_micros(1_250_000).unwrap().as_millis(), 1250);
assert_eq!(Time::default().checked_add_micros(250_000).unwrap().as_millis(), 250);
assert_eq!(Time::default().checked_add_micros(0).unwrap().as_millis(), 0);
assert_eq!(Time::default().checked_add_micros(-250_000).unwrap().as_millis(), -250);
assert_eq!(Time::default().checked_add_micros(-1_250_000).unwrap().as_millis(), -1250);
assert_eq!(Time::default().checked_sub_micros(1_250_000).unwrap().as_millis(), -1250);
assert_eq!(Time::default().checked_sub_micros(250_000).unwrap().as_millis(), -250);
assert_eq!(Time::default().checked_sub_micros(0).unwrap().as_millis(), 0);
assert_eq!(Time::default().checked_sub_micros(-250_000).unwrap().as_millis(), 250);
assert_eq!(Time::default().checked_sub_micros(-1_250_000).unwrap().as_millis(), 1250);
assert_eq!(Time::default().checked_add_millis(1_250).unwrap().as_millis(), 1250);
assert_eq!(Time::default().checked_add_millis(250).unwrap().as_millis(), 250);
assert_eq!(Time::default().checked_add_millis(0).unwrap().as_millis(), 0);
assert_eq!(Time::default().checked_add_millis(-250).unwrap().as_millis(), -250);
assert_eq!(Time::default().checked_add_millis(-1_250).unwrap().as_millis(), -1250);
assert_eq!(Time::default().checked_sub_millis(1_250).unwrap().as_millis(), -1250);
assert_eq!(Time::default().checked_sub_millis(250).unwrap().as_millis(), -250);
assert_eq!(Time::default().checked_sub_millis(0).unwrap().as_millis(), 0);
assert_eq!(Time::default().checked_sub_millis(-250).unwrap().as_millis(), 250);
assert_eq!(Time::default().checked_sub_millis(-1_250).unwrap().as_millis(), 1250);
assert_eq!(Time::default().checked_add_secs(2).unwrap().as_millis(), 2000);
assert_eq!(Time::default().checked_add_secs(0).unwrap().as_millis(), 0);
assert_eq!(Time::default().checked_add_secs(-2).unwrap().as_millis(), -2000);
assert_eq!(Time::default().checked_sub_secs(2).unwrap().as_millis(), -2000);
assert_eq!(Time::default().checked_sub_secs(0).unwrap().as_millis(), 0);
assert_eq!(Time::default().checked_sub_secs(-2).unwrap().as_millis(), 2000);
assert_eq!(Time::default().checked_add_mins(2).unwrap().as_secs(), 120);
assert_eq!(Time::default().checked_add_mins(0).unwrap().as_secs(), 0);
assert_eq!(Time::default().checked_add_mins(-2).unwrap().as_secs(), -120);
assert_eq!(Time::default().checked_sub_mins(2).unwrap().as_secs(), -120);
assert_eq!(Time::default().checked_sub_mins(0).unwrap().as_secs(), 0);
assert_eq!(Time::default().checked_sub_mins(-2).unwrap().as_secs(), 120);
assert_eq!(Time::default().checked_add_hours(2).unwrap().as_secs(), 2 * 60 * 60);
assert_eq!(Time::default().checked_add_hours(0).unwrap().as_secs(), 0);
assert_eq!(Time::default().checked_add_hours(-2).unwrap().as_secs(), -2 * 60 * 60);
assert_eq!(Time::default().checked_sub_hours(2).unwrap().as_secs(), -2 * 60 * 60);
assert_eq!(Time::default().checked_sub_hours(0).unwrap().as_secs(), 0);
assert_eq!(Time::default().checked_sub_hours(-2).unwrap().as_secs(), 2 * 60 * 60);
}
#[test]
fn verify_timebase() {
let tb1 = TimeBase::try_new(1, 320).unwrap(); let tb2 = TimeBase::try_new(1_000_000, 320_000_000).unwrap();
assert_eq!(tb1.calc_time(Timestamp::from(0)).unwrap(), Time::try_new(0, 0).unwrap());
assert_eq!(
tb1.calc_time(Timestamp::from(i64::MAX)).unwrap(),
Time::try_new(28823037615171174, 396875000).unwrap()
);
assert_eq!(
tb1.calc_time(Timestamp::from(i64::MIN)).unwrap(),
Time::try_new(-28823037615171175, 600000000).unwrap()
);
assert_eq!(
tb1.calc_time(Timestamp::from(12345)).unwrap(),
Time::try_new(38, 578125000).unwrap()
);
assert_eq!(tb2.calc_time(Timestamp::from(0)).unwrap(), Time::try_new(0, 0).unwrap());
assert_eq!(
tb2.calc_time(Timestamp::from(i64::MAX)).unwrap(),
Time::try_new(28823037615171174, 396875000).unwrap()
);
assert_eq!(
tb2.calc_time(Timestamp::from(i64::MIN)).unwrap(),
Time::try_new(-28823037615171175, 600000000).unwrap()
);
assert_eq!(
tb2.calc_time(Timestamp::from(12345)).unwrap(),
Time::try_new(38, 578125000).unwrap()
);
assert_eq!(Time::try_new(0, 0).unwrap(), tb1.calc_time(Timestamp::from(0)).unwrap());
assert_eq!(
Time::try_new(28823037615171174, 396875000).unwrap(),
tb1.calc_time(Timestamp::from(i64::MAX)).unwrap()
);
assert_eq!(
Time::try_new(-28823037615171175, 600000000).unwrap(),
tb1.calc_time(Timestamp::from(i64::MIN)).unwrap()
);
assert_eq!(
Time::try_new(38, 578125000).unwrap(),
tb1.calc_time(Timestamp::from(12345)).unwrap()
);
assert_eq!(Time::try_new(0, 0).unwrap(), tb2.calc_time(Timestamp::from(0)).unwrap());
assert_eq!(
Time::try_new(28823037615171174, 396875000).unwrap(),
tb2.calc_time(Timestamp::from(i64::MAX)).unwrap()
);
assert_eq!(
Time::try_new(-28823037615171175, 600000000).unwrap(),
tb2.calc_time(Timestamp::from(i64::MIN)).unwrap()
);
assert_eq!(
Time::try_new(38, 578125000).unwrap(),
tb2.calc_time(Timestamp::from(12345)).unwrap()
);
let tb3 = TimeBase::try_new(u32::MAX, 1).unwrap(); let tb4 = TimeBase::try_new(1, u32::MAX).unwrap();
assert!(tb3.calc_time(Timestamp::from(i64::MIN)).is_none());
assert!(tb3.calc_time(Timestamp::from(i64::MAX)).is_none());
assert!(tb4.calc_timestamp(Time::MIN).is_none());
assert!(tb4.calc_timestamp(Time::MAX).is_none());
}
#[test]
fn verify_timebase_reduce() {
assert_eq!(TimeBase::try_new(42, 42).unwrap().reduce(), TimeBase::try_new(1, 1).unwrap());
assert_eq!(TimeBase::try_new(8, 12).unwrap().reduce(), TimeBase::try_new(2, 3).unwrap());
assert_eq!(TimeBase::try_new(100, 250).unwrap().reduce(), TimeBase::try_new(2, 5).unwrap());
assert_eq!(
TimeBase::try_new(1000, 10000).unwrap().reduce(),
TimeBase::try_new(1, 10).unwrap()
);
assert_eq!(
TimeBase::try_new(10000, 1000).unwrap().reduce(),
TimeBase::try_new(10, 1).unwrap()
);
assert_eq!(TimeBase::try_new(24, 6).unwrap().reduce(), TimeBase::try_new(4, 1).unwrap());
assert_eq!(TimeBase::try_new(5, 25).unwrap().reduce(), TimeBase::try_new(1, 5).unwrap());
assert_eq!(TimeBase::try_new(1, 2).unwrap().reduce(), TimeBase::try_new(1, 2).unwrap());
assert_eq!(TimeBase::try_new(17, 29).unwrap().reduce(), TimeBase::try_new(17, 29).unwrap());
assert_eq!(
TimeBase::try_new(u32::MAX, u32::MAX - 1).unwrap().reduce(),
TimeBase::try_new(u32::MAX, u32::MAX - 1).unwrap()
);
assert_eq!(
TimeBase::try_new(2_971_215_073, 1_836_311_903).unwrap().reduce(),
TimeBase::try_new(2_971_215_073, 1_836_311_903).unwrap()
);
assert_eq!(TimeBase::try_new(1, 1).unwrap().reduce(), TimeBase::try_new(1, 1).unwrap());
assert_eq!(
TimeBase::try_new(u32::MAX, 1).unwrap().reduce(),
TimeBase::try_new(u32::MAX, 1).unwrap()
);
assert_eq!(
TimeBase::try_new(1, u32::MAX).unwrap().reduce(),
TimeBase::try_new(1, u32::MAX).unwrap()
);
assert_eq!(
TimeBase::try_new(u32::MAX, u32::MAX).unwrap().reduce(),
TimeBase::try_new(1, 1).unwrap()
);
}
#[test]
fn verify_ts_roundtrip_with_time() {
let bases = [
TimeBase::try_new(1, 2).unwrap(),
TimeBase::try_new(1, 25).unwrap(),
TimeBase::try_new(1001, 30_000).unwrap(),
TimeBase::try_new(1, 48_000).unwrap(),
TimeBase::try_new(1, 1).unwrap(),
TimeBase::try_new(1, 2).unwrap(),
TimeBase::try_new(2, 3).unwrap(),
TimeBase::try_new(3, 7).unwrap(),
TimeBase::try_new(1, 1_000_000_000).unwrap(),
TimeBase::try_new(u32::MAX, u32::MAX).unwrap(),
];
let ticks = [
i64::MIN,
i64::MIN + 1,
-1_000_000,
-12_345,
-1,
0,
1,
12_345,
1_000_000,
i64::MAX - 1,
i64::MAX,
];
fn test_roundtrip(ts: i64, tb: TimeBase) {
let time = tb.calc_time(Timestamp::from(ts)).expect("time should not overflow");
let rtts = tb.calc_timestamp(time).expect("ticks should not overflow");
let diff = rtts.get() - ts;
assert!((-1..=0).contains(&diff));
}
for tb in bases {
for &ts in &ticks {
test_roundtrip(ts, tb);
}
}
for tb in bases {
for ts in -10_000..=10_000 {
test_roundtrip(ts, tb);
}
}
}
}