From e53aabd914618e7ecbd5d37c090db08e1b27e754 Mon Sep 17 00:00:00 2001 From: Ben Schofield Date: Sat, 3 Jun 2023 09:58:36 -0700 Subject: [PATCH 01/12] add datetime parser Add a relaxed datetime parser. This datetime parser functions by using `chrono`s own parsing utilities and a try/succeed approach to parsing. This implementation of the datetime parser has some drawbacks and some positives. On the positive side: - it was easy to implement - it is easy to add more datetime formats to In order to add additionally supported formats, a developer can add the required format string to the `format` mod in `parse_datetime.rs`, and then add it as a potential format to the relevant `fmts` vec. On the negative: - It is not easily customiseable beyond the supported `chrono` parsing formats. E.g., `chrono` does not currently support parsing offsets without trailing zeros. `from_str("UTC+1")` should return a valid response but `chrono` fails to parse this. - Because it is an attempt driven parser, it is likely not that performant. I have not done any performance testing as part of this change, but I would expect a custom parser to perform much better. --- src/lib.rs | 3 + src/parse_datetime.rs | 216 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 src/parse_datetime.rs diff --git a/src/lib.rs b/src/lib.rs index b53891a..21a4b46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,9 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +// Expose parse_datetime +pub mod parse_datetime; + use chrono::{Duration, Local, NaiveDate, Utc}; use regex::{Error as RegexError, Regex}; use std::error::Error; diff --git a/src/parse_datetime.rs b/src/parse_datetime.rs new file mode 100644 index 0000000..5e031ac --- /dev/null +++ b/src/parse_datetime.rs @@ -0,0 +1,216 @@ +use chrono::{DateTime, FixedOffset, Local, LocalResult, NaiveDateTime, TimeZone}; + +use crate::ParseDurationError; + +/// Formats that parse input can take. +/// Taken from `touch` core util +mod format { + pub(crate) const ISO_8601: &str = "%Y-%m-%d"; + pub(crate) const ISO_8601_NO_SEP: &str = "%Y%m%d"; + pub(crate) const POSIX_LOCALE: &str = "%a %b %e %H:%M:%S %Y"; + pub(crate) const YYYYMMDDHHMM_DOT_SS: &str = "%Y%m%d%H%M.%S"; + pub(crate) const YYYYMMDDHHMMSS: &str = "%Y-%m-%d %H:%M:%S.%f"; + pub(crate) const YYYYMMDDHHMMS: &str = "%Y-%m-%d %H:%M:%S"; + pub(crate) const YYYY_MM_DD_HH_MM: &str = "%Y-%m-%d %H:%M"; + pub(crate) const YYYYMMDDHHMM: &str = "%Y%m%d%H%M"; + pub(crate) const YYYYMMDDHHMM_OFFSET: &str = "%Y%m%d%H%M %z"; + pub(crate) const YYYYMMDDHHMM_UTC_OFFSET: &str = "%Y%m%d%H%MUTC%z"; + pub(crate) const YYYYMMDDHHMM_ZULU_OFFSET: &str = "%Y%m%d%H%MZ%z"; + pub(crate) const YYYYMMDDHHMM_HYPHENATED_OFFSET: &str = "%Y-%m-%d %H:%M %z"; + pub(crate) const YYYYMMDDHHMMS_T_SEP: &str = "%Y-%m-%dT%H:%M:%S"; + pub(crate) const UTC_OFFSET: &str = "UTC%#z"; + pub(crate) const ZULU_OFFSET: &str = "Z%#z"; +} + +/// Loosely parses a time string and returns a `DateTime` representing the +/// absolute time of the string. +/// +/// # Arguments +/// +/// * `s` - A string slice representing the time. +/// +/// # Examples +/// +/// ``` +/// use chrono::{DateTime, Utc}; +/// let time = humantime_to_duration::parse_datetime::from_str("2023-06-03 12:00:01Z"); +/// assert_eq!(time.unwrap(), Utc.with_ymd_and_hms(2023, 06, 03, 12, 00, 01).unwrap()); +/// ``` +/// +/// # Supported formats +/// +/// The function supports the following formats for time: +/// +/// * ISO formats +/// * timezone offsets, e.g., "UTC-0100" +/// +/// # Returns +/// +/// * `Ok(DateTime)` - If the input string can be parsed as a time +/// * `Err(ParseDurationError)` - If the input string cannot be parsed as a relative time +/// +/// # Errors +/// +/// This function will return `Err(ParseDurationError::InvalidInput)` if the input string +/// cannot be parsed as a relative time. +/// +pub fn from_str + Clone>(s: S) -> Result, ParseDurationError> { + // TODO: Replace with a proper customiseable parsing solution using `nom`, `grmtools`, or + // similar + + // Formats with offsets don't require NaiveDateTime workaround + for fmt in [ + format::YYYYMMDDHHMM_OFFSET, + format::YYYYMMDDHHMM_HYPHENATED_OFFSET, + format::YYYYMMDDHHMM_UTC_OFFSET, + format::YYYYMMDDHHMM_ZULU_OFFSET, + ] { + if let Ok(parsed) = DateTime::parse_from_str(s.as_ref(), fmt) { + return Ok(parsed); + } + } + + // Parse formats with no offset, assume local time + for fmt in [ + format::YYYYMMDDHHMMS_T_SEP, + format::YYYYMMDDHHMM, + format::YYYYMMDDHHMMS, + format::YYYYMMDDHHMMSS, + format::YYYY_MM_DD_HH_MM, + format::YYYYMMDDHHMM_DOT_SS, + format::POSIX_LOCALE, + ] { + if let Ok(parsed) = NaiveDateTime::parse_from_str(s.as_ref(), fmt) { + if let Ok(dt) = naive_dt_to_fixed_offset(parsed) { + return Ok(dt); + } + } + } + + // Parse epoch seconds + if s.as_ref().bytes().next() == Some(b'@') { + if let Ok(parsed) = NaiveDateTime::parse_from_str(&s.as_ref()[1..], "%s") { + if let Ok(dt) = naive_dt_to_fixed_offset(parsed) { + return Ok(dt); + } + } + } + + let ts = s.as_ref().to_owned() + "0000"; + // Parse date only formats - assume midnight local timezone + for fmt in [format::ISO_8601, format::ISO_8601_NO_SEP] { + let f = fmt.to_owned() + "%H%M"; + if let Ok(parsed) = NaiveDateTime::parse_from_str(&ts, &f) { + if let Ok(dt) = naive_dt_to_fixed_offset(parsed) { + return Ok(dt); + } + } + } + + // Parse offsets. chrono doesn't provide any functionality to parse + // offsets, so instead we replicate parse_date behaviour by getting + // the current date with local, and create a date time string at midnight, + // before trying offset suffixes + let local = Local::now(); + let ts = format!("{}", local.format("%Y%m%d")) + "0000" + s.as_ref(); + for fmt in [format::UTC_OFFSET, format::ZULU_OFFSET] { + let f = format::YYYYMMDDHHMM.to_owned() + fmt; + if let Ok(parsed) = DateTime::parse_from_str(&ts, &f) { + return Ok(parsed); + } + } + + // Default parse and failure + s.as_ref() + .parse() + .map_err(|_| (ParseDurationError::InvalidInput)) +} + +// Convert NaiveDateTime to DateTime by assuming the offset +// is local time +fn naive_dt_to_fixed_offset(dt: NaiveDateTime) -> Result, ()> { + let now = Local::now(); + match now.offset().from_local_datetime(&dt) { + LocalResult::Single(dt) => Ok(dt), + _ => Err(()), + } +} + +#[cfg(test)] +mod tests { + static TEST_TIME: i64 = 1613371067; + + #[cfg(test)] + mod iso_8601 { + use std::env; + + use crate::{parse_datetime::from_str, parse_datetime::tests::TEST_TIME}; + + #[test] + fn test_t_sep() { + env::set_var("TZ", "UTC"); + let dt = "2021-02-15T06:37:47"; + let actual = from_str(dt); + assert_eq!(actual.unwrap().timestamp(), TEST_TIME); + } + + #[test] + fn test_space_sep() { + env::set_var("TZ", "UTC"); + let dt = "2021-02-15 06:37:47"; + let actual = from_str(dt); + assert_eq!(actual.unwrap().timestamp(), TEST_TIME); + } + + #[test] + fn test_space_sep_offset() { + env::set_var("TZ", "UTC"); + let dt = "2021-02-14 22:37:47 -0800"; + let actual = from_str(dt); + assert_eq!(actual.unwrap().timestamp(), TEST_TIME); + } + + #[test] + fn test_t_sep_offset() { + env::set_var("TZ", "UTC"); + let dt = "2021-02-14T22:37:47 -0800"; + let actual = from_str(dt); + assert_eq!(actual.unwrap().timestamp(), TEST_TIME); + } + } + + #[cfg(test)] + mod offsets { + use chrono::Local; + + use crate::parse_datetime::from_str; + + #[test] + fn test_positive_offsets() { + let offsets = vec![ + "UTC+07:00", + "UTC+0700", + "UTC+07", + "Z+07:00", + "Z+0700", + "Z+07", + ]; + + let expected = format!("{}{}", Local::now().format("%Y%m%d"), "0000+0700"); + for offset in offsets { + let actual = from_str(offset).unwrap(); + assert_eq!(expected, format!("{}", actual.format("%Y%m%d%H%M%z"))); + } + } + + #[test] + fn test_partial_offset() { + let offsets = vec!["UTC+00:15", "UTC+0015", "Z+00:15", "Z+0015"]; + let expected = format!("{}{}", Local::now().format("%Y%m%d"), "0000+0015"); + for offset in offsets { + let actual = from_str(offset).unwrap(); + assert_eq!(expected, format!("{}", actual.format("%Y%m%d%H%M%z"))); + } + } + } +} From c177117428385de656962743c90b4d71321d86d1 Mon Sep 17 00:00:00 2001 From: Ben Schofield Date: Mon, 5 Jun 2023 07:58:44 -0700 Subject: [PATCH 02/12] Fixes for comments --- src/parse_datetime.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/parse_datetime.rs b/src/parse_datetime.rs index 5e031ac..eae6ab8 100644 --- a/src/parse_datetime.rs +++ b/src/parse_datetime.rs @@ -1,9 +1,12 @@ +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + use chrono::{DateTime, FixedOffset, Local, LocalResult, NaiveDateTime, TimeZone}; use crate::ParseDurationError; /// Formats that parse input can take. -/// Taken from `touch` core util +/// Taken from `touch` coreutil mod format { pub(crate) const ISO_8601: &str = "%Y-%m-%d"; pub(crate) const ISO_8601_NO_SEP: &str = "%Y%m%d"; @@ -32,7 +35,7 @@ mod format { /// # Examples /// /// ``` -/// use chrono::{DateTime, Utc}; +/// use chrono::{DateTime, Utc, TimeZone}; /// let time = humantime_to_duration::parse_datetime::from_str("2023-06-03 12:00:01Z"); /// assert_eq!(time.unwrap(), Utc.with_ymd_and_hms(2023, 06, 03, 12, 00, 01).unwrap()); /// ``` @@ -144,7 +147,7 @@ mod tests { mod iso_8601 { use std::env; - use crate::{parse_datetime::from_str, parse_datetime::tests::TEST_TIME}; + use crate::{parse_datetime::from_str, parse_datetime::tests::TEST_TIME, ParseDurationError}; #[test] fn test_t_sep() { @@ -177,13 +180,21 @@ mod tests { let actual = from_str(dt); assert_eq!(actual.unwrap().timestamp(), TEST_TIME); } + + #[test] + fn invalid_formats() { + let invalid_dts = vec!["NotADate", "202104", "202104-12T22:37:47"]; + for dt in invalid_dts { + assert_eq!(from_str(dt), Err(ParseDurationError::InvalidInput)); + } + } } #[cfg(test)] mod offsets { use chrono::Local; - use crate::parse_datetime::from_str; + use crate::{parse_datetime::from_str, ParseDurationError}; #[test] fn test_positive_offsets() { @@ -212,5 +223,13 @@ mod tests { assert_eq!(expected, format!("{}", actual.format("%Y%m%d%H%M%z"))); } } + + #[test] + fn invalid_offset_format() { + let invalid_offsets = vec!["+0700", "UTC+2", "Z-1", "UTC+01005"]; + for offset in invalid_offsets { + assert_eq!(from_str(offset), Err(ParseDurationError::InvalidInput)); + } + } } } From 8d797fcbcbe3c31aee0ac3cd3fb06a6bb3ce9d0f Mon Sep 17 00:00:00 2001 From: Ben Schofield Date: Mon, 5 Jun 2023 08:00:07 -0700 Subject: [PATCH 03/12] typo fix --- src/parse_datetime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parse_datetime.rs b/src/parse_datetime.rs index eae6ab8..d3952a8 100644 --- a/src/parse_datetime.rs +++ b/src/parse_datetime.rs @@ -6,7 +6,7 @@ use chrono::{DateTime, FixedOffset, Local, LocalResult, NaiveDateTime, TimeZone} use crate::ParseDurationError; /// Formats that parse input can take. -/// Taken from `touch` coreutil +/// Taken from `touch` coreutils mod format { pub(crate) const ISO_8601: &str = "%Y-%m-%d"; pub(crate) const ISO_8601_NO_SEP: &str = "%Y%m%d"; From 29e7a5c335767b879a538570d3a5f4f3ddb1102b Mon Sep 17 00:00:00 2001 From: Ben Schofield <47790940+Benjscho@users.noreply.github.com> Date: Mon, 5 Jun 2023 08:04:22 -0700 Subject: [PATCH 04/12] Update src/parse_datetime.rs Co-authored-by: Sylvestre Ledru --- src/parse_datetime.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/parse_datetime.rs b/src/parse_datetime.rs index d3952a8..47094ee 100644 --- a/src/parse_datetime.rs +++ b/src/parse_datetime.rs @@ -147,7 +147,9 @@ mod tests { mod iso_8601 { use std::env; - use crate::{parse_datetime::from_str, parse_datetime::tests::TEST_TIME, ParseDurationError}; + use crate::{ + parse_datetime::from_str, parse_datetime::tests::TEST_TIME, ParseDurationError, + }; #[test] fn test_t_sep() { From bc33770075dcc952dc0fac1ff734648c9d449bc4 Mon Sep 17 00:00:00 2001 From: Ben Schofield Date: Tue, 6 Jun 2023 10:05:14 -0700 Subject: [PATCH 05/12] Update README Update the README and add a test module to parse_datetime for any examples presented in the README. --- README.md | 22 ++++++++++++++++++++-- src/parse_datetime.rs | 13 +++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d832441..8a2267f 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ [![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/uutils/humantime_to_duration/blob/main/LICENSE) [![CodeCov](https://codecov.io/gh/uutils/humantime_to_duration/branch/main/graph/badge.svg)](https://codecov.io/gh/uutils/humantime_to_duration) -A Rust crate for parsing human-readable relative time strings and converting them to a `Duration`. +A Rust crate for parsing human-readable relative time strings and converting them to a `Duration`, or parsing human-readable datetime strings and converting them to a `DateTime`. ## Features -- Parses a variety of human-readable time formats. +- Parses a variety of human-readable and standard time formats. - Supports positive and negative durations. - Allows for chaining time units (e.g., "1 hour 2 minutes" or "2 days and 2 hours"). - Calculate durations relative to a specified date. @@ -39,6 +39,15 @@ assert_eq!( ); ``` +For DateTime parsing, import the `parse_datetime` module: +``` +use humantime_to_duration::parse_datetime::from_str; +use chrono::{Local, TimeZone}; + +let dt = from_str("2021-02-14 06:37:47"); +assert_eq!(dt.unwrap(), Local.with_ymd_and_hms(2021, 2, 14, 6, 37, 47).unwrap()); +``` + ### Supported Formats The `from_str` and `from_str_at_date` functions support the following formats for relative time: @@ -56,6 +65,8 @@ The `from_str` and `from_str_at_date` functions support the following formats fo ## Return Values +### Duration + The `from_str` and `from_str_at_date` functions return: - `Ok(Duration)` - If the input string can be parsed as a relative time @@ -64,6 +75,13 @@ The `from_str` and `from_str_at_date` functions return: This function will return `Err(ParseDurationError::InvalidInput)` if the input string cannot be parsed as a relative time. +### parse_datetime + +The `from_str` function returns: + +- `Ok(DateTime)` - If the input string can be prsed as a datetime +- `Err(ParseDurationError::InvalidInput)` - If the input string cannot be parsed + ## Fuzzer To run the fuzzer: diff --git a/src/parse_datetime.rs b/src/parse_datetime.rs index 47094ee..76bc719 100644 --- a/src/parse_datetime.rs +++ b/src/parse_datetime.rs @@ -234,4 +234,17 @@ mod tests { } } } + + /// Used to test example code presented in the README. + mod readme_test { + use crate::parse_datetime::from_str; + use chrono::{TimeZone, Local}; + + #[test] + fn test_readme_code() { + let dt = from_str("2021-02-14 06:37:47"); + assert_eq!(dt.unwrap(), Local.with_ymd_and_hms(2021, 2, 14, 6, 37, 47).unwrap()); + } + + } } From 7ee33d1fb1191766b134293621ebb4b31903fba1 Mon Sep 17 00:00:00 2001 From: Ben Schofield Date: Tue, 6 Jun 2023 10:26:05 -0700 Subject: [PATCH 06/12] cargo fmt --- src/parse_datetime.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/parse_datetime.rs b/src/parse_datetime.rs index 76bc719..fcf5fae 100644 --- a/src/parse_datetime.rs +++ b/src/parse_datetime.rs @@ -234,17 +234,19 @@ mod tests { } } } - + /// Used to test example code presented in the README. mod readme_test { use crate::parse_datetime::from_str; - use chrono::{TimeZone, Local}; + use chrono::{Local, TimeZone}; #[test] fn test_readme_code() { let dt = from_str("2021-02-14 06:37:47"); - assert_eq!(dt.unwrap(), Local.with_ymd_and_hms(2021, 2, 14, 6, 37, 47).unwrap()); + assert_eq!( + dt.unwrap(), + Local.with_ymd_and_hms(2021, 2, 14, 6, 37, 47).unwrap() + ); } - } } From dc508c469b22c620369a36b0295edd92cd3d4729 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 7 Jun 2023 13:51:40 +0200 Subject: [PATCH 07/12] add a fuzzer for parse_datetime_from_str --- fuzz/Cargo.lock | 2 +- fuzz/Cargo.toml | 6 ++++++ fuzz/fuzz_targets/parse_datetime_from_str.rs | 8 ++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 fuzz/fuzz_targets/parse_datetime_from_str.rs diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index ce7cb9a..f6ed013 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -104,7 +104,7 @@ dependencies = [ [[package]] name = "humantime_to_duration" -version = "0.3.0" +version = "0.3.1" dependencies = [ "chrono", "regex", diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index de1f042..daaff8d 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -20,3 +20,9 @@ name = "fuzz_from_str" path = "fuzz_targets/from_str.rs" test = false doc = false + +[[bin]] +name = "fuzz_parse_datetime_from_str" +path = "fuzz_targets/parse_datetime_from_str.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/parse_datetime_from_str.rs b/fuzz/fuzz_targets/parse_datetime_from_str.rs new file mode 100644 index 0000000..2db587b --- /dev/null +++ b/fuzz/fuzz_targets/parse_datetime_from_str.rs @@ -0,0 +1,8 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let s = std::str::from_utf8(data).unwrap_or(""); + let _ = humantime_to_duration::parse_datetime::from_str(s); +}); From 22bdd8f49ca65b4e6e54ae4e4754840bc6883d4f Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 7 Jun 2023 13:52:00 +0200 Subject: [PATCH 08/12] run the fuzzers in the CI --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c187f34..7bece31 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -164,3 +164,27 @@ jobs: flags: ${{ steps.vars.outputs.CODECOV_FLAGS }} name: codecov-umbrella fail_ci_if_error: false + + fuzz: + name: Run the fuzzers + runs-on: ubuntu-latest + env: + RUN_FOR: 60 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@nightly + - name: Install `cargo-fuzz` + run: cargo install cargo-fuzz + - uses: Swatinem/rust-cache@v2 + - name: Run from_str for XX seconds + shell: bash + run: | + ## Run it + cd fuzz + cargo +nightly fuzz run fuzz_from_str -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 + - name: Run fuzz_parse_datetime_from_str for XX seconds + shell: bash + run: | + ## Run it + cd fuzz + cargo +nightly fuzz run fuzz_parse_datetime_from_str -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0 From 80f8fdf8834d18de9d61d2fcb708ebd0fbe796aa Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 7 Jun 2023 15:01:25 +0200 Subject: [PATCH 09/12] README: syntax highlighting --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 885c1f3..2680073 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ assert_eq!( ``` For DateTime parsing, import the `parse_datetime` module: -``` +```rs use humantime_to_duration::parse_datetime::from_str; use chrono::{Local, TimeZone}; From 673d5b600e2e0b65eb7c26b2060dc48db7c1a41e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 9 Jun 2023 10:15:23 +0200 Subject: [PATCH 10/12] rename from humantime_to_duration to parse_datetime --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 4 ++-- README.md | 14 +++++++------- fuzz/Cargo.lock | 18 +++++++++--------- fuzz/Cargo.toml | 2 +- fuzz/fuzz_targets/from_str.rs | 2 +- fuzz/fuzz_targets/parse_datetime_from_str.rs | 2 +- src/lib.rs | 6 +++--- src/parse_datetime.rs | 2 +- tests/simple.rs | 2 +- 10 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 30d32c6..cbf9f8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,14 +68,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" -[[package]] -name = "humantime_to_duration" -version = "0.3.1" -dependencies = [ - "chrono", - "regex", -] - [[package]] name = "iana-time-zone" version = "0.1.56" @@ -141,6 +133,14 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "parse_datetime" +version = "0.3.1" +dependencies = [ + "chrono", + "regex", +] + [[package]] name = "proc-macro2" version = "1.0.59" diff --git a/Cargo.toml b/Cargo.toml index a527bb2..7612569 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "humantime_to_duration" +name = "parse_datetime" description = " parsing human-readable relative time strings and converting them to a Duration" version = "0.3.1" edition = "2021" license = "MIT" -repository = "https://github.com/uutils/humantime_to_duration" +repository = "https://github.com/uutils/parse_datetime" readme = "README.md" [dependencies] diff --git a/README.md b/README.md index 2680073..04d2447 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# humantime_to_duration +# parse_datetime -[![Crates.io](https://img.shields.io/crates/v/humantime_to_duration.svg)](https://crates.io/crates/humantime_to_duration) -[![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/uutils/humantime_to_duration/blob/main/LICENSE) -[![CodeCov](https://codecov.io/gh/uutils/humantime_to_duration/branch/main/graph/badge.svg)](https://codecov.io/gh/uutils/humantime_to_duration) +[![Crates.io](https://img.shields.io/crates/v/parse_datetime.svg)](https://crates.io/crates/parse_datetime) +[![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/uutils/parse_datetime/blob/main/LICENSE) +[![CodeCov](https://codecov.io/gh/uutils/parse_datetime/branch/main/graph/badge.svg)](https://codecov.io/gh/uutils/parse_datetime) A Rust crate for parsing human-readable relative time strings and converting them to a `Duration`, or parsing human-readable datetime strings and converting them to a `DateTime`. @@ -20,12 +20,12 @@ Add this to your `Cargo.toml`: ```toml [dependencies] -humantime_to_duration = "0.3.0" +parse_datetime = "0.3.0" ``` Then, import the crate and use the `from_str` and `from_str_at_date` functions: ```rs -use humantime_to_duration::{from_str, from_str_at_date}; +use parse_datetime::{from_str, from_str_at_date}; use chrono::Duration; let duration = from_str("+3 days"); @@ -41,7 +41,7 @@ assert_eq!( For DateTime parsing, import the `parse_datetime` module: ```rs -use humantime_to_duration::parse_datetime::from_str; +use parse_datetime::parse_datetime::from_str; use chrono::{Local, TimeZone}; let dt = from_str("2021-02-14 06:37:47"); diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index f6ed013..9785c58 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -85,8 +85,8 @@ name = "fuzz_from_str" version = "0.1.0" dependencies = [ "chrono", - "humantime_to_duration", "libfuzzer-sys", + "parse_datetime", "rand", "regex", ] @@ -102,14 +102,6 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] -[[package]] -name = "humantime_to_duration" -version = "0.3.1" -dependencies = [ - "chrono", - "regex", -] - [[package]] name = "iana-time-zone" version = "0.1.56" @@ -195,6 +187,14 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "parse_datetime" +version = "0.3.1" +dependencies = [ + "chrono", + "regex", +] + [[package]] name = "ppv-lite86" version = "0.2.17" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index daaff8d..7b705b2 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -12,7 +12,7 @@ libfuzzer-sys = "0.4" regex = "1.8.4" chrono = "0.4" -[dependencies.humantime_to_duration] +[dependencies.parse_datetime] path = "../" [[bin]] diff --git a/fuzz/fuzz_targets/from_str.rs b/fuzz/fuzz_targets/from_str.rs index 5dd52aa..63b55d1 100644 --- a/fuzz/fuzz_targets/from_str.rs +++ b/fuzz/fuzz_targets/from_str.rs @@ -4,5 +4,5 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { let s = std::str::from_utf8(data).unwrap_or(""); - let _ = humantime_to_duration::from_str(s); + let _ = parse_datetime::from_str(s); }); diff --git a/fuzz/fuzz_targets/parse_datetime_from_str.rs b/fuzz/fuzz_targets/parse_datetime_from_str.rs index 2db587b..7d285e5 100644 --- a/fuzz/fuzz_targets/parse_datetime_from_str.rs +++ b/fuzz/fuzz_targets/parse_datetime_from_str.rs @@ -4,5 +4,5 @@ use libfuzzer_sys::fuzz_target; fuzz_target!(|data: &[u8]| { let s = std::str::from_utf8(data).unwrap_or(""); - let _ = humantime_to_duration::parse_datetime::from_str(s); + let _ = parse_datetime::parse_datetime::from_str(s); }); diff --git a/src/lib.rs b/src/lib.rs index 21a4b46..8a104da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,7 @@ impl From for ParseDurationError { /// /// ``` /// use chrono::Duration; -/// let duration = humantime_to_duration::from_str("+3 days"); +/// let duration = parse_datetime::from_str("+3 days"); /// assert_eq!(duration.unwrap(), Duration::days(3)); /// ``` /// @@ -85,7 +85,7 @@ impl From for ParseDurationError { /// /// ``` /// use chrono::Duration; -/// use humantime_to_duration::{from_str, ParseDurationError}; +/// use parse_datetime::{from_str, ParseDurationError}; /// /// assert_eq!(from_str("1 hour, 30 minutes").unwrap(), Duration::minutes(90)); /// assert_eq!(from_str("tomorrow").unwrap(), Duration::days(1)); @@ -112,7 +112,7 @@ pub fn from_str(s: &str) -> Result { /// /// ``` /// use chrono::{Duration, NaiveDate, Utc, Local}; -/// use humantime_to_duration::{from_str_at_date, ParseDurationError}; +/// use parse_datetime::{from_str_at_date, ParseDurationError}; /// let today = Local::now().date().naive_local(); /// let yesterday = today - Duration::days(1); /// assert_eq!( diff --git a/src/parse_datetime.rs b/src/parse_datetime.rs index fcf5fae..d6f5514 100644 --- a/src/parse_datetime.rs +++ b/src/parse_datetime.rs @@ -36,7 +36,7 @@ mod format { /// /// ``` /// use chrono::{DateTime, Utc, TimeZone}; -/// let time = humantime_to_duration::parse_datetime::from_str("2023-06-03 12:00:01Z"); +/// let time = parse_datetime::parse_datetime::from_str("2023-06-03 12:00:01Z"); /// assert_eq!(time.unwrap(), Utc.with_ymd_and_hms(2023, 06, 03, 12, 00, 01).unwrap()); /// ``` /// diff --git a/tests/simple.rs b/tests/simple.rs index 366f318..a538f9d 100644 --- a/tests/simple.rs +++ b/tests/simple.rs @@ -1,5 +1,5 @@ use chrono::{Duration, Utc}; -use humantime_to_duration::{from_str, from_str_at_date, ParseDurationError}; +use parse_datetime::{from_str, from_str_at_date, ParseDurationError}; #[test] fn test_invalid_input() { From 7ab1ad2f0e491e8d52c8a9a6e4e7d10377cbcabb Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 9 Jun 2023 13:43:04 +0200 Subject: [PATCH 11/12] document the old name --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 04d2447..a53b38b 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ cannot be parsed as a relative time. The `from_str` function returns: - `Ok(DateTime)` - If the input string can be prsed as a datetime -- `Err(ParseDurationError::InvalidInput)` - If the input string cannot be parsed +- `Err(ParseDurationError::InvalidInput)` - If the input string cannot be parsed ## Fuzzer @@ -92,3 +92,8 @@ $ cargo fuzz run fuzz_from_str ## License This project is licensed under the [MIT License](LICENSE). + +## Note + +At some point, this crate was called humantime_to_duration. +It has been renamed to cover more cases. From bed2d16e67aa63190b2a9b45f9737ba9c404f991 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 9 Jun 2023 13:43:54 +0200 Subject: [PATCH 12/12] Release version 0.4.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- README.md | 2 +- fuzz/Cargo.lock | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cbf9f8d..8cd1830 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,7 +135,7 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "parse_datetime" -version = "0.3.1" +version = "0.4.0" dependencies = [ "chrono", "regex", diff --git a/Cargo.toml b/Cargo.toml index 7612569..d571e37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "parse_datetime" description = " parsing human-readable relative time strings and converting them to a Duration" -version = "0.3.1" +version = "0.4.0" edition = "2021" license = "MIT" repository = "https://github.com/uutils/parse_datetime" diff --git a/README.md b/README.md index a53b38b..9cab9aa 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Add this to your `Cargo.toml`: ```toml [dependencies] -parse_datetime = "0.3.0" +parse_datetime = "0.4.0" ``` Then, import the crate and use the `from_str` and `from_str_at_date` functions: diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 9785c58..9ab97bf 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -189,7 +189,7 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "parse_datetime" -version = "0.3.1" +version = "0.4.0" dependencies = [ "chrono", "regex",