1

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?

ohe
  • 3,461
  • 3
  • 26
  • 50

1 Answers1

2

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.

BallpointBen
  • 9,406
  • 1
  • 32
  • 62
  • How wow. I understand now why I cannot get it working . Thank you. May I ask you why are we needing a triple map? (I'm still unfamiliar with Rust and Polars) – ohe Nov 21 '22 at 18:18
  • 2
    @ohe The three maps are completely different (unless you're coming from Haskell or something in which case all maps are the same). The first is a lazy transformation on an `Expr` (`col("duration")`). The second is mapping over the iterator on the series named `"duration"`. The third is mapping over an `Option`, which applies the function to the contained value if there is one (if it's a `Some`). – BallpointBen Nov 21 '22 at 19:40