diff --git a/crates/nu-command/src/conversions/into/datetime.rs b/crates/nu-command/src/conversions/into/datetime.rs index ffaba69645..ffca5d5c9f 100644 --- a/crates/nu-command/src/conversions/into/datetime.rs +++ b/crates/nu-command/src/conversions/into/datetime.rs @@ -1,4 +1,4 @@ -use chrono::{DateTime, FixedOffset, Local, LocalResult, Offset, TimeZone, Utc}; +use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc}; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::ast::CellPath; @@ -8,6 +8,7 @@ use nu_protocol::{ }; use crate::generate_strftime_list; +use crate::parse_date_from_string; struct Arguments { timezone: Option>, @@ -205,7 +206,7 @@ fn action( head: Span, ) -> Value { match input { - Value::String { val: s, span, .. } => { + Value::String { val: s, span } => { let ts = s.parse::(); // if timezone if specified, first check if the input is a timestamp. if let Some(tz) = timezone { @@ -253,7 +254,7 @@ fn action( return stampout; } }; - // if it's not, continue and negelect the timezone option. + // if it's not, continue and default to the system's local timezone. let out = match dateformat { Some(dt) => match DateTime::parse_from_str(s, &dt.0) { Ok(d) => Value::Date { val: d, span: head }, @@ -267,35 +268,15 @@ fn action( } } }, - None => match dtparse::parse(s) { - Ok((native_dt, fixed_offset)) => { - let offset = match fixed_offset { - Some(fo) => fo, - None => FixedOffset::east(0).fix(), - }; - match offset.from_local_datetime(&native_dt) { - LocalResult::Single(d) => Value::Date { val: d, span: head }, - LocalResult::Ambiguous(d, _) => Value::Date { val: d, span: head }, - LocalResult::None => { - return Value::Error { - error: ShellError::CantConvert( - "could not convert to a timezone-aware datetime" - .to_string(), - "local time representation is invalid".to_string(), - head, - ), - } - } - } - } - Err(_) => { - return Value::Error { - error: ShellError::UnsupportedInput( - "Cannot convert input string as datetime. Might be missing timezone or offset".to_string(), - *span, - ), - } - } + // Tries to automatically parse the date + // (i.e. without a format string) + // and assumes the system's local timezone if none is specified + None => match parse_date_from_string(s, *span) { + Ok(date) => Value::Date { + val: date, + span: *span, + }, + Err(err) => err, }, }; diff --git a/crates/nu-command/src/date/format.rs b/crates/nu-command/src/date/format.rs index 4d9bb7dfc6..2ce4624343 100644 --- a/crates/nu-command/src/date/format.rs +++ b/crates/nu-command/src/date/format.rs @@ -3,10 +3,10 @@ use nu_engine::CallExt; use nu_protocol::{ ast::Call, engine::{Command, EngineState, Stack}, - Category, Example, PipelineData, Signature, Span, SyntaxShape, Value, + Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value, }; -use super::utils::{parse_date_from_string, unsupported_input_error}; +use super::utils::parse_date_from_string; #[derive(Clone)] pub struct SubCommand; @@ -96,7 +96,7 @@ fn format_helper(value: Value, formatter: &str, span: Span) -> Value { val, span: val_span, } => { - let dt = parse_date_from_string(val, val_span); + let dt = parse_date_from_string(&val, val_span); match dt { Ok(x) => Value::String { val: x.format(formatter).to_string(), @@ -112,7 +112,9 @@ fn format_helper(value: Value, formatter: &str, span: Span) -> Value { span, } } - _ => unsupported_input_error(span), + _ => Value::Error { + error: ShellError::DatetimeParseError(span), + }, } } @@ -126,7 +128,7 @@ fn format_helper_rfc2822(value: Value, span: Span) -> Value { val, span: val_span, } => { - let dt = parse_date_from_string(val, val_span); + let dt = parse_date_from_string(&val, val_span); match dt { Ok(x) => Value::String { val: x.to_rfc2822(), @@ -142,7 +144,9 @@ fn format_helper_rfc2822(value: Value, span: Span) -> Value { span, } } - _ => unsupported_input_error(span), + _ => Value::Error { + error: ShellError::DatetimeParseError(span), + }, } } diff --git a/crates/nu-command/src/date/humanize.rs b/crates/nu-command/src/date/humanize.rs index f03d525970..df8db8e5c3 100644 --- a/crates/nu-command/src/date/humanize.rs +++ b/crates/nu-command/src/date/humanize.rs @@ -63,7 +63,7 @@ fn helper(value: Value, head: Span) -> Value { val, span: val_span, } => { - let dt = parse_date_from_string(val, val_span); + let dt = parse_date_from_string(&val, val_span); match dt { Ok(x) => Value::String { val: humanize_date(x), @@ -77,10 +77,7 @@ fn helper(value: Value, head: Span) -> Value { span: head, }, _ => Value::Error { - error: ShellError::UnsupportedInput( - String::from("Date cannot be parsed / date format is not supported"), - head, - ), + error: ShellError::DatetimeParseError(head), }, } } diff --git a/crates/nu-command/src/date/mod.rs b/crates/nu-command/src/date/mod.rs index d2b6e8f31e..1b7b8880cc 100644 --- a/crates/nu-command/src/date/mod.rs +++ b/crates/nu-command/src/date/mod.rs @@ -16,3 +16,4 @@ pub use list_timezone::SubCommand as DateListTimezones; pub use now::SubCommand as DateNow; pub use to_table::SubCommand as DateToTable; pub use to_timezone::SubCommand as DateToTimezone; +pub(crate) use utils::parse_date_from_string; diff --git a/crates/nu-command/src/date/to_table.rs b/crates/nu-command/src/date/to_table.rs index efdc40f22d..9f5215f32b 100644 --- a/crates/nu-command/src/date/to_table.rs +++ b/crates/nu-command/src/date/to_table.rs @@ -1,8 +1,10 @@ -use crate::date::utils::{parse_date_from_string, unsupported_input_error}; +use crate::date::utils::parse_date_from_string; use chrono::{DateTime, Datelike, FixedOffset, Local, Timelike}; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; -use nu_protocol::{Category, Example, PipelineData, Signature, Span, Value}; +use nu_protocol::{ + Category, Example, PipelineData, ShellError::DatetimeParseError, Signature, Span, Value, +}; #[derive(Clone)] pub struct SubCommand; @@ -140,7 +142,7 @@ fn helper(val: Value, head: Span) -> Value { val, span: val_span, } => { - let date = parse_date_from_string(val, val_span); + let date = parse_date_from_string(&val, val_span); parse_date_into_table(date, head) } Value::Nothing { span: _ } => { @@ -149,7 +151,9 @@ fn helper(val: Value, head: Span) -> Value { parse_date_into_table(Ok(n), head) } Value::Date { val, span: _ } => parse_date_into_table(Ok(val), head), - _ => unsupported_input_error(head), + _ => Value::Error { + error: DatetimeParseError(head), + }, } } diff --git a/crates/nu-command/src/date/to_timezone.rs b/crates/nu-command/src/date/to_timezone.rs index 0a6261873d..966088a28c 100644 --- a/crates/nu-command/src/date/to_timezone.rs +++ b/crates/nu-command/src/date/to_timezone.rs @@ -1,5 +1,5 @@ use super::parser::datetime_in_timezone; -use crate::date::utils::{parse_date_from_string, unsupported_input_error}; +use crate::date::utils::parse_date_from_string; use chrono::{DateTime, Local}; use nu_engine::CallExt; use nu_protocol::ast::Call; @@ -93,7 +93,7 @@ fn helper(value: Value, head: Span, timezone: &Spanned) -> Value { val, span: val_span, } => { - let time = parse_date_from_string(val, val_span); + let time = parse_date_from_string(&val, val_span); match time { Ok(dt) => _to_timezone(dt, timezone, head), Err(e) => e, @@ -104,7 +104,9 @@ fn helper(value: Value, head: Span, timezone: &Spanned) -> Value { let dt = Local::now(); _to_timezone(dt.with_timezone(dt.offset()), timezone, head) } - _ => unsupported_input_error(head), + _ => Value::Error { + error: ShellError::DatetimeParseError(head), + }, } } diff --git a/crates/nu-command/src/date/utils.rs b/crates/nu-command/src/date/utils.rs index 891362e778..278ec820a1 100644 --- a/crates/nu-command/src/date/utils.rs +++ b/crates/nu-command/src/date/utils.rs @@ -1,43 +1,26 @@ -use chrono::{DateTime, FixedOffset}; +use chrono::{DateTime, FixedOffset, Local, LocalResult, TimeZone}; use nu_protocol::{ShellError, Span, Value}; -pub fn unsupported_input_error(span: Span) -> Value { - Value::Error { - error: ShellError::UnsupportedInput( - String::from( - "Only dates with timezones are supported. The following formats are allowed \n - * %Y-%m-%d %H:%M:%S %z -- 2020-04-12 22:10:57 +02:00 \n - * %Y-%m-%d %H:%M:%S%.6f %z -- 2020-04-12 22:10:57.213231 +02:00 \n - * rfc3339 -- 2020-04-12T22:10:57+02:00 \n - * rfc2822 -- Tue, 1 Jul 2003 10:52:37 +0200", - ), - span, - ), - } -} - -pub fn parse_date_from_string(input: String, span: Span) -> Result, Value> { - let datetime = DateTime::parse_from_str(&input, "%Y-%m-%d %H:%M:%S %z"); // "2020-04-12 22:10:57 +02:00"; - match datetime { - Ok(x) => Ok(x), - Err(_) => { - let datetime = DateTime::parse_from_str(&input, "%Y-%m-%d %H:%M:%S%.6f %z"); // "2020-04-12 22:10:57.213231 +02:00"; - match datetime { - Ok(x) => Ok(x), - Err(_) => { - let datetime = DateTime::parse_from_rfc3339(&input); // "2020-04-12T22:10:57+02:00"; - match datetime { - Ok(x) => Ok(x), - Err(_) => { - let datetime = DateTime::parse_from_rfc2822(&input); // "Tue, 1 Jul 2003 10:52:37 +0200"; - match datetime { - Ok(x) => Ok(x), - Err(_) => Err(unsupported_input_error(span)), - } - } - } - } +pub(crate) fn parse_date_from_string( + input: &str, + span: Span, +) -> Result, Value> { + match dtparse::parse(input) { + Ok((native_dt, fixed_offset)) => { + let offset = match fixed_offset { + Some(fo) => fo, + None => *(Local::now().offset()), + }; + match offset.from_local_datetime(&native_dt) { + LocalResult::Single(d) => Ok(d), + LocalResult::Ambiguous(d, _) => Ok(d), + LocalResult::None => Err(Value::Error { + error: ShellError::DatetimeParseError(span), + }), } } + Err(_) => Err(Value::Error { + error: ShellError::DatetimeParseError(span), + }), } } diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 6d67197c92..88de14bc0d 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -154,6 +154,22 @@ pub enum ShellError { #[diagnostic(code(nu::shell::unsupported_input), url(docsrs))] UnsupportedInput(String, #[label("{0} not supported")] Span), + #[error("Unable to parse datetime")] + #[diagnostic( + code(nu::shell::datetime_parse_error), + url(docsrs), + help( + r#"Examples of supported inputs: + * "5 pm" + * "2020/12/4" + * "2020.12.04 22:10 +2" + * "2020-04-12 22:10:57 +02:00" + * "2020-04-12T22:10:57.213231+02:00" + * "Tue, 1 Jul 2003 10:52:37 +0200""# + ) + )] + DatetimeParseError(#[label("datetime parsing failed")] Span), + #[error("Network failure")] #[diagnostic(code(nu::shell::network_failure), url(docsrs))] NetworkFailure(String, #[label("{0}")] Span),