mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 07:00:37 +02: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:
@ -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::*;
|
||||
|
Reference in New Issue
Block a user