use core::time::Duration;
use crate::{
civil::{Date, DateTime, Time},
error::{err, ErrorContext},
fmt::{friendly, temporal},
tz::Offset,
util::{escape, rangeint::TryRFrom, t},
Error, RoundMode, Timestamp, Unit, Zoned,
};
#[cfg(not(feature = "std"))]
use crate::util::libm::Float;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SignedDuration {
secs: i64,
nanos: i32,
}
const NANOS_PER_SEC: i32 = 1_000_000_000;
const NANOS_PER_MILLI: i32 = 1_000_000;
const NANOS_PER_MICRO: i32 = 1_000;
const MILLIS_PER_SEC: i64 = 1_000;
const MICROS_PER_SEC: i64 = 1_000_000;
const SECS_PER_MINUTE: i64 = 60;
const MINS_PER_HOUR: i64 = 60;
impl SignedDuration {
pub const ZERO: SignedDuration = SignedDuration { secs: 0, nanos: 0 };
pub const MIN: SignedDuration =
SignedDuration { secs: i64::MIN, nanos: -(NANOS_PER_SEC - 1) };
pub const MAX: SignedDuration =
SignedDuration { secs: i64::MAX, nanos: NANOS_PER_SEC - 1 };
#[inline]
pub const fn new(mut secs: i64, mut nanos: i32) -> SignedDuration {
if !(-NANOS_PER_SEC < nanos && nanos < NANOS_PER_SEC) {
let addsecs = nanos / NANOS_PER_SEC;
secs = match secs.checked_add(addsecs as i64) {
Some(secs) => secs,
None => panic!(
"nanoseconds overflowed seconds in SignedDuration::new"
),
};
nanos = nanos % NANOS_PER_SEC;
}
if nanos == 0 || secs == 0 || secs.signum() == (nanos.signum() as i64)
{
return SignedDuration::new_unchecked(secs, nanos);
}
if secs < 0 {
debug_assert!(nanos > 0);
secs += 1;
nanos -= NANOS_PER_SEC;
} else {
debug_assert!(secs > 0);
debug_assert!(nanos < 0);
secs -= 1;
nanos += NANOS_PER_SEC;
}
SignedDuration::new_unchecked(secs, nanos)
}
#[inline]
pub(crate) const fn new_without_nano_overflow(
secs: i64,
nanos: i32,
) -> SignedDuration {
assert!(nanos <= 999_999_999);
assert!(nanos >= -999_999_999);
SignedDuration::new_unchecked(secs, nanos)
}
#[inline]
const fn new_unchecked(secs: i64, nanos: i32) -> SignedDuration {
debug_assert!(nanos <= 999_999_999);
debug_assert!(nanos >= -999_999_999);
SignedDuration { secs, nanos }
}
#[inline]
pub const fn from_secs(secs: i64) -> SignedDuration {
SignedDuration::new_unchecked(secs, 0)
}
#[inline]
pub const fn from_millis(millis: i64) -> SignedDuration {
let secs = millis / MILLIS_PER_SEC;
let nanos = (millis % MILLIS_PER_SEC) as i32 * NANOS_PER_MILLI;
SignedDuration::new_unchecked(secs, nanos)
}
#[inline]
pub const fn from_micros(micros: i64) -> SignedDuration {
let secs = micros / MICROS_PER_SEC;
let nanos = (micros % MICROS_PER_SEC) as i32 * NANOS_PER_MICRO;
SignedDuration::new_unchecked(secs, nanos)
}
#[inline]
pub const fn from_nanos(nanos: i64) -> SignedDuration {
let secs = nanos / (NANOS_PER_SEC as i64);
let nanos = (nanos % (NANOS_PER_SEC as i64)) as i32;
SignedDuration::new_unchecked(secs, nanos)
}
#[inline]
pub const fn from_hours(hours: i64) -> SignedDuration {
const MIN_HOUR: i64 = i64::MIN / (SECS_PER_MINUTE * MINS_PER_HOUR);
const MAX_HOUR: i64 = i64::MAX / (SECS_PER_MINUTE * MINS_PER_HOUR);
if hours < MIN_HOUR {
panic!("hours overflowed minimum number of SignedDuration seconds")
}
if hours > MAX_HOUR {
panic!("hours overflowed maximum number of SignedDuration seconds")
}
SignedDuration::from_secs(hours * MINS_PER_HOUR * SECS_PER_MINUTE)
}
#[inline]
pub const fn from_mins(minutes: i64) -> SignedDuration {
const MIN_MINUTE: i64 = i64::MIN / SECS_PER_MINUTE;
const MAX_MINUTE: i64 = i64::MAX / SECS_PER_MINUTE;
if minutes < MIN_MINUTE {
panic!(
"minutes overflowed minimum number of SignedDuration seconds"
)
}
if minutes > MAX_MINUTE {
panic!(
"minutes overflowed maximum number of SignedDuration seconds"
)
}
SignedDuration::from_secs(minutes * SECS_PER_MINUTE)
}
pub(crate) fn from_timestamp(timestamp: Timestamp) -> SignedDuration {
SignedDuration::new_unchecked(
timestamp.as_second(),
timestamp.subsec_nanosecond(),
)
}
#[inline]
pub const fn is_zero(&self) -> bool {
self.secs == 0 && self.nanos == 0
}
#[inline]
pub const fn as_secs(&self) -> i64 {
self.secs
}
#[inline]
pub const fn subsec_millis(&self) -> i32 {
self.nanos / NANOS_PER_MILLI
}
#[inline]
pub const fn subsec_micros(&self) -> i32 {
self.nanos / NANOS_PER_MICRO
}
#[inline]
pub const fn subsec_nanos(&self) -> i32 {
self.nanos
}
#[inline]
pub const fn as_millis(&self) -> i128 {
let millis = (self.secs as i128) * (MILLIS_PER_SEC as i128);
let subsec_millis = (self.nanos / NANOS_PER_MILLI) as i128;
millis + subsec_millis
}
#[inline]
pub const fn as_micros(&self) -> i128 {
let micros = (self.secs as i128) * (MICROS_PER_SEC as i128);
let subsec_micros = (self.nanos / NANOS_PER_MICRO) as i128;
micros + subsec_micros
}
#[inline]
pub const fn as_nanos(&self) -> i128 {
let nanos = (self.secs as i128) * (NANOS_PER_SEC as i128);
nanos + (self.nanos as i128)
}
#[inline]
pub const fn checked_add(
self,
rhs: SignedDuration,
) -> Option<SignedDuration> {
let Some(mut secs) = self.secs.checked_add(rhs.secs) else {
return None;
};
let mut nanos = self.nanos + rhs.nanos;
if nanos >= NANOS_PER_SEC {
nanos -= NANOS_PER_SEC;
secs = match secs.checked_add(1) {
None => return None,
Some(secs) => secs,
};
} else if nanos <= -NANOS_PER_SEC {
nanos += NANOS_PER_SEC;
secs = match secs.checked_sub(1) {
None => return None,
Some(secs) => secs,
};
}
if secs != 0 && nanos != 0 && secs.signum() != (nanos.signum() as i64)
{
if secs < 0 {
debug_assert!(nanos > 0);
secs += 1;
nanos -= NANOS_PER_SEC;
} else {
debug_assert!(secs > 0);
debug_assert!(nanos < 0);
secs -= 1;
nanos += NANOS_PER_SEC;
}
}
Some(SignedDuration::new_unchecked(secs, nanos))
}
#[inline]
pub const fn saturating_add(self, rhs: SignedDuration) -> SignedDuration {
let Some(sum) = self.checked_add(rhs) else {
return if rhs.is_negative() {
SignedDuration::MIN
} else {
SignedDuration::MAX
};
};
sum
}
#[inline]
pub const fn checked_sub(
self,
rhs: SignedDuration,
) -> Option<SignedDuration> {
let Some(rhs) = rhs.checked_neg() else { return None };
self.checked_add(rhs)
}
#[inline]
pub const fn saturating_sub(self, rhs: SignedDuration) -> SignedDuration {
let Some(diff) = self.checked_sub(rhs) else {
return if rhs.is_positive() {
SignedDuration::MIN
} else {
SignedDuration::MAX
};
};
diff
}
#[inline]
pub const fn checked_mul(self, rhs: i32) -> Option<SignedDuration> {
let rhs = rhs as i64;
let nanos = (self.nanos as i64) * rhs;
let addsecs = nanos / (NANOS_PER_SEC as i64);
let nanos = (nanos % (NANOS_PER_SEC as i64)) as i32;
let Some(secs) = self.secs.checked_mul(rhs) else { return None };
let Some(secs) = secs.checked_add(addsecs) else { return None };
Some(SignedDuration::new_unchecked(secs, nanos))
}
#[inline]
pub const fn saturating_mul(self, rhs: i32) -> SignedDuration {
let Some(product) = self.checked_mul(rhs) else {
let sign = (self.signum() as i64) * (rhs as i64).signum();
return if sign.is_negative() {
SignedDuration::MIN
} else {
SignedDuration::MAX
};
};
product
}
#[inline]
pub const fn checked_div(self, rhs: i32) -> Option<SignedDuration> {
if rhs == 0 || (self.secs == i64::MIN && rhs == -1) {
return None;
}
let secs = self.secs / (rhs as i64);
let addsecs = self.secs % (rhs as i64);
let mut nanos = self.nanos / rhs;
let addnanos = self.nanos % rhs;
let leftover_nanos =
(addsecs * (NANOS_PER_SEC as i64)) + (addnanos as i64);
nanos += (leftover_nanos / (rhs as i64)) as i32;
debug_assert!(nanos < NANOS_PER_SEC);
Some(SignedDuration::new_unchecked(secs, nanos))
}
#[inline]
pub fn as_secs_f64(&self) -> f64 {
(self.secs as f64) + ((self.nanos as f64) / (NANOS_PER_SEC as f64))
}
#[inline]
pub fn as_secs_f32(&self) -> f32 {
(self.secs as f32) + ((self.nanos as f32) / (NANOS_PER_SEC as f32))
}
#[inline]
pub fn as_millis_f64(&self) -> f64 {
((self.secs as f64) * (MILLIS_PER_SEC as f64))
+ ((self.nanos as f64) / (NANOS_PER_MILLI as f64))
}
#[inline]
pub fn as_millis_f32(&self) -> f32 {
((self.secs as f32) * (MILLIS_PER_SEC as f32))
+ ((self.nanos as f32) / (NANOS_PER_MILLI as f32))
}
#[inline]
pub fn from_secs_f64(secs: f64) -> SignedDuration {
SignedDuration::try_from_secs_f64(secs)
.expect("finite and in-bounds f64")
}
#[inline]
pub fn from_secs_f32(secs: f32) -> SignedDuration {
SignedDuration::try_from_secs_f32(secs)
.expect("finite and in-bounds f32")
}
#[inline]
pub fn try_from_secs_f64(secs: f64) -> Result<SignedDuration, Error> {
if !secs.is_finite() {
return Err(err!(
"could not convert non-finite seconds \
{secs} to signed duration",
));
}
if secs < (i64::MIN as f64) {
return Err(err!(
"floating point seconds {secs} overflows signed duration \
minimum value of {:?}",
SignedDuration::MIN,
));
}
if secs > (i64::MAX as f64) {
return Err(err!(
"floating point seconds {secs} overflows signed duration \
maximum value of {:?}",
SignedDuration::MAX,
));
}
let nanos = (secs.fract() * (NANOS_PER_SEC as f64)).round() as i32;
let secs = secs.trunc() as i64;
Ok(SignedDuration::new_unchecked(secs, nanos))
}
#[inline]
pub fn try_from_secs_f32(secs: f32) -> Result<SignedDuration, Error> {
if !secs.is_finite() {
return Err(err!(
"could not convert non-finite seconds \
{secs} to signed duration",
));
}
if secs < (i64::MIN as f32) {
return Err(err!(
"floating point seconds {secs} overflows signed duration \
minimum value of {:?}",
SignedDuration::MIN,
));
}
if secs > (i64::MAX as f32) {
return Err(err!(
"floating point seconds {secs} overflows signed duration \
maximum value of {:?}",
SignedDuration::MAX,
));
}
let nanos = (secs.fract() * (NANOS_PER_SEC as f32)).round() as i32;
let secs = secs.trunc() as i64;
Ok(SignedDuration::new_unchecked(secs, nanos))
}
#[inline]
pub fn mul_f64(self, rhs: f64) -> SignedDuration {
SignedDuration::from_secs_f64(rhs * self.as_secs_f64())
}
#[inline]
pub fn mul_f32(self, rhs: f32) -> SignedDuration {
SignedDuration::from_secs_f32(rhs * self.as_secs_f32())
}
#[inline]
pub fn div_f64(self, rhs: f64) -> SignedDuration {
SignedDuration::from_secs_f64(self.as_secs_f64() / rhs)
}
#[inline]
pub fn div_f32(self, rhs: f32) -> SignedDuration {
SignedDuration::from_secs_f32(self.as_secs_f32() / rhs)
}
#[inline]
pub fn div_duration_f64(self, rhs: SignedDuration) -> f64 {
let lhs_nanos =
(self.secs as f64) * (NANOS_PER_SEC as f64) + (self.nanos as f64);
let rhs_nanos =
(rhs.secs as f64) * (NANOS_PER_SEC as f64) + (rhs.nanos as f64);
lhs_nanos / rhs_nanos
}
#[inline]
pub fn div_duration_f32(self, rhs: SignedDuration) -> f32 {
let lhs_nanos =
(self.secs as f32) * (NANOS_PER_SEC as f32) + (self.nanos as f32);
let rhs_nanos =
(rhs.secs as f32) * (NANOS_PER_SEC as f32) + (rhs.nanos as f32);
lhs_nanos / rhs_nanos
}
}
impl SignedDuration {
#[inline]
pub const fn as_hours(&self) -> i64 {
self.as_secs() / (MINS_PER_HOUR * SECS_PER_MINUTE)
}
#[inline]
pub const fn as_mins(&self) -> i64 {
self.as_secs() / SECS_PER_MINUTE
}
#[inline]
pub const fn abs(self) -> SignedDuration {
SignedDuration::new_unchecked(self.secs.abs(), self.nanos.abs())
}
#[inline]
pub const fn unsigned_abs(self) -> Duration {
Duration::new(self.secs.unsigned_abs(), self.nanos.unsigned_abs())
}
#[inline]
pub const fn checked_neg(self) -> Option<SignedDuration> {
let Some(secs) = self.secs.checked_neg() else { return None };
Some(SignedDuration::new_unchecked(
secs,
-self.nanos,
))
}
#[inline]
pub const fn signum(self) -> i8 {
if self.is_zero() {
0
} else if self.is_positive() {
1
} else {
debug_assert!(self.is_negative());
-1
}
}
#[inline]
pub const fn is_positive(&self) -> bool {
self.secs.is_positive() || self.nanos.is_positive()
}
#[inline]
pub const fn is_negative(&self) -> bool {
self.secs.is_negative() || self.nanos.is_negative()
}
}
impl SignedDuration {
pub(crate) fn zoned_until(
zoned1: &Zoned,
zoned2: &Zoned,
) -> SignedDuration {
SignedDuration::timestamp_until(zoned1.timestamp(), zoned2.timestamp())
}
pub(crate) fn timestamp_until(
timestamp1: Timestamp,
timestamp2: Timestamp,
) -> SignedDuration {
timestamp2.as_jiff_duration() - timestamp1.as_jiff_duration()
}
pub(crate) fn datetime_until(
datetime1: DateTime,
datetime2: DateTime,
) -> SignedDuration {
let date_until =
SignedDuration::date_until(datetime1.date(), datetime2.date());
let time_until =
SignedDuration::time_until(datetime1.time(), datetime2.time());
date_until + time_until
}
pub(crate) fn date_until(date1: Date, date2: Date) -> SignedDuration {
let days = date1.until_days_ranged(date2);
let hours = 24 * i64::from(days.get());
SignedDuration::from_hours(hours)
}
pub(crate) fn time_until(time1: Time, time2: Time) -> SignedDuration {
let nanos = time1.until_nanoseconds(time2);
SignedDuration::from_nanos(nanos.get())
}
pub(crate) fn offset_until(
offset1: Offset,
offset2: Offset,
) -> SignedDuration {
let secs1 = i64::from(offset1.seconds());
let secs2 = i64::from(offset2.seconds());
let diff = secs2 - secs1;
SignedDuration::from_secs(diff)
}
#[cfg(feature = "std")]
#[inline]
pub fn system_until(
time1: std::time::SystemTime,
time2: std::time::SystemTime,
) -> Result<SignedDuration, Error> {
match time2.duration_since(time1) {
Ok(dur) => SignedDuration::try_from(dur).with_context(|| {
err!(
"unsigned duration {dur:?} for system time since \
Unix epoch overflowed signed duration"
)
}),
Err(err) => {
let dur = err.duration();
let dur =
SignedDuration::try_from(dur).with_context(|| {
err!(
"unsigned duration {dur:?} for system time before \
Unix epoch overflowed signed duration"
)
})?;
dur.checked_neg().ok_or_else(|| {
err!("negating duration {dur:?} from before the Unix epoch \
overflowed signed duration")
})
}
}
}
}
impl SignedDuration {
#[inline]
pub fn round<R: Into<SignedDurationRound>>(
self,
options: R,
) -> Result<SignedDuration, Error> {
let options: SignedDurationRound = options.into();
options.round(self)
}
}
impl core::fmt::Display for SignedDuration {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use crate::fmt::StdFmtWrite;
if f.alternate() {
friendly::DEFAULT_SPAN_PRINTER
.print_duration(self, StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
} else {
temporal::DEFAULT_SPAN_PRINTER
.print_duration(self, StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
}
}
}
impl core::fmt::Debug for SignedDuration {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use crate::fmt::StdFmtWrite;
friendly::DEFAULT_SPAN_PRINTER
.print_duration(self, StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
}
}
impl TryFrom<Duration> for SignedDuration {
type Error = Error;
fn try_from(d: Duration) -> Result<SignedDuration, Error> {
let secs = i64::try_from(d.as_secs()).map_err(|_| {
err!("seconds in unsigned duration {d:?} overflowed i64")
})?;
let nanos = i32::try_from(d.subsec_nanos()).unwrap();
Ok(SignedDuration::new_unchecked(secs, nanos))
}
}
impl TryFrom<SignedDuration> for Duration {
type Error = Error;
fn try_from(sd: SignedDuration) -> Result<Duration, Error> {
let secs = u64::try_from(sd.as_secs()).map_err(|_| {
err!("seconds in signed duration {sd:?} overflowed u64")
})?;
let nanos = u32::try_from(sd.subsec_nanos()).unwrap();
Ok(Duration::new(secs, nanos))
}
}
impl From<Offset> for SignedDuration {
fn from(offset: Offset) -> SignedDuration {
SignedDuration::from_secs(i64::from(offset.seconds()))
}
}
impl core::str::FromStr for SignedDuration {
type Err = Error;
#[inline]
fn from_str(string: &str) -> Result<SignedDuration, Error> {
parse_iso_or_friendly(string.as_bytes())
}
}
impl core::ops::Neg for SignedDuration {
type Output = SignedDuration;
#[inline]
fn neg(self) -> SignedDuration {
self.checked_neg().expect("overflow when negating signed duration")
}
}
impl core::ops::Add for SignedDuration {
type Output = SignedDuration;
#[inline]
fn add(self, rhs: SignedDuration) -> SignedDuration {
self.checked_add(rhs).expect("overflow when adding signed durations")
}
}
impl core::ops::AddAssign for SignedDuration {
#[inline]
fn add_assign(&mut self, rhs: SignedDuration) {
*self = *self + rhs;
}
}
impl core::ops::Sub for SignedDuration {
type Output = SignedDuration;
#[inline]
fn sub(self, rhs: SignedDuration) -> SignedDuration {
self.checked_sub(rhs)
.expect("overflow when subtracting signed durations")
}
}
impl core::ops::SubAssign for SignedDuration {
#[inline]
fn sub_assign(&mut self, rhs: SignedDuration) {
*self = *self - rhs;
}
}
impl core::ops::Mul<i32> for SignedDuration {
type Output = SignedDuration;
#[inline]
fn mul(self, rhs: i32) -> SignedDuration {
self.checked_mul(rhs)
.expect("overflow when multiplying signed duration by scalar")
}
}
impl core::ops::Mul<SignedDuration> for i32 {
type Output = SignedDuration;
#[inline]
fn mul(self, rhs: SignedDuration) -> SignedDuration {
rhs * self
}
}
impl core::ops::MulAssign<i32> for SignedDuration {
#[inline]
fn mul_assign(&mut self, rhs: i32) {
*self = *self * rhs;
}
}
impl core::ops::Div<i32> for SignedDuration {
type Output = SignedDuration;
#[inline]
fn div(self, rhs: i32) -> SignedDuration {
self.checked_div(rhs)
.expect("overflow when dividing signed duration by scalar")
}
}
impl core::ops::DivAssign<i32> for SignedDuration {
#[inline]
fn div_assign(&mut self, rhs: i32) {
*self = *self / rhs;
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for SignedDuration {
#[inline]
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for SignedDuration {
#[inline]
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<SignedDuration, D::Error> {
use serde::de;
struct SignedDurationVisitor;
impl<'de> de::Visitor<'de> for SignedDurationVisitor {
type Value = SignedDuration;
fn expecting(
&self,
f: &mut core::fmt::Formatter,
) -> core::fmt::Result {
f.write_str("a signed duration string")
}
#[inline]
fn visit_bytes<E: de::Error>(
self,
value: &[u8],
) -> Result<SignedDuration, E> {
parse_iso_or_friendly(value).map_err(de::Error::custom)
}
#[inline]
fn visit_str<E: de::Error>(
self,
value: &str,
) -> Result<SignedDuration, E> {
self.visit_bytes(value.as_bytes())
}
}
deserializer.deserialize_str(SignedDurationVisitor)
}
}
#[derive(Clone, Copy, Debug)]
pub struct SignedDurationRound {
smallest: Unit,
mode: RoundMode,
increment: i64,
}
impl SignedDurationRound {
#[inline]
pub fn new() -> SignedDurationRound {
SignedDurationRound {
smallest: Unit::Nanosecond,
mode: RoundMode::HalfExpand,
increment: 1,
}
}
#[inline]
pub fn smallest(self, unit: Unit) -> SignedDurationRound {
SignedDurationRound { smallest: unit, ..self }
}
#[inline]
pub fn mode(self, mode: RoundMode) -> SignedDurationRound {
SignedDurationRound { mode, ..self }
}
#[inline]
pub fn increment(self, increment: i64) -> SignedDurationRound {
SignedDurationRound { increment, ..self }
}
pub(crate) fn get_smallest(&self) -> Unit {
self.smallest
}
fn round(&self, dur: SignedDuration) -> Result<SignedDuration, Error> {
if self.smallest > Unit::Hour {
return Err(err!(
"rounding `SignedDuration` failed because \
a calendar unit of {plural} was provided \
(to round by calendar units, you must use a `Span`)",
plural = self.smallest.plural(),
));
}
let nanos = t::NoUnits128::new_unchecked(dur.as_nanos());
let increment = t::NoUnits::new_unchecked(self.increment);
let rounded = self.mode.round_by_unit_in_nanoseconds(
nanos,
self.smallest,
increment,
);
let seconds = rounded / t::NANOS_PER_SECOND;
let seconds =
t::NoUnits::try_rfrom("seconds", seconds).map_err(|_| {
err!(
"rounding `{dur:#}` to nearest {singular} in increments \
of {increment} resulted in {seconds} seconds, which does \
not fit into an i64 and thus overflows `SignedDuration`",
singular = self.smallest.singular(),
)
})?;
let subsec_nanos = rounded % t::NANOS_PER_SECOND;
let subsec_nanos = i32::try_from(subsec_nanos).unwrap();
Ok(SignedDuration::new(seconds.get(), subsec_nanos))
}
}
impl Default for SignedDurationRound {
fn default() -> SignedDurationRound {
SignedDurationRound::new()
}
}
impl From<Unit> for SignedDurationRound {
fn from(unit: Unit) -> SignedDurationRound {
SignedDurationRound::default().smallest(unit)
}
}
impl From<(Unit, i64)> for SignedDurationRound {
fn from((unit, increment): (Unit, i64)) -> SignedDurationRound {
SignedDurationRound::default().smallest(unit).increment(increment)
}
}
#[inline(always)]
fn parse_iso_or_friendly(bytes: &[u8]) -> Result<SignedDuration, Error> {
if bytes.is_empty() {
return Err(err!(
"an empty string is not a valid `SignedDuration`, \
expected either a ISO 8601 or Jiff's 'friendly' \
format",
));
}
let mut first = bytes[0];
if first == b'+' || first == b'-' {
if bytes.len() == 1 {
return Err(err!(
"found nothing after sign `{sign}`, \
which is not a valid `SignedDuration`, \
expected either a ISO 8601 or Jiff's 'friendly' \
format",
sign = escape::Byte(first),
));
}
first = bytes[1];
}
if first == b'P' || first == b'p' {
temporal::DEFAULT_SPAN_PARSER.parse_duration(bytes)
} else {
friendly::DEFAULT_SPAN_PARSER.parse_duration(bytes)
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use alloc::string::ToString;
use super::*;
#[test]
fn new() {
let d = SignedDuration::new(12, i32::MAX);
assert_eq!(d.as_secs(), 14);
assert_eq!(d.subsec_nanos(), 147_483_647);
let d = SignedDuration::new(-12, i32::MIN);
assert_eq!(d.as_secs(), -14);
assert_eq!(d.subsec_nanos(), -147_483_648);
let d = SignedDuration::new(i64::MAX, i32::MIN);
assert_eq!(d.as_secs(), i64::MAX - 3);
assert_eq!(d.subsec_nanos(), 852_516_352);
let d = SignedDuration::new(i64::MIN, i32::MAX);
assert_eq!(d.as_secs(), i64::MIN + 3);
assert_eq!(d.subsec_nanos(), -852_516_353);
}
#[test]
#[should_panic]
fn new_fail_positive() {
SignedDuration::new(i64::MAX, 1_000_000_000);
}
#[test]
#[should_panic]
fn new_fail_negative() {
SignedDuration::new(i64::MIN, -1_000_000_000);
}
#[test]
fn from_hours_limits() {
let d = SignedDuration::from_hours(2_562_047_788_015_215);
assert_eq!(d.as_secs(), 9223372036854774000);
let d = SignedDuration::from_hours(-2_562_047_788_015_215);
assert_eq!(d.as_secs(), -9223372036854774000);
}
#[test]
#[should_panic]
fn from_hours_fail_positive() {
SignedDuration::from_hours(2_562_047_788_015_216);
}
#[test]
#[should_panic]
fn from_hours_fail_negative() {
SignedDuration::from_hours(-2_562_047_788_015_216);
}
#[test]
fn from_minutes_limits() {
let d = SignedDuration::from_mins(153_722_867_280_912_930);
assert_eq!(d.as_secs(), 9223372036854775800);
let d = SignedDuration::from_mins(-153_722_867_280_912_930);
assert_eq!(d.as_secs(), -9223372036854775800);
}
#[test]
#[should_panic]
fn from_minutes_fail_positive() {
SignedDuration::from_mins(153_722_867_280_912_931);
}
#[test]
#[should_panic]
fn from_minutes_fail_negative() {
SignedDuration::from_mins(-153_722_867_280_912_931);
}
#[test]
fn add() {
let add = |(secs1, nanos1): (i64, i32),
(secs2, nanos2): (i64, i32)|
-> (i64, i32) {
let d1 = SignedDuration::new(secs1, nanos1);
let d2 = SignedDuration::new(secs2, nanos2);
let sum = d1.checked_add(d2).unwrap();
(sum.as_secs(), sum.subsec_nanos())
};
assert_eq!(add((1, 1), (1, 1)), (2, 2));
assert_eq!(add((1, 1), (-1, -1)), (0, 0));
assert_eq!(add((-1, -1), (1, 1)), (0, 0));
assert_eq!(add((-1, -1), (-1, -1)), (-2, -2));
assert_eq!(add((1, 500_000_000), (1, 500_000_000)), (3, 0));
assert_eq!(add((-1, -500_000_000), (-1, -500_000_000)), (-3, 0));
assert_eq!(
add((5, 200_000_000), (-1, -500_000_000)),
(3, 700_000_000)
);
assert_eq!(
add((-5, -200_000_000), (1, 500_000_000)),
(-3, -700_000_000)
);
}
#[test]
fn add_overflow() {
let add = |(secs1, nanos1): (i64, i32),
(secs2, nanos2): (i64, i32)|
-> Option<(i64, i32)> {
let d1 = SignedDuration::new(secs1, nanos1);
let d2 = SignedDuration::new(secs2, nanos2);
d1.checked_add(d2).map(|d| (d.as_secs(), d.subsec_nanos()))
};
assert_eq!(None, add((i64::MAX, 0), (1, 0)));
assert_eq!(None, add((i64::MIN, 0), (-1, 0)));
assert_eq!(None, add((i64::MAX, 1), (0, 999_999_999)));
assert_eq!(None, add((i64::MIN, -1), (0, -999_999_999)));
}
#[test]
fn signed_duration_deserialize_yaml() {
let expected = SignedDuration::from_secs(123456789);
let deserialized: SignedDuration =
serde_yaml::from_str("PT34293h33m9s").unwrap();
assert_eq!(deserialized, expected);
let deserialized: SignedDuration =
serde_yaml::from_slice("PT34293h33m9s".as_bytes()).unwrap();
assert_eq!(deserialized, expected);
let cursor = Cursor::new(b"PT34293h33m9s");
let deserialized: SignedDuration =
serde_yaml::from_reader(cursor).unwrap();
assert_eq!(deserialized, expected);
}
#[test]
fn from_str() {
let p = |s: &str| -> Result<SignedDuration, Error> { s.parse() };
insta::assert_snapshot!(
p("1 hour").unwrap(),
@"PT1H",
);
insta::assert_snapshot!(
p("+1 hour").unwrap(),
@"PT1H",
);
insta::assert_snapshot!(
p("-1 hour").unwrap(),
@"-PT1H",
);
insta::assert_snapshot!(
p("PT1h").unwrap(),
@"PT1H",
);
insta::assert_snapshot!(
p("+PT1h").unwrap(),
@"PT1H",
);
insta::assert_snapshot!(
p("-PT1h").unwrap(),
@"-PT1H",
);
insta::assert_snapshot!(
p("").unwrap_err(),
@"an empty string is not a valid `SignedDuration`, expected either a ISO 8601 or Jiff's 'friendly' format",
);
insta::assert_snapshot!(
p("+").unwrap_err(),
@"found nothing after sign `+`, which is not a valid `SignedDuration`, expected either a ISO 8601 or Jiff's 'friendly' format",
);
insta::assert_snapshot!(
p("-").unwrap_err(),
@"found nothing after sign `-`, which is not a valid `SignedDuration`, expected either a ISO 8601 or Jiff's 'friendly' format",
);
}
#[test]
fn serde_deserialize() {
let p = |s: &str| -> Result<SignedDuration, serde_json::Error> {
serde_json::from_str(&alloc::format!("\"{s}\""))
};
insta::assert_snapshot!(
p("1 hour").unwrap(),
@"PT1H",
);
insta::assert_snapshot!(
p("+1 hour").unwrap(),
@"PT1H",
);
insta::assert_snapshot!(
p("-1 hour").unwrap(),
@"-PT1H",
);
insta::assert_snapshot!(
p("PT1h").unwrap(),
@"PT1H",
);
insta::assert_snapshot!(
p("+PT1h").unwrap(),
@"PT1H",
);
insta::assert_snapshot!(
p("-PT1h").unwrap(),
@"-PT1H",
);
insta::assert_snapshot!(
p("").unwrap_err(),
@"an empty string is not a valid `SignedDuration`, expected either a ISO 8601 or Jiff's 'friendly' format at line 1 column 2",
);
insta::assert_snapshot!(
p("+").unwrap_err(),
@"found nothing after sign `+`, which is not a valid `SignedDuration`, expected either a ISO 8601 or Jiff's 'friendly' format at line 1 column 3",
);
insta::assert_snapshot!(
p("-").unwrap_err(),
@"found nothing after sign `-`, which is not a valid `SignedDuration`, expected either a ISO 8601 or Jiff's 'friendly' format at line 1 column 3",
);
}
#[test]
fn humantime_compatibility_parse() {
let dur = std::time::Duration::new(26_784, 123_456_789);
let formatted = humantime::format_duration(dur).to_string();
assert_eq!(formatted, "7h 26m 24s 123ms 456us 789ns");
let expected = SignedDuration::try_from(dur).unwrap();
assert_eq!(formatted.parse::<SignedDuration>().unwrap(), expected);
}
#[test]
fn humantime_compatibility_print() {
static PRINTER: friendly::SpanPrinter = friendly::SpanPrinter::new()
.designator(friendly::Designator::HumanTime);
let sdur = SignedDuration::new(26_784, 123_456_789);
let formatted = PRINTER.duration_to_string(&sdur);
assert_eq!(formatted, "7h 26m 24s 123ms 456us 789ns");
let dur = humantime::parse_duration(&formatted).unwrap();
let expected = std::time::Duration::try_from(sdur).unwrap();
assert_eq!(dur, expected);
}
}