diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d779205..da859c3 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,7 +1,7 @@ name: Lint & Test on: pull_request: - types: [opened, edited, reopened, synchronize] + types: [ opened, edited, reopened, synchronize ] concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -11,11 +11,11 @@ jobs: test: strategy: matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] + platform: [ ubuntu-latest, macos-latest, windows-latest ] runs-on: ${{ matrix.platform }} steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust Stable uses: dtolnay/rust-toolchain@stable diff --git a/CHANGELOG.md b/CHANGELOG.md index 810cbfd..85367e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,33 +1,55 @@ # Changelog -All notable changes to this project will be documented in this file. +All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [0.5.0] - 2025-06-22 + +### Changed + +- Updated deps. +- Updated CI config. +- Updated documentation. +- Updated to Rust 2024 edition. +- Optimized parsing performance for unknown alpha paths by pre-looking date part lookup prior to parsing. + ## [0.4.0] - 2023-12-29 + ### Changed + - Updated deps. - Updated CI config. - Update + Add documentation. - Add addition supported date format `%A %B %e %Y` (e.g. `Sunday, April 18th, 2021`). ## [0.3.0] - 2021-11-14 + ### Added + - Serde deserialize_with helper functions. - Examples directory ## [0.2.0] - 2021-11-09 + ### Changed + - Fixed Cargo.toml categories ## [0.1.0] - 2021-11-09 + ### Added + - Initial Release [Unreleased]: https://github.com/rust-playground/anydate/compare/v0.4.0...HEAD + [0.4.0]: https://github.com/rust-playground/anydate/compare/v0.3.0...v0.4.0 + [0.3.0]: https://github.com/rust-playground/anydate/compare/v0.2.0...v0.3.0 + [0.2.0]: https://github.com/rust-playground/anydate/compare/v0.1.0...v0.2.0 + [0.1.0]: https://github.com/rust-playground/anydate/commit/4ac9022aeeb9d2911a763651f70987cb8d98d47d \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 54e12c1..dbecd74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "anydate" description = "Date & DateTime string parser" -version = "0.4.0" -edition = "2021" +version = "0.5.0" +edition = "2024" authors = ["Dean Karn "] license = "MIT OR Apache-2.0" readme = "README.md" @@ -19,12 +19,12 @@ harness = false name = "bench" [dependencies] -chrono = { version = "0.4.31", default-features = false, features = ["std"] } -serde = {version = "1.0.192", features = ["derive"], optional = true } -thiserror = "1.0.50" +chrono = { version = "0.4.40", default-features = false, features = ["std"] } +serde = { version = "1.0.218", features = ["derive"], optional = true } +thiserror = "2.0.11" [dev-dependencies] -criterion = { version = "0.5.1", features = ["html_reports"] } +criterion = { version = "0.6.0", features = ["html_reports"] } serde_json = "1.0.108" [lib] diff --git a/README.md b/README.md index 4fd1d22..0c0d950 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # anydate   [![Latest Version]][crates.io] [Latest Version]: https://img.shields.io/crates/v/anydate.svg + [crates.io]: https://crates.io/crates/anydate This crate is used to parse an unknown DateTime or Date format into a normalized version. @@ -11,9 +12,10 @@ Any significant changes to anydate are documented in the [`CHANGELOG.md`](https://github.com/rust-playground/anydate/blob/main/CHANGELOG.md) file. ## Usage + ```toml [dependencies] -anydate = "0.4" +anydate = "0.5" ``` ### Features @@ -25,6 +27,7 @@ Optional features: [`serde`]: https://github.com/serde-rs/serde ### Example usages + ```rust fn main() -> Result<(), Box> { // see parse_utc() for convenience conversion to UTC diff --git a/src/date.rs b/src/date.rs index 287b885..46a6d1e 100644 --- a/src/date.rs +++ b/src/date.rs @@ -30,20 +30,33 @@ pub(crate) fn parse_with_alpha(s: &str) -> Result { fn parse_naive_dates(s: &str) -> Result { // Date parse formats - const PARSE_FORMATS: &[&str] = &[ - "%Y-%m-%d", - "%Y/%m/%d", - "%Y.%m.%d", - "%m/%d/%y", - "%m/%d/%Y", - "%m.%d.%y", - "%m.%d.%Y", - "%Y-%b-%d", - "%d %B %y", - "%d %B %Y", - "%Y年%m月%d日", - ]; - PARSE_FORMATS + const PARSE_FORMATS_DASHES: &[&str] = &["%Y-%m-%d", "%Y-%b-%d"]; + + const PARSE_FORMATS_SLASHES: &[&str] = &["%Y/%m/%d", "%m/%d/%y", "%m/%d/%Y"]; + + const PARSE_FORMATS_DOT: &[&str] = &["%Y.%m.%d", "%m.%d.%y", "%m.%d.%Y"]; + + const PARSE_FORMATS_SPACE: &[&str] = &["%d %B %y", "%d %B %Y"]; + + const PARSE_FORMATS_REMAINING: &[&str] = &["%Y年%m月%d日"]; + + for c in s.chars() { + if !c.is_alphanumeric() { + return match c { + '-' => PARSE_FORMATS_DASHES, + '/' => PARSE_FORMATS_SLASHES, + '.' => PARSE_FORMATS_DOT, + ' ' => PARSE_FORMATS_SPACE, + _ => break, + } + .iter() + .map(|fmt| NaiveDate::parse_from_str(s, fmt)) + .find_map(Result::ok) + .map_or_else(|| Err(Error::InvalidDate), Ok); + } + } + + PARSE_FORMATS_REMAINING .iter() .map(|fmt| NaiveDate::parse_from_str(s, fmt)) .find_map(Result::ok) @@ -114,6 +127,7 @@ mod tests { *expected, parse(input)? .and_time(NaiveTime::from_num_seconds_from_midnight_opt(0, 0).unwrap()) + .and_utc() .timestamp_nanos_opt() .unwrap() ); diff --git a/src/datetime.rs b/src/datetime.rs index 7a81005..7b40f69 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -138,31 +138,48 @@ fn parse_naive_datetime(s: &str) -> Result, Error> { fn parse_utc_naive_datetime_unknown_alpha(s: &str) -> Result, Error> { // DateTimes without timezone info - const PARSE_FORMATS: &[&str] = &[ + const PARSE_FORMATS_DASHES: &[&str] = &[ "%Y-%m-%d %H:%M:%S%.f", "%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", + "%Y-%m-%d %I:%M:%S %P", + "%Y-%m-%d %I:%M %P", + ]; + + const PARSE_FORMATS_SLASHES: &[&str] = &[ "%m/%d/%y %H:%M:%S", "%m/%d/%y %H:%M", "%m/%d/%y %H:%M:%S%.f", "%m/%d/%Y %H:%M:%S", "%m/%d/%Y %H:%M", "%m/%d/%Y %H:%M:%S%.f", - "%y%m%d %H:%M:%S", "%Y/%m/%d %H:%M:%S", "%Y/%m/%d %H:%M", "%Y/%m/%d %H:%M:%S%.f", - "%Y-%m-%d %I:%M:%S %P", - "%Y-%m-%d %I:%M %P", "%m/%d/%y %I:%M:%S %P", "%m/%d/%y %I:%M %P", "%m/%d/%Y %I:%M:%S %P", "%m/%d/%Y %I:%M %P", "%Y/%m/%d %I:%M:%S %P", "%Y/%m/%d %I:%M %P", - "%Y年%m月%d日%H时%M分%S秒", ]; - parse_utc_naive_datetime(s, PARSE_FORMATS) + + const PARSE_FORMATS_REMAINING: &[&str] = &["%y%m%d %H:%M:%S", "%Y年%m月%d日%H时%M分%S秒"]; + + for c in s.chars() { + if !c.is_alphanumeric() { + return parse_utc_naive_datetime( + s, + match c { + '-' => PARSE_FORMATS_DASHES, + '/' => PARSE_FORMATS_SLASHES, + _ => break, + }, + ); + } + } + + parse_utc_naive_datetime(s, PARSE_FORMATS_REMAINING) } fn parse_utc_naive_datetime_alpha_prefix(s: &str) -> Result, Error> { @@ -211,9 +228,9 @@ fn parse_utc_naive_datetime(s: &str, formats: &[&str]) -> Result Result de::Visitor<'de> for AnydateVisitor { +impl de::Visitor<'_> for AnydateVisitor { type Value = DateTime; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -40,7 +40,7 @@ pub mod deserialize { //! println!("{:?}", dt); //! //! ``` - use super::{de, AnydateVisitor, DateTime, FixedOffset, Utc}; + use super::{AnydateVisitor, DateTime, FixedOffset, Utc, de}; /// deserializes to a [`DateTime`] /// @@ -143,7 +143,7 @@ pub mod deserialize { None => { assert_eq!(s.dt, None); } - }; + } } Ok(()) } @@ -197,7 +197,7 @@ pub mod deserialize { None => { assert_eq!(s.dt, None); } - }; + } } Ok(()) }