use crate::ParsingErrors;
use crate::{Errors, SECONDS_PER_CENTURY, SECONDS_PER_DAY, SECONDS_PER_HOUR, SECONDS_PER_MINUTE};
pub use crate::{Freq, Frequencies, TimeUnits, Unit};
#[cfg(feature = "std")]
extern crate core;
use core::cmp::Ordering;
use core::convert::TryInto;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
#[cfg(feature = "serde")]
use serde_derive::{Deserialize, Serialize};
use core::str::FromStr;
#[cfg(feature = "python")]
use pyo3::prelude::*;
#[cfg(feature = "python")]
use pyo3::pyclass::CompareOp;
#[cfg(feature = "python")]
use pyo3::types::PyType;
#[cfg(not(feature = "std"))]
use num_traits::Float;
#[cfg(kani)]
use kani::Arbitrary;
pub const DAYS_PER_CENTURY_U64: u64 = 36_525;
pub const NANOSECONDS_PER_MICROSECOND: u64 = 1_000;
pub const NANOSECONDS_PER_MILLISECOND: u64 = 1_000 * NANOSECONDS_PER_MICROSECOND;
pub const NANOSECONDS_PER_SECOND: u64 = 1_000 * NANOSECONDS_PER_MILLISECOND;
pub(crate) const NANOSECONDS_PER_SECOND_U32: u32 = 1_000_000_000;
pub const NANOSECONDS_PER_MINUTE: u64 = 60 * NANOSECONDS_PER_SECOND;
pub const NANOSECONDS_PER_HOUR: u64 = 60 * NANOSECONDS_PER_MINUTE;
pub const NANOSECONDS_PER_DAY: u64 = 24 * NANOSECONDS_PER_HOUR;
pub const NANOSECONDS_PER_CENTURY: u64 = DAYS_PER_CENTURY_U64 * NANOSECONDS_PER_DAY;
#[derive(Clone, Copy, Debug, PartialOrd, Eq, Ord)]
#[repr(C)]
#[cfg_attr(feature = "python", pyclass)]
#[cfg_attr(feature = "python", pyo3(module = "hifitime"))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Duration {
pub(crate) centuries: i16,
pub(crate) nanoseconds: u64,
}
#[cfg(kani)]
impl Arbitrary for Duration {
#[inline(always)]
fn any() -> Self {
let centuries: i16 = kani::any();
let nanoseconds: u64 = kani::any();
Duration::from_parts(centuries, nanoseconds)
}
}
impl PartialEq for Duration {
fn eq(&self, other: &Self) -> bool {
if self.centuries == other.centuries {
self.nanoseconds == other.nanoseconds
} else if (self.centuries.saturating_sub(other.centuries)).saturating_abs() == 1
&& (self.centuries == 0 || other.centuries == 0)
{
if self.centuries < 0 {
(NANOSECONDS_PER_CENTURY - self.nanoseconds) == other.nanoseconds
} else {
(NANOSECONDS_PER_CENTURY - other.nanoseconds) == self.nanoseconds
}
} else {
false
}
}
}
impl Hash for Duration {
fn hash<H: Hasher>(&self, hasher: &mut H) {
self.centuries.hash(hasher);
self.nanoseconds.hash(hasher);
}
}
impl Default for Duration {
fn default() -> Self {
Duration::ZERO
}
}
impl Duration {
#[must_use]
#[deprecated(note = "Prefer from_parts()", since = "3.6.0")]
pub fn new(centuries: i16, nanoseconds: u64) -> Self {
let mut out = Self {
centuries,
nanoseconds,
};
out.normalize();
out
}
#[must_use]
pub fn from_parts(centuries: i16, nanoseconds: u64) -> Self {
let mut me = Self {
centuries,
nanoseconds,
};
me.normalize();
me
}
#[must_use]
pub fn from_total_nanoseconds(nanos: i128) -> Self {
if nanos == 0 {
Self::ZERO
} else {
let centuries_i128 = nanos.div_euclid(NANOSECONDS_PER_CENTURY.into());
let remaining_nanos_i128 = nanos.rem_euclid(NANOSECONDS_PER_CENTURY.into());
if centuries_i128 > i16::MAX.into() {
Self::MAX
} else if centuries_i128 < i16::MIN.into() {
Self::MIN
} else {
Self::from_parts(centuries_i128 as i16, remaining_nanos_i128 as u64)
}
}
}
#[must_use]
pub fn from_truncated_nanoseconds(nanos: i64) -> Self {
if nanos < 0 {
let ns = nanos.unsigned_abs();
let extra_centuries = ns.div_euclid(NANOSECONDS_PER_CENTURY);
let rem_nanos = ns.rem_euclid(NANOSECONDS_PER_CENTURY);
Self::from_parts(
-1 - (extra_centuries as i16),
NANOSECONDS_PER_CENTURY - rem_nanos,
)
} else {
Self::from_parts(0, nanos.unsigned_abs())
}
}
#[must_use]
pub fn from_f64(value: f64, unit: Unit) -> Self {
unit * value
}
#[must_use]
pub fn from_days(value: f64) -> Self {
value * Unit::Day
}
#[must_use]
pub fn from_hours(value: f64) -> Self {
value * Unit::Hour
}
#[must_use]
pub fn from_seconds(value: f64) -> Self {
value * Unit::Second
}
#[must_use]
pub fn from_milliseconds(value: f64) -> Self {
value * Unit::Millisecond
}
#[must_use]
pub fn from_microseconds(value: f64) -> Self {
value * Unit::Microsecond
}
#[must_use]
pub fn from_nanoseconds(value: f64) -> Self {
value * Unit::Nanosecond
}
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn compose(
sign: i8,
days: u64,
hours: u64,
minutes: u64,
seconds: u64,
milliseconds: u64,
microseconds: u64,
nanoseconds: u64,
) -> Self {
Self::compose_f64(
sign,
days as f64,
hours as f64,
minutes as f64,
seconds as f64,
milliseconds as f64,
microseconds as f64,
nanoseconds as f64,
)
}
#[allow(clippy::too_many_arguments)]
#[must_use]
pub fn compose_f64(
sign: i8,
days: f64,
hours: f64,
minutes: f64,
seconds: f64,
milliseconds: f64,
microseconds: f64,
nanoseconds: f64,
) -> Self {
let me: Self = days.days()
+ hours.hours()
+ minutes.minutes()
+ seconds.seconds()
+ milliseconds.milliseconds()
+ microseconds.microseconds()
+ nanoseconds.nanoseconds();
if sign < 0 {
-me
} else {
me
}
}
#[must_use]
pub fn from_tz_offset(sign: i8, hours: i64, minutes: i64) -> Self {
let dur = hours * Unit::Hour + minutes * Unit::Minute;
if sign < 0 {
-dur
} else {
dur
}
}
}
#[cfg_attr(feature = "python", pymethods)]
impl Duration {
fn normalize(&mut self) {
let extra_centuries = self.nanoseconds.div_euclid(NANOSECONDS_PER_CENTURY);
if extra_centuries > 0 {
let rem_nanos = self.nanoseconds.rem_euclid(NANOSECONDS_PER_CENTURY);
if self.centuries == i16::MAX {
if self.nanoseconds.saturating_add(rem_nanos) > Self::MAX.nanoseconds {
*self = Self::MAX;
}
} else if *self != Self::MAX && *self != Self::MIN {
match self.centuries.checked_add(extra_centuries as i16) {
Some(centuries) => {
self.centuries = centuries;
self.nanoseconds = rem_nanos;
}
None => {
if self.centuries >= 0 {
*self = Self::MAX;
} else {
*self = Self::MIN;
}
}
}
}
}
}
#[must_use]
pub const fn to_parts(&self) -> (i16, u64) {
(self.centuries, self.nanoseconds)
}
#[must_use]
pub fn total_nanoseconds(&self) -> i128 {
if self.centuries == -1 {
-i128::from(NANOSECONDS_PER_CENTURY - self.nanoseconds)
} else if self.centuries >= 0 {
i128::from(self.centuries) * i128::from(NANOSECONDS_PER_CENTURY)
+ i128::from(self.nanoseconds)
} else {
i128::from(self.centuries) * i128::from(NANOSECONDS_PER_CENTURY)
- i128::from(self.nanoseconds)
}
}
pub fn try_truncated_nanoseconds(&self) -> Result<i64, Errors> {
if self.centuries == i16::MIN || self.centuries.abs() >= 3 {
Err(Errors::Overflow)
} else if self.centuries == -1 {
Ok(-((NANOSECONDS_PER_CENTURY - self.nanoseconds) as i64))
} else if self.centuries >= 0 {
match i64::from(self.centuries).checked_mul(NANOSECONDS_PER_CENTURY as i64) {
Some(centuries_as_ns) => {
match centuries_as_ns.checked_add(self.nanoseconds as i64) {
Some(truncated_ns) => Ok(truncated_ns),
None => Err(Errors::Overflow),
}
}
None => Err(Errors::Overflow),
}
} else {
Ok(
i64::from(self.centuries) * NANOSECONDS_PER_CENTURY as i64
+ self.nanoseconds as i64,
)
}
}
#[must_use]
pub fn truncated_nanoseconds(&self) -> i64 {
match self.try_truncated_nanoseconds() {
Ok(val) => val,
Err(_) => {
if self.centuries < 0 {
i64::MIN
} else {
i64::MAX
}
}
}
}
#[must_use]
pub fn to_seconds(&self) -> f64 {
let seconds = self.nanoseconds.div_euclid(NANOSECONDS_PER_SECOND);
let subseconds = self.nanoseconds.rem_euclid(NANOSECONDS_PER_SECOND);
if self.centuries == 0 {
(seconds as f64) + (subseconds as f64) * 1e-9
} else {
f64::from(self.centuries) * SECONDS_PER_CENTURY
+ (seconds as f64)
+ (subseconds as f64) * 1e-9
}
}
#[must_use]
pub fn to_unit(&self, unit: Unit) -> f64 {
self.to_seconds() * unit.from_seconds()
}
#[must_use]
pub fn abs(&self) -> Self {
if self.centuries.is_negative() {
-*self
} else {
*self
}
}
#[must_use]
pub const fn signum(&self) -> i8 {
self.centuries.signum() as i8
}
#[must_use]
pub fn decompose(&self) -> (i8, u64, u64, u64, u64, u64, u64, u64) {
let sign = self.signum();
match self.try_truncated_nanoseconds() {
Ok(total_ns) => {
let ns_left = total_ns.abs();
let (days, ns_left) = div_rem_i64(ns_left, NANOSECONDS_PER_DAY as i64);
let (hours, ns_left) = div_rem_i64(ns_left, NANOSECONDS_PER_HOUR as i64);
let (minutes, ns_left) = div_rem_i64(ns_left, NANOSECONDS_PER_MINUTE as i64);
let (seconds, ns_left) = div_rem_i64(ns_left, NANOSECONDS_PER_SECOND as i64);
let (milliseconds, ns_left) =
div_rem_i64(ns_left, NANOSECONDS_PER_MILLISECOND as i64);
let (microseconds, ns_left) =
div_rem_i64(ns_left, NANOSECONDS_PER_MICROSECOND as i64);
(
sign,
days.try_into().unwrap(),
hours.try_into().unwrap(),
minutes.try_into().unwrap(),
seconds.try_into().unwrap(),
milliseconds.try_into().unwrap(),
microseconds.try_into().unwrap(),
ns_left.try_into().unwrap(),
)
}
Err(_) => {
let total_ns = self.total_nanoseconds();
let ns_left = total_ns.abs();
let (days, ns_left) = div_rem_i128(ns_left, i128::from(NANOSECONDS_PER_DAY));
let (hours, ns_left) = div_rem_i128(ns_left, i128::from(NANOSECONDS_PER_HOUR));
let (minutes, ns_left) = div_rem_i128(ns_left, i128::from(NANOSECONDS_PER_MINUTE));
let (seconds, ns_left) = div_rem_i128(ns_left, i128::from(NANOSECONDS_PER_SECOND));
let (milliseconds, ns_left) =
div_rem_i128(ns_left, i128::from(NANOSECONDS_PER_MILLISECOND));
let (microseconds, ns_left) =
div_rem_i128(ns_left, i128::from(NANOSECONDS_PER_MICROSECOND));
(
sign,
days.try_into().unwrap(),
hours.try_into().unwrap(),
minutes.try_into().unwrap(),
seconds.try_into().unwrap(),
milliseconds.try_into().unwrap(),
microseconds.try_into().unwrap(),
ns_left.try_into().unwrap(),
)
}
}
}
pub fn floor(&self, duration: Self) -> Self {
Self::from_total_nanoseconds(
self.total_nanoseconds() - self.total_nanoseconds() % duration.total_nanoseconds(),
)
}
pub fn ceil(&self, duration: Self) -> Self {
let floored = self.floor(duration);
match floored
.total_nanoseconds()
.checked_add(duration.abs().total_nanoseconds())
{
Some(total_ns) => Self::from_total_nanoseconds(total_ns),
None => Self::MAX,
}
}
pub fn round(&self, duration: Self) -> Self {
let floored = self.floor(duration);
let ceiled = self.ceil(duration);
if *self - floored < (ceiled - *self).abs() {
floored
} else {
ceiled
}
}
pub fn approx(&self) -> Self {
let (_, days, hours, minutes, seconds, milli, us, _) = self.decompose();
let round_to = if days > 0 {
1 * Unit::Day
} else if hours > 0 {
1 * Unit::Hour
} else if minutes > 0 {
1 * Unit::Minute
} else if seconds > 0 {
1 * Unit::Second
} else if milli > 0 {
1 * Unit::Millisecond
} else if us > 0 {
1 * Unit::Microsecond
} else {
1 * Unit::Nanosecond
};
self.round(round_to)
}
pub fn min(&self, other: Self) -> Self {
if *self < other {
*self
} else {
other
}
}
pub fn max(&self, other: Self) -> Self {
if *self > other {
*self
} else {
other
}
}
pub const fn is_negative(&self) -> bool {
self.centuries.is_negative()
}
pub const ZERO: Self = Self {
centuries: 0,
nanoseconds: 0,
};
pub const MAX: Self = Self {
centuries: i16::MAX,
nanoseconds: NANOSECONDS_PER_CENTURY,
};
pub const MIN: Self = Self {
centuries: i16::MIN,
nanoseconds: 0,
};
pub const EPSILON: Self = Self {
centuries: 0,
nanoseconds: 1,
};
pub const MIN_POSITIVE: Self = Self::EPSILON;
pub const MIN_NEGATIVE: Self = Self {
centuries: -1,
nanoseconds: NANOSECONDS_PER_CENTURY - 1,
};
#[cfg(feature = "python")]
#[new]
fn new_py(string_repr: String) -> PyResult<Self> {
match Self::from_str(&string_repr) {
Ok(d) => Ok(d),
Err(e) => Err(PyErr::from(e)),
}
}
#[cfg(feature = "python")]
fn __getnewargs__(&self) -> Result<(String,), PyErr> {
Ok((format!("{self}"),))
}
#[cfg(feature = "python")]
fn __str__(&self) -> String {
format!("{self}")
}
#[cfg(feature = "python")]
fn __repr__(&self) -> String {
format!("{self} @ {self:p}")
}
#[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
}
#[cfg(feature = "python")]
fn __div__(&self, other: f64) -> Duration {
*self / other
}
#[cfg(feature = "python")]
fn __eq__(&self, other: Self) -> bool {
*self == other
}
#[cfg(feature = "python")]
fn __le__(&self, other: Self) -> bool {
*self <= other
}
#[cfg(feature = "python")]
fn __lt__(&self, other: Self) -> bool {
*self < other
}
#[cfg(feature = "python")]
fn __ge__(&self, other: Self) -> bool {
*self >= other
}
#[cfg(feature = "python")]
fn __gt__(&self, other: Self) -> bool {
*self > other
}
#[cfg(feature = "python")]
#[classmethod]
fn zero(_cls: &PyType) -> Duration {
Duration::ZERO
}
#[cfg(feature = "python")]
#[classmethod]
fn epsilon(_cls: &PyType) -> Duration {
Duration::EPSILON
}
#[cfg(feature = "python")]
#[classmethod]
fn init_from_max(_cls: &PyType) -> Duration {
Duration::MAX
}
#[cfg(feature = "python")]
#[classmethod]
fn init_from_min(_cls: &PyType) -> Duration {
Duration::MIN
}
#[cfg(feature = "python")]
#[classmethod]
fn min_positive(_cls: &PyType) -> Duration {
Duration::MIN_POSITIVE
}
#[cfg(feature = "python")]
#[classmethod]
fn min_negative(_cls: &PyType) -> Duration {
Duration::MIN_NEGATIVE
}
#[cfg(feature = "python")]
#[classmethod]
fn init_from_parts(_cls: &PyType, centuries: i16, nanoseconds: u64) -> Self {
Self::from_parts(centuries, nanoseconds)
}
#[allow(clippy::too_many_arguments)]
#[cfg(feature = "python")]
#[classmethod]
#[must_use]
fn init_from_all_parts(
_cls: &PyType,
sign: i8,
days: u64,
hours: u64,
minutes: u64,
seconds: u64,
milliseconds: u64,
microseconds: u64,
nanoseconds: u64,
) -> Self {
Self::compose(
sign,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
)
}
#[cfg(feature = "python")]
#[classmethod]
fn init_from_total_nanoseconds(_cls: &PyType, nanos: i128) -> Self {
Self::from_total_nanoseconds(nanos)
}
#[cfg(feature = "python")]
#[classmethod]
fn init_from_truncated_nanoseconds(_cls: &PyType, nanos: i64) -> Self {
Self::from_truncated_nanoseconds(nanos)
}
}
impl Mul<i64> for Duration {
type Output = Duration;
fn mul(self, q: i64) -> Self::Output {
Duration::from_total_nanoseconds(
self.total_nanoseconds()
.saturating_mul((q * Unit::Nanosecond).total_nanoseconds()),
)
}
}
impl Mul<f64> for Duration {
type Output = Duration;
fn mul(self, q: f64) -> Self::Output {
let mut p: i32 = 0;
let mut new_val = q;
let ten: f64 = 10.0;
loop {
if (new_val.floor() - new_val).abs() < f64::EPSILON {
break;
}
p += 1;
new_val = q * ten.powi(p);
}
Duration::from_total_nanoseconds(
self.total_nanoseconds()
.saturating_mul(new_val as i128)
.saturating_div(10_i128.pow(p.try_into().unwrap())),
)
}
}
macro_rules! impl_ops_for_type {
($type:ident) => {
impl Mul<Unit> for $type {
type Output = Duration;
fn mul(self, q: Unit) -> Duration {
q * self
}
}
impl Mul<$type> for Freq {
type Output = Duration;
fn mul(self, q: $type) -> Duration {
let total_ns = match self {
Freq::GigaHertz => 1.0 / (q as f64),
Freq::MegaHertz => (NANOSECONDS_PER_MICROSECOND as f64) / (q as f64),
Freq::KiloHertz => NANOSECONDS_PER_MILLISECOND as f64 / (q as f64),
Freq::Hertz => (NANOSECONDS_PER_SECOND as f64) / (q as f64),
};
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)
}
}
}
impl Mul<Freq> for $type {
type Output = Duration;
fn mul(self, q: Freq) -> Duration {
q * self
}
}
#[allow(clippy::suspicious_arithmetic_impl)]
impl Div<$type> for Duration {
type Output = Duration;
fn div(self, q: $type) -> Self::Output {
Duration::from_total_nanoseconds(
self.total_nanoseconds()
.saturating_div((q * Unit::Nanosecond).total_nanoseconds()),
)
}
}
impl Mul<Duration> for $type {
type Output = Duration;
fn mul(self, q: Self::Output) -> Self::Output {
q * self
}
}
impl TimeUnits for $type {}
impl Frequencies for $type {}
};
}
impl fmt::Display for Duration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.total_nanoseconds() == 0 {
write!(f, "0 ns")
} else {
let (sign, days, hours, minutes, seconds, milli, us, nano) = self.decompose();
if sign == -1 {
write!(f, "-")?;
}
let values = [days, hours, minutes, seconds, milli, us, nano];
let units = ["days", "h", "min", "s", "ms", "μs", "ns"];
let mut insert_space = false;
for (val, unit) in values.iter().zip(units.iter()) {
if *val > 0 {
if insert_space {
write!(f, " ")?;
}
write!(f, "{} {}", val, unit)?;
insert_space = true;
}
}
Ok(())
}
}
}
impl fmt::LowerExp for Duration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let seconds_f64 = self.to_seconds();
let seconds_f64_abs = seconds_f64.abs();
if seconds_f64_abs < 1e-5 {
fmt::Display::fmt(&(seconds_f64 * 1e9), f)?;
write!(f, " ns")
} else if seconds_f64_abs < 1e-2 {
fmt::Display::fmt(&(seconds_f64 * 1e3), f)?;
write!(f, " ms")
} else if seconds_f64_abs < 3.0 * SECONDS_PER_MINUTE {
fmt::Display::fmt(&(seconds_f64), f)?;
write!(f, " s")
} else if seconds_f64_abs < SECONDS_PER_HOUR {
fmt::Display::fmt(&(seconds_f64 / SECONDS_PER_MINUTE), f)?;
write!(f, " min")
} else if seconds_f64_abs < SECONDS_PER_DAY {
fmt::Display::fmt(&(seconds_f64 / SECONDS_PER_HOUR), f)?;
write!(f, " h")
} else {
fmt::Display::fmt(&(seconds_f64 / SECONDS_PER_DAY), f)?;
write!(f, " days")
}
}
}
impl Add for Duration {
type Output = Duration;
#[allow(clippy::absurd_extreme_comparisons)]
fn add(self, rhs: Self) -> Duration {
let mut me = self;
match me.centuries.checked_add(rhs.centuries) {
None => {
if me.centuries < 0 {
return Self::MIN;
} else {
return Self::MAX;
}
}
Some(centuries) => {
me.centuries = centuries;
}
}
if me.centuries == Self::MIN.centuries && self.nanoseconds < Self::MIN.nanoseconds {
match me
.nanoseconds
.checked_sub(NANOSECONDS_PER_CENTURY - rhs.nanoseconds)
{
Some(nanos) => me.nanoseconds = nanos,
None => {
me.centuries += 1; me.nanoseconds = rhs.nanoseconds
}
}
} else {
match me.nanoseconds.checked_add(rhs.nanoseconds) {
Some(nanoseconds) => me.nanoseconds = nanoseconds,
None => {
let mut rhs = rhs;
rhs.normalize();
match me.centuries.checked_add(rhs.centuries) {
None => return Self::MAX,
Some(centuries) => me.centuries = centuries,
};
me.nanoseconds += rhs.nanoseconds;
}
}
}
me.normalize();
me
}
}
impl AddAssign for Duration {
fn add_assign(&mut self, rhs: Duration) {
*self = *self + rhs;
}
}
impl Sub for Duration {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
let mut me = self;
match me.centuries.checked_sub(rhs.centuries) {
None => {
return Self::MIN;
}
Some(centuries) => {
me.centuries = centuries;
}
}
match me.nanoseconds.checked_sub(rhs.nanoseconds) {
None => {
match me.centuries.checked_sub(1) {
Some(centuries) => {
me.centuries = centuries;
me.nanoseconds = me.nanoseconds + NANOSECONDS_PER_CENTURY - rhs.nanoseconds;
}
None => {
return Self::MIN;
}
};
}
Some(nanos) => me.nanoseconds = nanos,
};
me.normalize();
me
}
}
impl SubAssign for Duration {
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
}
}
impl Add<Unit> for Duration {
type Output = Self;
#[allow(clippy::identity_op)]
fn add(self, rhs: Unit) -> Self {
self + rhs * 1
}
}
impl AddAssign<Unit> for Duration {
#[allow(clippy::identity_op)]
fn add_assign(&mut self, rhs: Unit) {
*self = *self + rhs * 1;
}
}
impl Sub<Unit> for Duration {
type Output = Duration;
#[allow(clippy::identity_op)]
fn sub(self, rhs: Unit) -> Duration {
self - rhs * 1
}
}
impl SubAssign<Unit> for Duration {
#[allow(clippy::identity_op)]
fn sub_assign(&mut self, rhs: Unit) {
*self = *self - rhs * 1;
}
}
impl PartialEq<Unit> for Duration {
#[allow(clippy::identity_op)]
fn eq(&self, unit: &Unit) -> bool {
*self == *unit * 1
}
}
impl PartialOrd<Unit> for Duration {
#[allow(clippy::identity_op, clippy::comparison_chain)]
fn partial_cmp(&self, unit: &Unit) -> Option<Ordering> {
let unit_deref = *unit;
let unit_as_duration: Duration = unit_deref * 1;
if self < &unit_as_duration {
Some(Ordering::Less)
} else if self > &unit_as_duration {
Some(Ordering::Greater)
} else {
Some(Ordering::Equal)
}
}
}
impl Neg for Duration {
type Output = Self;
#[must_use]
fn neg(self) -> Self::Output {
if self == Self::MIN {
Self::MAX
} else if self == Self::MAX {
Self::MIN
} else {
match NANOSECONDS_PER_CENTURY.checked_sub(self.nanoseconds) {
Some(nanoseconds) => {
Self::from_parts(-self.centuries - 1, nanoseconds)
}
None => {
if self > Duration::ZERO {
let dur_to_max = Self::MAX - self;
Self::MIN + dur_to_max
} else {
let dur_to_min = Self::MIN + self;
Self::MAX - dur_to_min
}
}
}
}
}
}
#[cfg(not(kani))]
impl FromStr for Duration {
type Err = Errors;
fn from_str(s_in: &str) -> Result<Self, Self::Err> {
let mut decomposed = [0.0_f64; 7];
let mut prev_idx = 0;
let mut seeking_number = true;
let mut latest_value = 0.0;
let s = s_in.trim();
if s.is_empty() {
return Err(Errors::ParseError(ParsingErrors::ValueError));
}
if let Some(char) = s.chars().next() {
if char == '+' || char == '-' {
let offset_sign = if char == '-' { -1 } else { 1 };
let indexes: (usize, usize, usize) = (1, 3, 5);
let colon = if s.len() == 3 || s.len() == 5 || s.len() == 7 {
0
} else if s.len() == 4 || s.len() == 6 || s.len() == 9 {
1
} else {
return Err(Errors::ParseError(ParsingErrors::ValueError));
};
let hours: i64 = match lexical_core::parse(s[indexes.0..indexes.1].as_bytes()) {
Ok(val) => val,
Err(_) => return Err(Errors::ParseError(ParsingErrors::ValueError)),
};
let mut minutes: i64 = 0;
let mut seconds: i64 = 0;
match s.get(indexes.1 + colon..indexes.2 + colon) {
None => {
}
Some(subs) => {
match lexical_core::parse(subs.as_bytes()) {
Ok(val) => minutes = val,
Err(_) => return Err(Errors::ParseError(ParsingErrors::ValueError)),
}
match s.get(indexes.2 + 2 * colon..) {
None => {
}
Some(subs) => {
if !subs.is_empty() {
match lexical_core::parse(subs.as_bytes()) {
Ok(val) => seconds = val,
Err(_) => {
return Err(Errors::ParseError(
ParsingErrors::ValueError,
))
}
}
}
}
}
}
}
if offset_sign == -1 {
return Ok(-(hours * Unit::Hour
+ minutes * Unit::Minute
+ seconds * Unit::Second));
} else {
return Ok(hours * Unit::Hour
+ minutes * Unit::Minute
+ seconds * Unit::Second);
}
}
};
for (idx, char) in s.chars().enumerate() {
if char == ' ' || idx == s.len() - 1 {
if seeking_number {
if prev_idx == idx {
return Err(Errors::ParseError(ParsingErrors::UnknownOrMissingUnit));
}
match lexical_core::parse(s[prev_idx..idx].as_bytes()) {
Ok(val) => latest_value = val,
Err(_) => return Err(Errors::ParseError(ParsingErrors::ValueError)),
}
seeking_number = false;
} else {
let end_idx = if idx == s.len() - 1 { idx + 1 } else { idx };
let pos = match &s[prev_idx..end_idx] {
"d" | "days" | "day" => 0,
"h" | "hours" | "hour" => 1,
"min" | "mins" | "minute" | "minutes" => 2,
"s" | "second" | "seconds" => 3,
"ms" | "millisecond" | "milliseconds" => 4,
"us" | "microsecond" | "microseconds" => 5,
"ns" | "nanosecond" | "nanoseconds" => 6,
_ => {
return Err(Errors::ParseError(ParsingErrors::UnknownOrMissingUnit));
}
};
decomposed[pos] = latest_value;
seeking_number = true;
}
prev_idx = idx + 1;
}
}
Ok(Duration::compose_f64(
1,
decomposed[0],
decomposed[1],
decomposed[2],
decomposed[3],
decomposed[4],
decomposed[5],
decomposed[6],
))
}
}
impl_ops_for_type!(f64);
impl_ops_for_type!(i64);
const fn div_rem_i128(me: i128, rhs: i128) -> (i128, i128) {
(me.div_euclid(rhs), me.rem_euclid(rhs))
}
const fn div_rem_i64(me: i64, rhs: i64) -> (i64, i64) {
(me.div_euclid(rhs), me.rem_euclid(rhs))
}
#[cfg(feature = "std")]
impl From<Duration> for std::time::Duration {
fn from(hf_duration: Duration) -> Self {
let (sign, days, hours, minutes, seconds, milli, us, nano) = hf_duration.decompose();
if sign < 0 {
std::time::Duration::ZERO
} else {
let above_ns_f64: f64 =
Duration::compose(sign, days, hours, minutes, seconds, milli, us, 0).to_seconds();
std::time::Duration::new(above_ns_f64 as u64, nano as u32)
}
}
}
#[cfg(feature = "std")]
impl From<std::time::Duration> for Duration {
fn from(std_duration: std::time::Duration) -> Self {
std_duration.as_secs_f64() * Unit::Second
}
}
#[test]
#[cfg(feature = "serde")]
fn test_serdes() {
let dt = Duration::from_seconds(10.1);
let content = r#"{"centuries":0,"nanoseconds":10100000000}"#;
assert_eq!(content, serde_json::to_string(&dt).unwrap());
let parsed: Duration = serde_json::from_str(content).unwrap();
assert_eq!(dt, parsed);
}
#[test]
fn test_bounds() {
let min = Duration::MIN;
assert_eq!(min.centuries, i16::MIN);
assert_eq!(min.nanoseconds, 0);
let max = Duration::MAX;
assert_eq!(max.centuries, i16::MAX);
assert_eq!(max.nanoseconds, NANOSECONDS_PER_CENTURY);
let min_p = Duration::MIN_POSITIVE;
assert_eq!(min_p.centuries, 0);
assert_eq!(min_p.nanoseconds, 1);
let min_n = Duration::MIN_NEGATIVE;
assert_eq!(min_n.centuries, -1);
assert_eq!(min_n.nanoseconds, NANOSECONDS_PER_CENTURY - 1);
let min_n1 = Duration::MIN - 1 * Unit::Nanosecond;
assert_eq!(min_n1, Duration::MIN);
let max_n1 = Duration::MAX - 1 * Unit::Nanosecond;
assert_eq!(max_n1.centuries, i16::MAX);
assert_eq!(max_n1.nanoseconds, NANOSECONDS_PER_CENTURY - 1);
}
#[cfg(kani)]
#[kani::proof]
fn formal_duration_normalize_any() {
let dur: Duration = kani::any();
dur.decompose();
}
#[cfg(kani)]
#[kani::proof]
fn formal_duration_truncated_ns_reciprocity() {
let nanoseconds: i64 = kani::any();
let dur_from_part = Duration::from_truncated_nanoseconds(nanoseconds);
let u_ns = dur_from_part.nanoseconds;
let centuries = dur_from_part.centuries;
if centuries <= -3 || centuries >= 3 {
assert_eq!(
dur_from_part.try_truncated_nanoseconds(),
Err(Errors::Overflow)
);
} else if centuries == -1 {
let expect_rslt = -((NANOSECONDS_PER_CENTURY - u_ns) as i64);
let recip_ns = dur_from_part.try_truncated_nanoseconds().unwrap();
assert_eq!(recip_ns, expect_rslt);
} else if centuries < 0 {
let nanos = u_ns.rem_euclid(NANOSECONDS_PER_CENTURY);
let expect_rslt = i64::from(centuries) * NANOSECONDS_PER_CENTURY as i64 + nanos as i64;
let recip_ns = dur_from_part.try_truncated_nanoseconds().unwrap();
assert_eq!(recip_ns, expect_rslt);
} else {
let recip_ns = dur_from_part.try_truncated_nanoseconds().unwrap();
assert_eq!(recip_ns, nanoseconds);
}
}
#[cfg(kani)]
mod tests {
use super::*;
macro_rules! repeat_test {
($test_name:ident, $bounds:expr) => {
#[kani::proof]
fn $test_name() {
for pair in $bounds.windows(2) {
let seconds: f64 = kani::any();
kani::assume(seconds > pair[0]);
kani::assume(seconds < pair[1]);
if seconds.is_finite() {
let big_seconds = seconds * 1e9;
let floored = big_seconds.floor();
let truncated_ns = floored * 1e-9;
let duration: Duration = Duration::from_seconds(truncated_ns);
let truncated_out = duration.to_seconds();
let floored_out = truncated_out * 1e9;
if floored != floored_out {
let floored_out_bits = floored_out.to_bits();
let floored_bits = floored.to_bits();
if floored_out_bits > floored_bits {
assert_eq!(floored_out_bits - floored_bits, 1);
} else {
assert_eq!(floored_bits - floored_out_bits, 1);
}
} else {
assert_eq!(floored_out, floored);
}
}
}
}
};
}
repeat_test!(test_dur_f64_recip_0, [1e-9, 1e-8, 1e-7, 1e-6, 1e-5]);
repeat_test!(test_dur_f64_recip_1, [1e-5, 1e-4, 1e-3]);
}