diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /vendor/humantime/src | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/humantime/src')
-rw-r--r-- | vendor/humantime/src/date.rs | 616 | ||||
-rw-r--r-- | vendor/humantime/src/duration.rs | 449 | ||||
-rw-r--r-- | vendor/humantime/src/lib.rs | 34 | ||||
-rw-r--r-- | vendor/humantime/src/wrapper.rs | 107 |
4 files changed, 1206 insertions, 0 deletions
diff --git a/vendor/humantime/src/date.rs b/vendor/humantime/src/date.rs new file mode 100644 index 000000000..0afe1f250 --- /dev/null +++ b/vendor/humantime/src/date.rs @@ -0,0 +1,616 @@ +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 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(); + } +} diff --git a/vendor/humantime/src/duration.rs b/vendor/humantime/src/duration.rs new file mode 100644 index 000000000..8359b851d --- /dev/null +++ b/vendor/humantime/src/duration.rs @@ -0,0 +1,449 @@ +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 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)"); + } +} diff --git a/vendor/humantime/src/lib.rs b/vendor/humantime/src/lib.rs new file mode 100644 index 000000000..9be6f2285 --- /dev/null +++ b/vendor/humantime/src/lib.rs @@ -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}; diff --git a/vendor/humantime/src/wrapper.rs b/vendor/humantime/src/wrapper.rs new file mode 100644 index 000000000..99af6502a --- /dev/null +++ b/vendor/humantime/src/wrapper.rs @@ -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) + } +} |