Search code examples
rustrust-polars

Polars (Rust), formatting a duration to a string


How could I format a Duration in a HH:MM:SS format?

As a test sample, I have

fn main() {
    let df = df! {
      "a" => ["2022-11-21T12:00:00"],
      "b" => ["2022-11-21T14:00:00"]
    }
    .unwrap()
    .lazy()
    .with_column(
        col("a")
            .str()
            .strptime(StrpTimeOptions {
                date_dtype: DataType::Datetime(TimeUnit::Milliseconds, None),
                fmt: Some("%Y-%m-%dT%H:%M:%S".into()),
                strict: false,
                exact: true,
            })
            .alias("a"),
    )
    .with_column(
        col("b")
            .str()
            .strptime(StrpTimeOptions {
                date_dtype: DataType::Datetime(TimeUnit::Milliseconds, None),
                fmt: Some("%Y-%m-%dT%H:%M:%S".into()),
                strict: false,
                exact: true,
            })
            .alias("b"),
    )
    .with_column((col("b") - col("a")).alias("duration"))
    .collect()
    .unwrap();

    println!("{:?}", df);
}

It outputs

┌─────────────────────┬─────────────────────┬──────────────┐
│ a                   ┆ b                   ┆ duration     │
│ ---                 ┆ ---                 ┆ ---          │
│ datetime[ms]        ┆ datetime[ms]        ┆ duration[ms] │
╞═════════════════════╪═════════════════════╪══════════════╡
│ 2022-11-21 12:00:00 ┆ 2022-11-21 14:00:00 ┆ 2h           │
└─────────────────────┴─────────────────────┴──────────────┘

How could I convert duration to "02:00:00" in the previous example?


Solution

  • Unfortunately I don't think you can do any better than this (but I'd love to be proved wrong).

    .with_column(
        col("duration")
            .map(
                |srs| {
                    Ok(srs
                        .duration()?
                        .into_iter()
                        .map(|d| {
                            d.map(|millisecs| {
                                let secs = millisecs / 1000;
                                let h = secs / (60 * 60);
                                let m = (secs / 60) % 60;
                                let s = secs % 60;
                                format!("{}:{:0<2}:{:0<2}", h, m, s)
                            })
                        })
                        .collect::<Utf8Chunked>()
                        .into_series())
                },
                GetOutput::from_type(DataType::Utf8),
            )
            .alias("duration_str"),
    )
    

    This leads to 2:00:00. It's hardcoded that you're dealing with milliseconds; you might want to store a variable with the TimeUnit and then switch over it to determine the denominator instead of always using 1000.