mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +00:00
allow parsing of human readable datetimes (#11051)
# Description This PR adds the ability to parse human readable datetime strings as part of the `into datetime` command. I added a new `-n`/`--list-human` parameter that produces this list to give the user an idea of what is supported. ```nushell ❯ into datetime --list-human ╭#─┬parseable human datetime examples┬───result───╮ │0 │Today 18:30 │in 8 hours │ │1 │2022-11-07 13:25:30 │a year ago │ │2 │15:20 Friday │in 3 days │ │3 │This Friday 17:00 │in 3 days │ │4 │13:25, Next Tuesday │in a week │ │5 │Last Friday at 19:45 │3 days ago │ │6 │In 3 days │in 2 days │ │7 │In 2 hours │in 2 hours │ │8 │10 hours and 5 minutes ago │10 hours ago│ │9 │1 years ago │a year ago │ │10│A year ago │a year ago │ │11│A month ago │a month ago │ │12│A week ago │a week ago │ │13│A day ago │a day ago │ │14│An hour ago │an hour ago │ │15│A minute ago │a minute ago│ │16│A second ago │now │ │17│Now │now │ ╰#─┴parseable human datetime examples┴───result───╯ ``` Or with `$env.config.datetime_format.table` set. ```nushell ❯ into datetime --list-human ╭#─┬parseable human datetime examples┬──────result───────╮ │0 │Today 18:30 │11/14/23 06:30:00PM│ │1 │2022-11-07 13:25:30 │11/07/22 01:25:30PM│ │2 │15:20 Friday │11/17/23 03:20:00PM│ │3 │This Friday 17:00 │11/17/23 05:00:00PM│ │4 │13:25, Next Tuesday │11/21/23 01:25:00PM│ │5 │Last Friday at 19:45 │11/10/23 07:45:00PM│ │6 │In 3 days │11/17/23 10:12:54AM│ │7 │In 2 hours │11/14/23 12:12:54PM│ │8 │10 hours and 5 minutes ago │11/14/23 12:07:54AM│ │9 │1 years ago │11/13/22 10:12:54AM│ │10│A year ago │11/13/22 10:12:54AM│ │11│A month ago │10/15/23 11:12:54AM│ │12│A week ago │11/07/23 10:12:54AM│ │13│A day ago │11/13/23 10:12:54AM│ │14│An hour ago │11/14/23 09:12:54AM│ │15│A minute ago │11/14/23 10:11:54AM│ │16│A second ago │11/14/23 10:12:53AM│ │17│Now │11/14/23 10:12:54AM│ ╰#─┴parseable human datetime examples┴──────result───────╯ ``` # User-Facing Changes <!-- List of all changes that impact the user experience here. This helps us keep track of breaking changes. --> # Tests + Formatting <!-- Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging)) - `cargo run -- -c "use std testing; testing run-tests --path crates/nu-std"` to run the tests for the standard library > **Note** > from `nushell` you can also use the `toolkit` as follows > ```bash > use toolkit.nu # or use an `env_change` hook to activate it automatically > toolkit check pr > ``` --> # After Submitting <!-- If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date. -->
This commit is contained in:
parent
e9c298713e
commit
4367aa9f58
3 changed files with 160 additions and 6 deletions
66
Cargo.lock
generated
66
Cargo.lock
generated
|
@ -585,9 +585,11 @@ checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
|||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"pure-rust-locales",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
|
@ -1778,6 +1780,18 @@ version = "1.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "human-date-parser"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d65b3ad1fdc03306397b6004b4f8f765cf7467194a1080b4530eeed5a2f0bc"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.27"
|
||||
|
@ -2845,6 +2859,7 @@ dependencies = [
|
|||
"filetime",
|
||||
"fs_extra",
|
||||
"htmlescape",
|
||||
"human-date-parser",
|
||||
"indexmap 2.1.0",
|
||||
"indicatif",
|
||||
"itertools 0.11.0",
|
||||
|
@ -3593,6 +3608,51 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f658886ed52e196e850cfbbfddab9eaa7f6d90dd0929e264c31e5cec07e09e57"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pest",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.10.1"
|
||||
|
@ -5568,6 +5628,12 @@ dependencies = [
|
|||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
|
||||
|
||||
[[package]]
|
||||
name = "umask"
|
||||
version = "2.1.0"
|
||||
|
|
|
@ -47,6 +47,7 @@ filesize = "0.2"
|
|||
filetime = "0.2"
|
||||
fs_extra = "1.3"
|
||||
htmlescape = "0.3"
|
||||
human-date-parser = "0.1.1"
|
||||
indexmap = "2.1"
|
||||
indicatif = "0.17"
|
||||
itertools = "0.11"
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
use crate::{generate_strftime_list, parse_date_from_string};
|
||||
use chrono::NaiveTime;
|
||||
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
|
||||
use human_date_parser::{from_human_time, ParseResult};
|
||||
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
||||
SyntaxShape, Type, Value,
|
||||
ast::{Call, CellPath},
|
||||
engine::{Command, EngineState, Stack},
|
||||
record, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
|
||||
Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
struct Arguments {
|
||||
|
@ -95,6 +96,11 @@ impl Command for SubCommand {
|
|||
"Show all possible variables for use in --format flag",
|
||||
Some('l'),
|
||||
)
|
||||
.switch(
|
||||
"list-human",
|
||||
"Show human-readable datetime parsing examples",
|
||||
Some('n'),
|
||||
)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
|
@ -112,6 +118,8 @@ impl Command for SubCommand {
|
|||
) -> Result<PipelineData, ShellError> {
|
||||
if call.has_flag("list") {
|
||||
Ok(generate_strftime_list(call.head, true).into_pipeline_data())
|
||||
} else if call.has_flag("list-human") {
|
||||
Ok(list_human_readable_examples(call.head).into_pipeline_data())
|
||||
} else {
|
||||
let cell_paths = call.rest(engine_state, stack, 0)?;
|
||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||
|
@ -225,6 +233,21 @@ impl Command for SubCommand {
|
|||
Span::test_data(),
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: "Parsing human readable datetimes",
|
||||
example: "'Today at 18:30' | into datetime",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Parsing human readable datetimes",
|
||||
example: "'Last Friday at 19:45' | into datetime",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Parsing human readable datetimes",
|
||||
example: "'In 5 minutes and 30 seconds' | into datetime",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -241,7 +264,33 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||
if let Ok(input_val) = input.as_spanned_string() {
|
||||
match parse_date_from_string(&input_val.item, input_val.span) {
|
||||
Ok(date) => return Value::date(date, input_val.span),
|
||||
Err(err) => err,
|
||||
Err(_) => {
|
||||
if let Ok(date) = from_human_time(&input_val.item) {
|
||||
match date {
|
||||
ParseResult::Date(date) => {
|
||||
let time = NaiveTime::from_hms_opt(0, 0, 0).expect("valid time");
|
||||
let combined = date.and_time(time);
|
||||
let dt_fixed = DateTime::from_naive_utc_and_offset(
|
||||
combined,
|
||||
*Local::now().offset(),
|
||||
);
|
||||
return Value::date(dt_fixed, input_val.span);
|
||||
}
|
||||
ParseResult::DateTime(date) => {
|
||||
return Value::date(date.fixed_offset(), input_val.span)
|
||||
}
|
||||
ParseResult::Time(time) => {
|
||||
let date = Local::now().date_naive();
|
||||
let combined = date.and_time(time);
|
||||
let dt_fixed = DateTime::from_naive_utc_and_offset(
|
||||
combined,
|
||||
*Local::now().offset(),
|
||||
);
|
||||
return Value::date(dt_fixed, input_val.span);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -362,6 +411,44 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||
}
|
||||
}
|
||||
|
||||
fn list_human_readable_examples(span: Span) -> Value {
|
||||
let examples: Vec<String> = vec![
|
||||
"Today 18:30".into(),
|
||||
"2022-11-07 13:25:30".into(),
|
||||
"15:20 Friday".into(),
|
||||
"This Friday 17:00".into(),
|
||||
"13:25, Next Tuesday".into(),
|
||||
"Last Friday at 19:45".into(),
|
||||
"In 3 days".into(),
|
||||
"In 2 hours".into(),
|
||||
"10 hours and 5 minutes ago".into(),
|
||||
"1 years ago".into(),
|
||||
"A year ago".into(),
|
||||
"A month ago".into(),
|
||||
"A week ago".into(),
|
||||
"A day ago".into(),
|
||||
"An hour ago".into(),
|
||||
"A minute ago".into(),
|
||||
"A second ago".into(),
|
||||
"Now".into(),
|
||||
];
|
||||
|
||||
let records = examples
|
||||
.iter()
|
||||
.map(|s| {
|
||||
Value::record(
|
||||
record! {
|
||||
"parseable human datetime examples" => Value::test_string(s.to_string()),
|
||||
"result" => action(&Value::test_string(s.to_string()), &Arguments { zone_options: None, format_options: None, cell_paths: None }, span)
|
||||
},
|
||||
span,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<Value>>();
|
||||
|
||||
Value::list(records, span)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
Loading…
Reference in a new issue