diff --git a/src/duration.rs b/src/duration.rs index a5cb9d8..163061b 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -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 @@ -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 { + 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, Error> { @@ -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, - 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 { let mut numerator = 0u64; let mut denominator = 1u64; @@ -254,56 +227,110 @@ impl Parser<'_> { }) } - fn parse(mut self) -> Result { - 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, + 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 { + 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(()), } } } @@ -341,7 +368,6 @@ pub fn parse_duration(s: &str) -> Result { Parser { iter: s.chars(), src: s, - current: (0, 0), } .parse() }