use core::ops::{Add, Mul, Sub};
#[cfg(not(feature = "std"))]
use num_traits::Float;
#[cfg(feature = "python")]
use pyo3::prelude::*;
use crate::{
Duration, DAYS_PER_CENTURY, 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,
};
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord)]
#[cfg_attr(feature = "python", pyclass)]
pub enum Unit {
Nanosecond,
Microsecond,
Millisecond,
Second,
Minute,
Hour,
Day,
Century,
}
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord)]
#[cfg_attr(feature = "python", pyclass)]
pub enum Freq {
GigaHertz,
MegaHertz,
KiloHertz,
Hertz,
}
pub trait TimeUnits: Copy + Mul<Unit, Output = Duration> {
fn centuries(self) -> Duration {
self * Unit::Century
}
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 Default for Unit {
fn default() -> Self {
Self::Second
}
}
impl Default for Freq {
fn default() -> Self {
Self::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::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::Century => 7,
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::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::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.abs() < i64::MAX {
Duration::from_truncated_nanoseconds(total_ns)
} else {
Duration::from_total_nanoseconds(i128::from(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 {
let factor = match self {
Unit::Century => NANOSECONDS_PER_CENTURY as f64,
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;
if total_ns.abs() < (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 < 8 {
assert_eq!(unit_u8_back, unit_u8, "got {unit_u8_back} want {unit_u8}");
} else {
assert_eq!(unit, Unit::Second);
}
}
}