use core::str::FromStr;
use num_traits::ConstZero;
use crate::{
Duration, UnitRatio,
errors::{
CannotRepresentDecimalNumber, DurationComponentParsingError,
DurationDesignatorParsingError, DurationParsingError,
},
parse::DecimalNumber,
units::{Second, SecondsPerDay, SecondsPerHour, SecondsPerMinute, SecondsPerYear},
};
impl<Period> FromStr for Duration<i64, Period>
where
Period: UnitRatio,
{
type Err = DurationParsingError;
fn from_str(mut string: &str) -> Result<Self, Self::Err> {
if string.starts_with("P") {
string = string.get(1..).unwrap();
} else {
return Err(DurationParsingError::ExpectedDurationPrefix);
}
let mut duration = Self::ZERO;
let mut previous_designator = None;
loop {
let (component, remainder) = DurationComponent::parse_partial(string)?;
string = remainder;
if let Some(previous) = previous_designator {
if component.designator >= previous {
return Err(DurationParsingError::NonDecreasingDesignators {
current: component.designator,
previous,
});
}
previous_designator = Some(component.designator);
}
duration += component.into_period()?;
if component.has_decimal_fraction() && !string.is_empty() {
return Err(DurationParsingError::OnlyLowestOrderComponentMayHaveDecimalFraction);
}
if string.is_empty() {
return Ok(duration);
}
}
}
}
#[cfg(feature = "serde")]
impl<Representation, Period> serde::Serialize for Duration<Representation, Period>
where
Self: ToString,
Period: ?Sized,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let string = self.to_string();
serializer.serialize_str(&string)
}
}
#[cfg(feature = "serde")]
impl<'de, Representation, Period> serde::Deserialize<'de> for Duration<Representation, Period>
where
Self: FromStr,
<Self as FromStr>::Err: core::fmt::Display,
Period: ?Sized,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let string = String::deserialize(deserializer)?;
Self::from_str(&string).map_err(serde::de::Error::custom)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct DurationComponent {
number: DecimalNumber,
designator: DurationDesignator,
}
impl DurationComponent {
pub fn parse_partial(string: &str) -> Result<(Self, &str), DurationComponentParsingError> {
let (number, remainder) = DecimalNumber::parse_partial(string)?;
let (designator, remainder) = DurationDesignator::parse_partial(remainder)?;
Ok((Self { number, designator }, remainder))
}
fn has_decimal_fraction(&self) -> bool {
!self.number.is_integer()
}
fn into_period<Period>(self) -> Result<Duration<i64, Period>, CannotRepresentDecimalNumber>
where
Period: UnitRatio,
{
match self.designator {
DurationDesignator::Seconds => self.number.convert_period::<Second, Period, _>(),
DurationDesignator::Minutes => {
self.number.convert_period::<SecondsPerMinute, Period, _>()
}
DurationDesignator::Hours => self.number.convert_period::<SecondsPerHour, Period, _>(),
DurationDesignator::Days => self.number.convert_period::<SecondsPerDay, Period, _>(),
DurationDesignator::Years => self.number.convert_period::<SecondsPerYear, Period, _>(),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, derive_more::Display)]
pub enum DurationDesignator {
Seconds,
Minutes,
Hours,
Days,
Years,
}
impl DurationDesignator {
pub fn parse_partial(string: &str) -> Result<(Self, &str), DurationDesignatorParsingError> {
match string.chars().next() {
None => Err(DurationDesignatorParsingError::UnexpectedEndOfString),
Some(character) => {
let string = string.get(1..).unwrap();
let symbol = match character {
'Y' => DurationDesignator::Years,
'D' => DurationDesignator::Days,
'H' => DurationDesignator::Hours,
'M' => DurationDesignator::Minutes,
'S' => DurationDesignator::Seconds,
_ => {
return Err(DurationDesignatorParsingError::UnexpectedCharacter {
character,
});
}
};
Ok((symbol, string))
}
}
}
}
#[test]
fn simple_durations() {
use crate::{Days, Hours, Minutes, Seconds, Years};
let second = Seconds::from_str("P1S").unwrap();
assert_eq!(second, Seconds::new(1));
let seconds = Seconds::from_str("P42S").unwrap();
assert_eq!(seconds, Seconds::new(42));
let minute = Minutes::from_str("P1M").unwrap();
assert_eq!(minute, Minutes::new(1));
let minutes = Minutes::from_str("P1998M").unwrap();
assert_eq!(minutes, Minutes::new(1998));
let hour = Hours::from_str("P1H").unwrap();
assert_eq!(hour, Hours::new(1));
let hours = Hours::from_str("P76H").unwrap();
assert_eq!(hours, Hours::new(76));
let day = Days::from_str("P1D").unwrap();
assert_eq!(day, Days::new(1));
let days = Days::from_str("P31415D").unwrap();
assert_eq!(days, Days::new(31415));
let year = Years::from_str("P1Y").unwrap();
assert_eq!(year, Years::new(1));
let years = Years::from_str("P2000Y").unwrap();
assert_eq!(years, Years::new(2000));
}
#[test]
fn composite_durations() {
use crate::Seconds;
let duration = Seconds::from_str("P1Y2D3H4M5S").unwrap();
assert_eq!(
duration,
Seconds::new(31556952 + 2 * 86400 + 3 * 3600 + 4 * 60 + 5)
);
}
#[test]
fn sub_unit_durations() {
use crate::Hours;
let hour = Hours::from_str("P60M").unwrap();
assert_eq!(hour, Hours::new(1));
}
#[test]
fn fractional_durations() {
use crate::{MilliSeconds, Seconds};
let milliseconds = MilliSeconds::from_str("P5.123S").unwrap();
assert_eq!(milliseconds, MilliSeconds::new(5123));
let milliseconds = MilliSeconds::from_str("P23H59M58.123S").unwrap();
assert_eq!(
milliseconds,
MilliSeconds::new(58123 + 59 * 60_000 + 23 * 3_600_000)
);
let seconds = Seconds::from_str("P23H59.5M").unwrap();
assert_eq!(seconds, Seconds::new(23 * 3600 + 59 * 60 + 30));
}