feat: into duration accepts floats (#15297)

Issue #9887 which can be closed after this is merged.

# Description

This allows the "into duration" command to accept floats as inputs.

Examples:
<img width="767" alt="image"
src="https://github.com/user-attachments/assets/da181f2a-7ad6-4efb-a6db-f9c6d8929c71"
/>

<img width="710" alt="image"
src="https://github.com/user-attachments/assets/78623a39-33ad-42a0-9324-a147be86f95c"
/>

**How it works:**

Using strings, like `"1.234sec" | into duration`, is already working, so
if a user inputs `1.234 | into duration --sec`, I just convert this back
to a string and use the previous conversion functions.

**Limitations:**

there are some limitation to using floats, but it's a general limitation
that is already present for other use cases:
- only 3 digits are taken into account in the decimal part
- floating durations in nano seconds are always floored and not rounded

<img width="761" alt="image"
src="https://github.com/user-attachments/assets/a9076aab-da03-43f2-927c-c9703fc4f955"
/>


# User-Facing Changes
Users can inject floats with `into duration`

# Tests + Formatting
cargo fmt and clippy OK
Tests OK

# After Submitting
The example I added will automatically become part of the doc, I think
that's enough for documentation.
This commit is contained in:
Loïc Riegel 2025-04-03 14:05:18 +02:00 committed by GitHub
parent df74a0c961
commit 67b6188b19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -15,6 +15,7 @@ impl Command for IntoDuration {
Signature::build("into duration")
.input_output_types(vec![
(Type::Int, Type::Duration),
(Type::Float, Type::Duration),
(Type::String, Type::Duration),
(Type::Duration, Type::Duration),
(Type::table(), Type::table()),
@ -109,6 +110,11 @@ impl Command for IntoDuration {
example: "1_234 | into duration --unit ms",
result: Some(Value::test_duration(1_234 * 1_000_000)),
},
Example {
description: "Convert a floating point number of an arbitrary unit to duration",
example: "1.234 | into duration --unit sec",
result: Some(Value::test_duration(1_234 * 1_000_000)),
},
]
}
}
@ -236,22 +242,22 @@ fn action(input: &Value, unit: &str, span: Span) -> Value {
let value_span = input.span();
match input {
Value::Duration { .. } => input.clone(),
Value::String { val, .. } => match compound_to_duration(val, value_span) {
Ok(val) => Value::duration(val, span),
Err(error) => Value::error(error, span),
},
Value::String { val, .. } => {
if let Ok(num) = val.parse::<f64>() {
let ns = unit_to_ns_factor(unit);
return Value::duration((num * (ns as f64)) as i64, span);
}
match compound_to_duration(val, value_span) {
Ok(val) => Value::duration(val, span),
Err(error) => Value::error(error, span),
}
}
Value::Float { val, .. } => {
let ns = unit_to_ns_factor(unit);
Value::duration((*val * (ns as f64)) as i64, span)
}
Value::Int { val, .. } => {
let ns = match unit {
"ns" => 1,
"us" | "µs" => 1_000,
"ms" => 1_000_000,
"sec" => NS_PER_SEC,
"min" => NS_PER_SEC * 60,
"hr" => NS_PER_SEC * 60 * 60,
"day" => NS_PER_SEC * 60 * 60 * 24,
"wk" => NS_PER_SEC * 60 * 60 * 24 * 7,
_ => 0,
};
let ns = unit_to_ns_factor(unit);
Value::duration(*val * ns, span)
}
// Propagate errors by explicitly matching them before the final case.
@ -268,6 +274,20 @@ fn action(input: &Value, unit: &str, span: Span) -> Value {
}
}
fn unit_to_ns_factor(unit: &str) -> i64 {
match unit {
"ns" => 1,
"us" | "µs" => 1_000,
"ms" => 1_000_000,
"sec" => NS_PER_SEC,
"min" => NS_PER_SEC * 60,
"hr" => NS_PER_SEC * 60 * 60,
"day" => NS_PER_SEC * 60 * 60 * 24,
"wk" => NS_PER_SEC * 60 * 60 * 24 * 7,
_ => 0,
}
}
#[cfg(test)]
mod test {
use super::*;