From d5e00c0d5d30516d4f43989ceda6325512892b9c Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Fri, 5 Jul 2024 00:44:12 +0900 Subject: [PATCH] Support default offset with dateformat option (#13289) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Fixes #13280. After apply this patch, we can use non-timezone string + format option `into datetime` cmd # User-Facing Changes AS-IS (before fixing) ``` $ "09.02.2024 11:06:11" | into datetime --format '%m.%d.%Y %T' Error: nu::shell::cant_convert × Can't convert to could not parse as datetime using format '%m.%d.%Y %T'. ╭─[entry #1:1:25] 1 │ "09.02.2024 11:06:11" | into datetime --format '%m.%d.%Y %T' · ──────┬────── · ╰── can't convert input is not enough for unique date and time to could not parse as datetime using format '%m.%d.%Y %T' ╰──── help: you can use `into datetime` without a format string to enable flexible parsing $ "09.02.2024 11:06:11" | into datetime Mon, 2 Sep 2024 11:06:11 +0900 (in 2 months) ``` TO-BE(After fixing) ``` $ "09.02.2024 11:06:11" | into datetime --format '%m.%d.%Y %T' Mon, 2 Sep 2024 20:06:11 +0900 (in 2 months) $ "09.02.2024 11:06:11" | into datetime Mon, 2 Sep 2024 11:06:11 +0900 (in 2 months) ``` # Tests + Formatting If there is agreement on the direction, I will add a test. # After Submitting --------- Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com> --- .../src/conversions/into/datetime.rs | 62 ++++++++++++++++--- 1 file changed, 53 insertions(+), 9 deletions(-) diff --git a/crates/nu-command/src/conversions/into/datetime.rs b/crates/nu-command/src/conversions/into/datetime.rs index af10732aea..b928bebe23 100644 --- a/crates/nu-command/src/conversions/into/datetime.rs +++ b/crates/nu-command/src/conversions/into/datetime.rs @@ -1,5 +1,5 @@ use crate::{generate_strftime_list, parse_date_from_string}; -use chrono::{DateTime, FixedOffset, Local, NaiveTime, TimeZone, Utc}; +use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, NaiveTime, TimeZone, Utc}; use human_date_parser::{from_human_time, ParseResult}; use nu_cmd_base::input_handler::{operate, CmdArgument}; use nu_engine::command_prelude::*; @@ -162,24 +162,37 @@ impl Command for SubCommand { }; vec![ Example { - description: "Convert any standard timestamp string to datetime", + description: "Convert timestamp string to datetime with timezone offset", example: "'27.02.2021 1:55 pm +0000' | into datetime", #[allow(clippy::inconsistent_digit_grouping)] result: example_result_1(1614434100_000000000), }, Example { - description: "Convert any standard timestamp string to datetime", + description: "Convert standard timestamp string to datetime with timezone offset", example: "'2021-02-27T13:55:40.2246+00:00' | into datetime", #[allow(clippy::inconsistent_digit_grouping)] result: example_result_1(1614434140_224600000), }, Example { description: - "Convert non-standard timestamp string to datetime using a custom format", + "Convert non-standard timestamp string, with timezone offset, to datetime using a custom format", example: "'20210227_135540+0000' | into datetime --format '%Y%m%d_%H%M%S%z'", #[allow(clippy::inconsistent_digit_grouping)] result: example_result_1(1614434140_000000000), }, + Example { + description: "Convert non-standard timestamp string, without timezone offset, to datetime with custom formatting", + example: "'16.11.1984 8:00 am' | into datetime --format '%d.%m.%Y %H:%M %P'", + #[allow(clippy::inconsistent_digit_grouping)] + result: Some(Value::date( + DateTime::from_naive_utc_and_offset( + NaiveDateTime::parse_from_str("16.11.1984 8:00 am", "%d.%m.%Y %H:%M %P") + .expect("date calculation should not fail in test"), + *Local::now().offset(), + ), + Span::test_data(), + )), + }, Example { description: "Convert nanosecond-precision unix timestamp to a datetime with offset from UTC", @@ -372,10 +385,21 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value { Some(dt) => match DateTime::parse_from_str(val, &dt.0) { Ok(d) => Value::date ( d, head ), Err(reason) => { - Value::error ( - ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) }, - head, - ) + match NaiveDateTime::parse_from_str(val, &dt.0) { + Ok(d) => Value::date ( + DateTime::from_naive_utc_and_offset( + d, + *Local::now().offset(), + ), + head, + ), + Err(_) => { + Value::error ( + ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) }, + head, + ) + } + } } }, @@ -457,7 +481,7 @@ mod tests { } #[test] - fn takes_a_date_format() { + fn takes_a_date_format_with_timezone() { let date_str = Value::test_string("16.11.1984 8:00 am +0000"); let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string())); let args = Arguments { @@ -473,6 +497,26 @@ mod tests { assert_eq!(actual, expected) } + #[test] + fn takes_a_date_format_without_timezone() { + let date_str = Value::test_string("16.11.1984 8:00 am"); + let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P".to_string())); + let args = Arguments { + zone_options: None, + format_options: fmt_options, + cell_paths: None, + }; + let actual = action(&date_str, &args, Span::test_data()); + let expected = Value::date( + DateTime::from_naive_utc_and_offset( + NaiveDateTime::parse_from_str("16.11.1984 8:00 am", "%d.%m.%Y %H:%M %P").unwrap(), + *Local::now().offset(), + ), + Span::test_data(), + ); + assert_eq!(actual, expected) + } + #[test] fn takes_iso8601_date_format() { let date_str = Value::test_string("2020-08-04T16:39:18+00:00");