Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -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 }}
Expand All @@ -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
Expand Down
24 changes: 23 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 <dean.karn@gmail.com>"]
license = "MIT OR Apache-2.0"
readme = "README.md"
Expand All @@ -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]
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# anydate &emsp; [![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.
Expand All @@ -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
Expand All @@ -25,6 +27,7 @@ Optional features:
[`serde`]: https://github.com/serde-rs/serde

### Example usages

```rust
fn main() -> Result<(), Box<dyn std::error::Error>> {
// see parse_utc() for convenience conversion to UTC
Expand Down
42 changes: 28 additions & 14 deletions src/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,33 @@ pub(crate) fn parse_with_alpha(s: &str) -> Result<NaiveDate, Error> {

fn parse_naive_dates(s: &str) -> Result<NaiveDate, Error> {
// 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)
Expand Down Expand Up @@ -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()
);
Expand Down
37 changes: 27 additions & 10 deletions src/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,31 +138,48 @@ fn parse_naive_datetime(s: &str) -> Result<DateTime<FixedOffset>, Error> {

fn parse_utc_naive_datetime_unknown_alpha(s: &str) -> Result<DateTime<FixedOffset>, 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<DateTime<FixedOffset>, Error> {
Expand Down Expand Up @@ -211,9 +228,9 @@ fn parse_utc_naive_datetime(s: &str, formats: &[&str]) -> Result<DateTime<FixedO
)
}

// last ditch effort, timezone abbreviation can't 100% relied upon.
// last ditch effort, timezone abbreviation can't 100% be relied upon.
//
// It is not possible to reliably convert from an abbreviation to an offset, for example CDT can
// It is not possible to reliably convert from an abbreviation to an offset; for example, CDT can
// mean either Central Daylight Time (North America) or China Daylight Time.
//
// list sourced from https://www.utctime.net/time-zone-abbreviations
Expand All @@ -231,9 +248,9 @@ fn parse_timezone_abbreviation_unknown_alpha(s: &str) -> Result<DateTime<FixedOf
)
}

// last ditch effort, timezone abbreviation can't 100% relied upon.
// last ditch effort, timezone abbreviation can't 100% be relied upon.
//
// It is not possible to reliably convert from an abbreviation to an offset, for example CDT can
// It is not possible to reliably convert from an abbreviation to an offset; for example, CDT can
// mean either Central Daylight Time (North America) or China Daylight Time.
//
// list sourced from https://www.utctime.net/time-zone-abbreviations
Expand Down
8 changes: 4 additions & 4 deletions src/serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use serde::de;

struct AnydateVisitor;

impl<'de> de::Visitor<'de> for AnydateVisitor {
impl de::Visitor<'_> for AnydateVisitor {
type Value = DateTime<FixedOffset>;

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
Expand Down Expand Up @@ -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<FixedOffset>`]
///
Expand Down Expand Up @@ -143,7 +143,7 @@ pub mod deserialize {
None => {
assert_eq!(s.dt, None);
}
};
}
}
Ok(())
}
Expand Down Expand Up @@ -197,7 +197,7 @@ pub mod deserialize {
None => {
assert_eq!(s.dt, None);
}
};
}
}
Ok(())
}
Expand Down