/rust/registry/src/index.crates.io-1949cf8c6b5b557f/humantime-2.3.0/src/duration.rs
Line | Count | Source |
1 | | use std::error::Error as StdError; |
2 | | use std::fmt; |
3 | | use std::str::{Chars, FromStr}; |
4 | | use std::time::Duration; |
5 | | |
6 | | /// Error parsing human-friendly duration |
7 | | #[derive(Debug, PartialEq, Clone)] |
8 | | pub enum Error { |
9 | | /// Invalid character during parsing |
10 | | /// |
11 | | /// More specifically anything that is not alphanumeric is prohibited |
12 | | /// |
13 | | /// The field is an byte offset of the character in the string. |
14 | | InvalidCharacter(usize), |
15 | | /// Non-numeric value where number is expected |
16 | | /// |
17 | | /// This usually means that either time unit is broken into words, |
18 | | /// e.g. `m sec` instead of `msec`, or just number is omitted, |
19 | | /// for example `2 hours min` instead of `2 hours 1 min` |
20 | | /// |
21 | | /// The field is an byte offset of the errorneous character |
22 | | /// in the string. |
23 | | NumberExpected(usize), |
24 | | /// Unit in the number is not one of allowed units |
25 | | /// |
26 | | /// See documentation of `parse_duration` for the list of supported |
27 | | /// time units. |
28 | | /// |
29 | | /// The two fields are start and end (exclusive) of the slice from |
30 | | /// the original string, containing errorneous value |
31 | | UnknownUnit { |
32 | | /// Start of the invalid unit inside the original string |
33 | | start: usize, |
34 | | /// End of the invalid unit inside the original string |
35 | | end: usize, |
36 | | /// The unit verbatim |
37 | | unit: String, |
38 | | /// A number associated with the unit |
39 | | value: u64, |
40 | | }, |
41 | | /// The numeric value exceeds the limits of this library. |
42 | | /// |
43 | | /// This can mean two things: |
44 | | /// - The value is too large to be useful. |
45 | | /// For instance, the maximum duration written with subseconds unit is about 3000 years. |
46 | | /// - The attempted precision is not supported. |
47 | | /// For instance, a duration of `0.5ns` is not supported, |
48 | | /// because durations below one nanosecond cannot be represented. |
49 | | // NOTE: it would be more logical to create a separate `NumberPrecisionLimit` error, |
50 | | // but that would be a breaking change. Reconsider this for the next major version. |
51 | | NumberOverflow, |
52 | | /// The value was an empty string (or consists only whitespace) |
53 | | Empty, |
54 | | } |
55 | | |
56 | | impl StdError for Error {} |
57 | | |
58 | | impl fmt::Display for Error { |
59 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
60 | 0 | match self { |
61 | 0 | Error::InvalidCharacter(offset) => write!(f, "invalid character at {}", offset), |
62 | 0 | Error::NumberExpected(offset) => write!(f, "expected number at {}", offset), |
63 | 0 | Error::UnknownUnit { unit, value, .. } if unit.is_empty() => { |
64 | 0 | write!(f, "time unit needed, for example {0}sec or {0}ms", value) |
65 | | } |
66 | 0 | Error::UnknownUnit { unit, .. } => { |
67 | 0 | write!( |
68 | 0 | f, |
69 | 0 | "unknown time unit {:?}, \ |
70 | 0 | supported units: ns, us/µs, ms, sec, min, hours, days, \ |
71 | 0 | weeks, months, years (and few variations)", |
72 | | unit |
73 | | ) |
74 | | } |
75 | 0 | Error::NumberOverflow => write!(f, "number is too large or cannot be represented without a lack of precision (values below 1ns are not supported)"), |
76 | 0 | Error::Empty => write!(f, "value was empty"), |
77 | | } |
78 | 0 | } |
79 | | } |
80 | | |
81 | | /// A wrapper type that allows you to Display a Duration |
82 | | #[derive(Debug, Clone)] |
83 | | pub struct FormattedDuration(Duration); |
84 | | |
85 | | trait OverflowOp: Sized { |
86 | | fn mul(self, other: Self) -> Result<Self, Error>; |
87 | | fn add(self, other: Self) -> Result<Self, Error>; |
88 | | fn div(self, other: Self) -> Result<Self, Error>; |
89 | | } |
90 | | |
91 | | impl OverflowOp for u64 { |
92 | 0 | fn mul(self, other: Self) -> Result<Self, Error> { |
93 | 0 | self.checked_mul(other).ok_or(Error::NumberOverflow) |
94 | 0 | } |
95 | 0 | fn add(self, other: Self) -> Result<Self, Error> { |
96 | 0 | self.checked_add(other).ok_or(Error::NumberOverflow) |
97 | 0 | } |
98 | 0 | fn div(self, other: Self) -> Result<Self, Error> { |
99 | 0 | match self % other { |
100 | 0 | 0 => Ok(self / other), |
101 | 0 | _ => Err(Error::NumberOverflow), |
102 | | } |
103 | 0 | } |
104 | | } |
105 | | |
106 | | #[derive(Debug, Clone, Copy)] |
107 | | struct Fraction { |
108 | | numerator: u64, |
109 | | denominator: u64, |
110 | | } |
111 | | |
112 | | struct Parser<'a> { |
113 | | iter: Chars<'a>, |
114 | | src: &'a str, |
115 | | } |
116 | | |
117 | | impl Parser<'_> { |
118 | 0 | fn parse(mut self) -> Result<Duration, Error> { |
119 | 0 | let mut n = self.parse_first_char()?.ok_or(Error::Empty)?; // integer part |
120 | 0 | let mut out = Duration::ZERO; |
121 | | 'outer: loop { |
122 | 0 | let mut frac = None; // fractional part |
123 | 0 | let mut off = self.off(); |
124 | 0 | while let Some(c) = self.iter.next() { |
125 | 0 | match c { |
126 | 0 | '0'..='9' => { |
127 | 0 | n = n |
128 | 0 | .checked_mul(10) |
129 | 0 | .and_then(|x| x.checked_add(c as u64 - '0' as u64)) |
130 | 0 | .ok_or(Error::NumberOverflow)?; |
131 | | } |
132 | 0 | c if c.is_whitespace() => {} |
133 | 0 | 'a'..='z' | 'A'..='Z' | 'µ' => { |
134 | 0 | break; |
135 | | } |
136 | | '.' => { |
137 | | // decimal separator, the fractional part begins now |
138 | 0 | frac = Some(self.parse_fractional_part(&mut off)?); |
139 | 0 | break; |
140 | | } |
141 | | _ => { |
142 | 0 | return Err(Error::InvalidCharacter(off)); |
143 | | } |
144 | | } |
145 | 0 | off = self.off(); |
146 | | } |
147 | 0 | let start = off; |
148 | 0 | let mut off = self.off(); |
149 | 0 | while let Some(c) = self.iter.next() { |
150 | 0 | match c { |
151 | 0 | '0'..='9' => { |
152 | 0 | self.parse_unit(n, frac, start, off, &mut out)?; |
153 | 0 | n = c as u64 - '0' as u64; |
154 | 0 | continue 'outer; |
155 | | } |
156 | 0 | c if c.is_whitespace() => break, |
157 | 0 | 'a'..='z' | 'A'..='Z' | 'µ' => {} |
158 | | _ => { |
159 | 0 | return Err(Error::InvalidCharacter(off)); |
160 | | } |
161 | | } |
162 | 0 | off = self.off(); |
163 | | } |
164 | | |
165 | 0 | self.parse_unit(n, frac, start, off, &mut out)?; |
166 | 0 | n = match self.parse_first_char()? { |
167 | 0 | Some(n) => n, |
168 | 0 | None => return Ok(out), |
169 | | }; |
170 | | } |
171 | 0 | } |
172 | | |
173 | 0 | fn parse_first_char(&mut self) -> Result<Option<u64>, Error> { |
174 | 0 | let off = self.off(); |
175 | 0 | for c in self.iter.by_ref() { |
176 | 0 | match c { |
177 | 0 | '0'..='9' => { |
178 | 0 | return Ok(Some(c as u64 - '0' as u64)); |
179 | | } |
180 | 0 | c if c.is_whitespace() => continue, |
181 | | _ => { |
182 | 0 | return Err(Error::NumberExpected(off)); |
183 | | } |
184 | | } |
185 | | } |
186 | 0 | Ok(None) |
187 | 0 | } |
188 | | |
189 | 0 | fn parse_fractional_part(&mut self, off: &mut usize) -> Result<Fraction, Error> { |
190 | 0 | let mut numerator = 0u64; |
191 | 0 | let mut denominator = 1u64; |
192 | 0 | let mut zeros = true; |
193 | 0 | while let Some(c) = self.iter.next() { |
194 | 0 | match c { |
195 | | '0' => { |
196 | 0 | denominator = denominator.checked_mul(10).ok_or(Error::NumberOverflow)?; |
197 | 0 | if !zeros { |
198 | 0 | numerator = numerator.checked_mul(10).ok_or(Error::NumberOverflow)?; |
199 | 0 | } |
200 | | } |
201 | 0 | '1'..='9' => { |
202 | 0 | zeros = false; |
203 | 0 | denominator = denominator.checked_mul(10).ok_or(Error::NumberOverflow)?; |
204 | 0 | numerator = numerator |
205 | 0 | .checked_mul(10) |
206 | 0 | .and_then(|x| x.checked_add(c as u64 - '0' as u64)) |
207 | 0 | .ok_or(Error::NumberOverflow)?; |
208 | | } |
209 | 0 | c if c.is_whitespace() => {} |
210 | 0 | 'a'..='z' | 'A'..='Z' | 'µ' => { |
211 | 0 | break; |
212 | | } |
213 | | _ => { |
214 | 0 | return Err(Error::InvalidCharacter(*off)); |
215 | | } |
216 | | }; |
217 | | // update the offset used by the parsing loop |
218 | 0 | *off = self.off(); |
219 | | } |
220 | 0 | if denominator == 1 { |
221 | | // no digits were given after the separator, e.g. "1." |
222 | 0 | return Err(Error::InvalidCharacter(*off)); |
223 | 0 | } |
224 | 0 | Ok(Fraction { |
225 | 0 | numerator, |
226 | 0 | denominator, |
227 | 0 | }) |
228 | 0 | } |
229 | | |
230 | 0 | fn off(&self) -> usize { |
231 | 0 | self.src.len() - self.iter.as_str().len() |
232 | 0 | } |
233 | | |
234 | 0 | fn parse_unit( |
235 | 0 | &mut self, |
236 | 0 | n: u64, |
237 | 0 | frac: Option<Fraction>, |
238 | 0 | start: usize, |
239 | 0 | end: usize, |
240 | 0 | out: &mut Duration, |
241 | 0 | ) -> Result<(), Error> { |
242 | 0 | let unit = match Unit::from_str(&self.src[start..end]) { |
243 | 0 | Ok(u) => u, |
244 | | Err(()) => { |
245 | 0 | return Err(Error::UnknownUnit { |
246 | 0 | start, |
247 | 0 | end, |
248 | 0 | unit: self.src[start..end].to_owned(), |
249 | 0 | value: n, |
250 | 0 | }); |
251 | | } |
252 | | }; |
253 | | |
254 | | // add the integer part |
255 | 0 | let (sec, nsec) = match unit { |
256 | 0 | Unit::Nanosecond => (0u64, n), |
257 | 0 | Unit::Microsecond => (0u64, n.mul(1000)?), |
258 | 0 | Unit::Millisecond => (0u64, n.mul(1_000_000)?), |
259 | 0 | Unit::Second => (n, 0), |
260 | 0 | Unit::Minute => (n.mul(60)?, 0), |
261 | 0 | Unit::Hour => (n.mul(3600)?, 0), |
262 | 0 | Unit::Day => (n.mul(86400)?, 0), |
263 | 0 | Unit::Week => (n.mul(86400 * 7)?, 0), |
264 | 0 | Unit::Month => (n.mul(2_630_016)?, 0), // 30.44d |
265 | 0 | Unit::Year => (n.mul(31_557_600)?, 0), // 365.25d |
266 | | }; |
267 | 0 | add_current(sec, nsec, out)?; |
268 | | |
269 | | // add the fractional part |
270 | | if let Some(Fraction { |
271 | 0 | numerator: n, |
272 | 0 | denominator: d, |
273 | 0 | }) = frac |
274 | | { |
275 | 0 | let (sec, nsec) = match unit { |
276 | 0 | Unit::Nanosecond => return Err(Error::NumberOverflow), |
277 | 0 | Unit::Microsecond => (0, n.mul(1000)?.div(d)?), |
278 | 0 | Unit::Millisecond => (0, n.mul(1_000_000)?.div(d)?), |
279 | 0 | Unit::Second => (0, n.mul(1_000_000_000)?.div(d)?), |
280 | 0 | Unit::Minute => (0, n.mul(60_000_000_000)?.div(d)?), |
281 | 0 | Unit::Hour => (n.mul(3600)?.div(d)?, 0), |
282 | 0 | Unit::Day => (n.mul(86400)?.div(d)?, 0), |
283 | 0 | Unit::Week => (n.mul(86400 * 7)?.div(d)?, 0), |
284 | 0 | Unit::Month => (n.mul(2_630_016)?.div(d)?, 0), // 30.44d |
285 | 0 | Unit::Year => (n.mul(31_557_600)?.div(d)?, 0), // 365.25d |
286 | | }; |
287 | 0 | add_current(sec, nsec, out)?; |
288 | 0 | } |
289 | | |
290 | 0 | Ok(()) |
291 | 0 | } |
292 | | } |
293 | | |
294 | 0 | fn add_current(mut sec: u64, nsec: u64, out: &mut Duration) -> Result<(), Error> { |
295 | 0 | let mut nsec = (out.subsec_nanos() as u64).add(nsec)?; |
296 | 0 | if nsec > 1_000_000_000 { |
297 | 0 | sec = sec.add(nsec / 1_000_000_000)?; |
298 | 0 | nsec %= 1_000_000_000; |
299 | 0 | } |
300 | 0 | sec = out.as_secs().add(sec)?; |
301 | 0 | *out = Duration::new(sec, nsec as u32); |
302 | 0 | Ok(()) |
303 | 0 | } |
304 | | |
305 | | enum Unit { |
306 | | Nanosecond, |
307 | | Microsecond, |
308 | | Millisecond, |
309 | | Second, |
310 | | Minute, |
311 | | Hour, |
312 | | Day, |
313 | | Week, |
314 | | Month, |
315 | | Year, |
316 | | } |
317 | | |
318 | | impl FromStr for Unit { |
319 | | type Err = (); |
320 | | |
321 | 0 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
322 | 0 | match s { |
323 | 0 | "nanos" | "nsec" | "ns" => Ok(Self::Nanosecond), |
324 | 0 | "usec" | "us" | "µs" => Ok(Self::Microsecond), |
325 | 0 | "millis" | "msec" | "ms" => Ok(Self::Millisecond), |
326 | 0 | "seconds" | "second" | "secs" | "sec" | "s" => Ok(Self::Second), |
327 | 0 | "minutes" | "minute" | "min" | "mins" | "m" => Ok(Self::Minute), |
328 | 0 | "hours" | "hour" | "hr" | "hrs" | "h" => Ok(Self::Hour), |
329 | 0 | "days" | "day" | "d" => Ok(Self::Day), |
330 | 0 | "weeks" | "week" | "wk" | "wks" | "w" => Ok(Self::Week), |
331 | 0 | "months" | "month" | "M" => Ok(Self::Month), |
332 | 0 | "years" | "year" | "yr" | "yrs" | "y" => Ok(Self::Year), |
333 | 0 | _ => Err(()), |
334 | | } |
335 | 0 | } |
336 | | } |
337 | | |
338 | | /// Parse duration object `1hour 12min 5s` |
339 | | /// |
340 | | /// The duration object is a concatenation of time spans. Where each time |
341 | | /// span is an integer number and a suffix. Supported suffixes: |
342 | | /// |
343 | | /// * `nsec`, `ns` -- nanoseconds |
344 | | /// * `usec`, `us`, `µs` -- microseconds |
345 | | /// * `msec`, `ms` -- milliseconds |
346 | | /// * `seconds`, `second`, `sec`, `s` |
347 | | /// * `minutes`, `minute`, `min`, `m` |
348 | | /// * `hours`, `hour`, `hr`, `hrs`, `h` |
349 | | /// * `days`, `day`, `d` |
350 | | /// * `weeks`, `week`, `wk`, `wks`, `w` |
351 | | /// * `months`, `month`, `M` -- defined as 30.44 days |
352 | | /// * `years`, `year`, `yr`, `yrs`, `y` -- defined as 365.25 days |
353 | | /// |
354 | | /// # Examples |
355 | | /// |
356 | | /// ``` |
357 | | /// use std::time::Duration; |
358 | | /// use humantime::parse_duration; |
359 | | /// |
360 | | /// assert_eq!(parse_duration("2h 37min"), Ok(Duration::new(9420, 0))); |
361 | | /// assert_eq!(parse_duration("32ms"), Ok(Duration::new(0, 32_000_000))); |
362 | | /// assert_eq!(parse_duration("4.2s"), Ok(Duration::new(4, 200_000_000))); |
363 | | /// ``` |
364 | 0 | pub fn parse_duration(s: &str) -> Result<Duration, Error> { |
365 | 0 | if s == "0" { |
366 | 0 | return Ok(Duration::ZERO); |
367 | 0 | } |
368 | 0 | Parser { |
369 | 0 | iter: s.chars(), |
370 | 0 | src: s, |
371 | 0 | } |
372 | 0 | .parse() |
373 | 0 | } |
374 | | |
375 | | /// Formats duration into a human-readable string |
376 | | /// |
377 | | /// Note: this format is guaranteed to have same value when using |
378 | | /// parse_duration, but we can change some details of the exact composition |
379 | | /// of the value. |
380 | | /// |
381 | | /// # Examples |
382 | | /// |
383 | | /// ``` |
384 | | /// use std::time::Duration; |
385 | | /// use humantime::format_duration; |
386 | | /// |
387 | | /// let val1 = Duration::new(9420, 0); |
388 | | /// assert_eq!(format_duration(val1).to_string(), "2h 37m"); |
389 | | /// let val2 = Duration::new(0, 32_000_000); |
390 | | /// assert_eq!(format_duration(val2).to_string(), "32ms"); |
391 | | /// ``` |
392 | 0 | pub fn format_duration(val: Duration) -> FormattedDuration { |
393 | 0 | FormattedDuration(val) |
394 | 0 | } |
395 | | |
396 | 0 | fn item_plural(f: &mut fmt::Formatter, started: &mut bool, name: &str, value: u64) -> fmt::Result { |
397 | 0 | if value > 0 { |
398 | 0 | if *started { |
399 | 0 | f.write_str(" ")?; |
400 | 0 | } |
401 | 0 | write!(f, "{}{}", value, name)?; |
402 | 0 | if value > 1 { |
403 | 0 | f.write_str("s")?; |
404 | 0 | } |
405 | 0 | *started = true; |
406 | 0 | } |
407 | 0 | Ok(()) |
408 | 0 | } |
409 | 0 | fn item(f: &mut fmt::Formatter, started: &mut bool, name: &str, value: u32) -> fmt::Result { |
410 | 0 | if value > 0 { |
411 | 0 | if *started { |
412 | 0 | f.write_str(" ")?; |
413 | 0 | } |
414 | 0 | write!(f, "{}{}", value, name)?; |
415 | 0 | *started = true; |
416 | 0 | } |
417 | 0 | Ok(()) |
418 | 0 | } |
419 | | |
420 | | impl FormattedDuration { |
421 | | /// Returns a reference to the [`Duration`][] that is being formatted. |
422 | 0 | pub fn get_ref(&self) -> &Duration { |
423 | 0 | &self.0 |
424 | 0 | } |
425 | | } |
426 | | |
427 | | impl fmt::Display for FormattedDuration { |
428 | 0 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
429 | 0 | let secs = self.0.as_secs(); |
430 | 0 | let nanos = self.0.subsec_nanos(); |
431 | | |
432 | 0 | if secs == 0 && nanos == 0 { |
433 | 0 | f.write_str("0s")?; |
434 | 0 | return Ok(()); |
435 | 0 | } |
436 | | |
437 | 0 | let years = secs / 31_557_600; // 365.25d |
438 | 0 | let ydays = secs % 31_557_600; |
439 | 0 | let months = ydays / 2_630_016; // 30.44d |
440 | 0 | let mdays = ydays % 2_630_016; |
441 | 0 | let days = mdays / 86400; |
442 | 0 | let day_secs = mdays % 86400; |
443 | 0 | let hours = day_secs / 3600; |
444 | 0 | let minutes = day_secs % 3600 / 60; |
445 | 0 | let seconds = day_secs % 60; |
446 | | |
447 | 0 | let millis = nanos / 1_000_000; |
448 | 0 | let micros = nanos / 1000 % 1000; |
449 | 0 | let nanosec = nanos % 1000; |
450 | | |
451 | 0 | let started = &mut false; |
452 | 0 | item_plural(f, started, "year", years)?; |
453 | 0 | item_plural(f, started, "month", months)?; |
454 | 0 | item_plural(f, started, "day", days)?; |
455 | 0 | item(f, started, "h", hours as u32)?; |
456 | 0 | item(f, started, "m", minutes as u32)?; |
457 | 0 | item(f, started, "s", seconds as u32)?; |
458 | 0 | item(f, started, "ms", millis)?; |
459 | | #[cfg(feature = "mu")] |
460 | | item(f, started, "µs", micros)?; |
461 | | #[cfg(not(feature = "mu"))] |
462 | 0 | item(f, started, "us", micros)?; |
463 | 0 | item(f, started, "ns", nanosec)?; |
464 | 0 | Ok(()) |
465 | 0 | } |
466 | | } |
467 | | |
468 | | #[cfg(test)] |
469 | | mod test { |
470 | | use std::time::Duration; |
471 | | |
472 | | use rand::Rng; |
473 | | |
474 | | use super::Error; |
475 | | use super::{format_duration, parse_duration}; |
476 | | |
477 | | #[test] |
478 | | #[allow(clippy::cognitive_complexity)] |
479 | | fn test_units() { |
480 | | assert_eq!(parse_duration("17nsec"), Ok(Duration::new(0, 17))); |
481 | | assert_eq!(parse_duration("17nanos"), Ok(Duration::new(0, 17))); |
482 | | assert_eq!(parse_duration("33ns"), Ok(Duration::new(0, 33))); |
483 | | assert_eq!(parse_duration("3usec"), Ok(Duration::new(0, 3000))); |
484 | | assert_eq!(parse_duration("78us"), Ok(Duration::new(0, 78000))); |
485 | | assert_eq!(parse_duration("163µs"), Ok(Duration::new(0, 163000))); |
486 | | assert_eq!(parse_duration("31msec"), Ok(Duration::new(0, 31_000_000))); |
487 | | assert_eq!(parse_duration("31millis"), Ok(Duration::new(0, 31_000_000))); |
488 | | assert_eq!(parse_duration("6ms"), Ok(Duration::new(0, 6_000_000))); |
489 | | assert_eq!(parse_duration("3000s"), Ok(Duration::new(3000, 0))); |
490 | | assert_eq!(parse_duration("300sec"), Ok(Duration::new(300, 0))); |
491 | | assert_eq!(parse_duration("300secs"), Ok(Duration::new(300, 0))); |
492 | | assert_eq!(parse_duration("50seconds"), Ok(Duration::new(50, 0))); |
493 | | assert_eq!(parse_duration("1second"), Ok(Duration::new(1, 0))); |
494 | | assert_eq!(parse_duration("100m"), Ok(Duration::new(6000, 0))); |
495 | | assert_eq!(parse_duration("12min"), Ok(Duration::new(720, 0))); |
496 | | assert_eq!(parse_duration("12mins"), Ok(Duration::new(720, 0))); |
497 | | assert_eq!(parse_duration("1minute"), Ok(Duration::new(60, 0))); |
498 | | assert_eq!(parse_duration("7minutes"), Ok(Duration::new(420, 0))); |
499 | | assert_eq!(parse_duration("2h"), Ok(Duration::new(7200, 0))); |
500 | | assert_eq!(parse_duration("7hr"), Ok(Duration::new(25200, 0))); |
501 | | assert_eq!(parse_duration("7hrs"), Ok(Duration::new(25200, 0))); |
502 | | assert_eq!(parse_duration("1hour"), Ok(Duration::new(3600, 0))); |
503 | | assert_eq!(parse_duration("24hours"), Ok(Duration::new(86400, 0))); |
504 | | assert_eq!(parse_duration("1day"), Ok(Duration::new(86400, 0))); |
505 | | assert_eq!(parse_duration("2days"), Ok(Duration::new(172_800, 0))); |
506 | | assert_eq!(parse_duration("365d"), Ok(Duration::new(31_536_000, 0))); |
507 | | assert_eq!(parse_duration("1week"), Ok(Duration::new(604_800, 0))); |
508 | | assert_eq!(parse_duration("7weeks"), Ok(Duration::new(4_233_600, 0))); |
509 | | assert_eq!( |
510 | | parse_duration("104wks"), |
511 | | Ok(Duration::new(2 * 31_449_600, 0)) |
512 | | ); |
513 | | assert_eq!(parse_duration("100wk"), Ok(Duration::new(60_480_000, 0))); |
514 | | assert_eq!(parse_duration("52w"), Ok(Duration::new(31_449_600, 0))); |
515 | | assert_eq!(parse_duration("1month"), Ok(Duration::new(2_630_016, 0))); |
516 | | assert_eq!( |
517 | | parse_duration("3months"), |
518 | | Ok(Duration::new(3 * 2_630_016, 0)) |
519 | | ); |
520 | | assert_eq!(parse_duration("12M"), Ok(Duration::new(31_560_192, 0))); |
521 | | assert_eq!(parse_duration("1year"), Ok(Duration::new(31_557_600, 0))); |
522 | | assert_eq!( |
523 | | parse_duration("7years"), |
524 | | Ok(Duration::new(7 * 31_557_600, 0)) |
525 | | ); |
526 | | assert_eq!( |
527 | | parse_duration("15yrs"), |
528 | | Ok(Duration::new(15 * 31_557_600, 0)) |
529 | | ); |
530 | | assert_eq!( |
531 | | parse_duration("10yr"), |
532 | | Ok(Duration::new(10 * 31_557_600, 0)) |
533 | | ); |
534 | | assert_eq!(parse_duration("17y"), Ok(Duration::new(536_479_200, 0))); |
535 | | } |
536 | | |
537 | | #[test] |
538 | | fn test_fractional_bad_input() { |
539 | | assert!(matches!( |
540 | | parse_duration("1.s"), |
541 | | Err(Error::InvalidCharacter(_)) |
542 | | )); |
543 | | assert!(matches!( |
544 | | parse_duration("1..s"), |
545 | | Err(Error::InvalidCharacter(_)) |
546 | | )); |
547 | | assert!(matches!( |
548 | | parse_duration(".1s"), |
549 | | Err(Error::NumberExpected(_)) |
550 | | )); |
551 | | assert!(matches!(parse_duration("."), Err(Error::NumberExpected(_)))); |
552 | | assert_eq!( |
553 | | parse_duration("0.000123456789s"), |
554 | | Err(Error::NumberOverflow) |
555 | | ); |
556 | | } |
557 | | |
558 | | #[test] |
559 | | fn test_fractional_units() { |
560 | | // nanos |
561 | | for input in &["17.5nsec", "5.1nanos", "0.0005ns"] { |
562 | | let bad_ns_frac = parse_duration(input); |
563 | | assert!( |
564 | | matches!(bad_ns_frac, Err(Error::NumberOverflow)), |
565 | | "fractions of nanoseconds should fail, but got {bad_ns_frac:?}" |
566 | | ); |
567 | | } |
568 | | |
569 | | // micros |
570 | | assert_eq!(parse_duration("3.1usec"), Ok(Duration::new(0, 3100))); |
571 | | assert_eq!(parse_duration("3.1us"), Ok(Duration::new(0, 3100))); |
572 | | assert_eq!(parse_duration("3.01us"), Ok(Duration::new(0, 3010))); |
573 | | assert_eq!(parse_duration("3.001us"), Ok(Duration::new(0, 3001))); |
574 | | for input in &["3.0001us", "0.0001us", "0.123456us"] { |
575 | | let bad_ms_frac = parse_duration(input); |
576 | | assert!( |
577 | | matches!(bad_ms_frac, Err(Error::NumberOverflow)), |
578 | | "too small fractions of microseconds should fail, but got {bad_ms_frac:?}" |
579 | | ); |
580 | | } |
581 | | |
582 | | // millis |
583 | | assert_eq!(parse_duration("31.1msec"), Ok(Duration::new(0, 31_100_000))); |
584 | | assert_eq!( |
585 | | parse_duration("31.1millis"), |
586 | | Ok(Duration::new(0, 31_100_000)) |
587 | | ); |
588 | | assert_eq!(parse_duration("31.1ms"), Ok(Duration::new(0, 31_100_000))); |
589 | | assert_eq!(parse_duration("31.01ms"), Ok(Duration::new(0, 31_010_000))); |
590 | | assert_eq!(parse_duration("31.001ms"), Ok(Duration::new(0, 31_001_000))); |
591 | | assert_eq!( |
592 | | parse_duration("31.0001ms"), |
593 | | Ok(Duration::new(0, 31_000_100)) |
594 | | ); |
595 | | assert_eq!( |
596 | | parse_duration("31.00001ms"), |
597 | | Ok(Duration::new(0, 31_000_010)) |
598 | | ); |
599 | | assert_eq!( |
600 | | parse_duration("31.000001ms"), |
601 | | Ok(Duration::new(0, 31_000_001)) |
602 | | ); |
603 | | assert!(matches!( |
604 | | parse_duration("31.0000001ms"), |
605 | | Err(Error::NumberOverflow) |
606 | | )); |
607 | | |
608 | | // seconds |
609 | | assert_eq!(parse_duration("300.0sec"), Ok(Duration::new(300, 0))); |
610 | | assert_eq!(parse_duration("300.0secs"), Ok(Duration::new(300, 0))); |
611 | | assert_eq!(parse_duration("300.0seconds"), Ok(Duration::new(300, 0))); |
612 | | assert_eq!(parse_duration("300.0s"), Ok(Duration::new(300, 0))); |
613 | | assert_eq!(parse_duration("0.0s"), Ok(Duration::new(0, 0))); |
614 | | assert_eq!(parse_duration("0.2s"), Ok(Duration::new(0, 200_000_000))); |
615 | | assert_eq!(parse_duration("1.2s"), Ok(Duration::new(1, 200_000_000))); |
616 | | assert_eq!(parse_duration("1.02s"), Ok(Duration::new(1, 20_000_000))); |
617 | | assert_eq!(parse_duration("1.002s"), Ok(Duration::new(1, 2_000_000))); |
618 | | assert_eq!(parse_duration("1.0002s"), Ok(Duration::new(1, 200_000))); |
619 | | assert_eq!(parse_duration("1.00002s"), Ok(Duration::new(1, 20_000))); |
620 | | assert_eq!(parse_duration("1.000002s"), Ok(Duration::new(1, 2_000))); |
621 | | assert_eq!(parse_duration("1.0000002s"), Ok(Duration::new(1, 200))); |
622 | | assert_eq!(parse_duration("1.00000002s"), Ok(Duration::new(1, 20))); |
623 | | assert_eq!(parse_duration("1.000000002s"), Ok(Duration::new(1, 2))); |
624 | | assert_eq!( |
625 | | parse_duration("1.123456789s"), |
626 | | Ok(Duration::new(1, 123_456_789)) |
627 | | ); |
628 | | assert!(matches!( |
629 | | parse_duration("1.0000000002s"), |
630 | | Err(Error::NumberOverflow) |
631 | | )); |
632 | | assert!(matches!( |
633 | | parse_duration("0.0000000002s"), |
634 | | Err(Error::NumberOverflow) |
635 | | )); |
636 | | |
637 | | // minutes |
638 | | assert_eq!(parse_duration("100.0m"), Ok(Duration::new(6000, 0))); |
639 | | assert_eq!(parse_duration("12.1min"), Ok(Duration::new(726, 0))); |
640 | | assert_eq!(parse_duration("12.1mins"), Ok(Duration::new(726, 0))); |
641 | | assert_eq!(parse_duration("1.5minute"), Ok(Duration::new(90, 0))); |
642 | | assert_eq!(parse_duration("1.5minutes"), Ok(Duration::new(90, 0))); |
643 | | |
644 | | // hours |
645 | | assert_eq!(parse_duration("2.0h"), Ok(Duration::new(7200, 0))); |
646 | | assert_eq!(parse_duration("2.0hr"), Ok(Duration::new(7200, 0))); |
647 | | assert_eq!(parse_duration("2.0hrs"), Ok(Duration::new(7200, 0))); |
648 | | assert_eq!(parse_duration("2.0hours"), Ok(Duration::new(7200, 0))); |
649 | | assert_eq!(parse_duration("2.5h"), Ok(Duration::new(9000, 0))); |
650 | | assert_eq!(parse_duration("0.5h"), Ok(Duration::new(1800, 0))); |
651 | | |
652 | | // days |
653 | | assert_eq!( |
654 | | parse_duration("1.5day"), |
655 | | Ok(Duration::new(86400 + 86400 / 2, 0)) |
656 | | ); |
657 | | assert_eq!( |
658 | | parse_duration("1.5days"), |
659 | | Ok(Duration::new(86400 + 86400 / 2, 0)) |
660 | | ); |
661 | | assert_eq!( |
662 | | parse_duration("1.5d"), |
663 | | Ok(Duration::new(86400 + 86400 / 2, 0)) |
664 | | ); |
665 | | assert!(matches!( |
666 | | parse_duration("0.00000005d"), |
667 | | Err(Error::NumberOverflow) |
668 | | )); |
669 | | } |
670 | | |
671 | | #[test] |
672 | | fn test_fractional_combined() { |
673 | | assert_eq!(parse_duration("7.120us 3ns"), Ok(Duration::new(0, 7123))); |
674 | | assert_eq!(parse_duration("7.123us 4ns"), Ok(Duration::new(0, 7127))); |
675 | | assert_eq!( |
676 | | parse_duration("1.234s 789ns"), |
677 | | Ok(Duration::new(1, 234_000_789)) |
678 | | ); |
679 | | assert_eq!( |
680 | | parse_duration("1.234s 0.789us"), |
681 | | Ok(Duration::new(1, 234_000_789)) |
682 | | ); |
683 | | assert_eq!( |
684 | | parse_duration("1.234567s 0.789us"), |
685 | | Ok(Duration::new(1, 234_567_789)) |
686 | | ); |
687 | | assert_eq!( |
688 | | parse_duration("1.234s 1.345ms 1.678us 1ns"), |
689 | | Ok(Duration::new(1, 235_346_679)) |
690 | | ); |
691 | | assert_eq!( |
692 | | parse_duration("1.234s 0.345ms 0.678us 0ns"), |
693 | | Ok(Duration::new(1, 234_345_678)) |
694 | | ); |
695 | | assert_eq!( |
696 | | parse_duration("1.234s0.345ms0.678us0ns"), |
697 | | Ok(Duration::new(1, 234_345_678)) |
698 | | ); |
699 | | } |
700 | | |
701 | | #[test] |
702 | | fn allow_0_with_no_unit() { |
703 | | assert_eq!(parse_duration("0"), Ok(Duration::new(0, 0))); |
704 | | } |
705 | | |
706 | | #[test] |
707 | | fn test_combo() { |
708 | | assert_eq!( |
709 | | parse_duration("20 min 17 nsec "), |
710 | | Ok(Duration::new(1200, 17)) |
711 | | ); |
712 | | assert_eq!(parse_duration("2h 15m"), Ok(Duration::new(8100, 0))); |
713 | | } |
714 | | |
715 | | #[test] |
716 | | fn all_86400_seconds() { |
717 | | for second in 0..86400 { |
718 | | // scan leap year and non-leap year |
719 | | let d = Duration::new(second, 0); |
720 | | assert_eq!(d, parse_duration(&format_duration(d).to_string()).unwrap()); |
721 | | } |
722 | | } |
723 | | |
724 | | #[test] |
725 | | fn random_second() { |
726 | | for _ in 0..10000 { |
727 | | let sec = rand::rng().random_range(0..253_370_764_800); |
728 | | let d = Duration::new(sec, 0); |
729 | | assert_eq!(d, parse_duration(&format_duration(d).to_string()).unwrap()); |
730 | | } |
731 | | } |
732 | | |
733 | | #[test] |
734 | | fn random_any() { |
735 | | for _ in 0..10000 { |
736 | | let sec = rand::rng().random_range(0..253_370_764_800); |
737 | | let nanos = rand::rng().random_range(0..1_000_000_000); |
738 | | let d = Duration::new(sec, nanos); |
739 | | assert_eq!(d, parse_duration(&format_duration(d).to_string()).unwrap()); |
740 | | } |
741 | | } |
742 | | |
743 | | #[test] |
744 | | fn test_overlow() { |
745 | | // Overflow on subseconds is earlier because of how we do conversion |
746 | | // we could fix it, but I don't see any good reason for this |
747 | | assert_eq!( |
748 | | parse_duration("100000000000000000000ns"), |
749 | | Err(Error::NumberOverflow) |
750 | | ); |
751 | | assert_eq!( |
752 | | parse_duration("100000000000000000us"), |
753 | | Err(Error::NumberOverflow) |
754 | | ); |
755 | | assert_eq!( |
756 | | parse_duration("100000000000000ms"), |
757 | | Err(Error::NumberOverflow) |
758 | | ); |
759 | | |
760 | | assert_eq!( |
761 | | parse_duration("100000000000000000000s"), |
762 | | Err(Error::NumberOverflow) |
763 | | ); |
764 | | assert_eq!( |
765 | | parse_duration("10000000000000000000m"), |
766 | | Err(Error::NumberOverflow) |
767 | | ); |
768 | | assert_eq!( |
769 | | parse_duration("1000000000000000000h"), |
770 | | Err(Error::NumberOverflow) |
771 | | ); |
772 | | assert_eq!( |
773 | | parse_duration("100000000000000000d"), |
774 | | Err(Error::NumberOverflow) |
775 | | ); |
776 | | assert_eq!( |
777 | | parse_duration("10000000000000000w"), |
778 | | Err(Error::NumberOverflow) |
779 | | ); |
780 | | assert_eq!( |
781 | | parse_duration("1000000000000000M"), |
782 | | Err(Error::NumberOverflow) |
783 | | ); |
784 | | assert_eq!( |
785 | | parse_duration("10000000000000y"), |
786 | | Err(Error::NumberOverflow) |
787 | | ); |
788 | | } |
789 | | |
790 | | #[test] |
791 | | fn test_nice_error_message() { |
792 | | assert_eq!( |
793 | | parse_duration("123").unwrap_err().to_string(), |
794 | | "time unit needed, for example 123sec or 123ms" |
795 | | ); |
796 | | assert_eq!( |
797 | | parse_duration("10 months 1").unwrap_err().to_string(), |
798 | | "time unit needed, for example 1sec or 1ms" |
799 | | ); |
800 | | assert_eq!( |
801 | | parse_duration("10nights").unwrap_err().to_string(), |
802 | | "unknown time unit \"nights\", supported units: \ |
803 | | ns, us/µs, ms, sec, min, hours, days, weeks, months, \ |
804 | | years (and few variations)" |
805 | | ); |
806 | | } |
807 | | |
808 | | #[cfg(feature = "mu")] |
809 | | #[test] |
810 | | fn test_format_micros() { |
811 | | assert_eq!( |
812 | | format_duration(Duration::from_micros(123)).to_string(), |
813 | | "123µs" |
814 | | ); |
815 | | } |
816 | | |
817 | | #[cfg(not(feature = "mu"))] |
818 | | #[test] |
819 | | fn test_format_micros() { |
820 | | assert_eq!( |
821 | | format_duration(Duration::from_micros(123)).to_string(), |
822 | | "123us" |
823 | | ); |
824 | | } |
825 | | |
826 | | #[test] |
827 | | fn test_error_cases() { |
828 | | assert_eq!( |
829 | | parse_duration("\0").unwrap_err().to_string(), |
830 | | "expected number at 0" |
831 | | ); |
832 | | assert_eq!( |
833 | | parse_duration("\r").unwrap_err().to_string(), |
834 | | "value was empty" |
835 | | ); |
836 | | assert_eq!( |
837 | | parse_duration("1~").unwrap_err().to_string(), |
838 | | "invalid character at 1" |
839 | | ); |
840 | | assert_eq!( |
841 | | parse_duration("1Nå").unwrap_err().to_string(), |
842 | | "invalid character at 2" |
843 | | ); |
844 | | assert_eq!(parse_duration("222nsec221nanosmsec7s5msec572s").unwrap_err().to_string(), |
845 | | "unknown time unit \"nanosmsec\", supported units: ns, us/µs, ms, sec, min, hours, days, weeks, months, years (and few variations)"); |
846 | | } |
847 | | } |