1. 问题描述

1d2s500ms 之类的字符串解析成 std::time::Duration 对象。


2. Cargo.toml

[package]
name = "time-parser"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
nom = "7.1.1"
thiserror = "1.0.35"

3. src/lib.rs

use std::time::Duration;
use nom::{
    sequence::{terminated, Tuple},
    combinator::{opt, fail},
    character::complete::digit1,
    bytes::complete::tag,
    branch::alt,
    IResult,
    error::{Error, ErrorKind},
};

#[derive(thiserror::Error, Debug)]
pub enum ParseTimeError {
    #[error("Parse time error {0}")]
    ParseTimeError(String),
}

fn parse_from_ms(input: &str) -> IResult<&str, (u64, u64, u64)> {
    let (remaining, ms) = terminated(
        digit1,
        alt((tag("milliseconds"), tag("millisecond"), tag("ms"))),
    )(input)?;
    match ms.parse::<u64>() {
        Ok(ms) => { Ok((remaining, (0, 0, ms))) }
        Err(_) => { fail(input) }
    }
}

fn parse_from_m(input: &str) -> IResult<&str, (u64, u64, u64)> {
    let (remaining, (m, s, ms)) = (
        opt(terminated(digit1, alt((tag("minutes"), tag("minute"), tag("m"))))),
        opt(terminated(digit1, alt((tag("seconds"), tag("second"), tag("s"))))),
        opt(terminated(digit1, alt((tag("milliseconds"), tag("millisecond"), tag("ms"))))),
    ).parse(input)?;
    let map_err_fn = |_| nom::Err::Failure(Error::new(input, ErrorKind::Fail));
    let m = m.unwrap_or("0").parse::<u64>().map_err(map_err_fn)?;
    let s = s.unwrap_or("0").parse::<u64>().map_err(map_err_fn)?;
    let ms = ms.unwrap_or("0").parse::<u64>().map_err(map_err_fn)?;
    Ok((remaining, (m, s, ms)))
}

/// parse_time 用于将时间字符串(比如,1d(ay)2h(ours)3m(inutes)4s(econds)5m(illi)s(econds))解析成 Duration 对象
pub fn parse_time<T: AsRef<str>>(input: T) -> Result<Duration, ParseTimeError> {
    match (
        opt(terminated(digit1, alt((tag("days"), tag("day"), tag("d"))))),
        opt(terminated(digit1, alt((tag("hours"), tag("hour"), tag("h"))))),
        // 由于 m 是 ms 的前缀,所以从此处开始进行分支处理
        alt((parse_from_ms, parse_from_m)),
    ).parse(input.as_ref()) {
        Err(e) => Err(ParseTimeError::ParseTimeError(format!("{e}"))),
        Ok((remaining, (d, h, (m, s, ms)))) => {
            // 如果字符串尾部出现不可解析的部分,那么返回错误
            if remaining.len() > 0 {
                return Err(ParseTimeError::ParseTimeError(format!("unparseable tail {}", remaining)));
            }
            let d = d.unwrap_or("0").parse::<u64>()
                .map_err(|e| ParseTimeError::ParseTimeError(format!("{e}")))?;
            let h = h.unwrap_or("0").parse::<u64>()
                .map_err(|e| ParseTimeError::ParseTimeError(format!("{e}")))?;
            Ok(Duration::from_millis((d * 86400 + h * 3600 + m * 60 + s) * 1000 + ms))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::parse_time;

    #[test]
    fn test_d_h_m_s_ms() {
        assert_eq!(
            parse_time("1d2h3m4s5ms").unwrap().as_millis(),
            (1 * 86400 + 2 * 3600 + 3 * 60 + 4) * 1000 + 5,
        );
    }

    #[test]
    fn test_h_m_s() {
        let s = "2h3m4s";
        let result = parse_time(s);
        assert!(result.is_ok());
        let d = result.unwrap();
        assert_eq!(d.as_secs(), 2 * 3600 + 3 * 60 + 4);
    }

    #[test]
    fn test_m_s_ms() {
        let s = "5m6s7ms";
        let result = parse_time(s);
        assert!(result.is_ok());
        let d = result.unwrap();
        assert_eq!(d.as_millis(), (5 * 60 + 6) * 1000 + 7);
    }

    #[test]
    fn test_m_ms() {
        assert_eq!(
            parse_time("1m2ms").unwrap().as_millis(),
            (1 * 60) * 1000 + 2,
        );
    }

    #[test]
    fn test_m() {
        assert_eq!(
            parse_time("6m").unwrap().as_secs(),
            6 * 60,
        );
    }

    #[test]
    fn test_ms() {
        assert_eq!(
            parse_time("100ms").unwrap().as_millis(),
            100
        )
    }

    // 测试非简写
    #[test]
    fn test_non_abbr() {
        assert_eq!(
            parse_time("10minutes100milliseconds").unwrap().as_millis(),
            10 * 60 * 1000 + 100,
        );
    }

    #[test]
    fn test_unparseable_tail() {
        let s = "1d2h3m4s5msXXX";
        let result = parse_time(s);
        assert!(result.is_err());
    }
}

4. src/main.rs

use time_parser::parse_time;

fn main() {
    let s = "1m30s30ms";
    let d = parse_time(s).unwrap();
    println!("1m30s30ms = {} milliseconds", d.as_millis());
}