将 1d2s500ms
之类的字符串解析成 std::time::Duration
对象。
[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"
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());
}
}
use time_parser::parse_time;
fn main() {
let s = "1m30s30ms";
let d = parse_time(s).unwrap();
println!("1m30s30ms = {} milliseconds", d.as_millis());
}