rustclap

Using clap's #[derive(Parser)], how can I accept a std::time::Duration?


I want to accept a std::time::Duration on a command line. I'm using clap with #[derive(Parser)] to generate the parameter parser. Is there any way I can directly accept an input, rather than accepting a number and doing the conversion later?

Something like this:

#[derive(Debug, Parser)]
pub struct Config {
    #[clap( ??? )]
    interval: std::time::Duration,
}

Solution

  • Clap 3.0:

    To do custom parsing, you should use #[clap(parse(try_from_str = ...))] and define a custom function to parsing the argument. Here's an example:

    use clap::Parser;
    
    #[derive(Debug, Parser)]
    pub struct Config {
        #[clap(parse(try_from_str = parse_duration))]
        interval: std::time::Duration,
    }
    
    fn parse_duration(arg: &str) -> Result<std::time::Duration, std::num::ParseIntError> {
        let seconds = arg.parse()?;
        Ok(std::time::Duration::from_secs(seconds))
    }
    

    Clap 4.0:

    Almost same as above; the helper function can stay the same, but the attribute syntax has changed:

    use clap::Parser;
    
    #[derive(Debug, Parser)]
    pub struct Config {
        #[arg(value_parser = parse_duration)]
        interval: std::time::Duration,
    }
    
    fn parse_duration(arg: &str) -> Result<std::time::Duration, std::num::ParseIntError> {
        let seconds = arg.parse()?;
        Ok(std::time::Duration::from_secs(seconds))
    }
    

    Parsing with humantime

    This parsing is pretty limited (I don't know what format you'd expect the duration to be in), but it shows how you'd do it.

    If you want to be flexible with your duration arguments, consider using a crate like humantime, which lets you provide durations with unit labels like "5s" or "3 hours". You can either use their helper function to parse into a std::time::Duration (using clap 4 syntax):

    use clap::Parser;
    
    #[derive(Debug, Parser)]
    pub struct Config {
        #[clap(value_parser = humantime::parse_duration, default_value = "500ms")]
        interval: std::time::Duration,
    }
    

    Or you can use humantime::Duration without special attributes since it implements FromStr.

    use clap::Parser;
    
    #[derive(Debug, Parser)]
    pub struct Config {
        #[clap(default_value = "500ms")]
        interval: humantime::Duration,
    }