1 Commits

Author SHA1 Message Date
Sylvestre Ledru bf6f14bc09 version 0.2.1 2023-05-12 13:57:25 +02:00
19 changed files with 626 additions and 3488 deletions
+56 -55
View File
@@ -4,7 +4,6 @@ name: Basic CI
env:
CARGO_TERM_COLOR: always
RUST_MIN_SRV: "1.71.1"
jobs:
check:
@@ -14,9 +13,15 @@ jobs:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo check
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: check
test:
name: cargo test
@@ -25,18 +30,31 @@ jobs:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo test
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: actions-rs/cargo@v1
with:
command: test
fmt:
name: cargo fmt --all -- --check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- run: rustup component add rustfmt
- run: cargo fmt --all -- --check
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
clippy:
name: cargo clippy -- -D warnings
@@ -45,23 +63,17 @@ jobs:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: rustup component add clippy
- run: cargo clippy --all-targets -- -D warnings
min_version:
name: Minimum Supported Rust Version
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_MIN_SRV }}
- run: cargo test
profile: minimal
toolchain: stable
override: true
- run: rustup component add clippy
- uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
coverage:
name: Code Coverage
@@ -74,7 +86,7 @@ jobs:
- { os: macos-latest , features: macos }
- { os: windows-latest , features: windows }
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Initialize workflow variables
id: vars
shell: bash
@@ -89,22 +101,28 @@ jobs:
if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
outputs TOOLCHAIN
# target-specific options
# * CARGO_FEATURES_OPTION
CARGO_FEATURES_OPTION='--all -- --check' ; ## default to '--all-features' for code coverage
# * CODECOV_FLAGS
CODECOV_FLAGS=$( echo "${{ matrix.job.os }}" | sed 's/[^[:alnum:]]/_/g' )
outputs CODECOV_FLAGS
- name: rust toolchain ~ install
uses: dtolnay/rust-toolchain@nightly
uses: actions-rs/toolchain@v1
with:
components: llvm-tools-preview
toolchain: ${{ steps.vars.outputs.TOOLCHAIN }}
default: true
profile: minimal # minimal component installation (ie, no documentation)
- name: Test
run: cargo test --no-fail-fast
uses: actions-rs/cargo@v1
with:
command: test
args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast
env:
CARGO_INCREMENTAL: "0"
RUSTC_WRAPPER: ""
RUSTFLAGS: "-Cinstrument-coverage -Zcoverage-options=branch -Ccodegen-units=1 -Copt-level=0 -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
RUSTDOCFLAGS: "-Cpanic=abort"
LLVM_PROFILE_FILE: "parse_datetime-%p-%m.profraw"
- name: "`grcov` ~ install"
id: build_grcov
shell: bash
@@ -132,34 +150,17 @@ jobs:
COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info"
mkdir -p "${COVERAGE_REPORT_DIR}"
# display coverage files
grcov . --binary-path="${COVERAGE_REPORT_DIR}" --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique
grcov . --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique
# generate coverage report
grcov . --binary-path="${COVERAGE_REPORT_DIR}" --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"
grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"
echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT
- name: Upload coverage results (to Codecov.io)
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v3
# if: steps.vars.outputs.HAS_CODECOV_TOKEN
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ${{ steps.coverage.outputs.report }}
# token: ${{ secrets.CODECOV_TOKEN }}
file: ${{ steps.coverage.outputs.report }}
## flags: IntegrationTests, UnitTests, ${{ steps.vars.outputs.CODECOV_FLAGS }}
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@v4
- 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_parse_datetime -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
Generated
+23 -309
View File
@@ -4,185 +4,32 @@ version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
dependencies = [
"memchr",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
name = "humantime_to_duration"
version = "0.2.1"
dependencies = [
"libc",
"regex",
"time",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bumpalo"
version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "cc"
version = "1.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
dependencies = [
"android-tzdata",
"iana-time-zone",
"num-traits",
"windows-link",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "iana-time-zone"
version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "memchr"
version = "2.7.4"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "parse_datetime"
version = "0.10.0"
dependencies = [
"chrono",
"num-traits",
"regex",
"winnow",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "regex"
version = "1.11.1"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
dependencies = [
"aho-corasick",
"memchr",
@@ -191,161 +38,28 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.5"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
[[package]]
name = "rustversion"
version = "1.0.20"
name = "serde"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
[[package]]
name = "shlex"
version = "1.3.0"
name = "time"
version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
"serde",
"time-core",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
name = "time-core"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "windows-core"
version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]]
name = "windows-result"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
dependencies = [
"windows-link",
]
[[package]]
name = "winnow"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec"
dependencies = [
"memchr",
]
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
+6 -9
View File
@@ -1,15 +1,12 @@
[package]
name = "parse_datetime"
description = "parsing human-readable time strings and converting them to a DateTime"
version = "0.10.0"
name = "humantime_to_duration"
description = " parsing human-readable relative time strings and converting them to a Duration"
version = "0.2.1"
edition = "2021"
license = "MIT"
repository = "https://github.com/uutils/parse_datetime"
repository = "https://github.com/uutils/humantime_to_duration"
readme = "README.md"
rust-version = "1.71.1"
[dependencies]
regex = "1.10.4"
chrono = { version="0.4.38", default-features=false, features=["std", "alloc", "clock"] }
winnow = "0.7.10"
num-traits = "0.2.19"
regex = "1.7"
time = "0.3.20"
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2023 Sylvestre Ledru and others
Copyright (c) 2023 uutils
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+27 -45
View File
@@ -1,55 +1,46 @@
# parse_datetime
# 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)
[![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)
A Rust crate for parsing human-readable relative time strings and human-readable datetime strings and converting them to a `DateTime`.
A Rust crate for parsing human-readable relative time strings and converting them to a `Duration`.
## Features
- Parses a variety of human-readable and standard time formats.
- Parses a variety of human-readable 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.
- Relies on Chrono
## Usage
Add `parse_datetime` to your `Cargo.toml` with:
Add this to your `Cargo.toml`:
```
cargo add parse_datetime
```toml
[dependencies]
humantime_to_duration = "0.2.1"
```
Then, import the crate and use the `parse_datetime_at_date` function:
Then, import the crate and use the `from_str` and `from_str_at_date` functions:
```
use humantime_to_duration::{from_str, from_str_at_date};
use time::Duration;
```rs
use chrono::{Duration, Local};
use parse_datetime::parse_datetime_at_date;
let now = Local::now();
let after = parse_datetime_at_date(now, "+3 days");
let duration = from_str("+3 days");
assert_eq!(duration.unwrap(), Duration::days(3));
let today = OffsetDateTime::now_utc().date();
let yesterday = today - Duration::days(1);
assert_eq!(
(now + Duration::days(3)).naive_utc(),
after.unwrap().naive_utc()
from_str_at_date(yesterday, "2 days").unwrap(),
Duration::days(1)
);
```
For DateTime parsing, import the `parse_datetime` function:
```rs
use parse_datetime::parse_datetime;
use chrono::{Local, TimeZone};
let dt = parse_datetime("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 `parse_datetime` and `parse_datetime_at_date` functions support absolute datetime and the following relative times:
The `from_str` and `from_str_at_date` functions support the following formats for relative time:
- `num` `unit` (e.g., "-1 hour", "+3 days")
- `unit` (e.g., "hour", "day")
@@ -57,37 +48,28 @@ The `parse_datetime` and `parse_datetime_at_date` functions support absolute dat
- "yesterday"
- "tomorrow"
- use "ago" for the past
- use "next" or "last" with `unit` (e.g., "next week", "last year")
- combined units with "and" or "," (e.g., "2 years and 1 month", "1 day, 2 hours" or "2 weeks 1 second")
- unix timestamps (for example "@0" "@1344000")
`num` can be a positive or negative integer.
`unit` can be one of the following: "fortnight", "week", "day", "hour", "minute", "min", "second", "sec" and their plural forms.
## Return Values
### parse_datetime and parse_datetime_at_date
The `from_str` and `from_str_at_date` functions return:
The `parse_datetime` and `parse_datetime_at_date` function return:
- `Ok(Duration)` - If the input string can be parsed as a relative time
- `Err(ParseDurationError)` - If the input string cannot be parsed as a relative time
- `Ok(DateTime<FixedOffset>)` - If the input string can be parsed as a datetime
- `Err(ParseDateTimeError::InvalidInput)` - If the input string cannot be parsed
This function will return `Err(ParseDurationError::InvalidInput)` if the input string
cannot be parsed as a relative time.
## Fuzzer
To run the fuzzer:
```
$ cd fuzz
$ cargo install cargo-fuzz
$ cargo +nightly fuzz run fuzz_parse_datetime
$ 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.
-1
View File
@@ -1 +0,0 @@
corpus
+64 -279
View File
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
version = 3
[[package]]
name = "aho-corasick"
@@ -11,48 +11,19 @@ dependencies = [
"memchr",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "arbitrary"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bumpalo"
version = "3.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
[[package]]
name = "cc"
version = "1.2.10"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
dependencies = [
"jobserver",
"libc",
"shlex",
]
[[package]]
@@ -62,74 +33,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
name = "fuzz_from_str"
version = "0.1.0"
dependencies = [
"android-tzdata",
"iana-time-zone",
"num-traits",
"windows-link",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "fuzz_parse_datetime"
version = "0.2.0"
dependencies = [
"chrono",
"humantime_to_duration",
"libfuzzer-sys",
"parse_datetime",
"rand",
"regex",
"time",
]
[[package]]
name = "iana-time-zone"
version = "0.1.61"
name = "getrandom"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
name = "humantime_to_duration"
version = "0.1.0"
dependencies = [
"cc",
"regex",
"time",
]
[[package]]
name = "jobserver"
version = "0.1.32"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.142"
@@ -138,34 +79,20 @@ checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "libfuzzer-sys"
version = "0.4.9"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75"
checksum = "beb09950ae85a0a94b27676cccf37da5ff13f27076aa1adbc6545dd0d0e1bd4e"
dependencies = [
"arbitrary",
"cc",
"once_cell",
]
[[package]]
name = "log"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de"
[[package]]
name = "memchr"
version = "2.6.2"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "once_cell"
@@ -174,50 +101,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "parse_datetime"
version = "0.10.0"
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"chrono",
"num-traits",
"regex",
"winnow",
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "proc-macro2"
version = "1.0.59"
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"unicode-ident",
"ppv-lite86",
"rand_core",
]
[[package]]
name = "quote"
version = "1.0.28"
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"proc-macro2",
"getrandom",
]
[[package]]
name = "regex"
version = "1.11.1"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
dependencies = [
"aho-corasick",
"memchr",
@@ -226,172 +149,34 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.5"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
[[package]]
name = "shlex"
version = "1.3.0"
name = "serde"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
[[package]]
name = "syn"
version = "2.0.18"
name = "time"
version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
"serde",
"time-core",
]
[[package]]
name = "unicode-ident"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
[[package]]
name = "wasm-bindgen"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
dependencies = [
"cfg-if",
"once_cell",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-link"
name = "time-core"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
[[package]]
name = "windows-targets"
version = "0.52.6"
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec"
dependencies = [
"memchr",
]
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+10 -9
View File
@@ -1,21 +1,22 @@
[package]
name = "fuzz_parse_datetime"
version = "0.2.0"
edition = "2021"
name = "fuzz_from_str"
version = "0.1.0"
edition = "2018"
[package.metadata]
cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.4.7"
regex = "1.10.4"
chrono = { version="0.4", default-features=false, features=["std", "alloc", "clock"] }
rand = "0.8.4"
libfuzzer-sys = "0.4"
regex = "1.8.1"
time = "0.3.20"
[dependencies.parse_datetime]
[dependencies.humantime_to_duration]
path = "../"
[[bin]]
name = "fuzz_parse_datetime"
path = "fuzz_targets/parse_datetime.rs"
name = "fuzz_from_str"
path = "fuzz_targets/from_str.rs"
test = false
doc = false
@@ -4,5 +4,5 @@ use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
let s = std::str::from_utf8(data).unwrap_or("");
let _ = parse_datetime::parse_datetime(s);
let _ = humantime_to_duration::from_str(s);
});
-6
View File
@@ -1,6 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
]
}
-78
View File
@@ -1,78 +0,0 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//! Parse an ISO 8601 date and time item
//!
//! The GNU docs state:
//!
//! > The ISO 8601 date and time of day extended format consists of an ISO 8601
//! > date, a T character separator, and an ISO 8601 time of day. This format
//! > is also recognized if the T is replaced by a space.
//! >
//! > In this format, the time of day should use 24-hour notation. Fractional
//! > seconds are allowed, with either comma or period preceding the fraction.
//! > ISO 8601 fractional minutes and hours are not supported. Typically, hosts
//! > support nanosecond timestamp resolution; excess precision is silently discarded.
use winnow::{
combinator::{alt, trace},
seq, ModalResult, Parser,
};
use crate::items::space;
use super::{
date::{self, Date},
s,
time::{self, Time},
};
#[derive(PartialEq, Debug, Clone, Default)]
pub struct DateTime {
pub(crate) date: Date,
pub(crate) time: Time,
}
pub fn parse(input: &mut &str) -> ModalResult<DateTime> {
seq!(DateTime {
date: trace("date iso", alt((date::iso1, date::iso2))),
// Note: the `T` is lowercased by the main parse function
_: alt((s('t').void(), (' ', space).void())),
time: trace("time iso", time::iso),
})
.parse_next(input)
}
#[cfg(test)]
mod tests {
use super::{parse, DateTime};
use crate::items::{date::Date, time::Time};
#[test]
fn some_date() {
let reference = Some(DateTime {
date: Date {
day: 10,
month: 10,
year: Some(2022),
},
time: Time {
hour: 10,
minute: 10,
second: 55.0,
offset: None,
},
});
for mut s in [
"2022-10-10t10:10:55",
"2022-10-10 10:10:55",
"2022-10-10 t 10:10:55",
"2022-10-10 10:10:55",
"2022-10-10 (A comment!) t 10:10:55",
"2022-10-10 (A comment!) 10:10:55",
] {
let old_s = s.to_owned();
assert_eq!(parse(&mut s).ok(), reference, "Failed string: {old_s}")
}
}
}
-284
View File
@@ -1,284 +0,0 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//! Parse a date item (without time component)
//!
//! The GNU docs say:
//!
//! > A calendar date item specifies a day of the year. It is specified
//! > differently, depending on whether the month is specified numerically
//! > or literally.
//! >
//! > ...
//! >
//! > For numeric months, the ISO 8601 format year-month-day is allowed,
//! > where year is any positive number, month is a number between 01 and
//! > 12, and day is a number between 01 and 31. A leading zero must be
//! > present if a number is less than ten. If year is 68 or smaller, then
//! > 2000 is added to it; otherwise, if year is less than 100, then 1900
//! > is added to it. The construct month/day/year, popular in the United
//! > States, is accepted. Also month/day, omitting the year.
//! >
//! > Literal months may be spelled out in full: January, February,
//! > March, April, May, June, July, August, September,
//! > October, November or December. Literal months may be
//! > abbreviated to their first three letters, possibly followed by an
//! > abbreviating dot. It is also permitted to write Sept instead of
//! > September.
use winnow::{
ascii::alpha1,
combinator::{alt, opt, preceded, trace},
seq,
stream::AsChar,
token::{take, take_while},
ModalResult, Parser,
};
use super::{dec_uint, s};
use crate::ParseDateTimeError;
#[derive(PartialEq, Eq, Clone, Debug, Default)]
pub struct Date {
pub day: u32,
pub month: u32,
pub year: Option<u32>,
}
pub fn parse(input: &mut &str) -> ModalResult<Date> {
alt((iso1, iso2, us, literal1, literal2)).parse_next(input)
}
/// Parse `YYYY-MM-DD` or `YY-MM-DD`
///
/// This is also used by [`combined`](super::combined).
pub fn iso1(input: &mut &str) -> ModalResult<Date> {
seq!(Date {
year: year.map(Some),
_: s('-'),
month: month,
_: s('-'),
day: day,
})
.parse_next(input)
}
/// Parse `YYYYMMDD`
///
/// This is also used by [`combined`](super::combined).
pub fn iso2(input: &mut &str) -> ModalResult<Date> {
s((
take(4usize).try_map(|s: &str| s.parse::<u32>()),
take(2usize).try_map(|s: &str| s.parse::<u32>()),
take(2usize).try_map(|s: &str| s.parse::<u32>()),
))
.map(|(year, month, day): (u32, u32, u32)| Date {
day,
month,
year: Some(year),
})
.parse_next(input)
}
/// Parse `MM/DD/YYYY`, `MM/DD/YY` or `MM/DD`
fn us(input: &mut &str) -> ModalResult<Date> {
seq!(Date {
month: month,
_: s('/'),
day: day,
year: opt(preceded(s('/'), year)),
})
.parse_next(input)
}
/// Parse `14 November 2022`, `14 Nov 2022`, "14nov2022", "14-nov-2022", "14-nov2022", "14nov-2022"
fn literal1(input: &mut &str) -> ModalResult<Date> {
seq!(Date {
day: day,
_: opt(s('-')),
month: literal_month,
year: opt(preceded(opt(s('-')), year)),
})
.parse_next(input)
}
/// Parse `November 14, 2022` and `Nov 14, 2022`
fn literal2(input: &mut &str) -> ModalResult<Date> {
seq!(Date {
month: literal_month,
day: day,
// FIXME: GNU requires _some_ space between the day and the year,
// probably to distinguish with floats.
year: opt(preceded(s(","), year)),
})
.parse_next(input)
}
pub fn year(input: &mut &str) -> ModalResult<u32> {
// 2147485547 is the maximum value accepted
// by GNU, but chrono only behaves like GNU
// for years in the range: [0, 9999], so we
// keep in the range [0, 9999]
trace(
"year",
s(
take_while(1..=4, AsChar::is_dec_digit).map(|number_str: &str| {
let year = number_str.parse::<u32>().unwrap();
if number_str.len() == 2 {
if year <= 68 {
year + 2000
} else {
year + 1900
}
} else {
year
}
}),
),
)
.parse_next(input)
}
fn month(input: &mut &str) -> ModalResult<u32> {
s(dec_uint)
.try_map(|x| {
(1..=12)
.contains(&x)
.then_some(x)
.ok_or(ParseDateTimeError::InvalidInput)
})
.parse_next(input)
}
fn day(input: &mut &str) -> ModalResult<u32> {
s(dec_uint)
.try_map(|x| {
(1..=31)
.contains(&x)
.then_some(x)
.ok_or(ParseDateTimeError::InvalidInput)
})
.parse_next(input)
}
/// Parse the name of a month (case-insensitive)
fn literal_month(input: &mut &str) -> ModalResult<u32> {
s(alpha1)
.verify_map(|s: &str| {
Some(match s {
"january" | "jan" => 1,
"february" | "feb" => 2,
"march" | "mar" => 3,
"april" | "apr" => 4,
"may" => 5,
"june" | "jun" => 6,
"july" | "jul" => 7,
"august" | "aug" => 8,
"september" | "sep" | "sept" => 9,
"october" | "oct" => 10,
"november" | "nov" => 11,
"december" | "dec" => 12,
_ => return None,
})
})
.parse_next(input)
}
#[cfg(test)]
mod tests {
use super::{parse, Date};
// Test cases from the GNU docs:
//
// ```
// 2022-11-14 # ISO 8601.
// 22-11-14 # Assume 19xx for 69 through 99,
// # 20xx for 00 through 68 (not recommended).
// 11/14/2022 # Common U.S. writing.
// 14 November 2022
// 14 Nov 2022 # Three-letter abbreviations always allowed.
// November 14, 2022
// 14-nov-2022
// 14nov2022
// ```
#[test]
fn with_year() {
let reference = Date {
year: Some(2022),
month: 11,
day: 14,
};
for mut s in [
"2022-11-14",
"2022 - 11 - 14",
"22-11-14",
"2022---11----14",
"22(comment 1)-11(comment 2)-14",
"11/14/2022",
"11--/14--/2022",
"11(comment 1)/(comment 2)14(comment 3)/(comment 4)2022",
"11 / 14 / 2022",
"11/14/22",
"14 november 2022",
"14 nov 2022",
"november 14, 2022",
"november 14 , 2022",
"nov 14, 2022",
"14-nov-2022",
"14nov2022",
"14nov 2022",
] {
let old_s = s.to_owned();
assert_eq!(parse(&mut s).unwrap(), reference, "Format string: {old_s}");
}
}
#[test]
fn no_year() {
let reference = Date {
year: None,
month: 11,
day: 14,
};
for mut s in [
"11/14",
"14 november",
"14 nov",
"14(comment!)nov",
"november 14",
"november(comment!)14",
"nov 14",
"14-nov",
"14nov",
"14(comment????)nov",
] {
assert_eq!(parse(&mut s).unwrap(), reference);
}
}
#[test]
fn test_year() {
use super::year;
// the minimun input length is 2
// assert!(year(&mut "0").is_err());
// -> GNU accepts year 0
// test $(date -d '1-1-1' '+%Y') -eq '0001'
// test $(date -d '68-1-1' '+%Y') -eq '2068'
// 2-characters are converted to 19XX/20XX
assert_eq!(year(&mut "10").unwrap(), 2010u32);
assert_eq!(year(&mut "68").unwrap(), 2068u32);
assert_eq!(year(&mut "69").unwrap(), 1969u32);
assert_eq!(year(&mut "99").unwrap(), 1999u32);
// 3,4-characters are converted verbatim
assert_eq!(year(&mut "468").unwrap(), 468u32);
assert_eq!(year(&mut "469").unwrap(), 469u32);
assert_eq!(year(&mut "1568").unwrap(), 1568u32);
assert_eq!(year(&mut "1569").unwrap(), 1569u32);
// consumes at most 4 characters from the input
//assert_eq!(year(&mut "1234567").unwrap(), 1234u32);
}
}
-543
View File
File diff suppressed because it is too large Load Diff
-46
View File
@@ -1,46 +0,0 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use super::s;
use winnow::{
ascii::{alpha1, dec_uint},
combinator::{alt, opt},
ModalResult, Parser,
};
pub fn ordinal(input: &mut &str) -> ModalResult<i32> {
alt((text_ordinal, number_ordinal)).parse_next(input)
}
fn number_ordinal(input: &mut &str) -> ModalResult<i32> {
let sign = opt(alt(('+'.value(1), '-'.value(-1)))).map(|s| s.unwrap_or(1));
(s(sign), s(dec_uint))
.verify_map(|(s, u): (i32, u32)| {
let i: i32 = u.try_into().ok()?;
Some(s * i)
})
.parse_next(input)
}
fn text_ordinal(input: &mut &str) -> ModalResult<i32> {
s(alpha1)
.verify_map(|s: &str| {
Some(match s {
"last" => -1,
"this" => 0,
"next" | "first" => 1,
"third" => 3,
"fourth" => 4,
"fifth" => 5,
"sixth" => 6,
"seventh" => 7,
"eight" => 8,
"ninth" => 9,
"tenth" => 10,
"eleventh" => 11,
"twelfth" => 12,
_ => return None,
})
})
.parse_next(input)
}
-190
View File
@@ -1,190 +0,0 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//! Parse a relative datetime item
//!
//! The GNU docs state:
//!
//! > The unit of time displacement may be selected by the string year or
//! > month for moving by whole years or months. These are fuzzy units, as
//! > years and months are not all of equal duration. More precise units are
//! > fortnight which is worth 14 days, week worth 7 days, day worth 24
//! > hours, hour worth 60 minutes, minute or min worth 60 seconds, and
//! > second or sec worth one second. An s suffix on these units is
//! > accepted and ignored.
//! >
//! > The unit of time may be preceded by a multiplier, given as an optionally
//! > signed number. Unsigned numbers are taken as positively signed. No number
//! > at all implies 1 for a multiplier. Following a relative item by the
//! > string ago is equivalent to preceding the unit by a multiplier with
//! > value -1.
//! >
//! > The string tomorrow is worth one day in the future (equivalent to
//! > day), the string yesterday is worth one day in the past (equivalent
//! > to day ago).
//! >
//! > The strings now or today are relative items corresponding to
//! > zero-valued time displacement, these strings come from the fact a
//! > zero-valued time displacement represents the current time when not
//! > otherwise changed by previous items. They may be used to stress other
//! > items, like in 12:00 today. The string this also has the meaning of a
//! > zero-valued time displacement, but is preferred in date strings like
//! > this thursday.
use winnow::{
ascii::{alpha1, float},
combinator::{alt, opt},
ModalResult, Parser,
};
use super::{ordinal::ordinal, s};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Relative {
Years(i32),
Months(i32),
Days(i32),
Hours(i32),
Minutes(i32),
// Seconds are special because they can be given as a float
Seconds(f64),
}
impl Relative {
fn mul(self, n: i32) -> Self {
match self {
Self::Years(x) => Self::Years(n * x),
Self::Months(x) => Self::Months(n * x),
Self::Days(x) => Self::Days(n * x),
Self::Hours(x) => Self::Hours(n * x),
Self::Minutes(x) => Self::Minutes(n * x),
Self::Seconds(x) => Self::Seconds(f64::from(n) * x),
}
}
}
pub fn parse(input: &mut &str) -> ModalResult<Relative> {
alt((
s("tomorrow").value(Relative::Days(1)),
s("yesterday").value(Relative::Days(-1)),
// For "today" and "now", the unit is arbitrary
s("today").value(Relative::Days(0)),
s("now").value(Relative::Days(0)),
seconds,
other,
))
.parse_next(input)
}
fn seconds(input: &mut &str) -> ModalResult<Relative> {
(
opt(alt((s(float), ordinal.map(|x| x as f64)))),
s(alpha1).verify(|s: &str| matches!(s, "seconds" | "second" | "sec" | "secs")),
ago,
)
.map(|(n, _, ago)| Relative::Seconds(n.unwrap_or(1.0) * if ago { -1.0 } else { 1.0 }))
.parse_next(input)
}
fn other(input: &mut &str) -> ModalResult<Relative> {
(opt(ordinal), integer_unit, ago)
.map(|(n, unit, ago)| unit.mul(n.unwrap_or(1) * if ago { -1 } else { 1 }))
.parse_next(input)
}
fn ago(input: &mut &str) -> ModalResult<bool> {
opt(s("ago")).map(|o| o.is_some()).parse_next(input)
}
fn integer_unit(input: &mut &str) -> ModalResult<Relative> {
s(alpha1)
.verify_map(|s: &str| {
Some(match s.strip_suffix('s').unwrap_or(s) {
"year" => Relative::Years(1),
"month" => Relative::Months(1),
"fortnight" => Relative::Days(14),
"week" => Relative::Days(7),
"day" => Relative::Days(1),
"hour" => Relative::Hours(1),
"minute" | "min" => Relative::Minutes(1),
_ => return None,
})
})
.parse_next(input)
}
#[cfg(test)]
mod tests {
use super::{parse, Relative};
#[test]
fn all() {
for (s, rel) in [
// Seconds
("second", Relative::Seconds(1.0)),
("sec", Relative::Seconds(1.0)),
("seconds", Relative::Seconds(1.0)),
("secs", Relative::Seconds(1.0)),
("second ago", Relative::Seconds(-1.0)),
("3 seconds", Relative::Seconds(3.0)),
("3.5 seconds", Relative::Seconds(3.5)),
// ("+3.5 seconds", Relative::Seconds(3.5)),
("3.5 seconds ago", Relative::Seconds(-3.5)),
("-3.5 seconds ago", Relative::Seconds(3.5)),
// Minutes
("minute", Relative::Minutes(1)),
("minutes", Relative::Minutes(1)),
("min", Relative::Minutes(1)),
("mins", Relative::Minutes(1)),
("10 minutes", Relative::Minutes(10)),
("-10 minutes", Relative::Minutes(-10)),
("10 minutes ago", Relative::Minutes(-10)),
("-10 minutes ago", Relative::Minutes(10)),
// Hours
("hour", Relative::Hours(1)),
("hours", Relative::Hours(1)),
("10 hours", Relative::Hours(10)),
("+10 hours", Relative::Hours(10)),
("-10 hours", Relative::Hours(-10)),
("10 hours ago", Relative::Hours(-10)),
("-10 hours ago", Relative::Hours(10)),
// Days
("day", Relative::Days(1)),
("days", Relative::Days(1)),
("10 days", Relative::Days(10)),
("+10 days", Relative::Days(10)),
("-10 days", Relative::Days(-10)),
("10 days ago", Relative::Days(-10)),
("-10 days ago", Relative::Days(10)),
// Multiple days
("fortnight", Relative::Days(14)),
("fortnights", Relative::Days(14)),
("2 fortnights ago", Relative::Days(-28)),
("+2 fortnights ago", Relative::Days(-28)),
("week", Relative::Days(7)),
("weeks", Relative::Days(7)),
("2 weeks ago", Relative::Days(-14)),
// Other
("year", Relative::Years(1)),
("years", Relative::Years(1)),
("month", Relative::Months(1)),
("months", Relative::Months(1)),
// Special
("yesterday", Relative::Days(-1)),
("tomorrow", Relative::Days(1)),
("today", Relative::Days(0)),
("now", Relative::Days(0)),
// This something
("this day", Relative::Days(0)),
("this second", Relative::Seconds(0.0)),
("this year", Relative::Years(0)),
// Weird stuff
("next week ago", Relative::Days(-7)),
("last week ago", Relative::Days(7)),
("this week ago", Relative::Days(0)),
] {
let mut t = s;
assert_eq!(parse(&mut t).ok(), Some(rel), "Failed string: {s}")
}
}
}
-853
View File
File diff suppressed because it is too large Load Diff
-137
View File
@@ -1,137 +0,0 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore wednes
//! The GNU docs state:
//!
//! > The explicit mention of a day of the week will forward the date (only if
//! > necessary) to reach that day of the week in the future.
//! >
//! > Days of the week may be spelled out in full: Sunday, Monday,
//! > Tuesday, Wednesday, Thursday, Friday or Saturday. Days may be
//! > abbreviated to their first three letters, optionally followed by a
//! > period. The special abbreviations Tues for Tuesday, Wednes for
//! > Wednesday and Thur or Thurs for Thursday are also allowed.
//! >
//! > A number may precede a day of the week item to move forward supplementary
//! > weeks. It is best used in expression like third monday. In this
//! > context, last day or next day is also acceptable; they move one week
//! > before or after the day that day by itself would represent.
//! >
//! > A comma following a day of the week item is ignored.
use winnow::{ascii::alpha1, combinator::opt, seq, ModalResult, Parser};
use super::{ordinal::ordinal, s};
#[derive(PartialEq, Eq, Debug)]
pub(crate) enum Day {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}
#[derive(PartialEq, Eq, Debug)]
pub struct Weekday {
pub(crate) offset: i32,
pub(crate) day: Day,
}
impl From<Day> for chrono::Weekday {
fn from(value: Day) -> Self {
match value {
Day::Monday => chrono::Weekday::Mon,
Day::Tuesday => chrono::Weekday::Tue,
Day::Wednesday => chrono::Weekday::Wed,
Day::Thursday => chrono::Weekday::Thu,
Day::Friday => chrono::Weekday::Fri,
Day::Saturday => chrono::Weekday::Sat,
Day::Sunday => chrono::Weekday::Sun,
}
}
}
pub fn parse(input: &mut &str) -> ModalResult<Weekday> {
seq!(Weekday {
offset: opt(ordinal).map(|o| o.unwrap_or_default()),
day: day,
})
.parse_next(input)
}
fn day(input: &mut &str) -> ModalResult<Day> {
s(alpha1)
.verify_map(|s: &str| {
Some(match s {
"monday" | "mon" | "mon." => Day::Monday,
"tuesday" | "tue" | "tue." | "tues" => Day::Tuesday,
"wednesday" | "wed" | "wed." | "wednes" => Day::Wednesday,
"thursday" | "thu" | "thu." | "thur" | "thurs" => Day::Thursday,
"friday" | "fri" | "fri." => Day::Friday,
"saturday" | "sat" | "sat." => Day::Saturday,
"sunday" | "sun" | "sun." => Day::Sunday,
_ => return None,
})
})
.parse_next(input)
}
#[cfg(test)]
mod tests {
use super::{parse, Day, Weekday};
#[test]
fn this_monday() {
for mut s in [
"monday",
"mon",
"mon.",
"this monday",
"this mon",
"this mon.",
"this monday",
"this - monday",
"0 monday",
] {
assert_eq!(
parse(&mut s).unwrap(),
Weekday {
offset: 0,
day: Day::Monday,
}
);
}
}
#[test]
fn next_tuesday() {
for s in ["tuesday", "tue", "tue.", "tues"] {
let s = format!("next {s}");
assert_eq!(
parse(&mut s.as_ref()).unwrap(),
Weekday {
offset: 1,
day: Day::Tuesday,
}
);
}
}
#[test]
fn last_wednesday() {
for s in ["wednesday", "wed", "wed.", "wednes"] {
let s = format!("last {s}");
assert_eq!(
parse(&mut s.as_ref()).unwrap(),
Weekday {
offset: -1,
day: Day::Wednesday,
}
);
}
}
}
+287 -642
View File
File diff suppressed because it is too large Load Diff
+151
View File
@@ -1 +1,152 @@
use humantime_to_duration::{from_str, from_str_at_date, ParseDurationError};
use time::{Duration, OffsetDateTime};
#[test]
fn test_invalid_input() {
let result = from_str("foobar");
println!("{result:?}");
assert_eq!(result, Err(ParseDurationError::InvalidInput));
let result = from_str("invalid 1");
assert_eq!(result, Err(ParseDurationError::InvalidInput));
}
#[test]
fn test_duration_parsing() {
assert_eq!(from_str("1 year").unwrap(), Duration::seconds(31_536_000));
assert_eq!(
from_str("-2 years").unwrap(),
Duration::seconds(-63_072_000)
);
assert_eq!(
from_str("2 years ago").unwrap(),
Duration::seconds(-63_072_000)
);
assert_eq!(from_str("year").unwrap(), Duration::seconds(31_536_000));
assert_eq!(from_str("1 month").unwrap(), Duration::seconds(2_592_000));
assert_eq!(
from_str("1 month and 2 weeks").unwrap(),
Duration::seconds(3_801_600)
);
assert_eq!(
from_str("1 month, 2 weeks").unwrap(),
Duration::seconds(3_801_600)
);
assert_eq!(
from_str("1 months 2 weeks").unwrap(),
Duration::seconds(3_801_600)
);
assert_eq!(
from_str("1 month and 2 weeks ago").unwrap(),
Duration::seconds(-3_801_600)
);
assert_eq!(from_str("2 months").unwrap(), Duration::seconds(5_184_000));
assert_eq!(from_str("month").unwrap(), Duration::seconds(2_592_000));
assert_eq!(
from_str("1 fortnight").unwrap(),
Duration::seconds(1_209_600)
);
assert_eq!(
from_str("3 fortnights").unwrap(),
Duration::seconds(3_628_800)
);
assert_eq!(from_str("fortnight").unwrap(), Duration::seconds(1_209_600));
assert_eq!(from_str("1 week").unwrap(), Duration::seconds(604_800));
assert_eq!(
from_str("1 week 3 days").unwrap(),
Duration::seconds(864_000)
);
assert_eq!(
from_str("1 week 3 days ago").unwrap(),
Duration::seconds(-864_000)
);
assert_eq!(from_str("-2 weeks").unwrap(), Duration::seconds(-1_209_600));
assert_eq!(
from_str("2 weeks ago").unwrap(),
Duration::seconds(-1_209_600)
);
assert_eq!(from_str("week").unwrap(), Duration::seconds(604_800));
assert_eq!(from_str("1 day").unwrap(), Duration::seconds(86_400));
assert_eq!(from_str("2 days ago").unwrap(), Duration::seconds(-172_800));
assert_eq!(from_str("-2 days").unwrap(), Duration::seconds(-172_800));
assert_eq!(from_str("day").unwrap(), Duration::seconds(86_400));
assert_eq!(from_str("1 hour").unwrap(), Duration::seconds(3_600));
assert_eq!(from_str("1 h").unwrap(), Duration::seconds(3_600));
assert_eq!(from_str("1 hour ago").unwrap(), Duration::seconds(-3_600));
assert_eq!(from_str("-2 hours").unwrap(), Duration::seconds(-7_200));
assert_eq!(from_str("hour").unwrap(), Duration::seconds(3_600));
assert_eq!(from_str("1 minute").unwrap(), Duration::seconds(60));
assert_eq!(from_str("1 min").unwrap(), Duration::seconds(60));
assert_eq!(from_str("2 minutes").unwrap(), Duration::seconds(120));
assert_eq!(from_str("2 mins").unwrap(), Duration::seconds(120));
assert_eq!(from_str("2m").unwrap(), Duration::seconds(120));
assert_eq!(from_str("min").unwrap(), Duration::seconds(60));
assert_eq!(from_str("1 second").unwrap(), Duration::seconds(1));
assert_eq!(from_str("1 s").unwrap(), Duration::seconds(1));
assert_eq!(from_str("2 seconds").unwrap(), Duration::seconds(2));
assert_eq!(from_str("2 secs").unwrap(), Duration::seconds(2));
assert_eq!(from_str("2 sec").unwrap(), Duration::seconds(2));
assert_eq!(from_str("sec").unwrap(), Duration::seconds(1));
assert_eq!(from_str("now").unwrap(), Duration::ZERO);
assert_eq!(from_str("today").unwrap(), Duration::ZERO);
assert_eq!(
from_str("1 year 2 months 4 weeks 3 days and 2 seconds").unwrap(),
Duration::seconds(39_398_402)
);
assert_eq!(
from_str("1 year 2 months 4 weeks 3 days and 2 seconds ago").unwrap(),
Duration::seconds(-39_398_402)
);
}
#[test]
fn test_display_parse_duration_error_through_from_str() {
let invalid_input = "9223372036854775807 seconds and 1 second";
let error = from_str(invalid_input).unwrap_err();
assert_eq!(
format!("{error}"),
"Invalid input string: cannot be parsed as a relative time"
);
}
#[test]
fn test_display_should_fail() {
let invalid_input = "Thu Jan 01 12:34:00 2015";
let error = from_str(invalid_input).unwrap_err();
assert_eq!(
format!("{error}"),
"Invalid input string: cannot be parsed as a relative time"
);
}
#[test]
fn test_from_str_at_date_day() {
let today = OffsetDateTime::now_utc().date();
let yesterday = today - Duration::days(1);
assert_eq!(
from_str_at_date(yesterday, "2 days").unwrap(),
Duration::days(1)
);
}
#[test]
fn test_invalid_input_at_date() {
let today = OffsetDateTime::now_utc().date();
let result = from_str_at_date(today, "foobar");
println!("{result:?}");
assert_eq!(result, Err(ParseDurationError::InvalidInput));
let result = from_str_at_date(today, "invalid 1r");
assert_eq!(result, Err(ParseDurationError::InvalidInput));
}