use std::error::Error;
use std::fmt;
use chrono::Duration;
const NANOSECOND: u64 = 1;
const MICROSECOND: u64 = 1000 * NANOSECOND;
const MILLISECOND: u64 = 1000 * MICROSECOND;
const SECOND: u64 = 1000 * MILLISECOND;
const MINUTE: u64 = 60 * SECOND;
const HOUR: u64 = 60 * MINUTE;
#[derive(Debug)]
pub enum ParseDurationError {
InvalidDuration(String),
UnknownUnit(String),
Overflow,
}
impl fmt::Display for ParseDurationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseDurationError::InvalidDuration(dur) => {
write!(f, "invalid duration: {dur}")
}
ParseDurationError::UnknownUnit(unit) => {
write!(f, "unknown unit: {unit}")
}
ParseDurationError::Overflow => {
write!(f, "overflow")
}
}
}
}
impl Error for ParseDurationError {}
pub fn parse_duration(mut s: &str) -> Result<Duration, ParseDurationError> {
let orig = s;
let neg = if s.starts_with('-') {
s = &s[1..];
true
} else if s.starts_with('+') {
s = &s[1..];
false
} else {
false
};
if s == "0" {
return Ok(Duration::zero());
}
if s.is_empty() {
return Err(ParseDurationError::InvalidDuration(orig.to_string()));
}
let mut dur = 0u64;
while !s.is_empty() {
if !(s.starts_with('.') || s.starts_with(|c: char| c.is_ascii_digit())) {
return Err(ParseDurationError::InvalidDuration(orig.to_string()));
}
let previous_len = s.len();
let (mut v, rem) = leading_int(s)?;
s = rem;
let pre = previous_len != s.len();
let mut post = false;
let mut f = 0;
let mut scale = 0.0;
if !s.is_empty() && s.starts_with('.') {
s = &s[1..];
let previous_len = s.len();
(f, scale, s) = leading_fraction(s);
post = previous_len != s.len();
}
if !pre && !post {
return Err(ParseDurationError::InvalidDuration(orig.to_string()));
}
let mut idx = 0;
for (i, c) in s.char_indices() {
if c == '.' || c.is_ascii_digit() {
break;
}
idx = i;
}
let unit = match &s[..idx + 1] {
"ns" => NANOSECOND,
"us" => MICROSECOND,
"µs" => MICROSECOND, "μs" => MICROSECOND, "ms" => MILLISECOND,
"s" => SECOND,
"m" => MINUTE,
"h" => HOUR,
unkonwn => return Err(ParseDurationError::UnknownUnit(unkonwn.to_string())),
};
s = &s[idx + 1..];
if v > ((1 << 63) / unit) {
return Err(ParseDurationError::InvalidDuration(orig.to_string()));
}
v *= unit;
if f > 0 {
v += (f as f64 * (unit as f64 / scale)) as u64;
if v > 1 << 63 {
return Err(ParseDurationError::InvalidDuration(orig.to_string()));
}
}
dur += v;
if dur > 1 << 63 {
return Err(ParseDurationError::InvalidDuration(orig.to_string()));
}
}
if neg {
let dur = dur as i64;
if dur < 0 {
return Ok(Duration::nanoseconds(dur));
}
return Ok(-Duration::nanoseconds(dur));
}
if dur > i64::MAX as u64 {
return Err(ParseDurationError::InvalidDuration(orig.to_string()));
}
Ok(Duration::nanoseconds(dur as i64))
}
fn leading_int(s: &str) -> Result<(u64, &str), ParseDurationError> {
let mut last_idx = 0;
let mut num: u64 = 0;
for (i, c) in s.char_indices() {
last_idx = i;
let n = match c.to_digit(10) {
Some(n) => n as u64,
None => break,
};
if num > ((1 << 63) / 10) {
return Err(ParseDurationError::Overflow);
}
num = num * 10 + n;
if num > 1 << 63 {
return Err(ParseDurationError::Overflow);
}
}
Ok((num, &s[last_idx..]))
}
fn leading_fraction(s: &str) -> (u64, f64, &str) {
let mut num: u64 = 0;
let mut scale = 1.0;
let mut overflow = false;
let mut last_idx = 0;
for (i, c) in s.char_indices() {
last_idx = i;
let n = match c.to_digit(10) {
Some(n) => n as u64,
None => break,
};
if overflow {
continue;
}
if num > (i64::MAX as u64 / 10) {
overflow = true;
continue;
}
let y = num * 10 + n;
if y > 1 << 63 {
overflow = true;
continue;
}
num = y;
scale *= 10.0;
}
(num, scale, &s[last_idx..])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_durations() {
for (input, expected_dur) in [
("0", Duration::zero()),
("5s", Duration::seconds(5)),
("30s", Duration::seconds(30)),
("1478s", Duration::seconds(1478)),
("-5s", -Duration::seconds(5)),
("+5s", Duration::seconds(5)),
("-0", Duration::zero()),
("+0", Duration::zero()),
("5.0s", Duration::seconds(5)),
("5.6s", Duration::seconds(5) + Duration::milliseconds(600)),
("5.s", Duration::seconds(5)),
(".5s", Duration::milliseconds(500)),
("1.0s", Duration::seconds(1)),
("1.00s", Duration::seconds(1)),
("1.004s", Duration::seconds(1) + Duration::milliseconds(4)),
("1.0040s", Duration::seconds(1) + Duration::milliseconds(4)),
(
"100.00100s",
Duration::seconds(100) + Duration::milliseconds(1),
),
("10ns", Duration::nanoseconds(10)),
("11us", Duration::microseconds(11)),
("12µs", Duration::microseconds(12)), ("12μs", Duration::microseconds(12)), ("13ms", Duration::milliseconds(13)),
("14s", Duration::seconds(14)),
("15m", Duration::minutes(15)),
("16h", Duration::hours(16)),
("3h30m", Duration::hours(3) + Duration::minutes(30)),
(
"10.5s4m",
Duration::minutes(4) + Duration::seconds(10) + Duration::milliseconds(500),
),
(
"-2m3.4s",
-(Duration::minutes(2) + Duration::seconds(3) + Duration::milliseconds(400)),
),
(
"1h2m3s4ms5us6ns",
Duration::hours(1)
+ Duration::minutes(2)
+ Duration::seconds(3)
+ Duration::milliseconds(4)
+ Duration::microseconds(5)
+ Duration::nanoseconds(6),
),
(
"39h9m14.425s",
Duration::hours(39)
+ Duration::minutes(9)
+ Duration::seconds(14)
+ Duration::milliseconds(425),
),
("52763797000ns", Duration::nanoseconds(52763797000)),
("0.3333333333333333333h", Duration::minutes(20)),
("9007199254740993ns", Duration::nanoseconds((1 << 53) + 1)),
("9223372036854775807ns", Duration::nanoseconds(i64::MAX)),
("9223372036854775.807us", Duration::nanoseconds(i64::MAX)),
(
"9223372036s854ms775us807ns",
Duration::nanoseconds(i64::MAX),
),
("-9223372036854775808ns", Duration::nanoseconds(i64::MIN)),
("-9223372036854775.808us", Duration::nanoseconds(i64::MIN)),
(
"-9223372036s854ms775us808ns",
Duration::nanoseconds(i64::MIN),
),
("-9223372036854775808ns", Duration::nanoseconds(i64::MIN)),
("-2562047h47m16.854775808s", Duration::nanoseconds(i64::MIN)),
("0.100000000000000000000h", Duration::minutes(6)),
(
"0.830103483285477580700h",
Duration::minutes(49) + Duration::seconds(48) + Duration::nanoseconds(372539827),
),
] {
let dur = parse_duration(input).unwrap();
assert_eq!(dur, expected_dur);
}
}
}