use std::fmt;
use std::str::Chars;
use std::time::Duration;
use std::error::Error as StdError;
quick_error! {
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Error {
InvalidCharacter(offset: usize) {
display("invalid character at {}", offset)
description("invalid character")
}
NumberExpected(offset: usize) {
display("expected number at {}", offset)
description("expected number")
}
UnknownUnit(start: usize, end: usize) {
display("unknown unit at {}-{}", start, end)
description("unknown unit")
}
NumberOverflow {
display(self_) -> ("{}", self_.description())
description("number is too large")
}
Empty {
display(self_) -> ("{}", self_.description())
description("value was empty")
}
}
}
#[derive(Debug, Clone)]
pub struct FormattedDuration(Duration);
trait OverflowOp: Sized {
fn mul(self, other: Self) -> Result<Self, Error>;
fn add(self, other: Self) -> Result<Self, Error>;
}
impl OverflowOp for u64 {
fn mul(self, other: Self) -> Result<Self, Error> {
self.checked_mul(other).ok_or(Error::NumberOverflow)
}
fn add(self, other: Self) -> Result<Self, Error> {
self.checked_add(other).ok_or(Error::NumberOverflow)
}
}
struct Parser<'a> {
iter: Chars<'a>,
src: &'a str,
current: (u64, u64),
}
impl<'a> Parser<'a> {
fn off(&self) -> usize {
self.src.len() - self.iter.as_str().len()
}
fn parse_first_char(&mut self) -> Result<Option<u64>, Error> {
let off = self.off();
for c in self.iter.by_ref() {
match c {
'0'...'9' => {
return Ok(Some(c as u64 - '0' as u64));
}
c if c.is_whitespace() => continue,
_ => {
return Err(Error::NumberExpected(off));
}
}
}
return Ok(None);
}
fn parse_unit(&mut self, n: u64, start: usize, end: usize)
-> Result<(), Error>
{
let (mut sec, nsec) = match &self.src[start..end] {
"nanos" | "nsec" | "ns" => (0u64, n),
"usec" | "us" => (0u64, try!(n.mul(1000))),
"millis" | "msec" | "ms" => (0u64, try!(n.mul(1000_000))),
"seconds" | "second" | "secs" | "sec" | "s" => (n, 0),
"minutes" | "minute" | "min" | "mins" | "m"
=> (try!(n.mul(60)), 0),
"hours" | "hour" | "hr" | "hrs" | "h" => (try!(n.mul(3600)), 0),
"days" | "day" | "d" => (try!(n.mul(86400)), 0),
"weeks" | "week" | "w" => (try!(n.mul(86400*7)), 0),
"months" | "month" | "M" => (try!(n.mul(2630016)), 0),
"years" | "year" | "y" => (try!(n.mul(31557600)), 0),
_ => return Err(Error::UnknownUnit(start, end)),
};
let mut nsec = try!(self.current.1.add(nsec));
if nsec > 1000_000_000 {
sec = try!(sec.add(nsec / 1000_000_000));
nsec %= 1000_000_000;
}
sec = try!(self.current.0.add(sec));
self.current = (sec, nsec);
Ok(())
}
fn parse(mut self) -> Result<Duration, Error> {
let mut n = try!(try!(self.parse_first_char()).ok_or(Error::Empty));
'outer: loop {
let mut off = self.off();
while let Some(c) = self.iter.next() {
match c {
'0'...'9' => {
n = try!(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;
}
_ => {
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' => {
try!(self.parse_unit(n, 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();
}
try!(self.parse_unit(n, start, off));
n = match try!(self.parse_first_char()) {
Some(n) => n,
None => return Ok(
Duration::new(self.current.0, self.current.1 as u32)),
};
}
}
}
pub fn parse_duration(s: &str) -> Result<Duration, Error> {
Parser {
iter: s.chars(),
src: s,
current: (0, 0),
}.parse()
}
pub fn format_duration(val: Duration) -> FormattedDuration {
FormattedDuration(val)
}
fn item_plural(f: &mut fmt::Formatter, started: &mut bool,
name: &str, value: u64)
-> fmt::Result
{
if value > 0 {
if *started {
f.write_str(" ")?;
}
write!(f, "{}{}", value, name)?;
if value > 1 {
f.write_str("s")?;
}
*started = true;
}
Ok(())
}
fn item(f: &mut fmt::Formatter, started: &mut bool, name: &str, value: u32)
-> fmt::Result
{
if value > 0 {
if *started {
f.write_str(" ")?;
}
write!(f, "{}{}", value, name)?;
*started = true;
}
Ok(())
}
impl fmt::Display for FormattedDuration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let secs = self.0.as_secs();
let nanos = self.0.subsec_nanos();
if secs == 0 && nanos == 0 {
f.write_str("0s")?;
return Ok(());
}
let years = secs / 31557600;
let ydays = secs % 31557600;
let months = ydays / 2630016;
let mdays = ydays % 2630016;
let days = mdays / 86400;
let day_secs = mdays % 86400;
let hours = day_secs / 3600;
let minutes = day_secs % 3600 / 60;
let seconds = day_secs % 60;
let millis = nanos / 1_000_000;
let micros = nanos / 1000 % 1000;
let nanosec = nanos % 1000;
let ref mut started = false;
item_plural(f, started, "year", years)?;
item_plural(f, started, "month", months)?;
item_plural(f, started, "day", days)?;
item(f, started, "h", hours as u32)?;
item(f, started, "m", minutes as u32)?;
item(f, started, "s", seconds as u32)?;
item(f, started, "ms", millis)?;
item(f, started, "us", micros)?;
item(f, started, "ns", nanosec)?;
Ok(())
}
}
#[cfg(test)]
mod test {
extern crate rand;
use std::time::Duration;
use self::rand::Rng;
use super::{parse_duration, format_duration};
use super::Error;
#[test]
fn test_units() {
assert_eq!(parse_duration("17nsec"), Ok(Duration::new(0, 17)));
assert_eq!(parse_duration("17nanos"), Ok(Duration::new(0, 17)));
assert_eq!(parse_duration("33ns"), Ok(Duration::new(0, 33)));
assert_eq!(parse_duration("3usec"), Ok(Duration::new(0, 3000)));
assert_eq!(parse_duration("78us"), Ok(Duration::new(0, 78000)));
assert_eq!(parse_duration("31msec"), Ok(Duration::new(0, 31000000)));
assert_eq!(parse_duration("31millis"), Ok(Duration::new(0, 31000000)));
assert_eq!(parse_duration("6ms"), Ok(Duration::new(0, 6000000)));
assert_eq!(parse_duration("3000s"), Ok(Duration::new(3000, 0)));
assert_eq!(parse_duration("300sec"), Ok(Duration::new(300, 0)));
assert_eq!(parse_duration("300secs"), Ok(Duration::new(300, 0)));
assert_eq!(parse_duration("50seconds"), Ok(Duration::new(50, 0)));
assert_eq!(parse_duration("1second"), Ok(Duration::new(1, 0)));
assert_eq!(parse_duration("100m"), Ok(Duration::new(6000, 0)));
assert_eq!(parse_duration("12min"), Ok(Duration::new(720, 0)));
assert_eq!(parse_duration("12mins"), Ok(Duration::new(720, 0)));
assert_eq!(parse_duration("1minute"), Ok(Duration::new(60, 0)));
assert_eq!(parse_duration("7minutes"), Ok(Duration::new(420, 0)));
assert_eq!(parse_duration("2h"), Ok(Duration::new(7200, 0)));
assert_eq!(parse_duration("7hr"), Ok(Duration::new(25200, 0)));
assert_eq!(parse_duration("7hrs"), Ok(Duration::new(25200, 0)));
assert_eq!(parse_duration("1hour"), Ok(Duration::new(3600, 0)));
assert_eq!(parse_duration("24hours"), Ok(Duration::new(86400, 0)));
assert_eq!(parse_duration("1day"), Ok(Duration::new(86400, 0)));
assert_eq!(parse_duration("2days"), Ok(Duration::new(172800, 0)));
assert_eq!(parse_duration("365d"), Ok(Duration::new(31536000, 0)));
assert_eq!(parse_duration("1week"), Ok(Duration::new(604800, 0)));
assert_eq!(parse_duration("7weeks"), Ok(Duration::new(4233600, 0)));
assert_eq!(parse_duration("52w"), Ok(Duration::new(31449600, 0)));
assert_eq!(parse_duration("1month"), Ok(Duration::new(2630016, 0)));
assert_eq!(parse_duration("3months"), Ok(Duration::new(3*2630016, 0)));
assert_eq!(parse_duration("12M"), Ok(Duration::new(31560192, 0)));
assert_eq!(parse_duration("1year"), Ok(Duration::new(31557600, 0)));
assert_eq!(parse_duration("7years"), Ok(Duration::new(7*31557600, 0)));
assert_eq!(parse_duration("17y"), Ok(Duration::new(536479200, 0)));
}
#[test]
fn test_combo() {
assert_eq!(parse_duration("20 min 17 nsec "), Ok(Duration::new(1200, 17)));
assert_eq!(parse_duration("2h 15m"), Ok(Duration::new(8100, 0)));
}
#[test]
fn all_86400_seconds() {
for second in 0..86400 {
let d = Duration::new(second, 0);
assert_eq!(d,
parse_duration(&format_duration(d).to_string()).unwrap());
}
}
#[test]
fn random_second() {
for _ in 0..10000 {
let sec = rand::thread_rng().gen_range(0, 253370764800);
let d = Duration::new(sec, 0);
assert_eq!(d,
parse_duration(&format_duration(d).to_string()).unwrap());
}
}
#[test]
fn random_any() {
for _ in 0..10000 {
let sec = rand::thread_rng().gen_range(0, 253370764800);
let nanos = rand::thread_rng().gen_range(0, 1_000_000_000);
let d = Duration::new(sec, nanos);
assert_eq!(d,
parse_duration(&format_duration(d).to_string()).unwrap());
}
}
#[test]
fn test_overlow() {
assert_eq!(parse_duration("100000000000000000000ns"),
Err(Error::NumberOverflow));
assert_eq!(parse_duration("100000000000000000us"),
Err(Error::NumberOverflow));
assert_eq!(parse_duration("100000000000000ms"),
Err(Error::NumberOverflow));
assert_eq!(parse_duration("100000000000000000000s"),
Err(Error::NumberOverflow));
assert_eq!(parse_duration("10000000000000000000m"),
Err(Error::NumberOverflow));
assert_eq!(parse_duration("1000000000000000000h"),
Err(Error::NumberOverflow));
assert_eq!(parse_duration("100000000000000000d"),
Err(Error::NumberOverflow));
assert_eq!(parse_duration("10000000000000000w"),
Err(Error::NumberOverflow));
assert_eq!(parse_duration("1000000000000000M"),
Err(Error::NumberOverflow));
assert_eq!(parse_duration("10000000000000y"),
Err(Error::NumberOverflow));
}
}