parse most common date formats using dtparse crate (#2303)

* use dtparse crate to parse most common date formats

* use dtparse crate to parse most common date formats - cargo fmt
This commit is contained in:
Nico Mandery 2020-08-05 02:44:52 +02:00 committed by GitHub
parent e1c5940b04
commit 6202705eb6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 72 additions and 23 deletions

View file

@ -34,8 +34,9 @@ codespan-reporting = "0.9.5"
csv = "1.1" csv = "1.1"
ctrlc = {version = "3.1.4", optional = true} ctrlc = {version = "3.1.4", optional = true}
derive-new = "0.5.8" derive-new = "0.5.8"
directories = {version = "2.0.2", optional = true} directories = {version = "2.0.2", optional = true}
dirs = {version = "2.0.2", optional = true} dirs = {version = "2.0.2", optional = true}
dtparse = "1.1.0"
dunce = "1.0.1" dunce = "1.0.1"
eml-parser = "0.1.0" eml-parser = "0.1.0"
filesize = "0.2.0" filesize = "0.2.0"

View file

@ -8,7 +8,7 @@ use nu_protocol::{
use nu_source::{Tag, Tagged}; use nu_source::{Tag, Tagged};
use nu_value_ext::ValueExt; use nu_value_ext::ValueExt;
use chrono::DateTime; use chrono::{DateTime, FixedOffset, LocalResult, Offset, TimeZone};
#[derive(Deserialize)] #[derive(Deserialize)]
struct Arguments { struct Arguments {
@ -51,11 +51,23 @@ impl WholeStreamCommand for SubCommand {
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![
description: "Convert to datetime", Example {
example: "echo '16.11.1984 8:00 am +0000' | str to-datetime", description: "Convert to datetime",
result: None, example: "echo '16.11.1984 8:00 am +0000' | str to-datetime",
}] result: None,
},
Example {
description: "Convert to datetime",
example: "echo '2020-08-04T16:39:18+00:00' | str to-datetime",
result: None,
},
Example {
description: "Convert to datetime using a custom format",
example: "echo '20200904_163918+0000' | str to-datetime -f '%Y%m%d_%H%M%S%z'",
result: None,
},
]
} }
} }
@ -73,9 +85,9 @@ async fn operate(
let column_paths: Vec<_> = rest; let column_paths: Vec<_> = rest;
let options = if let Some(Tagged { item: fmt, .. }) = format { let options = if let Some(Tagged { item: fmt, .. }) = format {
DatetimeFormat(fmt) Some(DatetimeFormat(fmt))
} else { } else {
DatetimeFormat(String::from("%d.%m.%Y %H:%M %P %z")) None
}; };
Ok(input Ok(input
@ -102,23 +114,49 @@ async fn operate(
fn action( fn action(
input: &Value, input: &Value,
options: &DatetimeFormat, options: &Option<DatetimeFormat>,
tag: impl Into<Tag>, tag: impl Into<Tag>,
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
match &input.value { match &input.value {
UntaggedValue::Primitive(Primitive::Line(s)) UntaggedValue::Primitive(Primitive::Line(s))
| UntaggedValue::Primitive(Primitive::String(s)) => { | UntaggedValue::Primitive(Primitive::String(s)) => {
let dt = &options.0; let out = match options {
Some(dt) => match DateTime::parse_from_str(s, &dt.0) {
let out = match DateTime::parse_from_str(s, dt) { Ok(d) => UntaggedValue::date(d),
Ok(d) => UntaggedValue::date(d), Err(reason) => {
Err(reason) => { return Err(ShellError::labeled_error(
return Err(ShellError::labeled_error( format!("could not parse as datetime using format '{}'", dt.0),
"could not parse as datetime", reason.to_string(),
reason.to_string(), tag.into().span,
tag.into().span, ))
)) }
} },
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) => UntaggedValue::date(d),
LocalResult::Ambiguous(d, _) => UntaggedValue::date(d),
LocalResult::None => {
return Err(ShellError::labeled_error(
"could not convert to a timezone-aware datetime",
"local time representation is invalid",
tag.into().span,
))
}
}
}
Err(reason) => {
return Err(ShellError::labeled_error(
"could not parse as datetime",
reason.to_string(),
tag.into().span,
))
}
},
}; };
Ok(out.into_value(tag)) Ok(out.into_value(tag))
@ -152,7 +190,7 @@ mod tests {
fn takes_a_date_format() { fn takes_a_date_format() {
let date_str = string("16.11.1984 8:00 am +0000"); let date_str = string("16.11.1984 8:00 am +0000");
let fmt_options = DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()); let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
let actual = action(&date_str, &fmt_options, Tag::unknown()).unwrap(); let actual = action(&date_str, &fmt_options, Tag::unknown()).unwrap();
@ -162,11 +200,21 @@ mod tests {
} }
} }
#[test]
fn takes_iso8601_date_format() {
let date_str = string("2020-08-04T16:39:18+00:00");
let actual = action(&date_str, &None, Tag::unknown()).unwrap();
match actual.value {
UntaggedValue::Primitive(Primitive::Date(_)) => {}
_ => panic!("Didn't convert to date"),
}
}
#[test] #[test]
fn communicates_parsing_error_given_an_invalid_datetimelike_string() { fn communicates_parsing_error_given_an_invalid_datetimelike_string() {
let date_str = string("16.11.1984 8:00 am Oops0000"); let date_str = string("16.11.1984 8:00 am Oops0000");
let fmt_options = DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()); let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
let actual = action(&date_str, &fmt_options, Tag::unknown()); let actual = action(&date_str, &fmt_options, Tag::unknown());