diff --git a/crates/nu-command/src/conversions/into/duration.rs b/crates/nu-command/src/conversions/into/duration.rs index 2b1d1bf52b..336e3fdf19 100644 --- a/crates/nu-command/src/conversions/into/duration.rs +++ b/crates/nu-command/src/conversions/into/duration.rs @@ -19,6 +19,7 @@ impl Command for SubCommand { fn signature(&self) -> Signature { Signature::build("into duration") .input_output_types(vec![ + (Type::Int, Type::Duration), (Type::String, Type::Duration), (Type::Duration, Type::Duration), (Type::Table(vec![]), Type::Table(vec![])), @@ -26,6 +27,12 @@ impl Command for SubCommand { //(Type::Record(vec![]), Type::Record(vec![])), ]) //.allow_variants_without_examples(true) + .named( + "unit", + SyntaxShape::String, + "Unit to convert number into (will have an effect only with integer input)", + Some('u'), + ) .rest( "rest", SyntaxShape::CellPath, @@ -107,6 +114,16 @@ impl Command for SubCommand { example: "420sec | into duration", result: Some(Value::duration(7 * 60 * NS_PER_SEC, span)), }, + Example { + description: "Convert a number of ns to duration", + example: "1_234_567 | into duration", + result: Some(Value::duration(1_234_567, span)), + }, + Example { + description: "Convert a number of an arbitraty unit to duration", + example: "1_234 | into duration --unit ms", + result: Some(Value::duration(1_234 * 1_000_000, span)), + }, ] } } @@ -123,15 +140,39 @@ fn into_duration( }; let column_paths: Vec = call.rest(engine_state, stack, 0)?; + let unit = match call.get_flag::(engine_state, stack, "unit")? { + Some(sep) => { + if ["ns", "us", "µs", "ms", "sec", "min", "hr", "day", "wk"] + .iter() + .any(|d| d == &sep) + { + sep + } else { + return Err(ShellError::CantConvertToDuration { + details: sep, + dst_span: span, + src_span: span, + help: Some( + "supported units are ns, us/µs, ms, sec, min, hr, day, and wk".to_string(), + ), + }); + } + } + None => "ns".to_string(), + }; + input.map( move |v| { if column_paths.is_empty() { - action(&v, span) + action(&v, &unit.clone(), span) } else { + let unitclone = &unit.clone(); let mut ret = v; for path in &column_paths { - let r = - ret.update_cell_path(&path.members, Box::new(move |old| action(old, span))); + let r = ret.update_cell_path( + &path.members, + Box::new(move |old| action(old, unitclone, span)), + ); if let Err(error) = r { return Value::error(error, span); } @@ -202,7 +243,7 @@ fn string_to_duration(s: &str, span: Span) -> Result { }) } -fn action(input: &Value, span: Span) -> Value { +fn action(input: &Value, unit: &str, span: Span) -> Value { let value_span = input.span(); match input { Value::Duration { .. } => input.clone(), @@ -210,6 +251,20 @@ fn action(input: &Value, span: Span) -> Value { Ok(val) => Value::duration(val, span), Err(error) => Value::error(error, 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, + }; + Value::duration(*val * ns, span) + } // Propagate errors by explicitly matching them before the final case. Value::Error { .. } => input.clone(), other => Value::error( @@ -253,7 +308,11 @@ mod test { #[case("14ns 3hr 17sec", 14 + 3 * 3600 * NS_PER_SEC + 17 * NS_PER_SEC)] // compound string with units in random order fn turns_string_to_duration(#[case] phrase: &str, #[case] expected_duration_val: i64) { - let actual = action(&Value::test_string(phrase), Span::new(0, phrase.len())); + let actual = action( + &Value::test_string(phrase), + "ns", + Span::new(0, phrase.len()), + ); match actual { Value::Duration { val: observed_val, ..