From 9850fbd77de6c7ce94df652fd24ee8a76f52039f Mon Sep 17 00:00:00 2001 From: Access Date: Sun, 27 Nov 2022 02:19:02 +0800 Subject: [PATCH] chore: chrono_update (#7132) chrono version update # Description upgrade chrono to 0.4.23 # Major Changes If you're considering making any major change to nushell, before starting work on it, seek feedback from regular contributors and get approval for the idea from the core team either on [Discord](https://discordapp.com/invite/NtAbbGn) or [GitHub issue](https://github.com/nushell/nushell/issues/new/choose). Making sure we're all on board with the change saves everybody's time. Thanks! # Tests + Formatting Make sure you've done the following, if applicable: - Add tests that cover your changes (either in the command examples, the crate/tests folder, or in the /tests folder) - Try to think about corner cases and various ways how your changes could break. Cover those in the tests 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 --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace --features=extra` to check that all tests pass # After Submitting * Help us keep the docs up to date: If your PR affects the user experience of Nushell (adding/removing a command, changing an input/output type, etc.), make sure the changes are reflected in the documentation (https://github.com/nushell/nushell.github.io) after the PR is merged. Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com> --- Cargo.lock | 4 +- Cargo.toml | 2 +- crates/nu-cli/Cargo.toml | 2 +- crates/nu-command/Cargo.toml | 2 +- .../src/conversions/into/datetime.rs | 171 +++++++++++++----- .../values/nu_dataframe/conversion.rs | 54 +++++- crates/nu-command/src/date/to_timezone.rs | 28 ++- crates/nu-command/src/formats/to/toml.rs | 2 +- crates/nu-command/src/generators/cal.rs | 4 +- crates/nu-command/src/generators/seq_date.rs | 7 +- crates/nu-command/tests/commands/touch.rs | 24 ++- crates/nu-engine/Cargo.toml | 2 +- crates/nu-parser/Cargo.toml | 2 +- crates/nu-protocol/Cargo.toml | 2 +- crates/nu-system/Cargo.toml | 2 +- crates/nu-system/src/windows.rs | 19 +- tests/fixtures/formats/cargo_sample.toml | 2 +- 17 files changed, 239 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 82fdab2289..b45ccdd365 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,9 +481,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "iana-time-zone", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index 6e5e377c03..afea76437e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ members = [ ] [dependencies] -chrono = { version = "0.4.21", features = ["serde"] } +chrono = { version = "0.4.23", features = ["serde"] } crossterm = "0.24.0" ctrlc = "3.2.1" log = "0.4" diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index 808b42a438..07f0e73bdf 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -23,7 +23,7 @@ nu-color-config = { path = "../nu-color-config", version = "0.71.1" } reedline = { version = "0.14.0", features = ["bashisms", "sqlite"]} atty = "0.2.14" -chrono = { default-features = false, features = ["std"], version = "0.4.21" } +chrono = { default-features = false, features = ["std"], version = "0.4.23" } crossterm = "0.24.0" fancy-regex = "0.10.0" fuzzy-matcher = "0.3.7" diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 1a82c93e51..ac3e38eee4 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -32,7 +32,7 @@ base64 = "0.13.0" byteorder = "1.4.3" bytesize = "1.1.0" calamine = "0.18.0" -chrono = { version = "0.4.21", features = ["unstable-locales", "std"], default-features = false } +chrono = { version = "0.4.23", features = ["unstable-locales", "std"], default-features = false } chrono-humanize = "0.2.1" chrono-tz = "0.6.3" crossterm = "0.24.0" diff --git a/crates/nu-command/src/conversions/into/datetime.rs b/crates/nu-command/src/conversions/into/datetime.rs index 01e238f916..3c0289a159 100644 --- a/crates/nu-command/src/conversions/into/datetime.rs +++ b/crates/nu-command/src/conversions/into/datetime.rs @@ -1,6 +1,6 @@ use crate::input_handler::{operate, CmdArgument}; use crate::{generate_strftime_list, parse_date_from_string}; -use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc}; +use chrono::{DateTime, FixedOffset, Local, LocalResult, TimeZone, Utc}; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::ast::CellPath; @@ -147,38 +147,63 @@ impl Command for SubCommand { } fn examples(&self) -> Vec { + let example_result_1 = |secs: i64, nsecs: u32| { + let dt = match Utc.timestamp_opt(secs, nsecs) { + LocalResult::Single(dt) => Some(dt), + _ => None, + }; + match dt { + Some(dt) => Some(Value::Date { + val: dt.into(), + span: Span::test_data(), + }), + None => Some(Value::Error { + error: ShellError::UnsupportedInput( + "The given datetime representation is unsupported.".to_string(), + Span::test_data(), + ), + }), + } + }; + let example_result_2 = |millis: i64| { + let dt = match Utc.timestamp_millis_opt(millis) { + LocalResult::Single(dt) => Some(dt), + _ => None, + }; + match dt { + Some(dt) => Some(Value::Date { + val: dt.into(), + span: Span::test_data(), + }), + None => Some(Value::Error { + error: ShellError::UnsupportedInput( + "The given datetime representation is unsupported.".to_string(), + Span::test_data(), + ), + }), + } + }; vec![ Example { description: "Convert to datetime", example: "'27.02.2021 1:55 pm +0000' | into datetime", - result: Some(Value::Date { - val: Utc.timestamp(1614434100, 0).into(), - span: Span::test_data(), - }), + result: example_result_1(1614434100,0) }, Example { description: "Convert to datetime", example: "'2021-02-27T13:55:40+00:00' | into datetime", - result: Some(Value::Date { - val: Utc.timestamp(1614434140, 0).into(), - span: Span::test_data(), - }), + result: example_result_1(1614434140, 0) }, Example { description: "Convert to datetime using a custom format", example: "'20210227_135540+0000' | into datetime -f '%Y%m%d_%H%M%S%z'", - result: Some(Value::Date { - val: Utc.timestamp(1614434140, 0).into(), - span: Span::test_data(), - }), + result: example_result_1(1614434140, 0) + }, Example { description: "Convert timestamp (no larger than 8e+12) to a UTC datetime", example: "1614434140 | into datetime", - result: Some(Value::Date { - val: Utc.timestamp(1614434140, 0).into(), - span: Span::test_data(), - }), + result: example_result_1(1614434140, 0) }, Example { description: @@ -190,10 +215,7 @@ impl Command for SubCommand { description: "Convert timestamps like the sqlite history t", example: "1656165681720 | into datetime", - result: Some(Value::Date { - val: Utc.timestamp_millis(1656165681720).into(), - span: Span::test_data(), - }), + result: example_result_2(1656165681720) }, ] } @@ -239,8 +261,31 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value { // be able to convert chrono::Utc::now() let dt = match ts.to_string().len() { x if x > 13 => Utc.timestamp_nanos(ts).into(), - x if x > 10 => Utc.timestamp_millis(ts).into(), - _ => Utc.timestamp(ts, 0).into(), + x if x > 10 => match Utc.timestamp_millis_opt(ts) { + LocalResult::Single(dt) => dt.into(), + _ => { + return Value::Error { + // This error message is from chrono + error: ShellError::UnsupportedInput( + "The given local datetime representation is invalid." + .to_string(), + head, + ), + }; + } + }, + _ => match Utc.timestamp_opt(ts, 0) { + LocalResult::Single(dt) => dt.into(), + _ => { + return Value::Error { + error: ShellError::UnsupportedInput( + "The given local datetime representation is invalid." + .to_string(), + head, + ), + } + } + }, }; Value::Date { @@ -249,28 +294,64 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value { } } Some(Spanned { item, span }) => match item { - Zone::Utc => Value::Date { - val: Utc.timestamp(ts, 0).into(), - span: head, - }, - Zone::Local => Value::Date { - val: Local.timestamp(ts, 0).into(), - span: head, - }, - Zone::East(i) => { - let eastoffset = FixedOffset::east((*i as i32) * HOUR); - Value::Date { - val: eastoffset.timestamp(ts, 0), + Zone::Utc => match Utc.timestamp_opt(ts, 0) { + LocalResult::Single(val) => Value::Date { + val: val.into(), span: head, - } - } - Zone::West(i) => { - let westoffset = FixedOffset::west((*i as i32) * HOUR); - Value::Date { - val: westoffset.timestamp(ts, 0), + }, + _ => Value::Error { + error: ShellError::UnsupportedInput( + "The given local datetime representation is invalid.".to_string(), + *span, + ), + }, + }, + Zone::Local => match Local.timestamp_opt(ts, 0) { + LocalResult::Single(val) => Value::Date { + val: val.into(), span: head, - } - } + }, + _ => Value::Error { + error: ShellError::UnsupportedInput( + "The given local datetime representation is invalid.".to_string(), + *span, + ), + }, + }, + Zone::East(i) => match FixedOffset::east_opt((*i as i32) * HOUR) { + Some(eastoffset) => match eastoffset.timestamp_opt(ts, 0) { + LocalResult::Single(val) => Value::Date { val, span: head }, + _ => Value::Error { + error: ShellError::UnsupportedInput( + "The given local datetime representation is invalid.".to_string(), + *span, + ), + }, + }, + None => Value::Error { + error: ShellError::UnsupportedInput( + "The given local datetime representation is invalid.".to_string(), + *span, + ), + }, + }, + Zone::West(i) => match FixedOffset::west_opt((*i as i32) * HOUR) { + Some(westoffset) => match westoffset.timestamp_opt(ts, 0) { + LocalResult::Single(val) => Value::Date { val, span: head }, + _ => Value::Error { + error: ShellError::UnsupportedInput( + "The given local datetime representation is invalid.".to_string(), + *span, + ), + }, + }, + None => Value::Error { + error: ShellError::UnsupportedInput( + "The given local datetime representation is invalid.".to_string(), + *span, + ), + }, + }, Zone::Error => Value::Error { error: ShellError::UnsupportedInput( "Cannot convert given timezone or offset to timestamp".to_string(), @@ -425,7 +506,7 @@ mod tests { }; let actual = action(&date_str, &args, Span::test_data()); let expected = Value::Date { - val: Local.timestamp(1614434140, 0).into(), + val: Local.timestamp_opt(1614434140, 0).unwrap().into(), span: Span::test_data(), }; @@ -443,7 +524,7 @@ mod tests { let actual = action(&date_str, &args, Span::test_data()); let expected = Value::Date { - val: Utc.timestamp(1614434140, 0).into(), + val: Utc.timestamp_opt(1614434140, 0).unwrap().into(), span: Span::test_data(), }; diff --git a/crates/nu-command/src/dataframe/values/nu_dataframe/conversion.rs b/crates/nu-command/src/dataframe/values/nu_dataframe/conversion.rs index 0322d8b952..c4d448a1d7 100644 --- a/crates/nu-command/src/dataframe/values/nu_dataframe/conversion.rs +++ b/crates/nu-command/src/dataframe/values/nu_dataframe/conversion.rs @@ -460,10 +460,31 @@ pub fn create_column( Some(a) => { // elapsed time in day since 1970-01-01 let seconds = a as i64 * SECS_PER_DAY; - let naive_datetime = NaiveDateTime::from_timestamp(seconds, 0); - + let naive_datetime = match NaiveDateTime::from_timestamp_opt(seconds, 0) { + Some(val) => val, + None => { + return Value::Error { + error: ShellError::UnsupportedInput( + "The given local datetime representation is invalid." + .to_string(), + span, + ), + } + } + }; // Zero length offset - let offset = FixedOffset::east(0); + let offset = match FixedOffset::east_opt(0) { + Some(val) => val, + None => { + return Value::Error { + error: ShellError::UnsupportedInput( + "The given local datetime representation is invalid." + .to_string(), + span, + ), + } + } + }; let datetime = DateTime::::from_utc(naive_datetime, offset); Value::Date { @@ -496,10 +517,31 @@ pub fn create_column( Some(a) => { // elapsed time in milliseconds since 1970-01-01 let seconds = a / 1000; - let naive_datetime = NaiveDateTime::from_timestamp(seconds, 0); - + let naive_datetime = match NaiveDateTime::from_timestamp_opt(seconds, 0) { + Some(val) => val, + None => { + return Value::Error { + error: ShellError::UnsupportedInput( + "The given local datetime representation is invalid." + .to_string(), + span, + ), + } + } + }; // Zero length offset - let offset = FixedOffset::east(0); + let offset = match FixedOffset::east_opt(0) { + Some(val) => val, + None => { + return Value::Error { + error: ShellError::UnsupportedInput( + "The given local datetime representation is invalid." + .to_string(), + span, + ), + } + } + }; let datetime = DateTime::::from_utc(naive_datetime, offset); Value::Date { diff --git a/crates/nu-command/src/date/to_timezone.rs b/crates/nu-command/src/date/to_timezone.rs index 7a33c301e9..a32f6b3803 100644 --- a/crates/nu-command/src/date/to_timezone.rs +++ b/crates/nu-command/src/date/to_timezone.rs @@ -1,6 +1,6 @@ use super::parser::datetime_in_timezone; use crate::date::utils::parse_date_from_string; -use chrono::{DateTime, Local}; +use chrono::{DateTime, Local, LocalResult}; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; @@ -65,13 +65,25 @@ impl Command for SubCommand { fn examples(&self) -> Vec { let example_result_1 = || { - let dt = FixedOffset::east(5 * 3600) - .ymd(2020, 10, 10) - .and_hms(13, 00, 00); - Some(Value::Date { - val: dt, - span: Span::test_data(), - }) + let dt = match FixedOffset::east_opt(5 * 3600) { + Some(dt) => match dt.with_ymd_and_hms(2020, 10, 10, 13, 00, 00) { + LocalResult::Single(dt) => Some(dt), + _ => None, + }, + _ => None, + }; + match dt { + Some(dt) => Some(Value::Date { + val: dt, + span: Span::test_data(), + }), + None => Some(Value::Error { + error: ShellError::UnsupportedInput( + "The given datetime representation is unsupported.".to_string(), + Span::test_data(), + ), + }), + } }; vec![ diff --git a/crates/nu-command/src/formats/to/toml.rs b/crates/nu-command/src/formats/to/toml.rs index db8cb227f2..0ba25709e8 100644 --- a/crates/nu-command/src/formats/to/toml.rs +++ b/crates/nu-command/src/formats/to/toml.rs @@ -231,7 +231,7 @@ mod tests { [dependencies] rustyline = "4.1.0" sysinfo = "0.8.4" - chrono = { version = "0.4.21", features = ["serde"] } + chrono = { version = "0.4.23", features = ["serde"] } "#, ), Span::test_data(), diff --git a/crates/nu-command/src/generators/cal.rs b/crates/nu-command/src/generators/cal.rs index dad74beb14..114b05db82 100644 --- a/crates/nu-command/src/generators/cal.rs +++ b/crates/nu-command/src/generators/cal.rs @@ -184,12 +184,12 @@ impl MonthHelper { let next_month_naive_date = NaiveDate::from_ymd_opt(selected_year, selected_month, 1).ok_or(())?; - Ok(next_month_naive_date.pred().day()) + Ok(next_month_naive_date.pred_opt().unwrap_or_default().day()) } } fn get_current_date() -> (i32, u32, u32) { - let local_now_date = Local::now().date(); + let local_now_date = Local::now().date_naive(); let current_year: i32 = local_now_date.year(); let current_month: u32 = local_now_date.month(); diff --git a/crates/nu-command/src/generators/seq_date.rs b/crates/nu-command/src/generators/seq_date.rs index 109ed247c7..1c080ffd7d 100644 --- a/crates/nu-command/src/generators/seq_date.rs +++ b/crates/nu-command/src/generators/seq_date.rs @@ -195,10 +195,9 @@ pub fn run_seq_dates( day_count: Option, reverse: bool, ) -> Result { - let today = Local::today().naive_local(); - let mut step_size: i64 = increment - .as_i64() - .expect("unable to change increment to i64"); + let today = Local::now().date_naive(); + // if cannot convert , it will return error + let mut step_size: i64 = increment.as_i64()?; if step_size == 0 { return Err(ShellError::GenericError( diff --git a/crates/nu-command/tests/commands/touch.rs b/crates/nu-command/tests/commands/touch.rs index fc64f31fd3..ea19ce8ebe 100644 --- a/crates/nu-command/tests/commands/touch.rs +++ b/crates/nu-command/tests/commands/touch.rs @@ -1,4 +1,4 @@ -use chrono::{Date, DateTime, Local}; +use chrono::{DateTime, Local}; use nu_test_support::fs::Stub; use nu_test_support::nu; use nu_test_support::playground::Playground; @@ -45,9 +45,10 @@ fn change_modified_time_of_file_to_today() { let path = dirs.test().join("file.txt"); // Check only the date since the time may not match exactly - let date: Date = Local::now().date(); - let actual_date: Date = - DateTime::from(path.metadata().unwrap().modified().unwrap()).date(); + let date = Local::now().date_naive(); + let actual_date_time: DateTime = + DateTime::from(path.metadata().unwrap().modified().unwrap()); + let actual_date = actual_date_time.date_naive(); assert_eq!(date, actual_date); }) @@ -66,9 +67,10 @@ fn change_access_time_of_file_to_today() { let path = dirs.test().join("file.txt"); // Check only the date since the time may not match exactly - let date: Date = Local::now().date(); - let actual_date: Date = - DateTime::from(path.metadata().unwrap().accessed().unwrap()).date(); + let date = Local::now().date_naive(); + let actual_date_time: DateTime = + DateTime::from(path.metadata().unwrap().accessed().unwrap()); + let actual_date = actual_date_time.date_naive(); assert_eq!(date, actual_date); }) @@ -87,9 +89,11 @@ fn change_modified_and_access_time_of_file_to_today() { let metadata = dirs.test().join("file.txt").metadata().unwrap(); // Check only the date since the time may not match exactly - let date: Date = Local::now().date(); - let adate: Date = DateTime::from(metadata.accessed().unwrap()).date(); - let mdate: Date = DateTime::from(metadata.modified().unwrap()).date(); + let date = Local::now().date_naive(); + let adate_time: DateTime = DateTime::from(metadata.accessed().unwrap()); + let adate = adate_time.date_naive(); + let mdate_time: DateTime = DateTime::from(metadata.modified().unwrap()); + let mdate = mdate_time.date_naive(); assert_eq!(date, adate); assert_eq!(date, mdate); diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index 52974771a8..417ed3448d 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -13,7 +13,7 @@ nu-path = { path = "../nu-path", version = "0.71.1" } nu-glob = { path = "../nu-glob", version = "0.71.1" } nu-utils = { path = "../nu-utils", version = "0.71.1" } -chrono = { version="0.4.21", features = ["std"], default-features = false } +chrono = { version="0.4.23", features = ["std"], default-features = false } sysinfo ="0.26.2" [features] diff --git a/crates/nu-parser/Cargo.toml b/crates/nu-parser/Cargo.toml index 3c8d047c37..255023125d 100644 --- a/crates/nu-parser/Cargo.toml +++ b/crates/nu-parser/Cargo.toml @@ -9,7 +9,7 @@ version = "0.71.1" [dependencies] bytesize = "1.1.0" -chrono = { default-features = false, features = ['std'], version = "0.4.21" } +chrono = { default-features = false, features = ['std'], version = "0.4.23" } itertools = "0.10" miette = {version = "5.1.0", features = ["fancy-no-backtrace"]} thiserror = "1.0.31" diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index 3c3dc1ba91..8365c30a61 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -14,7 +14,7 @@ nu-utils = { path = "../nu-utils", version = "0.71.1" } nu-json = { path = "../nu-json", version = "0.71.1" } byte-unit = "4.0.9" -chrono = { version="0.4.21", features= ["serde", "std"], default-features = false } +chrono = { version="0.4.23", features= ["serde", "std"], default-features = false } chrono-humanize = "0.2.1" fancy-regex = "0.10.0" indexmap = { version="1.7" } diff --git a/crates/nu-system/Cargo.toml b/crates/nu-system/Cargo.toml index 23e5d80c07..98c21b0a05 100644 --- a/crates/nu-system/Cargo.toml +++ b/crates/nu-system/Cargo.toml @@ -27,6 +27,6 @@ mach2 = "0.4" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3.9", features = ["tlhelp32", "fileapi", "handleapi", "ifdef", "ioapiset", "minwindef", "pdh", "psapi", "synchapi", "sysinfoapi", "winbase", "winerror", "winioctl", "winnt", "oleauto", "wbemcli", "rpcdce", "combaseapi", "objidl", "powerbase", "netioapi", "lmcons", "lmaccess", "lmapibuf", "memoryapi", "shellapi", "std", "securitybaseapi"] } -chrono = "0.4.21" +chrono = "0.4.23" ntapi = "0.4" once_cell = "1.0" diff --git a/crates/nu-system/src/windows.rs b/crates/nu-system/src/windows.rs index 763a046d10..c7ba79f0b3 100644 --- a/crates/nu-system/src/windows.rs +++ b/crates/nu-system/src/windows.rs @@ -136,11 +136,22 @@ pub fn collect_proc(interval: Duration, _with_thread: bool) -> Vec let start_time = if let Some((start, _, _, _)) = times { let time = chrono::Duration::seconds(start as i64 / 10_000_000); - let base = NaiveDate::from_ymd(1600, 1, 1).and_hms(0, 0, 0); - let time = base + time; - Local.from_utc_datetime(&time) + let base = + NaiveDate::from_ymd_opt(1600, 1, 1).and_then(|nd| nd.and_hms_opt(0, 0, 0)); + if let Some(base) = base { + let time = base + time; + Local.from_utc_datetime(&time) + } else { + continue; + } } else { - Local.from_utc_datetime(&NaiveDate::from_ymd(1600, 1, 1).and_hms(0, 0, 0)) + let time = + NaiveDate::from_ymd_opt(1600, 1, 1).and_then(|nt| nt.and_hms_opt(0, 0, 0)); + if let Some(time) = time { + Local.from_utc_datetime(&time) + } else { + continue; + } }; let cpu_info = if let Some((_, _, curr_sys, curr_user)) = times { diff --git a/tests/fixtures/formats/cargo_sample.toml b/tests/fixtures/formats/cargo_sample.toml index 53311c7c3e..87bd32b77c 100644 --- a/tests/fixtures/formats/cargo_sample.toml +++ b/tests/fixtures/formats/cargo_sample.toml @@ -11,7 +11,7 @@ edition = "2018" [dependencies] rustyline = "4.1.0" sysinfo = "0.8.4" -chrono = { version = "0.4.21", features = ["serde"] } +chrono = { version = "0.4.23", features = ["serde"] } chrono-tz = "0.6.3" derive-new = "0.5.6" prettytable-rs = "0.8.0"