Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 156 additions & 130 deletions src/duration.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::error::Error as StdError;
use std::fmt;
use std::str::Chars;
use std::str::{Chars, FromStr};
use std::time::Duration;

/// Error parsing human-friendly duration
Expand Down Expand Up @@ -112,12 +112,62 @@ struct Fraction {
struct Parser<'a> {
iter: Chars<'a>,
src: &'a str,
current: (u64, u64),
}

impl Parser<'_> {
fn off(&self) -> usize {
self.src.len() - self.iter.as_str().len()
fn parse(mut self) -> Result<Duration, Error> {
let mut n = self.parse_first_char()?.ok_or(Error::Empty)?; // integer part
let mut out = Duration::ZERO;
'outer: loop {
let mut frac = None; // fractional part
let mut off = self.off();
while let Some(c) = self.iter.next() {
match c {
'0'..='9' => {
n = n
.checked_mul(10)
.and_then(|x| x.checked_add(c as u64 - '0' as u64))
.ok_or(Error::NumberOverflow)?;
}
c if c.is_whitespace() => {}
'a'..='z' | 'A'..='Z' | 'µ' => {
break;
}
'.' => {
// decimal separator, the fractional part begins now
frac = Some(self.parse_fractional_part(&mut off)?);
break;
}
_ => {
return Err(Error::InvalidCharacter(off));
}
}
off = self.off();
}
let start = off;
let mut off = self.off();
while let Some(c) = self.iter.next() {
match c {
'0'..='9' => {
self.parse_unit(n, frac, start, off, &mut out)?;
n = c as u64 - '0' as u64;
continue 'outer;
}
c if c.is_whitespace() => break,
'a'..='z' | 'A'..='Z' | 'µ' => {}
_ => {
return Err(Error::InvalidCharacter(off));
}
}
off = self.off();
}

self.parse_unit(n, frac, start, off, &mut out)?;
n = match self.parse_first_char()? {
Some(n) => n,
None => return Ok(out),
};
}
}

fn parse_first_char(&mut self) -> Result<Option<u64>, Error> {
Expand All @@ -136,83 +186,6 @@ impl Parser<'_> {
Ok(None)
}

fn add_current(&mut self, mut sec: u64, nsec: u64) -> Result<(), Error> {
let mut nsec = self.current.1.add(nsec)?;
if nsec > 1_000_000_000 {
sec = sec.add(nsec / 1_000_000_000)?;
nsec %= 1_000_000_000;
}
sec = self.current.0.add(sec)?;
self.current = (sec, nsec);
Ok(())
}

fn parse_unit(
&mut self,
n: u64,
frac: Option<Fraction>,
start: usize,
end: usize,
) -> Result<(), Error> {
let unit = &self.src[start..end];
// add the integer part
let (sec, nsec) = match unit {
"nanos" | "nsec" | "ns" => (0u64, n),
"usec" | "us" | "µs" => (0u64, n.mul(1000)?),
"millis" | "msec" | "ms" => (0u64, n.mul(1_000_000)?),
"seconds" | "second" | "secs" | "sec" | "s" => (n, 0),
"minutes" | "minute" | "min" | "mins" | "m" => (n.mul(60)?, 0),
"hours" | "hour" | "hr" | "hrs" | "h" => (n.mul(3600)?, 0),
"days" | "day" | "d" => (n.mul(86400)?, 0),
"weeks" | "week" | "wk" | "wks" | "w" => (n.mul(86400 * 7)?, 0),
"months" | "month" | "M" => (n.mul(2_630_016)?, 0), // 30.44d
"years" | "year" | "yr" | "yrs" | "y" => (n.mul(31_557_600)?, 0), // 365.25d
_ => {
if self.src.chars().all(|c| c == '0') {
self.current = (0, 0);
return Ok(());
}
return Err(Error::UnknownUnit {
start,
end,
unit: unit.to_string(),
value: n,
});
}
};
self.add_current(sec, nsec)?;

// add the fractional part
if let Some(Fraction {
numerator: n,
denominator: d,
}) = frac
{
let (sec, nsec) = match unit {
"nanos" | "nsec" | "ns" => return Err(Error::NumberOverflow),
"usec" | "us" | "µs" => (0, n.mul(1000)?.div(d)?),
"millis" | "msec" | "ms" => (0, n.mul(1_000_000)?.div(d)?),
"seconds" | "second" | "secs" | "sec" | "s" => (0, n.mul(1_000_000_000)?.div(d)?),
"minutes" | "minute" | "min" | "mins" | "m" => (0, n.mul(60_000_000_000)?.div(d)?),
"hours" | "hour" | "hr" | "hrs" | "h" => (n.mul(3600)?.div(d)?, 0),
"days" | "day" | "d" => (n.mul(86400)?.div(d)?, 0),
"weeks" | "week" | "wk" | "wks" | "w" => (n.mul(86400 * 7)?.div(d)?, 0),
"months" | "month" | "M" => (n.mul(2_630_016)?.div(d)?, 0), // 30.44d
"years" | "year" | "yr" | "yrs" | "y" => (n.mul(31_557_600)?.div(d)?, 0), // 365.25d
_ => {
return Err(Error::UnknownUnit {
start,
end,
unit: self.src[start..end].to_string(),
value: n,
});
}
};
self.add_current(sec, nsec)?;
}
Ok(())
}

