Skip to content

Commit

Permalink
allow parsing of human readable datetimes (nushell#11051)
Browse files Browse the repository at this point in the history
# 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.
-->
  • Loading branch information
fdncred authored and hardfau1t committed Dec 14, 2023
1 parent 6f07894 commit 6b57c48
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 6 deletions.
66 changes: 66 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/nu-command/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
99 changes: 93 additions & 6 deletions crates/nu-command/src/conversions/into/datetime.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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,
},
]
}
}
Expand All @@ -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);
}
}
}
}
};
}
}
Expand Down Expand Up @@ -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::*;
Expand Down

0 comments on commit 6b57c48

Please sign in to comment.