use core::ops::{Add, Mul, Sub};
#[cfg(not(feature = "std"))]
#[allow(unused_imports)] use num_traits::Float;
#[cfg(feature = "python")]
use pyo3::prelude::*;
use crate::{
Duration, DAYS_PER_CENTURY, DAYS_PER_WEEK, DAYS_PER_WEEK_I64, NANOSECONDS_PER_CENTURY,
NANOSECONDS_PER_DAY, NANOSECONDS_PER_HOUR, NANOSECONDS_PER_MICROSECOND,
NANOSECONDS_PER_MILLISECOND, NANOSECONDS_PER_MINUTE, NANOSECONDS_PER_SECOND, SECONDS_PER_DAY,
SECONDS_PER_HOUR, SECONDS_PER_MINUTE,
};
#[cfg_attr(kani, derive(kani::Arbitrary))]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord)]
#[cfg_attr(feature = "python", pyclass(eq, eq_int))]
#[derive(Default)]
pub enum Unit {
Nanosecond,
Microsecond,
Millisecond,
#[default]
Second,
Minute,
Hour,
Day,
Week,
Century,
}
#[cfg_attr(kani, derive(kani::Arbitrary))]
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord)]
#[cfg_attr(feature = "python", pyclass(eq, eq_int))]
#[derive(Default)]
pub enum Freq {
GigaHertz,
MegaHertz,
KiloHertz,
#[default]
Hertz,
}
pub trait TimeUnits: Copy + Mul<Unit, Output = Duration> {
fn centuries(self) -> Duration {
self * Unit::Century
}
fn weeks(self) -> Duration {
self * Unit::Week
}
fn days(self) -> Duration {
self * Unit::Day
}
fn hours(self) -> Duration {
self * Unit::Hour
}
fn minutes(self) -> Duration {
self * Unit::Minute
}
fn seconds(self) -> Duration {
self * Unit::Second
}
fn milliseconds(self) -> Duration {
self * Unit::Millisecond
}
fn microseconds(self) -> Duration {
self * Unit::Microsecond
}
fn nanoseconds(self) -> Duration {
self * Unit::Nanosecond
}
}
#[allow(non_snake_case)]
pub trait Frequencies: Copy + Mul<Freq, Output = Duration> {
fn GHz(self) -> Duration {
self * Freq::GigaHertz
}
fn MHz(self) -> Duration {
self * Freq::MegaHertz
}
fn kHz(self) -> Duration {
self * Freq::KiloHertz
}
fn Hz(self) -> Duration {
self * Freq::Hertz
}
}
impl Add for Unit {
type Output = Duration;
#[allow(clippy::identity_op)]
fn add(self, rhs: Self) -> Duration {
self * 1 + rhs * 1
}
}
impl Sub for Unit {
type Output = Duration;
#[allow(clippy::identity_op)]
fn sub(self, rhs: Self) -> Duration {
self * 1 - rhs * 1
}
}
#[cfg_attr(feature = "python", pymethods)]
impl Unit {
#[must_use]
pub fn in_seconds(&self) -> f64 {
match self {
Unit::Century => DAYS_PER_CENTURY * SECONDS_PER_DAY,
Unit::Week => DAYS_PER_WEEK * SECONDS_PER_DAY,
Unit::Day => SECONDS_PER_DAY,
Unit::Hour => SECONDS_PER_HOUR,
Unit::Minute => SECONDS_PER_MINUTE,
Unit::Second => 1.0,
Unit::Millisecond => 1e-3,
Unit::Microsecond => 1e-6,
Unit::Nanosecond => 1e-9,
}
}
#[must_use]
pub fn from_seconds(&self) -> f64 {
1.0 / self.in_seconds()
}
#[cfg(feature = "python")]
fn __add__(&self, other: Self) -> Duration {
*self + other
}
#[cfg(feature = "python")]
fn __sub__(&self, other: Self) -> Duration {
*self - other
}
#[cfg(feature = "python")]
fn __mul__(&self, other: f64) -> Duration {
*self * other
}
}
impl From<Unit> for u8 {
fn from(unit: Unit) -> Self {
match unit {
Unit::Nanosecond => 1,
Unit::Microsecond => 2,
Unit::Millisecond => 3,
Unit::Minute => 4,
Unit::Hour => 5,
Unit::Day => 6,
Unit::Week => 7,
Unit::Century => 8,
Unit::Second => 0,
}
}
}
impl From<&Unit> for u8 {
fn from(unit: &Unit) -> Self {
u8::from(*unit)
}
}
impl From<u8> for Unit {
fn from(val: u8) -> Self {
match val {
1 => Unit::Nanosecond,
2 => Unit::Microsecond,
3 => Unit::Millisecond,
4 => Unit::Minute,
5 => Unit::Hour,
6 => Unit::Day,
7 => Unit::Week,
8 => Unit::Century,
_ => Unit::Second,
}
}
}
impl Mul<i64> for Unit {
type Output = Duration;
fn mul(self, q: i64) -> Duration {
let factor = match self {
Unit::Century => NANOSECONDS_PER_CENTURY as i64,
Unit::Week => NANOSECONDS_PER_DAY as i64 * DAYS_PER_WEEK_I64,
Unit::Day => NANOSECONDS_PER_DAY as i64,
Unit::Hour => NANOSECONDS_PER_HOUR as i64,
Unit::Minute => NANOSECONDS_PER_MINUTE as i64,
Unit::Second => NANOSECONDS_PER_SECOND as i64,
Unit::Millisecond => NANOSECONDS_PER_MILLISECOND as i64,
Unit::Microsecond => NANOSECONDS_PER_MICROSECOND as i64,
Unit::Nanosecond => 1,
};
match q.checked_mul(factor) {
Some(total_ns) => {
if total_ns.unsigned_abs() < i64::MAX as u64 {
Duration::from_truncated_nanoseconds(total_ns)
} else {
Duration::from_total_nanoseconds(i128::from(total_ns))
}
}
None => {
let q = i128::from(q);
match q.checked_mul(factor.into()) {
Some(total_ns) => Duration::from_total_nanoseconds(total_ns),
None => {
if q.is_negative() {
Duration::MIN
} else {
Duration::MAX
}
}
}
}
}
}
}
impl Mul<f64> for Unit {
type Output = Duration;
fn mul(self, q: f64) -> Duration {
self.const_multiply(q)
}
}
impl Unit {
#[cfg_attr(kani, kani::ensures(|result: &Duration| {
let (c, n) = result.to_parts();
n < NANOSECONDS_PER_CENTURY
|| (c == i16::MAX && n == NANOSECONDS_PER_CENTURY)
|| (c == i16::MIN && n == 0)
}))]
#[cfg_attr(kani, kani::requires(q.is_finite()))]
pub(crate) const fn const_multiply(self, q: f64) -> Duration {
assert!(
q.is_finite(),
"Attempted to create a Duration from a non-finite number"
);
let factor = match self {
Unit::Century => NANOSECONDS_PER_CENTURY as f64,
Unit::Week => NANOSECONDS_PER_DAY as f64 * DAYS_PER_WEEK,
Unit::Day => NANOSECONDS_PER_DAY as f64,
Unit::Hour => NANOSECONDS_PER_HOUR as f64,
Unit::Minute => NANOSECONDS_PER_MINUTE as f64,
Unit::Second => NANOSECONDS_PER_SECOND as f64,
Unit::Millisecond => NANOSECONDS_PER_MILLISECOND as f64,
Unit::Microsecond => NANOSECONDS_PER_MICROSECOND as f64,
Unit::Nanosecond => 1.0,
};
if q >= f64::MAX / factor {
Duration::MAX
} else if q <= f64::MIN / factor {
Duration::MIN
} else {
let total_ns = q * factor;
let absolute_nanoseconds = if total_ns >= 0.0 { total_ns } else { -total_ns };
if absolute_nanoseconds < (i64::MAX as f64) {
Duration::from_truncated_nanoseconds(total_ns as i64)
} else {
Duration::from_total_nanoseconds(total_ns as i128)
}
}
}
}
#[test]
fn test_unit_conversion() {
for unit_u8 in 0..u8::MAX {
let unit = Unit::from(unit_u8);
let unit_u8_back: u8 = unit.into();
if unit_u8 < 9 {
assert_eq!(unit_u8_back, unit_u8, "got {unit_u8_back} want {unit_u8}");
} else {
assert_eq!(unit, Unit::Second);
}
}
}