mirror of
https://github.com/nushell/nushell
synced 2024-12-27 21:43:09 +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_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::ast::CellPath;
|
use nu_protocol::ast::CellPath;
|
||||||
|
@ -8,6 +8,7 @@ use nu_protocol::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::generate_strftime_list;
|
use crate::generate_strftime_list;
|
||||||
|
use crate::parse_date_from_string;
|
||||||
|
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
timezone: Option<Spanned<String>>,
|
timezone: Option<Spanned<String>>,
|
||||||
|
@ -205,7 +206,7 @@ fn action(
|
||||||
head: Span,
|
head: Span,
|
||||||
) -> Value {
|
) -> Value {
|
||||||
match input {
|
match input {
|
||||||
Value::String { val: s, span, .. } => {
|
Value::String { val: s, span } => {
|
||||||
let ts = s.parse::<i64>();
|
let ts = s.parse::<i64>();
|
||||||
// if timezone if specified, first check if the input is a timestamp.
|
// if timezone if specified, first check if the input is a timestamp.
|
||||||
if let Some(tz) = timezone {
|
if let Some(tz) = timezone {
|
||||||
|
@ -253,7 +254,7 @@ fn action(
|
||||||
return stampout;
|
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 {
|
let out = match dateformat {
|
||||||
Some(dt) => match DateTime::parse_from_str(s, &dt.0) {
|
Some(dt) => match DateTime::parse_from_str(s, &dt.0) {
|
||||||
Ok(d) => Value::Date { val: d, span: head },
|
Ok(d) => Value::Date { val: d, span: head },
|
||||||
|
@ -267,35 +268,15 @@ fn action(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None => match dtparse::parse(s) {
|
// Tries to automatically parse the date
|
||||||
Ok((native_dt, fixed_offset)) => {
|
// (i.e. without a format string)
|
||||||
let offset = match fixed_offset {
|
// and assumes the system's local timezone if none is specified
|
||||||
Some(fo) => fo,
|
None => match parse_date_from_string(s, *span) {
|
||||||
None => FixedOffset::east(0).fix(),
|
Ok(date) => Value::Date {
|
||||||
};
|
val: date,
|
||||||
match offset.from_local_datetime(&native_dt) {
|
span: *span,
|
||||||
LocalResult::Single(d) => Value::Date { val: d, span: head },
|
},
|
||||||
LocalResult::Ambiguous(d, _) => Value::Date { val: d, span: head },
|
Err(err) => err,
|
||||||
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,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,10 @@ use nu_engine::CallExt;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{Command, EngineState, Stack},
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
|
@ -96,7 +96,7 @@ fn format_helper(value: Value, formatter: &str, span: Span) -> Value {
|
||||||
val,
|
val,
|
||||||
span: val_span,
|
span: val_span,
|
||||||
} => {
|
} => {
|
||||||
let dt = parse_date_from_string(val, val_span);
|
let dt = parse_date_from_string(&val, val_span);
|
||||||
match dt {
|
match dt {
|
||||||
Ok(x) => Value::String {
|
Ok(x) => Value::String {
|
||||||
val: x.format(formatter).to_string(),
|
val: x.format(formatter).to_string(),
|
||||||
|
@ -112,7 +112,9 @@ fn format_helper(value: Value, formatter: &str, span: Span) -> Value {
|
||||||
span,
|
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,
|
val,
|
||||||
span: val_span,
|
span: val_span,
|
||||||
} => {
|
} => {
|
||||||
let dt = parse_date_from_string(val, val_span);
|
let dt = parse_date_from_string(&val, val_span);
|
||||||
match dt {
|
match dt {
|
||||||
Ok(x) => Value::String {
|
Ok(x) => Value::String {
|
||||||
val: x.to_rfc2822(),
|
val: x.to_rfc2822(),
|
||||||
|
@ -142,7 +144,9 @@ fn format_helper_rfc2822(value: Value, span: Span) -> Value {
|
||||||
span,
|
span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => unsupported_input_error(span),
|
_ => Value::Error {
|
||||||
|
error: ShellError::DatetimeParseError(span),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ fn helper(value: Value, head: Span) -> Value {
|
||||||
val,
|
val,
|
||||||
span: val_span,
|
span: val_span,
|
||||||
} => {
|
} => {
|
||||||
let dt = parse_date_from_string(val, val_span);
|
let dt = parse_date_from_string(&val, val_span);
|
||||||
match dt {
|
match dt {
|
||||||
Ok(x) => Value::String {
|
Ok(x) => Value::String {
|
||||||
val: humanize_date(x),
|
val: humanize_date(x),
|
||||||
|
@ -77,10 +77,7 @@ fn helper(value: Value, head: Span) -> Value {
|
||||||
span: head,
|
span: head,
|
||||||
},
|
},
|
||||||
_ => Value::Error {
|
_ => Value::Error {
|
||||||
error: ShellError::UnsupportedInput(
|
error: ShellError::DatetimeParseError(head),
|
||||||
String::from("Date cannot be parsed / date format is not supported"),
|
|
||||||
head,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,3 +16,4 @@ pub use list_timezone::SubCommand as DateListTimezones;
|
||||||
pub use now::SubCommand as DateNow;
|
pub use now::SubCommand as DateNow;
|
||||||
pub use to_table::SubCommand as DateToTable;
|
pub use to_table::SubCommand as DateToTable;
|
||||||
pub use to_timezone::SubCommand as DateToTimezone;
|
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 chrono::{DateTime, Datelike, FixedOffset, Local, Timelike};
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct SubCommand;
|
pub struct SubCommand;
|
||||||
|
@ -140,7 +142,7 @@ fn helper(val: Value, head: Span) -> Value {
|
||||||
val,
|
val,
|
||||||
span: val_span,
|
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)
|
parse_date_into_table(date, head)
|
||||||
}
|
}
|
||||||
Value::Nothing { span: _ } => {
|
Value::Nothing { span: _ } => {
|
||||||
|
@ -149,7 +151,9 @@ fn helper(val: Value, head: Span) -> Value {
|
||||||
parse_date_into_table(Ok(n), head)
|
parse_date_into_table(Ok(n), head)
|
||||||
}
|
}
|
||||||
Value::Date { val, span: _ } => parse_date_into_table(Ok(val), 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 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 chrono::{DateTime, Local};
|
||||||
use nu_engine::CallExt;
|
use nu_engine::CallExt;
|
||||||
use nu_protocol::ast::Call;
|
use nu_protocol::ast::Call;
|
||||||
|
@ -93,7 +93,7 @@ fn helper(value: Value, head: Span, timezone: &Spanned<String>) -> Value {
|
||||||
val,
|
val,
|
||||||
span: val_span,
|
span: val_span,
|
||||||
} => {
|
} => {
|
||||||
let time = parse_date_from_string(val, val_span);
|
let time = parse_date_from_string(&val, val_span);
|
||||||
match time {
|
match time {
|
||||||
Ok(dt) => _to_timezone(dt, timezone, head),
|
Ok(dt) => _to_timezone(dt, timezone, head),
|
||||||
Err(e) => e,
|
Err(e) => e,
|
||||||
|
@ -104,7 +104,9 @@ fn helper(value: Value, head: Span, timezone: &Spanned<String>) -> Value {
|
||||||
let dt = Local::now();
|
let dt = Local::now();
|
||||||
_to_timezone(dt.with_timezone(dt.offset()), timezone, head)
|
_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};
|
use nu_protocol::{ShellError, Span, Value};
|
||||||
|
|
||||||
pub fn unsupported_input_error(span: Span) -> Value {
|
pub(crate) fn parse_date_from_string(
|
||||||
Value::Error {
|
input: &str,
|
||||||
error: ShellError::UnsupportedInput(
|
span: Span,
|
||||||
String::from(
|
) -> Result<DateTime<FixedOffset>, Value> {
|
||||||
"Only dates with timezones are supported. The following formats are allowed \n
|
match dtparse::parse(input) {
|
||||||
* %Y-%m-%d %H:%M:%S %z -- 2020-04-12 22:10:57 +02:00 \n
|
Ok((native_dt, fixed_offset)) => {
|
||||||
* %Y-%m-%d %H:%M:%S%.6f %z -- 2020-04-12 22:10:57.213231 +02:00 \n
|
let offset = match fixed_offset {
|
||||||
* rfc3339 -- 2020-04-12T22:10:57+02:00 \n
|
Some(fo) => fo,
|
||||||
* rfc2822 -- Tue, 1 Jul 2003 10:52:37 +0200",
|
None => *(Local::now().offset()),
|
||||||
),
|
};
|
||||||
span,
|
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),
|
||||||
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)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Err(_) => Err(Value::Error {
|
||||||
|
error: ShellError::DatetimeParseError(span),
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,6 +154,22 @@ pub enum ShellError {
|
||||||
#[diagnostic(code(nu::shell::unsupported_input), url(docsrs))]
|
#[diagnostic(code(nu::shell::unsupported_input), url(docsrs))]
|
||||||
UnsupportedInput(String, #[label("{0} not supported")] Span),
|
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")]
|
#[error("Network failure")]
|
||||||
#[diagnostic(code(nu::shell::network_failure), url(docsrs))]
|
#[diagnostic(code(nu::shell::network_failure), url(docsrs))]
|
||||||
NetworkFailure(String, #[label("{0}")] Span),
|
NetworkFailure(String, #[label("{0}")] Span),
|
||||||
|
|
Loading…
Reference in a new issue