Skip to main content

reaction_plugin/
time.rs

1//! This module provides [`parse_duration`], which parses duration in reaction's format (ie. `6h`, `3 days`)
2//!
3//! Like in those reaction core settings:
4//! - [Filters' `retryperiod`](https://reaction.ppom.me/reference.html#retryperiod)
5//! - [Actions' `after`](https://reaction.ppom.me/reference.html#after).
6
7use std::time::Duration;
8
9/// Parses the &str argument as a Duration
10/// Returns Ok(Duration) if successful, or Err(String).
11///
12/// Format is defined as follows: `<integer> <unit>`
13/// - whitespace between the integer and unit is optional
14/// - integer must be positive (>= 0)
15/// - unit can be one of:
16///   - `ms` / `millis` / `millisecond` / `milliseconds`
17///   - `s` / `sec` / `secs` / `second` / `seconds`
18///   - `m` / `min` / `mins` / `minute` / `minutes`
19///   - `h` / `hour` / `hours`
20///   - `d` / `day` / `days`
21pub fn parse_duration(d: &str) -> Result<Duration, String> {
22    let d_trimmed = d.trim();
23    let chars = d_trimmed.as_bytes();
24    let mut value = 0;
25    let mut i = 0;
26    while i < chars.len() && chars[i].is_ascii_digit() {
27        value = value * 10 + (chars[i] - b'0') as u32;
28        i += 1;
29    }
30    if i == 0 {
31        return Err(format!("duration '{}' doesn't start with digits", d));
32    }
33    let ok_as = |func: fn(u64) -> Duration| -> Result<_, String> { Ok(func(value as u64)) };
34
35    match d_trimmed[i..].trim() {
36        "ms" | "millis" | "millisecond" | "milliseconds" => ok_as(Duration::from_millis),
37        "s" | "sec" | "secs" | "second" | "seconds" => ok_as(Duration::from_secs),
38        "m" | "min" | "mins" | "minute" | "minutes" => ok_as(Duration::from_mins),
39        "h" | "hour" | "hours" => ok_as(Duration::from_hours),
40        "d" | "day" | "days" => ok_as(|d: u64| Duration::from_hours(d * 24)),
41        unit => Err(format!(
42            "unit {} not recognised. must be one of s/sec/seconds, m/min/minutes, h/hours, d/days",
43            unit
44        )),
45    }
46}
47
48#[cfg(test)]
49mod tests {
50
51    use super::*;
52
53    #[test]
54    fn char_conversion() {
55        assert_eq!(b'9' - b'0', 9);
56    }
57
58    #[test]
59    fn parse_duration_test() {
60        assert_eq!(parse_duration("1s"), Ok(Duration::from_secs(1)));
61        assert_eq!(parse_duration("12s"), Ok(Duration::from_secs(12)));
62        assert_eq!(parse_duration("  12  secs  "), Ok(Duration::from_secs(12)));
63        assert_eq!(parse_duration("2m"), Ok(Duration::from_mins(2)));
64        assert_eq!(parse_duration("6 hours"), Ok(Duration::from_hours(6)));
65        assert_eq!(parse_duration("1d"), Ok(Duration::from_hours(1 * 24)));
66        assert_eq!(parse_duration("365d"), Ok(Duration::from_hours(365 * 24)));
67
68        assert!(parse_duration("d 3").is_err());
69        assert!(parse_duration("d3").is_err());
70        assert!(parse_duration("3da").is_err());
71        assert!(parse_duration("3_days").is_err());
72        assert!(parse_duration("_3d").is_err());
73        assert!(parse_duration("3 3d").is_err());
74        assert!(parse_duration("3.3d").is_err());
75    }
76}