mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +00:00
Date parse refactor (#4661)
* More flexible and DRY datetime parsing * Update error messages * cargo fmt * clippy * Add DatetimeParseError
This commit is contained in:
parent
0f437589fc
commit
ef70c8dbe4
8 changed files with 75 additions and 87 deletions
|
@ -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<Spanned<String>>,
|
||||
|
@ -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::<i64>();
|
||||
// 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,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<String>) -> 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<String>) -> Value {
|
|||
let dt = Local::now();
|
||||
_to_timezone(dt.with_timezone(dt.offset()), timezone, head)
|
||||
}
|
||||
_ => unsupported_input_error(head),
|
||||
_ => Value::Error {
|
||||
error: ShellError::DatetimeParseError(head),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<DateTime<FixedOffset>, 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<DateTime<FixedOffset>, 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),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in a new issue