use std::hash::Hash;
use std::str::FromStr;
use chrono::Duration;
use tea_error::{TError, TResult, tbail, tensure};
use crate::convert::*;
#[cfg(feature = "serde")]
#[serde_with::serde_as]
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct TimeDelta {
pub months: i32,
#[serde_as(as = "serde_with::DurationSeconds<i64>")]
pub inner: Duration,
}
#[cfg(not(feature = "serde"))]
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
pub struct TimeDelta {
pub months: i32,
pub inner: Duration,
}
impl FromStr for TimeDelta {
type Err = TError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
TimeDelta::parse(s)
}
}
impl From<&str> for TimeDelta {
#[inline]
fn from(s: &str) -> Self {
TimeDelta::parse(s).unwrap_or_else(|e| panic!("{}", e))
}
}
impl TimeDelta {
pub fn parse(duration: &str) -> TResult<Self> {
let mut nsecs = 0;
let mut secs = 0;
let mut months = 0;
let mut iter = duration.char_indices();
let mut start = 0;
let mut unit = String::with_capacity(2);
while let Some((i, mut ch)) = iter.next() {
if !ch.is_ascii_digit() && i != 0 {
let n = duration[start..i].parse::<i64>().unwrap();
loop {
if ch.is_ascii_alphabetic() {
unit.push(ch)
} else {
break;
}
match iter.next() {
Some((i, ch_)) => {
ch = ch_;
start = i
},
None => {
break;
},
}
}
tensure!(!unit.is_empty(), ParseError:"expected a unit in the duration string");
match unit.as_str() {
"ns" => nsecs += n,
"us" => nsecs += n * NANOS_PER_MICRO,
"ms" => nsecs += n * NANOS_PER_MILLI,
"s" => secs += n,
"m" => secs += n * SECS_PER_MINUTE,
"h" => secs += n * SECS_PER_HOUR,
"d" => secs += n * SECS_PER_DAY,
"w" => secs += n * SECS_PER_WEEK,
"mo" => months += n as i32,
"y" => months += n as i32 * 12,
unit => tbail!(ParseError:"unit: '{}' not supported", unit),
}
unit.clear();
}
}
let duration = Duration::seconds(secs) + Duration::nanoseconds(nsecs);
Ok(TimeDelta {
months,
inner: duration,
})
}
#[inline(always)]
pub const fn nat() -> Self {
Self {
months: i32::MIN,
inner: Duration::seconds(0),
}
}
#[allow(dead_code)]
#[inline(always)]
pub const fn is_nat(&self) -> bool {
self.months == i32::MIN
}
#[inline(always)]
pub const fn is_not_nat(&self) -> bool {
self.months != i32::MIN
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_timedelta() {
let cases = vec![
(
"1d",
TimeDelta {
months: 0,
inner: Duration::days(1),
},
),
(
"2w",
TimeDelta {
months: 0,
inner: Duration::weeks(2),
},
),
(
"3mo",
TimeDelta {
months: 3,
inner: Duration::seconds(0),
},
),
(
"1y2mo",
TimeDelta {
months: 14,
inner: Duration::seconds(0),
},
),
(
"1d12h30m",
TimeDelta {
months: 0,
inner: Duration::days(1) + Duration::hours(12) + Duration::minutes(30),
},
),
(
"1h30m45s",
TimeDelta {
months: 0,
inner: Duration::hours(1) + Duration::minutes(30) + Duration::seconds(45),
},
),
(
"500ms",
TimeDelta {
months: 0,
inner: Duration::milliseconds(500),
},
),
(
"1us",
TimeDelta {
months: 0,
inner: Duration::microseconds(1),
},
),
(
"100ns",
TimeDelta {
months: 0,
inner: Duration::nanoseconds(100),
},
),
];
for (input, expected) in cases {
let result = TimeDelta::parse(input).unwrap();
assert_eq!(result, expected, "Failed for input: {}", input);
}
}
#[test]
fn test_parse_timedelta_errors() {
let error_cases = vec![
"1x", "1.5d", ];
for input in error_cases {
assert!(
TimeDelta::parse(input).is_err(),
"Expected error for input: {}",
input
);
}
}
#[test]
fn test_nat_timedelta() {
let nat = TimeDelta::nat();
assert!(nat.is_nat());
assert!(!nat.is_not_nat());
let non_nat = TimeDelta::parse("1d").unwrap();
assert!(!non_nat.is_nat());
assert!(non_nat.is_not_nat());
}
}