use super::super::*;
use crate::constants::{NANOS_PER_SECOND, PACKAGE_PREFIX, TIME_NANOS_MAX};
impl Duration {
pub fn normalize(&mut self) {
if self.nanos <= -NANOS_PER_SECOND || self.nanos >= NANOS_PER_SECOND {
if let Some(seconds) = self
.seconds
.checked_add(i64::from(self.nanos / NANOS_PER_SECOND))
{
self.seconds = seconds;
self.nanos %= NANOS_PER_SECOND;
} else if self.nanos < 0 {
self.seconds = i64::MIN;
self.nanos = -TIME_NANOS_MAX;
} else {
self.seconds = i64::MAX;
self.nanos = TIME_NANOS_MAX;
}
}
if self.seconds < 0 && self.nanos > 0 {
if let Some(seconds) = self.seconds.checked_add(1) {
self.seconds = seconds;
self.nanos -= NANOS_PER_SECOND;
} else {
debug_assert_eq!(self.seconds, i64::MAX);
self.nanos = TIME_NANOS_MAX;
}
} else if self.seconds > 0 && self.nanos < 0 {
if let Some(seconds) = self.seconds.checked_sub(1) {
self.seconds = seconds;
self.nanos += NANOS_PER_SECOND;
} else {
debug_assert_eq!(self.seconds, i64::MIN);
self.nanos = -TIME_NANOS_MAX;
}
}
}
#[must_use]
pub fn normalized(&self) -> Self {
let mut result = *self;
result.normalize();
result
}
}
impl Name for Duration {
const PACKAGE: &'static str = PACKAGE_PREFIX;
const NAME: &'static str = "Duration";
fn type_url() -> String {
type_url_for::<Self>()
}
}
impl TryFrom<time::Duration> for Duration {
type Error = DurationError;
fn try_from(duration: time::Duration) -> Result<Self, DurationError> {
let seconds = i64::try_from(duration.as_secs()).map_err(|_| DurationError::OutOfRange)?;
let nanos: i32 = duration
.subsec_nanos()
.try_into()
.map_err(|_| DurationError::OutOfRange)?;
let duration = Self { seconds, nanos };
Ok(duration.normalized())
}
}
impl TryFrom<Duration> for time::Duration {
type Error = DurationError;
fn try_from(mut duration: Duration) -> Result<Self, DurationError> {
duration.normalize();
if duration.seconds >= 0 && duration.nanos >= 0 {
Ok(Self::new(
duration
.seconds
.try_into()
.map_err(|_| DurationError::OutOfRange)?,
duration
.nanos
.try_into()
.map_err(|_| DurationError::OutOfRange)?,
))
} else {
Err(DurationError::NegativeDuration(Self::new(
(-duration.seconds)
.try_into()
.map_err(|_| DurationError::OutOfRange)?,
(-duration.nanos)
.try_into()
.map_err(|_| DurationError::OutOfRange)?,
)))
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
#[non_exhaustive]
pub enum DurationError {
ParseFailure,
NegativeDuration(time::Duration),
OutOfRange,
}
impl fmt::Display for DurationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ParseFailure => write!(f, "failed to parse duration"),
Self::NegativeDuration(duration) => {
write!(f, "failed to convert negative duration: {duration:?}")
}
Self::OutOfRange => {
write!(f, "failed to convert duration out of range")
}
}
}
}
impl core::error::Error for DurationError {}
impl FromStr for Duration {
type Err = DurationError;
fn from_str(s: &str) -> Result<Self, DurationError> {
datetime_internal::parse_duration(s).ok_or(DurationError::ParseFailure)
}
}
#[cfg(feature = "chrono")]
mod chrono {
use crate::{Duration, duration::DurationError};
impl From<::chrono::TimeDelta> for Duration {
#[inline]
fn from(value: ::chrono::TimeDelta) -> Self {
let mut result = Self {
seconds: value.num_seconds(),
nanos: value.subsec_nanos(),
};
result.normalize();
result
}
}
impl TryFrom<Duration> for ::chrono::TimeDelta {
type Error = DurationError;
fn try_from(mut value: Duration) -> Result<Self, DurationError> {
value.normalize();
let seconds = Self::try_seconds(value.seconds).ok_or(DurationError::OutOfRange)?;
let nanos = Self::nanoseconds(value.nanos.into());
seconds
.checked_add(&nanos)
.ok_or(DurationError::OutOfRange)
}
}
}