fn parse_fractional_part(&mut self, off: &mut usize) -> Result<Fraction, Error> {
let mut numerator = 0u64;
let mut denominator = 1u64;
Expand Down Expand Up @@ -254,56 +227,110 @@ impl Parser<'_> {
})
}

fn parse(mut self) -> Result<Duration, Error> {
let mut n = self.parse_first_char()?.ok_or(Error::Empty)?; // integer part
'outer: loop {
let mut frac = None; // fractional part
let mut off = self.off();
while let Some(c) = self.iter.next() {
match c {
'0'..='9' => {
n = n
.checked_mul(10)
.and_then(|x| x.checked_add(c as u64 - '0' as u64))
.ok_or(Error::NumberOverflow)?;
}
c if c.is_whitespace() => {}
'a'..='z' | 'A'..='Z' | 'µ' => {
break;
}
'.' => {
// decimal separator, the fractional part begins now
frac = Some(self.parse_fractional_part(&mut off)?);
break;
}
_ => {
return Err(Error::InvalidCharacter(off));
}
}
off = self.off();
}
let start = off;
let mut off = self.off();
while let Some(c) = self.iter.next() {
match c {
'0'..='9' => {
self.parse_unit(n, frac, start, off)?;
n = c as u64 - '0' as u64;
continue 'outer;
}
c if c.is_whitespace() => break,
'a'..='z' | 'A'..='Z' | 'µ' => {}
_ => {
return Err(Error::InvalidCharacter(off));
}
}
off = self.off();
fn off(&self) -> usize {
self.src.len() - self.iter.as_str().len()
}

fn parse_unit(
&mut self,
n: u64,
frac: Option<Fraction>,
start: usize,
end: usize,
out: &mut Duration,
) -> Result<(), Error> {
let unit = match Unit::from_str(&self.src[start..end]) {
Ok(u) => u,
Err(()) => {
return Err(Error::UnknownUnit {
start,
end,
unit: self.src[start..end].to_owned(),
value: n,
});
}
self.parse_unit(n, frac, start, off)?;
n = match self.parse_first_char()? {
Some(n) => n,
None => return Ok(Duration::new(self.current.0, self.current.1 as u32)),
};

// add the integer part
let (sec, nsec) = match unit {
Unit::Nanosecond => (0u64, n),
Unit::Microsecond => (0u64, n.mul(1000)?),
Unit::Millisecond => (0u64, n.mul(1_000_000)?),
Unit::Second => (n, 0),
Unit::Minute => (n.mul(60)?, 0),
Unit::Hour => (n.mul(3600)?, 0),
Unit::Day => (n.mul(86400)?, 0),
Unit::Week => (n.mul(86400 * 7)?, 0),
Unit::Month => (n.mul(2_630_016)?, 0), // 30.44d
Unit::Year => (n.mul(31_557_600)?, 0), // 365.25d
};
add_current(sec, nsec, out)?;

// add the fractional part
if let Some(Fraction {
numerator: n,
denominator: d,
}) = frac
{
let (sec, nsec) = match unit {
Unit::Nanosecond => return Err(Error::NumberOverflow),
Unit::Microsecond => (0, n.mul(1000)?.div(d)?),
Unit::Millisecond => (0, n.mul(1_000_000)?.div(d)?),
Unit::Second => (0, n.mul(1_000_000_000)?.div(d)?),
Unit::Minute => (0, n.mul(60_000_000_000)?.div(d)?),
Unit::Hour => (n.mul(3600)?.div(d)?, 0),
Unit::Day => (n.mul(86400)?.div(d)?, 0),
Unit::Week => (n.mul(86400 * 7)?.div(d)?, 0),
Unit::Month => (n.mul(2_630_016)?.div(d)?, 0), // 30.44d
Unit::Year => (n.mul(31_557_600)?.div(d)?, 0), // 365.25d
};
add_current(sec, nsec, out)?;
}

Ok(())
}
}

fn add_current(mut sec: u64, nsec: u64, out: &mut Duration) -> Result<(), Error> {
let mut nsec = (out.subsec_nanos() as u64).add(nsec)?;
if nsec > 1_000_000_000 {
sec = sec.add(nsec / 1_000_000_000)?;
nsec %= 1_000_000_000;
}
sec = out.as_secs().add(sec)?;
*out = Duration::new(sec, nsec as u32);
Ok(())
}

enum Unit {
Nanosecond,
Microsecond,
Millisecond,
Second,
Minute,
Hour,
Day,
Week,
Month,
Year,
}

impl FromStr for Unit {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"nanos" | "nsec" | "ns" => Ok(Self::Nanosecond),
"usec" | "us" | "µs" => Ok(Self::Microsecond),
"millis" | "msec" | "ms" => Ok(Self::Millisecond),
"seconds" | "second" | "secs" | "sec" | "s" => Ok(Self::Second),
"minutes" | "minute" | "min" | "mins" | "m" => Ok(Self::Minute),
"hours" | "hour" | "hr" | "hrs" | "h" => Ok(Self::Hour),
"days" | "day" | "d" => Ok(Self::Day),
"weeks" | "week" | "wk" | "wks" | "w" => Ok(Self::Week),
"months" | "month" | "M" => Ok(Self::Month),
"years" | "year" | "yr" | "yrs" | "y" => Ok(Self::Year),
_ => Err(()),
}
}
}
Expand Down Expand Up @@ -341,7 +368,6 @@ pub fn parse_duration(s: &str) -> Result<Duration, Error> {
Parser {
iter: s.chars(),
src: s,
current: (0, 0),
}
.parse()
}
Expand Down
Loading