更新libclamav库1.0.0版本

This commit is contained in:
2023-01-14 18:28:39 +08:00
parent b879ee0b2e
commit 45fe15f472
8531 changed files with 1222046 additions and 177272 deletions

View File

@@ -0,0 +1,623 @@
use std::error::Error as StdError;
use std::fmt;
use std::str;
use std::time::{SystemTime, Duration, UNIX_EPOCH};
#[cfg(target_os="cloudabi")]
mod max {
pub const SECONDS: u64 = ::std::u64::MAX / 1_000_000_000;
#[allow(unused)]
pub const TIMESTAMP: &'static str = "2554-07-21T23:34:33Z";
}
#[cfg(all(
target_pointer_width="32",
not(target_os="cloudabi"),
not(target_os="windows"),
not(all(target_arch="wasm32", not(target_os="emscripten")))
))]
mod max {
pub const SECONDS: u64 = ::std::i32::MAX as u64;
#[allow(unused)]
pub const TIMESTAMP: &'static str = "2038-01-19T03:14:07Z";
}
#[cfg(any(
target_pointer_width="64",
target_os="windows",
all(target_arch="wasm32", not(target_os="emscripten")),
))]
mod max {
pub const SECONDS: u64 = 253_402_300_800-1; // last second of year 9999
#[allow(unused)]
pub const TIMESTAMP: &str = "9999-12-31T23:59:59Z";
}
/// Error parsing datetime (timestamp)
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum Error {
/// Numeric component is out of range
OutOfRange,
/// Bad character where digit is expected
InvalidDigit,
/// Other formatting errors
InvalidFormat,
}
impl StdError for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::OutOfRange => write!(f, "numeric component is out of range"),
Error::InvalidDigit => write!(f, "bad character where digit is expected"),
Error::InvalidFormat => write!(f, "timestamp format is invalid"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum Precision {
Smart,
Seconds,
Millis,
Micros,
Nanos,
}
/// A wrapper type that allows you to Display a SystemTime
#[derive(Debug, Clone)]
pub struct Rfc3339Timestamp(SystemTime, Precision);
#[inline]
fn two_digits(b1: u8, b2: u8) -> Result<u64, Error> {
if b1 < b'0' || b2 < b'0' || b1 > b'9' || b2 > b'9' {
return Err(Error::InvalidDigit);
}
Ok(((b1 - b'0')*10 + (b2 - b'0')) as u64)
}
/// Parse RFC3339 timestamp `2018-02-14T00:28:07Z`
///
/// Supported feature: any precision of fractional
/// digits `2018-02-14T00:28:07.133Z`.
///
/// Unsupported feature: localized timestamps. Only UTC is supported.
pub fn parse_rfc3339(s: &str) -> Result<SystemTime, Error> {
if s.len() < "2018-02-14T00:28:07Z".len() {
return Err(Error::InvalidFormat);
}
let b = s.as_bytes();
if b[10] != b'T' || b[b.len()-1] != b'Z' {
return Err(Error::InvalidFormat);
}
parse_rfc3339_weak(s)
}
/// Parse RFC3339-like timestamp `2018-02-14 00:28:07`
///
/// Supported features:
///
/// 1. Any precision of fractional digits `2018-02-14 00:28:07.133`.
/// 2. Supports timestamp with or without either of `T` or `Z`
/// 3. Anything valid for `parse_3339` is valid for this function
///
/// Unsupported feature: localized timestamps. Only UTC is supported, even if
/// `Z` is not specified.
///
/// This function is intended to use for parsing human input. Whereas
/// `parse_rfc3339` is for strings generated programmatically.
pub fn parse_rfc3339_weak(s: &str) -> Result<SystemTime, Error> {
if s.len() < "2018-02-14T00:28:07".len() {
return Err(Error::InvalidFormat);
}
let b = s.as_bytes(); // for careless slicing
if b[4] != b'-' || b[7] != b'-' || (b[10] != b'T' && b[10] != b' ') ||
b[13] != b':' || b[16] != b':'
{
return Err(Error::InvalidFormat);
}
let year = two_digits(b[0], b[1])? * 100 + two_digits(b[2], b[3])?;
let month = two_digits(b[5], b[6])?;
let day = two_digits(b[8], b[9])?;
let hour = two_digits(b[11], b[12])?;
let minute = two_digits(b[14], b[15])?;
let mut second = two_digits(b[17], b[18])?;
if year < 1970 || hour > 23 || minute > 59 || second > 60 {
return Err(Error::OutOfRange);
}
// TODO(tailhook) should we check that leaps second is only on midnight ?
if second == 60 {
second = 59
};
let leap_years = ((year - 1) - 1968) / 4 - ((year - 1) - 1900) / 100 +
((year - 1) - 1600) / 400;
let leap = is_leap_year(year);
let (mut ydays, mdays) = match month {
1 => (0, 31),
2 if leap => (31, 29),
2 => (31, 28),
3 => (59, 31),
4 => (90, 30),
5 => (120, 31),
6 => (151, 30),
7 => (181, 31),
8 => (212, 31),
9 => (243, 30),
10 => (273, 31),
11 => (304, 30),
12 => (334, 31),
_ => return Err(Error::OutOfRange),
};
if day > mdays || day == 0 {
return Err(Error::OutOfRange);
}
ydays += day - 1;
if leap && month > 2 {
ydays += 1;
}
let days = (year - 1970) * 365 + leap_years + ydays;
let time = second + minute * 60 + hour * 3600;
let mut nanos = 0;
let mut mult = 100_000_000;
if b.get(19) == Some(&b'.') {
for idx in 20..b.len() {
if b[idx] == b'Z' {
if idx == b.len()-1 {
break;
} else {
return Err(Error::InvalidDigit);
}
}
if b[idx] < b'0' || b[idx] > b'9' {
return Err(Error::InvalidDigit);
}
nanos += mult * (b[idx] - b'0') as u32;
mult /= 10;
}
} else if b.len() != 19 && (b.len() > 20 || b[19] != b'Z') {
return Err(Error::InvalidFormat);
}
let total_seconds = time + days * 86400;
if total_seconds > max::SECONDS {
return Err(Error::OutOfRange);
}
Ok(UNIX_EPOCH + Duration::new(total_seconds, nanos))
}
fn is_leap_year(y: u64) -> bool {
y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)
}
/// Format an RFC3339 timestamp `2018-02-14T00:28:07Z`
///
/// This function formats timestamp with smart precision: i.e. if it has no
/// fractional seconds, they aren't written at all. And up to nine digits if
/// they are.
///
/// The value is always UTC and ignores system timezone.
pub fn format_rfc3339(system_time: SystemTime) -> Rfc3339Timestamp {
Rfc3339Timestamp(system_time, Precision::Smart)
}
/// Format an RFC3339 timestamp `2018-02-14T00:28:07Z`
///
/// This format always shows timestamp without fractional seconds.
///
/// The value is always UTC and ignores system timezone.
pub fn format_rfc3339_seconds(system_time: SystemTime) -> Rfc3339Timestamp {
Rfc3339Timestamp(system_time, Precision::Seconds)
}
/// Format an RFC3339 timestamp `2018-02-14T00:28:07.000Z`
///
/// This format always shows milliseconds even if millisecond value is zero.
///
/// The value is always UTC and ignores system timezone.
pub fn format_rfc3339_millis(system_time: SystemTime) -> Rfc3339Timestamp {
Rfc3339Timestamp(system_time, Precision::Millis)
}
/// Format an RFC3339 timestamp `2018-02-14T00:28:07.000000Z`
///
/// This format always shows microseconds even if microsecond value is zero.
///
/// The value is always UTC and ignores system timezone.
pub fn format_rfc3339_micros(system_time: SystemTime) -> Rfc3339Timestamp {
Rfc3339Timestamp(system_time, Precision::Micros)
}
/// Format an RFC3339 timestamp `2018-02-14T00:28:07.000000000Z`
///
/// This format always shows nanoseconds even if nanosecond value is zero.
///
/// The value is always UTC and ignores system timezone.
pub fn format_rfc3339_nanos(system_time: SystemTime) -> Rfc3339Timestamp {
Rfc3339Timestamp(system_time, Precision::Nanos)
}
impl Rfc3339Timestamp {
/// Returns a reference to the [`SystemTime`][] that is being formatted.
pub fn get_ref(&self) -> &SystemTime {
&self.0
}
}
impl fmt::Display for Rfc3339Timestamp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Precision::*;
let dur = self.0.duration_since(UNIX_EPOCH)
.expect("all times should be after the epoch");
let secs_since_epoch = dur.as_secs();
let nanos = dur.subsec_nanos();
if secs_since_epoch >= 253_402_300_800 { // year 9999
return Err(fmt::Error);
}
/* 2000-03-01 (mod 400 year, immediately after feb29 */
const LEAPOCH: i64 = 11017;
const DAYS_PER_400Y: i64 = 365*400 + 97;
const DAYS_PER_100Y: i64 = 365*100 + 24;
const DAYS_PER_4Y: i64 = 365*4 + 1;
let days = (secs_since_epoch / 86400) as i64 - LEAPOCH;
let secs_of_day = secs_since_epoch % 86400;
let mut qc_cycles = days / DAYS_PER_400Y;
let mut remdays = days % DAYS_PER_400Y;
if remdays < 0 {
remdays += DAYS_PER_400Y;
qc_cycles -= 1;
}
let mut c_cycles = remdays / DAYS_PER_100Y;
if c_cycles == 4 { c_cycles -= 1; }
remdays -= c_cycles * DAYS_PER_100Y;
let mut q_cycles = remdays / DAYS_PER_4Y;
if q_cycles == 25 { q_cycles -= 1; }
remdays -= q_cycles * DAYS_PER_4Y;
let mut remyears = remdays / 365;
if remyears == 4 { remyears -= 1; }
remdays -= remyears * 365;
let mut year = 2000 +
remyears + 4*q_cycles + 100*c_cycles + 400*qc_cycles;
let months = [31,30,31,30,31,31,30,31,30,31,31,29];
let mut mon = 0;
for mon_len in months.iter() {
mon += 1;
if remdays < *mon_len {
break;
}
remdays -= *mon_len;
}
let mday = remdays+1;
let mon = if mon + 2 > 12 {
year += 1;
mon - 10
} else {
mon + 2
};
let mut buf: [u8; 30] = [
// Too long to write as: b"0000-00-00T00:00:00.000000000Z"
b'0', b'0', b'0', b'0', b'-', b'0', b'0', b'-', b'0', b'0', b'T',
b'0', b'0', b':', b'0', b'0', b':', b'0', b'0',
b'.', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'Z',
];
buf[0] = b'0' + (year / 1000) as u8;
buf[1] = b'0' + (year / 100 % 10) as u8;
buf[2] = b'0' + (year / 10 % 10) as u8;
buf[3] = b'0' + (year % 10) as u8;
buf[5] = b'0' + (mon / 10) as u8;
buf[6] = b'0' + (mon % 10) as u8;
buf[8] = b'0' + (mday / 10) as u8;
buf[9] = b'0' + (mday % 10) as u8;
buf[11] = b'0' + (secs_of_day / 3600 / 10) as u8;
buf[12] = b'0' + (secs_of_day / 3600 % 10) as u8;
buf[14] = b'0' + (secs_of_day / 60 / 10 % 6) as u8;
buf[15] = b'0' + (secs_of_day / 60 % 10) as u8;
buf[17] = b'0' + (secs_of_day / 10 % 6) as u8;
buf[18] = b'0' + (secs_of_day % 10) as u8;
let offset = if self.1 == Seconds || nanos == 0 && self.1 == Smart {
buf[19] = b'Z';
19
} else if self.1 == Millis {
buf[20] = b'0' + (nanos / 100_000_000) as u8;
buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8;
buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8;
buf[23] = b'Z';
23
} else if self.1 == Micros {
buf[20] = b'0' + (nanos / 100_000_000) as u8;
buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8;
buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8;
buf[23] = b'0' + (nanos / 100_000 % 10) as u8;
buf[24] = b'0' + (nanos / 10_000 % 10) as u8;
buf[25] = b'0' + (nanos / 1_000 % 10) as u8;
buf[26] = b'Z';
26
} else {
buf[20] = b'0' + (nanos / 100_000_000) as u8;
buf[21] = b'0' + (nanos / 10_000_000 % 10) as u8;
buf[22] = b'0' + (nanos / 1_000_000 % 10) as u8;
buf[23] = b'0' + (nanos / 100_000 % 10) as u8;
buf[24] = b'0' + (nanos / 10_000 % 10) as u8;
buf[25] = b'0' + (nanos / 1_000 % 10) as u8;
buf[26] = b'0' + (nanos / 100 % 10) as u8;
buf[27] = b'0' + (nanos / 10 % 10) as u8;
buf[28] = b'0' + (nanos / 1 % 10) as u8;
// 29th is 'Z'
29
};
// we know our chars are all ascii
f.write_str(str::from_utf8(&buf[..=offset]).expect("Conversion to utf8 failed"))
}
}
#[cfg(test)]
mod test {
use std::str::from_utf8;
use std::time::{UNIX_EPOCH, SystemTime, Duration};
use rand::Rng;
use super::{parse_rfc3339, parse_rfc3339_weak, format_rfc3339};
use super::{format_rfc3339_millis, format_rfc3339_micros};
use super::{format_rfc3339_nanos};
use super::max;
fn from_sec(sec: u64) -> (String, SystemTime) {
let s = time::at_utc(time::Timespec { sec: sec as i64, nsec: 0 })
.rfc3339().to_string();
let time = UNIX_EPOCH + Duration::new(sec, 0);
(s, time)
}
#[test]
#[cfg(all(target_pointer_width="32", target_os="linux"))]
fn year_after_2038_fails_gracefully() {
// next second
assert_eq!(parse_rfc3339("2038-01-19T03:14:08Z").unwrap_err(),
super::Error::OutOfRange);
assert_eq!(parse_rfc3339("9999-12-31T23:59:59Z").unwrap_err(),
super::Error::OutOfRange);
}
#[test]
fn smoke_tests_parse() {
assert_eq!(parse_rfc3339("1970-01-01T00:00:00Z").unwrap(),
UNIX_EPOCH + Duration::new(0, 0));
assert_eq!(parse_rfc3339("1970-01-01T00:00:01Z").unwrap(),
UNIX_EPOCH + Duration::new(1, 0));
assert_eq!(parse_rfc3339("2018-02-13T23:08:32Z").unwrap(),
UNIX_EPOCH + Duration::new(1_518_563_312, 0));
assert_eq!(parse_rfc3339("2012-01-01T00:00:00Z").unwrap(),
UNIX_EPOCH + Duration::new(1_325_376_000, 0));
}
#[test]
fn smoke_tests_format() {
assert_eq!(
format_rfc3339(UNIX_EPOCH + Duration::new(0, 0)).to_string(),
"1970-01-01T00:00:00Z");
assert_eq!(
format_rfc3339(UNIX_EPOCH + Duration::new(1, 0)).to_string(),
"1970-01-01T00:00:01Z");
assert_eq!(
format_rfc3339(UNIX_EPOCH + Duration::new(1_518_563_312, 0)).to_string(),
"2018-02-13T23:08:32Z");
assert_eq!(
format_rfc3339(UNIX_EPOCH + Duration::new(1_325_376_000, 0)).to_string(),
"2012-01-01T00:00:00Z");
}
#[test]
fn smoke_tests_format_millis() {
assert_eq!(
format_rfc3339_millis(UNIX_EPOCH +
Duration::new(0, 0)).to_string(),
"1970-01-01T00:00:00.000Z");
assert_eq!(
format_rfc3339_millis(UNIX_EPOCH +
Duration::new(1_518_563_312, 123_000_000)).to_string(),
"2018-02-13T23:08:32.123Z");
}
#[test]
fn smoke_tests_format_micros() {
assert_eq!(
format_rfc3339_micros(UNIX_EPOCH +
Duration::new(0, 0)).to_string(),
"1970-01-01T00:00:00.000000Z");
assert_eq!(
format_rfc3339_micros(UNIX_EPOCH +
Duration::new(1_518_563_312, 123_000_000)).to_string(),
"2018-02-13T23:08:32.123000Z");
assert_eq!(
format_rfc3339_micros(UNIX_EPOCH +
Duration::new(1_518_563_312, 456_123_000)).to_string(),
"2018-02-13T23:08:32.456123Z");
}
#[test]
fn smoke_tests_format_nanos() {
assert_eq!(
format_rfc3339_nanos(UNIX_EPOCH +
Duration::new(0, 0)).to_string(),
"1970-01-01T00:00:00.000000000Z");
assert_eq!(
format_rfc3339_nanos(UNIX_EPOCH +
Duration::new(1_518_563_312, 123_000_000)).to_string(),
"2018-02-13T23:08:32.123000000Z");
assert_eq!(
format_rfc3339_nanos(UNIX_EPOCH +
Duration::new(1_518_563_312, 789_456_123)).to_string(),
"2018-02-13T23:08:32.789456123Z");
}
#[test]
fn upper_bound() {
let max = UNIX_EPOCH + Duration::new(max::SECONDS, 0);
assert_eq!(parse_rfc3339(&max::TIMESTAMP).unwrap(), max);
assert_eq!(format_rfc3339(max).to_string(), max::TIMESTAMP);
}
#[test]
fn leap_second() {
assert_eq!(parse_rfc3339("2016-12-31T23:59:60Z").unwrap(),
UNIX_EPOCH + Duration::new(1_483_228_799, 0));
}
#[test]
fn first_731_days() {
let year_start = 0; // 1970
for day in 0..= 365 * 2 { // scan leap year and non-leap year
let (s, time) = from_sec(year_start + day * 86400);
assert_eq!(parse_rfc3339(&s).unwrap(), time);
assert_eq!(format_rfc3339(time).to_string(), s);
}
}
#[test]
fn the_731_consecutive_days() {
let year_start = 1_325_376_000; // 2012
for day in 0..= 365 * 2 { // scan leap year and non-leap year
let (s, time) = from_sec(year_start + day * 86400);
assert_eq!(parse_rfc3339(&s).unwrap(), time);
assert_eq!(format_rfc3339(time).to_string(), s);
}
}
#[test]
fn all_86400_seconds() {
let day_start = 1_325_376_000;
for second in 0..86400 { // scan leap year and non-leap year
let (s, time) = from_sec(day_start + second);
assert_eq!(parse_rfc3339(&s).unwrap(), time);
assert_eq!(format_rfc3339(time).to_string(), s);
}
}
#[test]
fn random_past() {
let upper = SystemTime::now().duration_since(UNIX_EPOCH).unwrap()
.as_secs();
for _ in 0..10000 {
let sec = rand::thread_rng().gen_range(0, upper);
let (s, time) = from_sec(sec);
assert_eq!(parse_rfc3339(&s).unwrap(), time);
assert_eq!(format_rfc3339(time).to_string(), s);
}
}
#[test]
fn random_wide_range() {
for _ in 0..100_000 {
let sec = rand::thread_rng().gen_range(0, max::SECONDS);
let (s, time) = from_sec(sec);
assert_eq!(parse_rfc3339(&s).unwrap(), time);
assert_eq!(format_rfc3339(time).to_string(), s);
}
}
#[test]
fn milliseconds() {
assert_eq!(parse_rfc3339("1970-01-01T00:00:00.123Z").unwrap(),
UNIX_EPOCH + Duration::new(0, 123_000_000));
assert_eq!(format_rfc3339(UNIX_EPOCH + Duration::new(0, 123_000_000))
.to_string(), "1970-01-01T00:00:00.123000000Z");
}
#[test]
#[should_panic(expected="OutOfRange")]
fn zero_month() {
parse_rfc3339("1970-00-01T00:00:00Z").unwrap();
}
#[test]
#[should_panic(expected="OutOfRange")]
fn big_month() {
parse_rfc3339("1970-32-01T00:00:00Z").unwrap();
}
#[test]
#[should_panic(expected="OutOfRange")]
fn zero_day() {
parse_rfc3339("1970-01-00T00:00:00Z").unwrap();
}
#[test]
#[should_panic(expected="OutOfRange")]
fn big_day() {
parse_rfc3339("1970-12-35T00:00:00Z").unwrap();
}
#[test]
#[should_panic(expected="OutOfRange")]
fn big_day2() {
parse_rfc3339("1970-02-30T00:00:00Z").unwrap();
}
#[test]
#[should_panic(expected="OutOfRange")]
fn big_second() {
parse_rfc3339("1970-12-30T00:00:78Z").unwrap();
}
#[test]
#[should_panic(expected="OutOfRange")]
fn big_minute() {
parse_rfc3339("1970-12-30T00:78:00Z").unwrap();
}
#[test]
#[should_panic(expected="OutOfRange")]
fn big_hour() {
parse_rfc3339("1970-12-30T24:00:00Z").unwrap();
}
#[test]
fn break_data() {
for pos in 0.."2016-12-31T23:59:60Z".len() {
let mut s = b"2016-12-31T23:59:60Z".to_vec();
s[pos] = b'x';
parse_rfc3339(from_utf8(&s).unwrap()).unwrap_err();
}
}
#[test]
fn weak_smoke_tests() {
assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00").unwrap(),
UNIX_EPOCH + Duration::new(0, 0));
parse_rfc3339("1970-01-01 00:00:00").unwrap_err();
assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00.000123").unwrap(),
UNIX_EPOCH + Duration::new(0, 123_000));
parse_rfc3339("1970-01-01 00:00:00.000123").unwrap_err();
assert_eq!(parse_rfc3339_weak("1970-01-01T00:00:00.000123").unwrap(),
UNIX_EPOCH + Duration::new(0, 123_000));
parse_rfc3339("1970-01-01T00:00:00.000123").unwrap_err();
assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00.000123Z").unwrap(),
UNIX_EPOCH + Duration::new(0, 123_000));
parse_rfc3339("1970-01-01 00:00:00.000123Z").unwrap_err();
assert_eq!(parse_rfc3339_weak("1970-01-01 00:00:00Z").unwrap(),
UNIX_EPOCH + Duration::new(0, 0));
parse_rfc3339("1970-01-01 00:00:00Z").unwrap_err();
}
}

