diff --git a/crates/nu-command/src/conversions/into/datetime.rs b/crates/nu-command/src/conversions/into/datetime.rs index b11afb35b..f39084c6e 100644 --- a/crates/nu-command/src/conversions/into/datetime.rs +++ b/crates/nu-command/src/conversions/into/datetime.rs @@ -59,29 +59,29 @@ impl Command for SubCommand { fn signature(&self) -> Signature { Signature::build("into datetime") - .switch( - "list", - "lists strftime cheatsheet", - Some('l'), - ) .named( "timezone", SyntaxShape::String, - "Specify timezone if the input is timestamp, like 'UTC/u' or 'LOCAL/l'", + "Specify timezone if the input is a Unix timestamp. Valid options: 'UTC' ('u') or 'LOCAL' ('l')", Some('z'), ) .named( "offset", SyntaxShape::Int, - "Specify timezone by offset if the input is timestamp, like '+8', '-4', prior than timezone", + "Specify timezone by offset from UTC if the input is a Unix timestamp, like '+8', '-4'", Some('o'), ) .named( "format", SyntaxShape::String, - "Specify date and time formatting", + "Specify an expected format for parsing strings to datetimes. Use --list to see all possible options", Some('f'), ) + .switch( + "list", + "Show all possible variables for use with the --format flag", + Some('l'), + ) .rest( "rest", SyntaxShape::CellPath, @@ -112,28 +112,40 @@ impl Command for SubCommand { vec![ Example { description: "Convert to datetime", - example: "'16.11.1984 8:00 am +0000' | into datetime", - result: None, + example: "'27.02.2021 1:55 pm +0000' | into datetime", + result: Some(Value::Date { + val: Utc.timestamp(1614434100, 0).into(), + span: Span::test_data(), + }), }, Example { description: "Convert to datetime", - example: "'2020-08-04T16:39:18+00:00' | into datetime", - result: None, + example: "'2021-02-27T13:55:40+00:00' | into datetime", + result: Some(Value::Date { + val: Utc.timestamp(1614434140, 0).into(), + span: Span::test_data(), + }), }, Example { description: "Convert to datetime using a custom format", - example: "'20200904_163918+0000' | into datetime -f '%Y%m%d_%H%M%S%z'", - result: None, + example: "'20210227_135540+0000' | into datetime -f '%Y%m%d_%H%M%S%z'", + result: Some(Value::Date { + val: Utc.timestamp(1614434140, 0).into(), + span: Span::test_data(), + }), }, Example { - description: "Convert timestamp (no larger than 8e+12) to datetime using a specified timezone", - example: "'1614434140' | into datetime -z 'UTC'", - result: None, + description: "Convert timestamp (no larger than 8e+12) to a UTC datetime", + example: "1614434140 | into datetime", + result: Some(Value::Date { + val: Utc.timestamp(1614434140, 0).into(), + span: Span::test_data(), + }), }, Example { description: "Convert timestamp (no larger than 8e+12) to datetime using a specified timezone offset (between -12 and 12)", - example: "'1614434140' | into datetime -o +9", + example: "1614434140 | into datetime -o +9", result: None, }, ] @@ -209,69 +221,74 @@ fn action( dateformat: &Option, head: Span, ) -> Value { - // if timezone is specified, first check if the input is a timestamp. - if let Some(tz) = timezone { + // Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?) + let timestamp = match input { + Value::Int { val, .. } => Ok(*val), + Value::String { val, .. } => val.parse::(), + other => { + return Value::Error { + error: ShellError::UnsupportedInput( + format!("Expected string or int, got {} instead", other.get_type()), + head, + ), + }; + } + }; + + if let Ok(ts) = timestamp { const TIMESTAMP_BOUND: i64 = 8.2e+12 as i64; + const HOUR: i32 = 3600; - let ts = match input { - Value::Int { val, .. } => Ok(*val), - Value::String { val, .. } => val.parse::(), - other => { - return Value::Error { - error: ShellError::UnsupportedInput( - format!("Expected string or int, got {} instead", other.get_type()), - head, - ), - }; - } - }; + if ts.abs() > TIMESTAMP_BOUND { + return Value::Error { + error: ShellError::UnsupportedInput( + "Given timestamp is out of range, it should between -8e+12 and 8e+12" + .to_string(), + head, + ), + }; + } - if let Ok(t) = ts { - if t.abs() > TIMESTAMP_BOUND { - return Value::Error { - error: ShellError::UnsupportedInput( - "Given timestamp is out of range, it should between -8e+12 and 8e+12" - .to_string(), - head, - ), - }; - } - const HOUR: i32 = 3600; - let stampout = match tz.item { + return match timezone { + // default to UTC + None => Value::Date { + val: Utc.timestamp(ts, 0).into(), + span: head, + }, + Some(Spanned { item, span }) => match item { Zone::Utc => Value::Date { - val: Utc.timestamp(t, 0).into(), + val: Utc.timestamp(ts, 0).into(), span: head, }, Zone::Local => Value::Date { - val: Local.timestamp(t, 0).into(), + val: Local.timestamp(ts, 0).into(), span: head, }, Zone::East(i) => { - let eastoffset = FixedOffset::east((i as i32) * HOUR); + let eastoffset = FixedOffset::east((*i as i32) * HOUR); Value::Date { - val: eastoffset.timestamp(t, 0), + val: eastoffset.timestamp(ts, 0), span: head, } } Zone::West(i) => { - let westoffset = FixedOffset::west((i as i32) * HOUR); + let westoffset = FixedOffset::west((*i as i32) * HOUR); Value::Date { - val: westoffset.timestamp(t, 0), + val: westoffset.timestamp(ts, 0), span: head, } } Zone::Error => Value::Error { error: ShellError::UnsupportedInput( "Cannot convert given timezone or offset to timestamp".to_string(), - tz.span, + *span, ), }, - }; - return stampout; - } - }; + }, + }; + } - // if it's not, continue and default to the system's local timezone. + // If input is not a timestamp, try parsing it as a string match input { Value::String { val, span } => { match dateformat { @@ -300,15 +317,9 @@ fn action( }, } } - Value::Int { .. } => Value::Error { - error: ShellError::UnsupportedInput( - "Received integer input but timezone not specified. Did you forget to specify a timezone?".to_string(), - head, - ), - }, other => Value::Error { error: ShellError::UnsupportedInput( - format!("Expected string or int, got {} instead", other.get_type()), + format!("Expected string, got {} instead", other.get_type()), head, ), }, @@ -403,6 +414,20 @@ mod tests { assert_eq!(actual, expected) } + #[test] + fn takes_timestamp_without_timezone() { + let date_str = Value::test_string("1614434140"); + let timezone_option = None; + let actual = action(&date_str, &timezone_option, &None, Span::test_data()); + + let expected = Value::Date { + val: Utc.timestamp(1614434140, 0).into(), + span: Span::test_data(), + }; + + assert_eq!(actual, expected) + } + #[test] fn takes_invalid_timestamp() { let date_str = Value::test_string("10440970000000");