|
| 1 | +//! Temporal quantification. |
| 2 | +
|
| 3 | +use std::convert; |
| 4 | +use std::fmt; |
| 5 | +use std::num; |
| 6 | +use std::ops; |
| 7 | +use std::str; |
| 8 | +use std::time; |
| 9 | + |
| 10 | +/// A Duration type to represent a span of time. |
| 11 | +/// |
| 12 | +/// A string representing the duration is of the form "72h3m0.5s". |
| 13 | +/// As a special case, durations less than one second format use a |
| 14 | +/// smaller unit (milli-, micro-, or nanoseconds) to ensure that the |
| 15 | +/// leading digit is non-zero. The zero duration formats as 0s. |
| 16 | +/// |
| 17 | +/// ``` |
| 18 | +/// use std::time; |
| 19 | +/// use pflag::Value; |
| 20 | +/// use pflag::time::Duration; |
| 21 | +/// |
| 22 | +/// let mut d = Duration::default(); |
| 23 | +/// d.set("1h1m10.987654321s".to_string()); |
| 24 | +/// |
| 25 | +/// assert_eq!(d, Duration::from(time::Duration::new(3670, 987654321))); |
| 26 | +/// assert_eq!(format!("{}", d), "1h1m10.987654321s"); |
| 27 | +/// ``` |
| 28 | +#[derive(Debug, Default, Copy, Clone, PartialEq, PartialOrd)] |
| 29 | +pub struct Duration(time::Duration); |
| 30 | + |
| 31 | +impl Duration { |
| 32 | + pub fn new(secs: u64, nanos: u32) -> Self { |
| 33 | + Self(time::Duration::new(secs, nanos)) |
| 34 | + } |
| 35 | +} |
| 36 | + |
| 37 | +impl str::FromStr for Duration { |
| 38 | + type Err = num::ParseIntError; |
| 39 | + |
| 40 | + fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 41 | + let mut chars = s.chars().peekable(); |
| 42 | + let mut secs = 0; |
| 43 | + let mut nanos = 0; |
| 44 | + |
| 45 | + loop { |
| 46 | + let (s, c) = scan_digits(&mut chars); |
| 47 | + let num: u64 = s.parse()?; |
| 48 | + match c { |
| 49 | + 'h' => { |
| 50 | + secs += num * 3600; |
| 51 | + } |
| 52 | + 'm' => { |
| 53 | + let c = chars.peek().unwrap(); |
| 54 | + if *c == 's' { |
| 55 | + chars.next(); |
| 56 | + nanos += num * 1e6 as u64; |
| 57 | + break; |
| 58 | + } |
| 59 | + secs += num * 60; |
| 60 | + } |
| 61 | + 'µ' => { |
| 62 | + nanos += num * 1e3 as u64; |
| 63 | + break; |
| 64 | + } |
| 65 | + 'n' => { |
| 66 | + nanos += num; |
| 67 | + break; |
| 68 | + } |
| 69 | + 's' => { |
| 70 | + secs += num; |
| 71 | + break; |
| 72 | + } |
| 73 | + '.' => { |
| 74 | + let (mut s, c) = scan_digits(&mut chars); |
| 75 | + let mut pad = 9; |
| 76 | + if c == 's' { |
| 77 | + secs += num; |
| 78 | + } else if c == 'm' { |
| 79 | + nanos += num * 1e6 as u64; |
| 80 | + pad = 6; |
| 81 | + } else { |
| 82 | + nanos += num * 1e3 as u64; |
| 83 | + pad = 3; |
| 84 | + } |
| 85 | + (0..pad - s.len()).for_each(|_| s.push('0')); |
| 86 | + let s = s.trim_start_matches('0'); |
| 87 | + let frac: u64 = s.parse()?; |
| 88 | + nanos += frac; |
| 89 | + break; |
| 90 | + } |
| 91 | + _ => break, |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + Ok(Self(time::Duration::new(secs, nanos as u32))) |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +fn scan_digits<I: Iterator<Item = char>>(iter: &mut I) -> (String, char) { |
| 100 | + let mut s = String::new(); |
| 101 | + loop { |
| 102 | + let c = iter.next().unwrap(); |
| 103 | + if c.is_alphabetic() || c == '.' { |
| 104 | + return (s, c); |
| 105 | + } |
| 106 | + |
| 107 | + s.push(c); |
| 108 | + } |
| 109 | +} |
| 110 | + |
| 111 | +impl fmt::Display for Duration { |
| 112 | + // largest time fmt is 5119296h10m10.000000000s |
| 113 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 114 | + if self.0 == time::Duration::default() { |
| 115 | + return write!(f, "0s"); |
| 116 | + } |
| 117 | + let mut buf = [0 as u8; 32]; |
| 118 | + let mut w = buf.len(); |
| 119 | + if self.0 < time::Duration::SECOND { |
| 120 | + let mut prec = 0; |
| 121 | + w -= 1; |
| 122 | + buf[w] = 's' as u8; |
| 123 | + w -= 1; |
| 124 | + if self.0 < time::Duration::MICROSECOND { |
| 125 | + buf[w] = 'n' as u8; |
| 126 | + } else if self.0 < time::Duration::MILLISECOND { |
| 127 | + prec = 3; |
| 128 | + w -= 1; |
| 129 | + // U+00B5 'µ' micro sign == 0xC2 0xB5 |
| 130 | + buf[w] = 0xC2; |
| 131 | + buf[w + 1] = 0xB5; |
| 132 | + } else { |
| 133 | + prec = 6; |
| 134 | + buf[w] = 'm' as u8; |
| 135 | + } |
| 136 | + let (w2, u) = fmt_frac(&mut buf[..w], self.0.as_nanos() as u64, prec); |
| 137 | + w = fmt_int(&mut buf[..w2], u); |
| 138 | + } else { |
| 139 | + w -= 1; |
| 140 | + buf[w] = 's' as u8; |
| 141 | + |
| 142 | + let (w2, mut u) = fmt_frac(&mut buf[..w], self.0.as_nanos() as u64, 9); |
| 143 | + |
| 144 | + w = fmt_int(&mut buf[..w2], u % 60); |
| 145 | + u /= 60; |
| 146 | + |
| 147 | + if u > 0 { |
| 148 | + w -= 1; |
| 149 | + buf[w] = 'm' as u8; |
| 150 | + w = fmt_int(&mut buf[..w], u % 60); |
| 151 | + u /= 60; |
| 152 | + |
| 153 | + if u > 0 { |
| 154 | + w -= 1; |
| 155 | + buf[w] = 'h' as u8; |
| 156 | + w = fmt_int(&mut buf[..w], u) |
| 157 | + } |
| 158 | + } |
| 159 | + } |
| 160 | + |
| 161 | + f.write_str(str::from_utf8(&buf[w..]).unwrap()) |
| 162 | + } |
| 163 | +} |
| 164 | + |
| 165 | +fn fmt_frac(buf: &mut [u8], mut v: u64, prec: u8) -> (usize, u64) { |
| 166 | + // Omit trailing zeros up to and including decimal point. |
| 167 | + let mut w = buf.len(); |
| 168 | + let mut print = false; |
| 169 | + for _ in 0..prec { |
| 170 | + let digit = v % 10; |
| 171 | + print = print || digit != 0; |
| 172 | + if print { |
| 173 | + w -= 1; |
| 174 | + buf[w] = digit as u8 + '0' as u8; |
| 175 | + } |
| 176 | + v /= 10; |
| 177 | + } |
| 178 | + if print { |
| 179 | + w -= 1; |
| 180 | + buf[w] = '.' as u8; |
| 181 | + } |
| 182 | + (w, v) |
| 183 | +} |
| 184 | + |
| 185 | +fn fmt_int(buf: &mut [u8], mut v: u64) -> usize { |
| 186 | + let mut w = buf.len(); |
| 187 | + if v == 0 { |
| 188 | + w -= 1; |
| 189 | + buf[w] = '0' as u8; |
| 190 | + } else { |
| 191 | + while v > 0 { |
| 192 | + w -= 1; |
| 193 | + buf[w] = (v % 10) as u8 + '0' as u8; |
| 194 | + v /= 10; |
| 195 | + } |
| 196 | + } |
| 197 | + w |
| 198 | +} |
| 199 | + |
| 200 | +impl From<time::Duration> for Duration { |
| 201 | + fn from(d: time::Duration) -> Self { |
| 202 | + Self(d) |
| 203 | + } |
| 204 | +} |
| 205 | + |
| 206 | +impl Into<time::Duration> for Duration { |
| 207 | + fn into(self) -> time::Duration { |
| 208 | + self.0 |
| 209 | + } |
| 210 | +} |
| 211 | + |
| 212 | +impl ops::Deref for Duration { |
| 213 | + type Target = time::Duration; |
| 214 | + |
| 215 | + fn deref(&self) -> &Self::Target { |
| 216 | + &self.0 |
| 217 | + } |
| 218 | +} |
| 219 | + |
| 220 | +impl convert::AsRef<time::Duration> for Duration { |
| 221 | + fn as_ref(&self) -> &time::Duration { |
| 222 | + &self.0 |
| 223 | + } |
| 224 | +} |
| 225 | + |
| 226 | +#[cfg(test)] |
| 227 | +mod tests { |
| 228 | + use super::*; |
| 229 | + use crate::value::Value; |
| 230 | + |
| 231 | + #[test] |
| 232 | + fn format() { |
| 233 | + let cases = vec![ |
| 234 | + ("3ns", time::Duration::new(0, 3)), |
| 235 | + ("3.000001ms", time::Duration::new(0, 3000001)), |
| 236 | + ("3s", time::Duration::new(3, 0)), |
| 237 | + ("1m0s", time::Duration::new(60, 0)), |
| 238 | + ("1h0m0s", time::Duration::new(3600, 0)), |
| 239 | + ("1h0m0.0001s", time::Duration::new(3600, 100000)), |
| 240 | + ]; |
| 241 | + |
| 242 | + cases |
| 243 | + .into_iter() |
| 244 | + .map(|(f, d)| (f, Duration::from(d))) |
| 245 | + .for_each(|(f, d)| { |
| 246 | + assert_eq!(f, format!("{}", d)); |
| 247 | + let mut d2 = Duration::default(); |
| 248 | + let res = d2.set(f.to_string()); |
| 249 | + assert!(res.is_ok()); |
| 250 | + assert_eq!(d, d2); |
| 251 | + }) |
| 252 | + } |
| 253 | +} |
0 commit comments