Allow int input when using a formatstring in into datetime (#13541)

# Description

When using a format string, `into datetime` would disallow an `int` even
when it logically made sense. This was mainly a problem when attempting
to convert a Unix epoch to Nushell `datetime`. Unix epochs are often
stored or returned as `int` in external data sources.

```nu
1722821463 | into datetime -f '%s'
Error: nu:🐚:only_supports_this_input_type

  × Input type not supported.
   ╭─[entry #3:1:1]
 1 │ 1722821463 | into datetime -f '%s'
   · ─────┬────   ──────┬──────
   ·      │             ╰── only string input data is supported
   ·      ╰── input type: int
   ╰────
```

While the solution was simply to `| to text` the `int`, this PR handles
the use-case automatically.

Essentially a ~5 line change that just moves the current parsing to a
closure that is called for both Strings and Ints-converted-to-Strings.

# User-Facing Changes

After the change:

```nu
[
  1722821463
  "1722821463"
  0
] | each { into datetime -f '%s' }
╭───┬──────────────╮
│ 0 │ 10 hours ago │
│ 1 │ 10 hours ago │
│ 2 │ 54 years ago │
╰───┴──────────────╯
```

# Tests + Formatting

Test case added.

- 🟢 `toolkit fmt`
- 🟢 `toolkit clippy`
- 🟢 `toolkit test`
- 🟢 `toolkit test stdlib`

# After Submitting
This commit is contained in:
NotTheDr01ds 2024-08-05 21:05:32 -04:00 committed by GitHub
parent 6d36941e55
commit 4e83ccdf86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -379,8 +379,8 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
// If input is not a timestamp, try parsing it as a string // If input is not a timestamp, try parsing it as a string
let span = input.span(); let span = input.span();
match input {
Value::String { val, .. } => { let parse_as_string = |val: &str| {
match dateformat { match dateformat {
Some(dt) => match DateTime::parse_from_str(val, &dt.0) { Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
Ok(d) => Value::date ( d, head ), Ok(d) => Value::date ( d, head ),
@ -414,7 +414,12 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
Err(err) => err, Err(err) => err,
}, },
} }
} };
match input {
Value::String { val, .. } => parse_as_string(val),
Value::Int { val, .. } => parse_as_string(&val.to_string()),
// Propagate errors by explicitly matching them before the final case. // Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(), Value::Error { .. } => input.clone(),
other => Value::error( other => Value::error(
@ -575,6 +580,24 @@ mod tests {
assert_eq!(actual, expected) assert_eq!(actual, expected)
} }
#[test]
fn takes_int_with_formatstring() {
let date_int = Value::test_int(1_614_434_140);
let fmt_options = Some(DatetimeFormat("%s".to_string()));
let args = Arguments {
zone_options: None,
format_options: fmt_options,
cell_paths: None,
};
let actual = action(&date_int, &args, Span::test_data());
let expected = Value::date(
DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z").unwrap(),
Span::test_data(),
);
assert_eq!(actual, expected)
}
#[test] #[test]
fn takes_timestamp() { fn takes_timestamp() {
let date_str = Value::test_string("1614434140000000000"); let date_str = Value::test_string("1614434140000000000");