-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit d1f32f7
Showing
5 changed files
with
313 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
/Cargo.lock | ||
/.vagga | ||
/target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
[package] | ||
name = "humantime" | ||
description = """ | ||
A human friendly format parser for std::time::Duration. | ||
""" | ||
license = "MIT/Apache-2.0" | ||
readme = "README.rst" | ||
keywords = ["linux", "container", "namespace", "docker", "process"] | ||
homepage = "http://github.com/tailhook/humantime" | ||
documentation = "http://tailhook.github.io/humantime/" | ||
version = "0.1.0" | ||
authors = ["paul@colomiets.name"] | ||
|
||
[dependencies] | ||
quick-error = "1.0.0" | ||
|
||
[dev-dependencies] | ||
argparse = "0.2.1" | ||
|
||
[lib] | ||
name = "humantime" | ||
path = "src/lib.rs" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,242 @@ | ||
use std::str::Chars; | ||
use std::time::Duration; | ||
use std::error::Error as StdError; | ||
|
||
quick_error! { | ||
#[derive(Debug, PartialEq)] | ||
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") | ||
} | ||
} | ||
} | ||
|
||
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] { | ||
"nsec" | "ns" => (0u64, n), | ||
"usec" | "us" => (0u64, try!(n.mul(1000))), | ||
"msec" | "ms" => (0u64, try!(n.mul(1000_000))), | ||
"seconds" | "second" | "sec" | "s" => (n, 0), | ||
"minutes" | "minute" | "min" | "m" => (try!(n.mul(60)), 0), | ||
"hours" | "hour" | "hr" | "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), // 30.44d | ||
"years" | "year" | "y" => (try!(n.mul(31557600)), 0), // 365.25d | ||
_ => 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)), | ||
}; | ||
} | ||
} | ||
|
||
} | ||
|
||
/// Parse duration object | ||
/// | ||
/// The duration object is a concatenation of time spans. Where each time | ||
/// span is an integer number and a suffix. Supported suffixes: | ||
/// * `nsec`, `ns` -- microseconds | ||
/// * `usec`, `us` -- microseconds | ||
/// * `msec`, `ms` -- milliseconds | ||
/// * `seconds`, `second`, `sec`, `s` | ||
/// * `minutes`, `minute`, `min`, `m` | ||
/// * `hours`, `hour`, `hr`, `h` | ||
/// * `days`, `day`, `d` | ||
/// * `weeks`, `week`, `w` | ||
/// * `months`, `month`, `M` -- defined as 30.44 days | ||
/// * `years`, `year`, `y` -- defined as 365.25 days | ||
/// | ||
/// Examples: | ||
/// | ||
/// ``` | ||
/// use std::time::Duration; | ||
/// use humantime::parse_duration; | ||
/// | ||
/// assert_eq!(parse_duration("2h 37min"), Ok(Duration::new(9420, 0))); | ||
/// ``` | ||
pub fn parse_duration(s: &str) -> Result<Duration, Error> { | ||
Parser { | ||
iter: s.chars(), | ||
src: s, | ||
current: (0, 0), | ||
}.parse() | ||
} | ||
|
||
#[test] | ||
fn test_units() { | ||
assert_eq!(parse_duration("17nsec"), 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("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("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("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("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 test_overlow() { | ||
// Overflow on subseconds is earlier because of how we do conversion | ||
// we could fix it, but I don't see any good reason for this | ||
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)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
//! Human-friendly time parser | ||
//! | ||
//! Currently this only currently implements parsing of duration. Relative and | ||
//! absolute times may be added in future. | ||
#[macro_use] extern crate quick_error; | ||
|
||
mod duration; | ||
|
||
pub use duration::{parse_duration, Error as DurationError}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
commands: | ||
|
||
cargo: !Command | ||
description: Run any cargo command | ||
container: ubuntu | ||
run: [cargo] | ||
|
||
make: !Command | ||
description: Build the library | ||
container: ubuntu | ||
run: [cargo, build] | ||
|
||
test: !Command | ||
description: Test the library | ||
container: ubuntu | ||
environ: { RUST_BACKTRACE: 1 } | ||
run: [cargo, test] | ||
|
||
containers: | ||
|
||
ubuntu: | ||
setup: | ||
- !Ubuntu trusty | ||
- !UbuntuUniverse ~ | ||
- !Install [make, checkinstall, wget, ca-certificates, | ||
libssl-dev, build-essential] | ||
|
||
- !TarInstall | ||
url: "http://static.rust-lang.org/dist/rust-1.8.0-x86_64-unknown-linux-gnu.tar.gz" | ||
script: "./install.sh --prefix=/usr \ | ||
--components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo" | ||
|
||
environ: | ||
HOME: /work/target | ||
USER: pc |