View File

@@ -0,0 +1,456 @@
use std::error::Error as StdError;
use std::fmt;
use std::str::Chars;
use std::time::Duration;
/// Error parsing human-friendly duration
#[derive(Debug, PartialEq, Clone)]
pub enum Error {
/// Invalid character during parsing
///
/// More specifically anything that is not alphanumeric is prohibited
///
/// The field is an byte offset of the character in the string.
InvalidCharacter(usize),
/// Non-numeric value where number is expected
///
/// This usually means that either time unit is broken into words,
/// e.g. `m sec` instead of `msec`, or just number is omitted,
/// for example `2 hours min` instead of `2 hours 1 min`
///
/// The field is an byte offset of the errorneous character
/// in the string.
NumberExpected(usize),
/// Unit in the number is not one of allowed units
///
/// See documentation of `parse_duration` for the list of supported
/// time units.
///
/// The two fields are start and end (exclusive) of the slice from
/// the original string, containing errorneous value
UnknownUnit {
/// Start of the invalid unit inside the original string
start: usize,
/// End of the invalid unit inside the original string
end: usize,
/// The unit verbatim
unit: String,
/// A number associated with the unit
value: u64,
},
/// The numeric value is too large
///
/// Usually this means value is too large to be useful. If user writes
/// data in subsecond units, then the maximum is about 3k years. When
/// using seconds, or larger units, the limit is even larger.
NumberOverflow,
/// The value was an empty string (or consists only whitespace)
Empty,
}
impl StdError for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::InvalidCharacter(offset) => write!(f, "invalid character at {}", offset),
Error::NumberExpected(offset) => write!(f, "expected number at {}", offset),
Error::UnknownUnit { unit, value, .. } if &unit == &"" => {
write!(f,
"time unit needed, for example {0}sec or {0}ms",
value,
)
}
Error::UnknownUnit { unit, .. } => {
write!(
f,
"unknown time unit {:?}, \
supported units: ns, us, ms, sec, min, hours, days, \
weeks, months, years (and few variations)",
unit
)
}
Error::NumberOverflow => write!(f, "number is too large"),
Error::Empty => write!(f, "value was empty"),
}
}
}
/// A wrapper type that allows you to Display a Duration
#[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));
}
}
}
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, 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" | "w" => (n.mul(86400*7)?, 0),
"months" | "month" | "M" => (n.mul(2_630_016)?, 0), // 30.44d
"years" | "year" | "y" => (n.mul(31_557_600)?, 0), // 365.25d
_ => {
return Err(Error::UnknownUnit {
start, end,
unit: self.src[start..end].to_string(),
value: n,
});
}
};
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(mut self) -> Result<Duration, Error> {
let mut n = 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 = 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' => {
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();
}
self.parse_unit(n, start, off)?;
n = match self.parse_first_char()? {
Some(n) => n,
None => return Ok(
Duration::new(self.current.0, self.current.1 as u32)),
};
}
}
}
/// Parse duration object `1hour 12min 5s`
///
/// The duration object is a concatenation of time spans. Where each time
/// span is an integer number and a suffix. Supported suffixes:
///
/// * `nsec`, `ns` -- nanoseconds
/// * `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)));
/// assert_eq!(parse_duration("32ms"), Ok(Duration::new(0, 32_000_000)));
/// ```
pub fn parse_duration(s: &str) -> Result<Duration, Error> {
Parser {
iter: s.chars(),
src: s,
current: (0, 0),
}.parse()
}
/// Formats duration into a human-readable string
///
/// Note: this format is guaranteed to have same value when using
/// parse_duration, but we can change some details of the exact composition
/// of the value.
///
/// # Examples
///
/// ```
/// use std::time::Duration;
/// use humantime::format_duration;
///
/// let val1 = Duration::new(9420, 0);
/// assert_eq!(format_duration(val1).to_string(), "2h 37m");
/// let val2 = Duration::new(0, 32_000_000);
/// assert_eq!(format_duration(val2).to_string(), "32ms");
/// ```
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 FormattedDuration {
/// Returns a reference to the [`Duration`][] that is being formatted.
pub fn get_ref(&self) -> &Duration {
&self.0
}
}
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 / 31_557_600; // 365.25d
let ydays = secs % 31_557_600;
let months = ydays / 2_630_016; // 30.44d
let mdays = ydays % 2_630_016;
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 {
use std::time::Duration;
use rand::Rng;
use super::{parse_duration, format_duration};
use super::Error;
#[test]
#[allow(clippy::cognitive_complexity)]
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, 31_000_000)));
assert_eq!(parse_duration("31millis"), Ok(Duration::new(0, 31_000_000)));
assert_eq!(parse_duration("6ms"), Ok(Duration::new(0, 6_000_000)));
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(172_800, 0)));
assert_eq!(parse_duration("365d"), Ok(Duration::new(31_536_000, 0)));
assert_eq!(parse_duration("1week"), Ok(Duration::new(604_800, 0)));
assert_eq!(parse_duration("7weeks"), Ok(Duration::new(4_233_600, 0)));
assert_eq!(parse_duration("52w"), Ok(Duration::new(31_449_600, 0)));
assert_eq!(parse_duration("1month"), Ok(Duration::new(2_630_016, 0)));
assert_eq!(parse_duration("3months"), Ok(Duration::new(3*2_630_016, 0)));
assert_eq!(parse_duration("12M"), Ok(Duration::new(31_560_192, 0)));
assert_eq!(parse_duration("1year"), Ok(Duration::new(31_557_600, 0)));
assert_eq!(parse_duration("7years"), Ok(Duration::new(7*31_557_600, 0)));
assert_eq!(parse_duration("17y"), Ok(Duration::new(536_479_200, 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 { // scan leap year and non-leap year
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, 253_370_764_800);
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, 253_370_764_800);
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() {
// 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));
}
#[test]
fn test_nice_error_message() {
assert_eq!(parse_duration("123").unwrap_err().to_string(),
"time unit needed, for example 123sec or 123ms");
assert_eq!(parse_duration("10 months 1").unwrap_err().to_string(),
"time unit needed, for example 1sec or 1ms");
assert_eq!(parse_duration("10nights").unwrap_err().to_string(),
"unknown time unit \"nights\", supported units: \
ns, us, ms, sec, min, hours, days, weeks, months, \
years (and few variations)");
}
}

View File

@@ -0,0 +1,34 @@
//! Human-friendly time parser and formatter
//!
//! Features:
//!
//! * Parses durations in free form like `15days 2min 2s`
//! * Formats durations in similar form `2years 2min 12us`
//! * Parses and formats timestamp in `rfc3339` format: `2018-01-01T12:53:00Z`
//! * Parses timestamps in a weaker format: `2018-01-01 12:53:00`
//!
//! Timestamp parsing/formatting is super-fast because format is basically
//! fixed.
//!
//! See [humantime-serde] for serde integration (previous crate [serde-humantime] looks unmaintained).
//!
//! [serde-humantime]: https://docs.rs/serde-humantime/0.1.1/serde_humantime/
//! [humantime-serde]: https://docs.rs/humantime-serde
#![forbid(unsafe_code)]
#![warn(missing_debug_implementations)]
#![warn(missing_docs)]
mod duration;
mod wrapper;
mod date;
pub use self::duration::{parse_duration, Error as DurationError};
pub use self::duration::{format_duration, FormattedDuration};
pub use self::wrapper::{Duration, Timestamp};
pub use self::date::{parse_rfc3339, parse_rfc3339_weak, Error as TimestampError};
pub use self::date::{
format_rfc3339, format_rfc3339_micros, format_rfc3339_millis, format_rfc3339_nanos,
format_rfc3339_seconds,
};
pub use self::date::{Rfc3339Timestamp};

View File

@@ -0,0 +1,107 @@
use std::str::FromStr;
use std::ops::Deref;
use std::fmt;
use std::time::{Duration as StdDuration, SystemTime};
use crate::duration::{self, parse_duration, format_duration};
use crate::date::{self, parse_rfc3339_weak, format_rfc3339};
/// A wrapper for duration that has `FromStr` implementation
///
/// This is useful if you want to use it somewhere where `FromStr` is
/// expected.
///
/// See `parse_duration` for the description of the format.
///
/// # Example
///
/// ```
/// use std::time::Duration;
/// let x: Duration;
/// x = "12h 5min 2ns".parse::<humantime::Duration>().unwrap().into();
/// assert_eq!(x, Duration::new(12*3600 + 5*60, 2))
/// ```
///
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct Duration(StdDuration);
/// A wrapper for SystemTime that has `FromStr` implementation
///
/// This is useful if you want to use it somewhere where `FromStr` is
/// expected.
///
/// See `parse_rfc3339_weak` for the description of the format. The "weak"
/// format is used as it's more pemissive for human input as this is the
/// expected use of the type (e.g. command-line parsing).
///
/// # Example
///
/// ```
/// use std::time::SystemTime;
/// let x: SystemTime;
/// x = "2018-02-16T00:31:37Z".parse::<humantime::Timestamp>().unwrap().into();
/// assert_eq!(humantime::format_rfc3339(x).to_string(), "2018-02-16T00:31:37Z");
/// ```
///
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Timestamp(SystemTime);
impl AsRef<StdDuration> for Duration {
fn as_ref(&self) -> &StdDuration { &self.0 }
}
impl Deref for Duration {
type Target = StdDuration;
fn deref(&self) -> &StdDuration { &self.0 }
}
impl Into<StdDuration> for Duration {
fn into(self) -> StdDuration { self.0 }
}
impl From<StdDuration> for Duration {
fn from(dur: StdDuration) -> Duration { Duration(dur) }
}
impl FromStr for Duration {
type Err = duration::Error;
fn from_str(s: &str) -> Result<Duration, Self::Err> {
parse_duration(s).map(Duration)
}
}
impl fmt::Display for Duration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
format_duration(self.0).fmt(f)
}
}
impl AsRef<SystemTime> for Timestamp {
fn as_ref(&self) -> &SystemTime { &self.0 }
}
impl Deref for Timestamp {
type Target = SystemTime;
fn deref(&self) -> &SystemTime { &self.0 }
}
impl Into<SystemTime> for Timestamp {
fn into(self) -> SystemTime { self.0 }
}
impl From<SystemTime> for Timestamp {
fn from(dur: SystemTime) -> Timestamp { Timestamp(dur) }
}
impl FromStr for Timestamp {
type Err = date::Error;
fn from_str(s: &str) -> Result<Timestamp, Self::Err> {
parse_rfc3339_weak(s).map(Timestamp)
}
}
impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
format_rfc3339(self.0).fmt(f)
}
}