use std::cmp::Ordering;
use std::fmt;
use std::str::FromStr;
use crate::{ParseError, TimeConfig, TimeConfigBuilder};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Duration {
pub positive: bool,
pub day: u32,
pub second: u32,
pub microsecond: u32,
}
impl fmt::Display for Duration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.positive {
write!(f, "-")?;
}
write!(f, "P")?;
if self.day != 0 {
let year = self.day / 365;
if year != 0 {
write!(f, "{year}Y")?;
}
let day = self.day % 365;
if day != 0 {
write!(f, "{day}D")?;
}
}
if self.second != 0 || self.microsecond != 0 {
let (hour, minute, sec) = self.to_hms();
write!(f, "T")?;
if hour != 0 {
write!(f, "{hour}H")?;
}
if minute != 0 {
write!(f, "{minute}M")?;
}
if sec != 0 || self.microsecond != 0 {
write!(f, "{sec}")?;
if self.microsecond != 0 {
let s = format!("{:06}", self.microsecond);
write!(f, ".{}", s.trim_end_matches('0'))?;
}
write!(f, "S")?;
}
}
if self.second == 0 && self.microsecond == 0 && self.day == 0 {
write!(f, "T0S")?;
}
Ok(())
}
}
impl FromStr for Duration {
type Err = ParseError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse_str(s)
}
}
impl Duration {
fn to_hms(&self) -> (u32, u32, u32) {
let hours = self.second / 3600;
let minutes = (self.second % 3600) / 60;
let remaining_seconds = self.second % 60;
(hours, minutes, remaining_seconds)
}
}
impl PartialOrd for Duration {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self.positive, other.positive) {
(true, false) => Some(Ordering::Greater),
(false, true) => Some(Ordering::Less),
(self_positive, _) => {
let self_t = (self.day, self.second, self.microsecond);
let other_t = (other.day, other.second, other.microsecond);
if self_positive {
self_t.partial_cmp(&other_t)
} else {
other_t.partial_cmp(&self_t)
}
}
}
}
}
macro_rules! checked {
($a:ident + $b:expr) => {
$a.checked_add($b).ok_or(ParseError::DurationValueTooLarge)?
};
($a:ident * $b:expr) => {
$a.checked_mul($b).ok_or(ParseError::DurationValueTooLarge)?
};
}
impl Duration {
pub fn new(positive: bool, day: u32, second: u32, microsecond: u32) -> Result<Self, ParseError> {
let mut d = Self {
positive,
day,
second,
microsecond,
};
d.normalize()?;
Ok(d)
}
#[inline]
pub fn parse_str(str: &str) -> Result<Self, ParseError> {
Self::parse_bytes(str.as_bytes())
}
#[inline]
pub fn parse_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
Duration::parse_bytes_with_config(bytes, &TimeConfigBuilder::new().build())
}
#[inline]
pub fn parse_bytes_with_config(bytes: &[u8], config: &TimeConfig) -> Result<Self, ParseError> {
let (positive, bytes) = match bytes {
[b'-', bytes @ ..] => (false, bytes),
[b'+', bytes @ ..] | bytes => (true, bytes),
};
let mut d = match bytes {
[] => return Err(ParseError::TooShort),
[b'P', iso_duration @ ..] => Self::parse_iso_duration(iso_duration)?,
bytes => {
if Self::is_duration_date_format(bytes) || bytes.len() < 5 {
Self::parse_days_time(bytes, config)?
} else {
Self::parse_time(bytes, config)?
}
}
};
d.positive = positive;
d.normalize()?;
Ok(d)
}
#[inline]
pub fn signed_total_seconds(&self) -> i64 {
let sign = if self.positive { 1 } else { -1 };
sign * (self.day as i64 * 86400 + self.second as i64)
}
#[inline]
pub fn signed_total_ms(&self) -> i64 {
self.signed_total_seconds() * 1000 + (self.signed_microseconds() as i64) / 1000
}
#[inline]
pub fn signed_microseconds(&self) -> i32 {
let sign = if self.positive { 1 } else { -1 };
sign * self.microsecond as i32
}
fn normalize(&mut self) -> Result<(), ParseError> {
if self.microsecond >= 1_000_000 {
self.second = self
.second
.checked_add(self.microsecond / 1_000_000)
.ok_or(ParseError::DurationValueTooLarge)?;
self.microsecond %= 1_000_000;
}
if self.second >= 86_400 {
self.day = self
.day
.checked_add(self.second / 86_400)
.ok_or(ParseError::DurationValueTooLarge)?;
self.second %= 86_400;
}
if self.day > 999_999_999 {
Err(ParseError::DurationDaysTooLarge)
} else {
Ok(())
}
}
fn parse_iso_duration(bytes: &[u8]) -> Result<Self, ParseError> {
let mut got_t = false;
let mut last_had_fraction = false;
let mut position: usize = 0;
let mut day: u32 = 0;
let mut second: u32 = 0;
let mut microsecond: u32 = 0;
loop {
match bytes.get(position).copied() {
Some(b'T') => {
if got_t {
return Err(ParseError::DurationTRepeated);
}
got_t = true;
}
Some(c) => {
let (value, op_fraction, offset) = Self::parse_number_frac(&bytes[position..], c)?;
if last_had_fraction {
return Err(ParseError::DurationInvalidFraction);
}
if op_fraction.is_some() {
last_had_fraction = true;
}
position += offset;
if got_t {
let mult: u32 = match bytes.get(position).copied() {
Some(b'H') => 3600,
Some(b'M') => 60,
Some(b'S') => 1,
_ => return Err(ParseError::DurationInvalidTimeUnit),
};
second = checked!(second + checked!(mult * value));
if let Some(fraction) = op_fraction {
let extra_seconds = fraction * mult as f64;
let extra_full_seconds = extra_seconds.trunc();
second = checked!(second + extra_full_seconds as u32);
let micro_extra = ((extra_seconds - extra_full_seconds) * 1_000_000.0).round() as u32;
microsecond = checked!(microsecond + micro_extra);
}
} else {
let mult: u32 = match bytes.get(position).copied() {
Some(b'Y') => 365,
Some(b'M') => 30,
Some(b'W') => 7,
Some(b'D') => 1,
_ => return Err(ParseError::DurationInvalidDateUnit),
};
day = checked!(day + checked!(value * mult));
if let Some(fraction) = op_fraction {
let extra_days = fraction * mult as f64;
let extra_full_days = extra_days.trunc();
day = checked!(day + extra_full_days as u32);
let extra_seconds = (extra_days - extra_full_days) * 86_400.0;
let extra_full_seconds = extra_seconds.trunc();
second = checked!(second + extra_full_seconds as u32);
microsecond += ((extra_seconds - extra_full_seconds) * 1_000_000.0).round() as u32;
}
}
}
None => break,
}
position += 1;
}
if position < 2 {
return Err(ParseError::TooShort);
}
Ok(Self {
positive: false, day,
second,
microsecond,
})
}
fn is_duration_date_format(bytes: &[u8]) -> bool {
bytes.iter().any(|&byte| byte == b'd' || byte == b'D')
}
fn parse_days_time(bytes: &[u8], config: &TimeConfig) -> Result<Self, ParseError> {
let (day, position) = match bytes.first().copied() {
Some(c) => Self::parse_number(bytes, c),
_ => Err(ParseError::TooShort),
}?;
let Some(
[b' ', b'd' | b'D', remaining @ ..] | [b'd' | b'D', remaining @ ..],
) = bytes.get(position..)
else {
return Err(ParseError::DurationInvalidDays);
};
let remaining = match remaining {
[b'a', b'y', b's', remaining @ ..]
| [b'a', b'y', remaining @ ..]
| [b'A', b'Y', b'S', remaining @ ..]
| [b'A', b'Y', remaining @ ..]
=> {
remaining
}
[b'a', ..] | [b'A', ..] => return Err(ParseError::DurationInvalidDays),
remaining => remaining,
};
let remaining = match remaining {
[b',', b' ', remaining @ ..]
| [b',', remaining @ ..]
| [b' ', remaining @ ..]
| remaining => remaining,
};
if remaining.is_empty() {
return Ok(Self {
positive: false, day,
second: 0,
microsecond: 0,
});
}
let t = Self::parse_time(remaining, config)?;
if t.day > 0 {
return Err(ParseError::DurationHourValueTooLarge);
}
Ok(Self {
positive: false, day,
second: t.second,
microsecond: t.microsecond,
})
}
fn parse_time(bytes: &[u8], config: &TimeConfig) -> Result<Self, ParseError> {
let byte_len = bytes.len();
if byte_len < 5 {
return Err(ParseError::TooShort);
}
const HOUR_NUMERIC_LIMIT: i64 = 24 * 10i64.pow(8);
let mut hour: i64 = 0;
let mut chunks = bytes.splitn(2, |&byte| byte == b':');
let (hour_part, mut remaining) = match (chunks.next(), chunks.next(), chunks.next()) {
(_, _, Some(_)) | (None, _, _) => unreachable!("should always be 1 or 2 chunks"),
(Some(_hour_part), None, _) => return Err(ParseError::InvalidCharHour),
(Some(hour_part), Some(remaining), None) => (hour_part, remaining),
};
if hour_part.len() > 10 {
return Err(ParseError::DurationHourValueTooLarge);
}
for byte in hour_part {
let h = byte.wrapping_sub(b'0');
if h > 9 {
return Err(ParseError::InvalidCharHour);
}
hour = (hour * 10) + (h as i64);
}
if hour > HOUR_NUMERIC_LIMIT {
return Err(ParseError::DurationHourValueTooLarge);
}
let mut new_bytes = *b"00:00:00.000000";
if 3 + remaining.len() > new_bytes.len() {
match config.microseconds_precision_overflow_behavior {
crate::MicrosecondsPrecisionOverflowBehavior::Truncate => remaining = &remaining[..new_bytes.len() - 3],
crate::MicrosecondsPrecisionOverflowBehavior::Error => return Err(ParseError::SecondFractionTooLong),
}
}
let new_bytes = &mut new_bytes[..3 + remaining.len()];
new_bytes[3..].copy_from_slice(remaining);
let t = crate::time::PureTime::parse(new_bytes, 0, config)?;
if new_bytes.len() > t.position {
return Err(ParseError::ExtraCharacters);
}
let day = hour as u32 / 24;
hour %= 24;
Ok(Self {
positive: false, day,
second: t.total_seconds() + (hour as u32) * 3_600,
microsecond: t.microsecond,
})
}
fn parse_number(bytes: &[u8], d1: u8) -> Result<(u32, usize), ParseError> {
let mut value = match d1 {
c if d1.is_ascii_digit() => (c - b'0') as u32,
_ => return Err(ParseError::DurationInvalidNumber),
};
let mut position = 1;
loop {
match bytes.get(position) {
Some(c) if c.is_ascii_digit() => {
value = checked!(value * 10);
value = checked!(value + (c - b'0') as u32);
position += 1;
}
_ => return Ok((value, position)),
}
}
}
fn parse_number_frac(bytes: &[u8], d1: u8) -> Result<(u32, Option<f64>, usize), ParseError> {
let (value, mut position) = Self::parse_number(bytes, d1)?;
let next_char = bytes.get(position).copied();
if next_char == Some(b'.') || next_char == Some(b',') {
let mut decimal = 0_f64;
let mut denominator = 1_f64;
loop {
position += 1;
match bytes.get(position) {
Some(c) if c.is_ascii_digit() => {
decimal *= 10.0;
decimal += (c - b'0') as f64;
denominator *= 10.0;
}
_ => return Ok((value, Some(decimal / denominator), position)),
}
}
} else {
Ok((value, None, position))
}
}
}