You've already forked parse_datetime
mirror of
https://github.com/uutils/parse_datetime.git
synced 2026-06-10 16:13:15 -07:00
Compare commits
389 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba69fb8772 | |||
| 912bc4e706 | |||
| c239ed0b36 | |||
| 3074fe3baa | |||
| 29e368662c | |||
| 1e757df39b | |||
| 7a32b1ee3d | |||
| c898cd84de | |||
| eb610870e1 | |||
| 9db7a54926 | |||
| e43652c0ee | |||
| d9c2ed2b11 | |||
| 6a16e61ab1 | |||
| f0c499593e | |||
| 5453d6777a | |||
| 0c09d5bd17 | |||
| 5662a1b47f | |||
| f75ec46d63 | |||
| 2dc4fa3ffb | |||
| 69649bf6c6 | |||
| 583eafe62c | |||
| 6815ba1406 | |||
| a4c29bafcb | |||
| e70d066e03 | |||
| e5f9894b11 | |||
| 1f4ebd93c6 | |||
| 74dd77db53 | |||
| 6f15b90e36 | |||
| 4db8bf4b22 | |||
| 84d27e6386 | |||
| a0f1376d2f | |||
| 59eac72c44 | |||
| d6bde134df | |||
| 43842ebf8e | |||
| c31711d351 | |||
| ea5d61b3eb | |||
| c55bea1d8b | |||
| 6baf7e301d | |||
| 795d7e6c43 | |||
| 3f4bd53eba | |||
| ec30e0e1f2 | |||
| 70055eaa21 | |||
| 871cae962e | |||
| 91692a17e0 | |||
| e225cf0557 | |||
| 8e2816d187 | |||
| 84d4e294df | |||
| c87e103745 | |||
| 7d0c3e4500 | |||
| ae25053473 | |||
| 17847a8d9c | |||
| 6fce02949d | |||
| cc5029c177 | |||
| 1b96dd5707 | |||
| b5439d8cc2 | |||
| d2e6e7819d | |||
| a8e26ab539 | |||
| 920cfa5f1b | |||
| a79ce1fc31 | |||
| c87b6f19eb | |||
| 25f9e48e55 | |||
| 14b8a9447c | |||
| 692f17526d | |||
| 5c686bcfdf | |||
| c86b137e6f | |||
| f075ba8d8d | |||
| 5ca5586523 | |||
| 2bc18c52b7 | |||
| 3eb58ab00a | |||
| 628011ae97 | |||
| 42d09dadf7 | |||
| 908356f3a3 | |||
| 65965a85d1 | |||
| 551985ef91 | |||
| 8fa8928a6a | |||
| fa90303836 | |||
| 46a5ef89c9 | |||
| 226d6a0588 | |||
| 28b9f11f2e | |||
| b68c325abd | |||
| 27a626d3d5 | |||
| 1630fd025e | |||
| cf5770838c | |||
| db9ff29c02 | |||
| 4cfe8db9be | |||
| 1e7a541528 | |||
| b2bb6dfe91 | |||
| d71c6ea41c | |||
| 3b6362e7c8 | |||
| 897a50f4d5 | |||
| 96f477121f | |||
| c47ce6870a | |||
| 77e1ab2d00 | |||
| a48ad6aec3 | |||
| 0ddeec70b4 | |||
| 2bb8ac8203 | |||
| 5b52929c91 | |||
| 3d6f79d826 | |||
| 324d0bca34 | |||
| 9f75f895e0 | |||
| 5ba4b18655 | |||
| 5d869b5264 | |||
| 793d18dce7 | |||
| 7182760266 | |||
| e7a8a52a21 | |||
| 0674254bc6 | |||
| 8bebfab576 | |||
| 6feeddfea1 | |||
| 6499842e15 | |||
| 949e0613d6 | |||
| 6e6ffc2dff | |||
| 67364fc6ba | |||
| d92f5eeb27 | |||
| 5c4a60fadf | |||
| 45bda33a6b | |||
| 64069cdbf2 | |||
| 4d6568b326 | |||
| f5c1a5e399 | |||
| 706ab45937 | |||
| 4d9afba2d4 | |||
| 05677a803d | |||
| 98d4931b53 | |||
| 0b5d3ffdf3 | |||
| 24fa78a927 | |||
| c0ada32ce1 | |||
| b5b1fd2b5e | |||
| 8d78de85a5 | |||
| 33ac3b75cc | |||
| 832942c019 | |||
| e031c8ce49 | |||
| 856a13ddb2 | |||
| f1f2c47120 | |||
| 2c0fa5a264 | |||
| 937572d557 | |||
| a7d9d1ab59 | |||
| 3be37c4d82 | |||
| 95897465b2 | |||
| 7260b0ae73 | |||
| 92daee09de | |||
| 73f69b1da0 | |||
| 30cb26d75c | |||
| 6e64f21967 | |||
| 9b67307512 | |||
| 76d8f17350 | |||
| c05b8217e0 | |||
| 41a53a2610 | |||
| 03cff2ce1e | |||
| 12f73c3d57 | |||
| c694587fdd | |||
| 3fe06c0b94 | |||
| 1a6a6f688a | |||
| 294bd5eeb2 | |||
| 60745300ad | |||
| 1552608bce | |||
| ee9beb07d3 | |||
| 6f073addf5 | |||
| a9676626be | |||
| da0af495e8 | |||
| e00555cd96 | |||
| ab91b2b6d7 | |||
| cc7c143b21 | |||
| 7721ae2cd8 | |||
| 557c3a3ae3 | |||
| bea8ec2641 | |||
| 852e49e224 | |||
| 2390ef0b25 | |||
| 77156f46d2 | |||
| bc6b70bc9e | |||
| 2479c61dd5 | |||
| 4c3d2221da | |||
| 07d4b80ea5 | |||
| b481a2bdec | |||
| a387463373 | |||
| b6795d9b2e | |||
| 3bdafeb2cf | |||
| b9630d4bd3 | |||
| 65fb9140d2 | |||
| 9b22a18bce | |||
| 28b788a300 | |||
| f6b6b3e4ea | |||
| 1a93bb4e3e | |||
| 9fc966308e | |||
| 7119769488 | |||
| 46a82b70b3 | |||
| e7ed14e8a4 | |||
| 9184428407 | |||
| 25825df8b7 | |||
| 0dbce58014 | |||
| 0c0a054f70 | |||
| 52dca03c56 | |||
| d4384ceec9 | |||
| 46cc4147c7 | |||
| 93e1c9bfe7 | |||
| e7a7a0b07f | |||
| d4353d0888 | |||
| 8aee979de5 | |||
| f1fada62a6 | |||
| ca6a39c0a7 | |||
| db18db4e1d | |||
| c337aba9cb | |||
| 63cfc01e80 | |||
| 1daaeb42a2 | |||
| 3fcbac6fae | |||
| 65cd15dba9 | |||
| 65c88f3586 | |||
| 2c55db21fa | |||
| badc887606 | |||
| f8adcb6c88 | |||
| d0dceb6eaf | |||
| 45b9d9f09a | |||
| 0965130c1b | |||
| 51ddcb793f | |||
| 7985aa04ba | |||
| 5c0c9c4405 | |||
| 8dd8051eb7 | |||
| 073b9f7b70 | |||
| 0318dabe3a | |||
| 57fe123e21 | |||
| 2fe83cabf1 | |||
| 94d4112707 | |||
| eda2422bb3 | |||
| c948274c42 | |||
| b209341259 | |||
| 9cd22afa29 | |||
| a7061bce12 | |||
| ea49dc94f0 | |||
| 938aa9694b | |||
| 51e294724c | |||
| fa53ec79bd | |||
| 90d22a2c37 | |||
| 06aac3f56f | |||
| 240abf791e | |||
| 34f226fd98 | |||
| 00dc90dd10 | |||
| 3c67f4b5c9 | |||
| faf363c2e6 | |||
| 56410cebcf | |||
| d777be1bb6 | |||
| 6429ef26c5 | |||
| fa311c0dd4 | |||
| 7ab9a02761 | |||
| 78877a1b98 | |||
| 623c626b3a | |||
| a9bccfa20a | |||
| dba1374f8e | |||
| 9b266bcb17 | |||
| 5e3fc539a3 | |||
| c26d83fd6e | |||
| a841ad0fc4 | |||
| 8ca89bdb55 | |||
| 48852068de | |||
| 3813a3bae9 | |||
| 313de7ba7c | |||
| 7913ecd888 | |||
| 8a528468ae | |||
| 212c6f7c24 | |||
| cd7020f038 | |||
| e543ee64a7 | |||
| 19d49cfe73 | |||
| 86d72fc867 | |||
| bbcafc42f9 | |||
| 1570d92fc5 | |||
| e41e26fef1 | |||
| d5015a641e | |||
| 5e6bddb6f5 | |||
| ced645a4f4 | |||
| d75338a51b | |||
| d4f67bbde0 | |||
| ba19b94acd | |||
| f7f789913a | |||
| 7e84a62b9e | |||
| db8c1d364f | |||
| e1aa18d263 | |||
| a05bd1d06e | |||
| a75107c69d | |||
| 0f5b6bf16e | |||
| 1bb93d7fe9 | |||
| 138999131b | |||
| fc14854394 | |||
| be45c2734d | |||
| acf9b9c76e | |||
| 04494f6ddb | |||
| 20a39748f0 | |||
| 113100e669 | |||
| ff2113eabe | |||
| ad9a056826 | |||
| d1263e0e7e | |||
| 5b81981a8a | |||
| 85957480b3 | |||
| 0d04eeb08d | |||
| 0dddb4b726 | |||
| deffff9803 | |||
| 96d8c7c294 | |||
| 249c079790 | |||
| 261b28db22 | |||
| cdc1b64fcf | |||
| 9e27be1e1a | |||
| 9bb047eeab | |||
| 040561c235 | |||
| 9009bfc4ed | |||
| c39e01748c | |||
| 325ae3a408 | |||
| 1ded0a062c | |||
| 0047868a35 | |||
| 5d11c142b5 | |||
| 8ef448acb5 | |||
| 0ad9ef3e6b | |||
| 4dfa20c7b0 | |||
| e8ab1a46e2 | |||
| 57d3527d81 | |||
| 48255280aa | |||
| bddcc6067f | |||
| be7fe1dc12 | |||
| 70fde9fa18 | |||
| 2d05b14a2d | |||
| 96db1a281e | |||
| 93c02dc0b7 | |||
| 29d641389d | |||
| adb5fabfa7 | |||
| 02e5c22985 | |||
| c6feb9a251 | |||
| 852638fef0 | |||
| 5c34517469 | |||
| 47c5f3620b | |||
| 1e43cc945f | |||
| f9ce5047a5 | |||
| 4c3fd2b1ed | |||
| 9a7f87ac09 | |||
| 0b0aa70193 | |||
| 0c44becec8 | |||
| 64dfea4b47 | |||
| 0bdabd5303 | |||
| 2112830d8b | |||
| 2737b4a0f2 | |||
| f10749eade | |||
| a7380508bc | |||
| 7f0a909752 | |||
| ff0f99bea5 | |||
| dc0d8a3aa0 | |||
| b615effc4e | |||
| cebf55cf82 | |||
| efee20bf01 | |||
| c084001056 | |||
| a35530ed4a | |||
| 6cfcc985a8 | |||
| 497bebb9e7 | |||
| dfd43a6d65 | |||
| 51dd843ded | |||
| c24d593821 | |||
| bed2d16e67 | |||
| 7ab1ad2f0e | |||
| c1a2bef4a6 | |||
| 673d5b600e | |||
| 83e1431db0 | |||
| 80f8fdf883 | |||
| 22bdd8f49c | |||
| dc508c469b | |||
| e503de7559 | |||
| 7ee33d1fb1 | |||
| bc33770075 | |||
| 70d3e53604 | |||
| 1ba787d1c3 | |||
| 33cefd7db2 | |||
| d0d913e161 | |||
| c784a721ee | |||
| 29e7a5c335 | |||
| 8d797fcbcb | |||
| c177117428 | |||
| 03c8e81a10 | |||
| 7547f31b76 | |||
| 1c32733f38 | |||
| 5278394b7c | |||
| e53aabd914 | |||
| e5d7fbcece | |||
| 060998e90e | |||
| a272f55948 | |||
| 9c5da7959c | |||
| a60f1d89f5 | |||
| 1d78cc5a3f | |||
| 6ba55a075b | |||
| b439960eb1 | |||
| f1a15ab2b3 | |||
| 0bd4dd716a | |||
| 674909c0f9 | |||
| 57bcf7f433 | |||
| 2e80358014 | |||
| 15fb185d95 | |||
| e64c163f47 | |||
| 86378bc326 |
+54
-55
@@ -4,6 +4,7 @@ name: Basic CI
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUST_MIN_SRV: "1.71.1"
|
||||
|
||||
jobs:
|
||||
check:
|
||||
@@ -13,15 +14,9 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
- uses: actions/checkout@v5
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo check
|
||||
|
||||
test:
|
||||
name: cargo test
|
||||
@@ -30,31 +25,18 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
- uses: actions/checkout@v5
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo test
|
||||
|
||||
fmt:
|
||||
name: cargo fmt --all -- --check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions/checkout@v5
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: rustup component add rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
- run: cargo fmt --all -- --check
|
||||
|
||||
clippy:
|
||||
name: cargo clippy -- -D warnings
|
||||
@@ -63,17 +45,23 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions/checkout@v5
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: rustup component add clippy
|
||||
- uses: actions-rs/cargo@v1
|
||||
- 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@v5
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
toolchain: ${{ env.RUST_MIN_SRV }}
|
||||
- run: cargo test
|
||||
|
||||
coverage:
|
||||
name: Code Coverage
|
||||
@@ -86,7 +74,7 @@ jobs:
|
||||
- { os: macos-latest , features: macos }
|
||||
- { os: windows-latest , features: windows }
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v5
|
||||
- name: Initialize workflow variables
|
||||
id: vars
|
||||
shell: bash
|
||||
@@ -101,28 +89,22 @@ 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: actions-rs/toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
toolchain: ${{ steps.vars.outputs.TOOLCHAIN }}
|
||||
default: true
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
components: llvm-tools-preview
|
||||
- name: Test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast
|
||||
run: cargo test --no-fail-fast
|
||||
env:
|
||||
CARGO_INCREMENTAL: "0"
|
||||
RUSTC_WRAPPER: ""
|
||||
RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
|
||||
RUSTFLAGS: "-Cinstrument-coverage -Zcoverage-options=branch -Ccodegen-units=1 -Copt-level=0 -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
|
||||
@@ -150,17 +132,34 @@ jobs:
|
||||
COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info"
|
||||
mkdir -p "${COVERAGE_REPORT_DIR}"
|
||||
# display coverage files
|
||||
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
|
||||
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
|
||||
# generate coverage report
|
||||
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\()"
|
||||
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\()"
|
||||
echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT
|
||||
- name: Upload coverage results (to Codecov.io)
|
||||
uses: codecov/codecov-action@v3
|
||||
# if: steps.vars.outputs.HAS_CODECOV_TOKEN
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||
file: ${{ steps.coverage.outputs.report }}
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ${{ 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@v5
|
||||
- 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
+410
-24
@@ -4,32 +4,239 @@ version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.1"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime_to_duration"
|
||||
version = "0.2.1"
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"time",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
name = "futures-task"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"jiff-tzdb-platform",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-tzdb"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524"
|
||||
|
||||
[[package]]
|
||||
name = "jiff-tzdb-platform"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8"
|
||||
dependencies = [
|
||||
"jiff-tzdb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse_datetime"
|
||||
version = "0.13.1"
|
||||
dependencies = [
|
||||
"jiff",
|
||||
"num-traits",
|
||||
"rstest",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.8.1"
|
||||
version = "1.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
|
||||
checksum = "8b5288124840bee7b386bc413c487869b360b2b4ec421ea56425128692f2a82c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "833eb9ce86d40ef33cb1306d8accf7bc8ec2bfea4355cbdebb3df68b40925cad"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -38,28 +245,207 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.1"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.160"
|
||||
name = "relative-path"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
|
||||
checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.20"
|
||||
name = "rstest"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
|
||||
checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"time-core",
|
||||
"futures-timer",
|
||||
"futures-util",
|
||||
"rstest_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.0"
|
||||
name = "rstest_macros"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
|
||||
checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"glob",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"relative-path",
|
||||
"rustc_version",
|
||||
"syn",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.221"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "341877e04a22458705eb4e131a1508483c877dca2792b3781d4e5d8a6019ec43"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.221"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c459bc0a14c840cb403fc14b148620de1e0778c96ecd6e0c8c3cacb6d8d00fe"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.221"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6185cf75117e20e62b1ff867b9518577271e58abe0037c40bb4794969355ab0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
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.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
+11
-6
@@ -1,12 +1,17 @@
|
||||
[package]
|
||||
name = "humantime_to_duration"
|
||||
description = " parsing human-readable relative time strings and converting them to a Duration"
|
||||
version = "0.2.1"
|
||||
name = "parse_datetime"
|
||||
description = "parsing human-readable time strings and converting them to a DateTime"
|
||||
version = "0.13.1"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/uutils/humantime_to_duration"
|
||||
repository = "https://github.com/uutils/parse_datetime"
|
||||
readme = "README.md"
|
||||
rust-version = "1.71.1"
|
||||
|
||||
[dependencies]
|
||||
regex = "1.7"
|
||||
time = "0.3.20"
|
||||
winnow = "0.7.10"
|
||||
num-traits = "0.2.19"
|
||||
jiff = { version = "0.2.15", default-features = false, features = ["tz-system", "tzdb-bundle-platform", "tzdb-zoneinfo"] }
|
||||
|
||||
[dev-dependencies]
|
||||
rstest = "0.26"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 uutils
|
||||
Copyright (c) 2023 Sylvestre Ledru and others
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,46 +1,55 @@
|
||||
# humantime_to_duration
|
||||
# parse_datetime
|
||||
|
||||
[](https://crates.io/crates/humantime_to_duration)
|
||||
[](https://github.com/uutils/humantime_to_duration/blob/main/LICENSE)
|
||||
[](https://codecov.io/gh/uutils/humantime_to_duration)
|
||||
[](https://crates.io/crates/parse_datetime)
|
||||
[](https://github.com/uutils/parse_datetime/blob/main/LICENSE)
|
||||
[](https://codecov.io/gh/uutils/parse_datetime)
|
||||
|
||||
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 human-readable datetime strings and converting them to a jiff's `Zoned` object.
|
||||
|
||||
## 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").
|
||||
- Allows for chaining time units (e.g., "1 hour 2 minutes" or "2 days 2 hours ago").
|
||||
- Calculate durations relative to a specified date.
|
||||
- Relies on Jiff
|
||||
|
||||
## Usage
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
Add `parse_datetime` to your `Cargo.toml` with:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
humantime_to_duration = "0.2.1"
|
||||
```
|
||||
cargo add parse_datetime
|
||||
```
|
||||
|
||||
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;
|
||||
Then, import the crate and use the `parse_datetime_at_date` function:
|
||||
|
||||
let duration = from_str("+3 days");
|
||||
assert_eq!(duration.unwrap(), Duration::days(3));
|
||||
```rs
|
||||
use jiff::{ToSpan, Zoned};
|
||||
use parse_datetime::parse_datetime_at_date;
|
||||
|
||||
let now = Zoned::now();
|
||||
let after = parse_datetime_at_date(now.clone(), "+3 days");
|
||||
|
||||
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)
|
||||
now.checked_add(3.days()).unwrap(),
|
||||
after.unwrap()
|
||||
);
|
||||
```
|
||||
|
||||
For DateTime parsing, import the `parse_datetime` function:
|
||||
|
||||
```rs
|
||||
use jiff::{civil::{date, time} ,Zoned};
|
||||
use parse_datetime::parse_datetime;
|
||||
|
||||
let dt = parse_datetime("2021-02-14 06:37:47");
|
||||
assert_eq!(dt.unwrap(), Zoned::now().with().date(date(2021, 2, 14)).time(time(6, 37, 47, 0)).build().unwrap());
|
||||
```
|
||||
|
||||
### Supported Formats
|
||||
|
||||
The `from_str` and `from_str_at_date` functions support the following formats for relative time:
|
||||
The `parse_datetime` and `parse_datetime_at_date` functions support absolute datetime and the following relative times:
|
||||
|
||||
- `num` `unit` (e.g., "-1 hour", "+3 days")
|
||||
- `unit` (e.g., "hour", "day")
|
||||
@@ -48,28 +57,36 @@ The `from_str` and `from_str_at_date` functions support the following formats fo
|
||||
- "yesterday"
|
||||
- "tomorrow"
|
||||
- use "ago" for the past
|
||||
- combined units with "and" or "," (e.g., "2 years and 1 month", "1 day, 2 hours" or "2 weeks 1 second")
|
||||
- use "next" or "last" with `unit` (e.g., "next week", "last year")
|
||||
- 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
|
||||
|
||||
The `from_str` and `from_str_at_date` functions return:
|
||||
### parse_datetime and parse_datetime_at_date
|
||||
|
||||
- `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
|
||||
The `parse_datetime` and `parse_datetime_at_date` function return:
|
||||
|
||||
This function will return `Err(ParseDurationError::InvalidInput)` if the input string
|
||||
cannot be parsed as a relative time.
|
||||
- `Ok(Zoned)` - If the input string can be parsed as a `Zoned` object
|
||||
- `Err(ParseDateTimeError::InvalidInput)` - If the input string cannot be parsed
|
||||
|
||||
## Fuzzer
|
||||
|
||||
To run the fuzzer:
|
||||
|
||||
```
|
||||
$ cargo fuzz run fuzz_from_str
|
||||
$ cd fuzz
|
||||
$ cargo install cargo-fuzz
|
||||
$ cargo +nightly fuzz run fuzz_parse_datetime
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
corpus
|
||||
Generated
+287
-77
@@ -1,146 +1,226 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.1"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.3.0"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e"
|
||||
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.79"
|
||||
version = "1.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||
checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9"
|
||||
|
||||
[[package]]
|
||||
name = "fuzz_from_str"
|
||||
version = "0.1.0"
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d"
|
||||
|
||||
[[package]]
|
||||
name = "fuzz_parse_datetime"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"humantime_to_duration",
|
||||
"libfuzzer-sys",
|
||||
"rand",
|
||||
"regex",
|
||||
"time",
|
||||
"parse_datetime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.9"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime_to_duration"
|
||||
version = "0.1.0"
|
||||
name = "jiff"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"time",
|
||||
"jiff-static",
|
||||
"jiff-tzdb-platform",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-tzdb"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524"
|
||||
|
||||
[[package]]
|
||||
name = "jiff-tzdb-platform"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8"
|
||||
dependencies = [
|
||||
"jiff-tzdb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.26"
|
||||
version = "0.1.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.142"
|
||||
version = "0.2.175"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.6"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "beb09950ae85a0a94b27676cccf37da5ff13f27076aa1adbc6545dd0d0e1bd4e"
|
||||
checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
version = "2.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.1"
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
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"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
name = "parse_datetime"
|
||||
version = "0.11.0"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"jiff",
|
||||
"num-traits",
|
||||
"regex",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
name = "portable-atomic"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
|
||||
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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.8.1"
|
||||
version = "1.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
|
||||
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -149,34 +229,164 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.1"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
|
||||
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.160"
|
||||
version = "1.0.221"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
|
||||
checksum = "341877e04a22458705eb4e131a1508483c877dca2792b3781d4e5d8a6019ec43"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"time-core",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.0"
|
||||
name = "serde_core"
|
||||
version = "1.0.221"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
|
||||
checksum = "0c459bc0a14c840cb403fc14b148620de1e0778c96ecd6e0c8c3cacb6d8d00fe"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.221"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6185cf75117e20e62b1ff867b9518577271e58abe0037c40bb4794969355ab0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
version = "0.14.5+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4"
|
||||
dependencies = [
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.0+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
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.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.45.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36"
|
||||
|
||||
+7
-10
@@ -1,22 +1,19 @@
|
||||
[package]
|
||||
name = "fuzz_from_str"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
name = "fuzz_parse_datetime"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8.4"
|
||||
libfuzzer-sys = "0.4"
|
||||
regex = "1.8.1"
|
||||
time = "0.3.20"
|
||||
libfuzzer-sys = "0.4.7"
|
||||
|
||||
[dependencies.humantime_to_duration]
|
||||
[dependencies.parse_datetime]
|
||||
path = "../"
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_from_str"
|
||||
path = "fuzz_targets/from_str.rs"
|
||||
name = "fuzz_parse_datetime"
|
||||
path = "fuzz_targets/parse_datetime.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 _ = humantime_to_duration::from_str(s);
|
||||
let _ = parse_datetime::parse_datetime(s);
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use jiff::{civil, Span, Zoned};
|
||||
|
||||
use super::{date, epoch, error, offset, relative, time, weekday, year, Item};
|
||||
|
||||
/// The builder is used to construct a DateTime object from various components.
|
||||
/// The parser creates a `DateTimeBuilder` object with the parsed components,
|
||||
/// but without the baseline date and time. So you normally need to set the base
|
||||
/// date and time using the `set_base()` method before calling `build()`, or
|
||||
/// leave it unset to use the current date and time as the base.
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct DateTimeBuilder {
|
||||
base: Option<Zoned>,
|
||||
timestamp: Option<epoch::Timestamp>,
|
||||
date: Option<date::Date>,
|
||||
time: Option<time::Time>,
|
||||
weekday: Option<weekday::Weekday>,
|
||||
offset: Option<offset::Offset>,
|
||||
timezone: Option<jiff::tz::TimeZone>,
|
||||
relative: Vec<relative::Relative>,
|
||||
}
|
||||
|
||||
impl DateTimeBuilder {
|
||||
pub(super) fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Sets the base date and time for the builder. If not set, the current
|
||||
/// date and time will be used.
|
||||
pub(super) fn set_base(mut self, base: Zoned) -> Self {
|
||||
self.base = Some(base);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the timezone rule for the builder.
|
||||
///
|
||||
/// By default, the builder uses the time zone rules indicated by the `TZ`
|
||||
/// environment variable, or the system default rules if `TZ` is not set.
|
||||
/// This method allows overriding the time zone rules.
|
||||
fn set_timezone(mut self, tz: jiff::tz::TimeZone) -> Result<Self, &'static str> {
|
||||
if self.timezone.is_some() {
|
||||
return Err("timezone rule cannot appear more than once");
|
||||
}
|
||||
|
||||
self.timezone = Some(tz);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Sets a timestamp value. Timestamp values are exclusive to other date/time
|
||||
/// items (date, time, weekday, timezone, relative adjustments).
|
||||
pub(super) fn set_timestamp(mut self, ts: epoch::Timestamp) -> Result<Self, &'static str> {
|
||||
if self.timestamp.is_some() {
|
||||
return Err("timestamp cannot appear more than once");
|
||||
} else if self.date.is_some()
|
||||
|| self.time.is_some()
|
||||
|| self.weekday.is_some()
|
||||
|| self.offset.is_some()
|
||||
|| !self.relative.is_empty()
|
||||
{
|
||||
return Err("timestamp cannot be combined with other date/time items");
|
||||
}
|
||||
|
||||
self.timestamp = Some(ts);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn set_date(mut self, date: date::Date) -> Result<Self, &'static str> {
|
||||
if self.timestamp.is_some() {
|
||||
return Err("timestamp cannot be combined with other date/time items");
|
||||
} else if self.date.is_some() {
|
||||
return Err("date cannot appear more than once");
|
||||
}
|
||||
|
||||
self.date = Some(date);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn set_time(mut self, time: time::Time) -> Result<Self, &'static str> {
|
||||
if self.timestamp.is_some() {
|
||||
return Err("timestamp cannot be combined with other date/time items");
|
||||
} else if self.time.is_some() {
|
||||
return Err("time cannot appear more than once");
|
||||
} else if self.offset.is_some() && time.offset.is_some() {
|
||||
return Err("time offset and timezone are mutually exclusive");
|
||||
}
|
||||
|
||||
self.time = Some(time);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn set_weekday(mut self, weekday: weekday::Weekday) -> Result<Self, &'static str> {
|
||||
if self.timestamp.is_some() {
|
||||
return Err("timestamp cannot be combined with other date/time items");
|
||||
} else if self.weekday.is_some() {
|
||||
return Err("weekday cannot appear more than once");
|
||||
}
|
||||
|
||||
self.weekday = Some(weekday);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn set_offset(mut self, timezone: offset::Offset) -> Result<Self, &'static str> {
|
||||
if self.timestamp.is_some() {
|
||||
return Err("timestamp cannot be combined with other date/time items");
|
||||
} else if self.offset.is_some()
|
||||
|| self.time.as_ref().and_then(|t| t.offset.as_ref()).is_some()
|
||||
{
|
||||
return Err("time offset cannot appear more than once");
|
||||
}
|
||||
|
||||
self.offset = Some(timezone);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
fn push_relative(mut self, relative: relative::Relative) -> Result<Self, &'static str> {
|
||||
if self.timestamp.is_some() {
|
||||
return Err("timestamp cannot be combined with other date/time items");
|
||||
}
|
||||
|
||||
self.relative.push(relative);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Sets a pure number that can be interpreted as either a year or time
|
||||
/// depending on the current state of the builder.
|
||||
///
|
||||
/// If a date is already set but lacks a year, the number is interpreted as
|
||||
/// a year. Otherwise, it's interpreted as a time in HHMM, HMM, HH, or H
|
||||
/// format.
|
||||
fn set_pure(mut self, pure: String) -> Result<Self, &'static str> {
|
||||
if self.timestamp.is_some() {
|
||||
return Err("timestamp cannot be combined with other date/time items");
|
||||
}
|
||||
|
||||
if let Some(date) = self.date.as_mut() {
|
||||
if date.year.is_none() {
|
||||
date.year = Some(year::year_from_str(&pure)?);
|
||||
return Ok(self);
|
||||
}
|
||||
}
|
||||
|
||||
let (mut hour_str, mut minute_str) = match pure.len() {
|
||||
1..=2 => (pure.as_str(), "0"),
|
||||
3..=4 => pure.split_at(pure.len() - 2),
|
||||
_ => {
|
||||
return Err("pure number must be 1-4 digits when interpreted as time");
|
||||
}
|
||||
};
|
||||
|
||||
let hour = time::hour24(&mut hour_str).map_err(|_| "invalid hour in pure number")?;
|
||||
let minute = time::minute(&mut minute_str).map_err(|_| "invalid minute in pure number")?;
|
||||
|
||||
let time = time::Time {
|
||||
hour,
|
||||
minute,
|
||||
..Default::default()
|
||||
};
|
||||
self.set_time(time)
|
||||
}
|
||||
|
||||
/// Build a `Zoned` object from the pieces accumulated in this builder.
|
||||
///
|
||||
/// Resolution order (mirrors GNU `date` semantics):
|
||||
///
|
||||
/// 1. Base instant.
|
||||
/// - a. If `self.base` is provided, start with it.
|
||||
/// - b. Else if a `timezone` rule is present, start with "now" in that
|
||||
/// timezone.
|
||||
/// - c. Else start with current system local time.
|
||||
///
|
||||
/// 2. Absolute timestamp override.
|
||||
/// - a. If `self.timestamp` is set, it fully determines the result.
|
||||
///
|
||||
/// 3. Time of day truncation.
|
||||
/// - a. If any of date, time, weekday, offset, timezone is set, zero the
|
||||
/// time of day to 00:00:00 before applying fields.
|
||||
///
|
||||
/// 4. Fieldwise resolution (applied to the base instant).
|
||||
/// - a. Apply date. If year is absent in the parsed date, inherit the year
|
||||
/// from the base instant.
|
||||
/// - b. Apply time. If time carries an explicit numeric offset, apply the
|
||||
/// offset before setting time.
|
||||
/// - c. Apply weekday (e.g., "next Friday" or "last Monday").
|
||||
/// - d. Apply relative adjustments (e.g., "+3 days", "-2 months").
|
||||
/// - e. Apply final fixed offset if present.
|
||||
pub(super) fn build(self) -> Result<Zoned, error::Error> {
|
||||
// 1. Choose the base instant.
|
||||
let base = match (self.base, &self.timezone) {
|
||||
(Some(b), _) => b,
|
||||
(None, Some(tz)) => jiff::Timestamp::now().to_zoned(tz.clone()),
|
||||
(None, None) => Zoned::now(),
|
||||
};
|
||||
|
||||
// 2. Absolute timestamp override everything else.
|
||||
if let Some(ts) = self.timestamp {
|
||||
let ts = jiff::Timestamp::try_from(ts)?;
|
||||
return Ok(ts.to_zoned(base.offset().to_time_zone()));
|
||||
}
|
||||
|
||||
// 3. Determine whether to truncate the time of day.
|
||||
let need_midnight = self.date.is_some()
|
||||
|| self.time.is_some()
|
||||
|| self.weekday.is_some()
|
||||
|| self.offset.is_some()
|
||||
|| self.timezone.is_some();
|
||||
|
||||
let mut dt = if need_midnight {
|
||||
base.with().time(civil::time(0, 0, 0, 0)).build()?
|
||||
} else {
|
||||
base
|
||||
};
|
||||
|
||||
// 4a. Apply date.
|
||||
if let Some(date) = self.date {
|
||||
let d: civil::Date = if date.year.is_some() {
|
||||
date.try_into()?
|
||||
} else {
|
||||
date.with_year(dt.date().year() as u16).try_into()?
|
||||
};
|
||||
dt = dt.with().date(d).build()?;
|
||||
}
|
||||
|
||||
// 4b. Apply time.
|
||||
if let Some(time) = self.time.clone() {
|
||||
if let Some(offset) = &time.offset {
|
||||
dt = dt.datetime().to_zoned(offset.try_into()?)?;
|
||||
}
|
||||
|
||||
let t: civil::Time = time.try_into()?;
|
||||
dt = dt.with().time(t).build()?;
|
||||
}
|
||||
|
||||
// 4c. Apply weekday.
|
||||
if let Some(weekday::Weekday { mut offset, day }) = self.weekday {
|
||||
if self.time.is_none() {
|
||||
dt = dt.with().time(civil::time(0, 0, 0, 0)).build()?;
|
||||
}
|
||||
|
||||
let target = day.into();
|
||||
|
||||
// If the current day is not the target day, we need to adjust
|
||||
// the x value to ensure we find the correct day.
|
||||
//
|
||||
// Consider this:
|
||||
// Assuming today is Monday, next Friday is actually THIS Friday;
|
||||
// but next Monday is indeed NEXT Monday.
|
||||
if dt.date().weekday() != target && offset > 0 {
|
||||
offset -= 1;
|
||||
}
|
||||
|
||||
// Calculate the delta to the target day.
|
||||
//
|
||||
// Assuming today is Thursday, here are some examples:
|
||||
//
|
||||
// Example 1: last Thursday (x = -1, day = Thursday)
|
||||
// delta = (3 - 3) % 7 + (-1) * 7 = -7
|
||||
//
|
||||
// Example 2: last Monday (x = -1, day = Monday)
|
||||
// delta = (0 - 3) % 7 + (-1) * 7 = -3
|
||||
//
|
||||
// Example 3: next Monday (x = 1, day = Monday)
|
||||
// delta = (0 - 3) % 7 + (0) * 7 = 4
|
||||
// (Note that we have adjusted the x value above)
|
||||
//
|
||||
// Example 4: next Thursday (x = 1, day = Thursday)
|
||||
// delta = (3 - 3) % 7 + (1) * 7 = 7
|
||||
let delta = (target.since(civil::Weekday::Monday) as i32
|
||||
- dt.date().weekday().since(civil::Weekday::Monday) as i32)
|
||||
.rem_euclid(7)
|
||||
+ offset.checked_mul(7).ok_or("multiplication overflow")?;
|
||||
|
||||
dt = dt.checked_add(Span::new().try_days(delta)?)?;
|
||||
}
|
||||
|
||||
// 4d. Apply relative adjustments.
|
||||
for rel in self.relative {
|
||||
dt = dt.checked_add::<Span>(if let relative::Relative::Months(x) = rel {
|
||||
// *NOTE* This is done in this way to conform to GNU behavior.
|
||||
let days = dt.date().last_of_month().day() as i32;
|
||||
Span::new().try_days(days.checked_mul(x).ok_or("multiplication overflow")?)?
|
||||
} else {
|
||||
rel.try_into()?
|
||||
})?;
|
||||
}
|
||||
|
||||
// 4e. Apply final fixed offset.
|
||||
if let Some(offset) = self.offset {
|
||||
let (offset, hour_adjustment) = offset.normalize();
|
||||
dt = dt.checked_add(Span::new().hours(hour_adjustment))?;
|
||||
dt = dt.datetime().to_zoned((&offset).try_into()?)?;
|
||||
}
|
||||
|
||||
Ok(dt)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<Item>> for DateTimeBuilder {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(items: Vec<Item>) -> Result<Self, Self::Error> {
|
||||
let mut builder = DateTimeBuilder::new();
|
||||
|
||||
for item in items {
|
||||
builder = match item {
|
||||
Item::Timestamp(ts) => builder.set_timestamp(ts)?,
|
||||
Item::DateTime(dt) => builder.set_date(dt.date)?.set_time(dt.time)?,
|
||||
Item::Date(d) => builder.set_date(d)?,
|
||||
Item::Time(t) => builder.set_time(t)?,
|
||||
Item::Weekday(weekday) => builder.set_weekday(weekday)?,
|
||||
Item::Offset(offset) => builder.set_offset(offset)?,
|
||||
Item::Relative(rel) => builder.push_relative(rel)?,
|
||||
Item::TimeZone(tz) => builder.set_timezone(tz)?,
|
||||
Item::Pure(pure) => builder.set_pure(pure)?,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(builder)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// 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, primitive::s, time};
|
||||
|
||||
#[derive(PartialEq, Debug, Clone, Default)]
|
||||
pub(crate) struct DateTime {
|
||||
pub(crate) date: date::Date,
|
||||
pub(crate) time: time::Time,
|
||||
}
|
||||
|
||||
pub(crate) fn parse(input: &mut &str) -> ModalResult<DateTime> {
|
||||
seq!(DateTime {
|
||||
date: trace("iso_date", alt((date::iso1, date::iso2))),
|
||||
// Note: the `T` is lowercased by the main parse function
|
||||
_: alt((s('t').void(), (' ', space).void())),
|
||||
time: trace("iso_time", 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,
|
||||
nanosecond: 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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,137 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
//! Parse a timestamp item.
|
||||
//!
|
||||
//! From the GNU docs:
|
||||
//!
|
||||
//! > If you precede a number with ‘@’, it represents an internal timestamp as
|
||||
//! > a count of seconds. The number can contain an internal decimal point
|
||||
//! > (either ‘.’ or ‘,’); any excess precision not supported by the internal
|
||||
//! > representation is truncated toward minus infinity. Such a number cannot
|
||||
//! > be combined with any other date item, as it specifies a complete
|
||||
//! > timestamp.
|
||||
//! >
|
||||
//! > On most hosts, these counts ignore the presence of leap seconds. For
|
||||
//! > example, on most hosts ‘@1483228799’ represents 2016-12-31 23:59:59 UTC,
|
||||
//! > ‘@1483228800’ represents 2017-01-01 00:00:00 UTC, and there is no way to
|
||||
//! > represent the intervening leap second 2016-12-31 23:59:60 UTC.
|
||||
|
||||
use winnow::{
|
||||
ascii::digit1,
|
||||
combinator::{opt, preceded},
|
||||
token::one_of,
|
||||
ModalResult, Parser,
|
||||
};
|
||||
|
||||
use super::primitive::{dec_uint, plus_or_minus, s};
|
||||
|
||||
/// Represents a timestamp with nanosecond accuracy.
|
||||
///
|
||||
/// # Invariants
|
||||
///
|
||||
/// - `nanosecond` is always in the range of `0..1_000_000_000`.
|
||||
/// - Negative timestamps are represented by a negative `second` value and a
|
||||
/// positive `nanosecond` value.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(super) struct Timestamp {
|
||||
second: i64,
|
||||
nanosecond: u32,
|
||||
}
|
||||
|
||||
impl TryFrom<Timestamp> for jiff::Timestamp {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(ts: Timestamp) -> Result<Self, Self::Error> {
|
||||
jiff::Timestamp::new(
|
||||
ts.second,
|
||||
i32::try_from(ts.nanosecond).map_err(|_| "nanosecond in timestamp exceeds i32::MAX")?,
|
||||
)
|
||||
.map_err(|_| "timestamp value is out of valid range")
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a timestamp in the form of `@1234567890` or `@-1234567890.12345` or
|
||||
/// `@1234567890,12345`.
|
||||
pub(super) fn parse(input: &mut &str) -> ModalResult<Timestamp> {
|
||||
(s("@"), opt(plus_or_minus), s(sec_and_nsec))
|
||||
.verify_map(|(_, sign, (sec, nsec))| {
|
||||
let sec = i64::try_from(sec).ok()?;
|
||||
let (second, nanosecond) = match (sign, nsec) {
|
||||
(Some('-'), 0) => (-sec, 0),
|
||||
// Truncate towards minus infinity.
|
||||
(Some('-'), _) => ((-sec).checked_sub(1)?, 1_000_000_000 - nsec),
|
||||
_ => (sec, nsec),
|
||||
};
|
||||
Some(Timestamp { second, nanosecond })
|
||||
})
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
/// Parse a second value in the form of `1234567890` or `1234567890.12345` or
|
||||
/// `1234567890,12345`.
|
||||
///
|
||||
/// The first part represents whole seconds. The optional second part represents
|
||||
/// fractional seconds, parsed as a nanosecond value from up to 9 digits
|
||||
/// (padded with zeros on the right if fewer digits are present). If the second
|
||||
/// part is omitted, it defaults to 0 nanoseconds.
|
||||
pub(super) fn sec_and_nsec(input: &mut &str) -> ModalResult<(u64, u32)> {
|
||||
(dec_uint, opt(preceded(one_of(['.', ',']), digit1)))
|
||||
.verify_map(|(sec, opt_nsec_str)| match opt_nsec_str {
|
||||
Some(nsec_str) if nsec_str.len() >= 9 => Some((sec, nsec_str[..9].parse().ok()?)),
|
||||
Some(nsec_str) => {
|
||||
let multiplier = 10_u32.pow(9 - nsec_str.len() as u32);
|
||||
Some((sec, nsec_str.parse::<u32>().ok()?.checked_mul(multiplier)?))
|
||||
}
|
||||
None => Some((sec, 0)),
|
||||
})
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn ts(second: i64, nanosecond: u32) -> Timestamp {
|
||||
Timestamp { second, nanosecond }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_sec_and_nsec() {
|
||||
for (input, expected) in [
|
||||
("1234567890", (1234567890, 0)), // only seconds
|
||||
("1234567890.12345", (1234567890, 123450000)), // seconds and nanoseconds, '.' as floating point
|
||||
("1234567890,12345", (1234567890, 123450000)), // seconds and nanoseconds, ',' as floating point
|
||||
("1234567890.1234567890123", (1234567890, 123456789)), // nanoseconds with more than 9 digits, truncated
|
||||
] {
|
||||
let mut s = input;
|
||||
assert_eq!(sec_and_nsec(&mut s).unwrap(), expected, "{input}");
|
||||
}
|
||||
|
||||
for input in [
|
||||
".1234567890", // invalid: no leading seconds
|
||||
"-1234567890", // invalid: negative input not allowed
|
||||
] {
|
||||
let mut s = input;
|
||||
assert!(sec_and_nsec(&mut s).is_err(), "{input}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timestamp() {
|
||||
for (input, expected) in [
|
||||
("@1234567890", ts(1234567890, 0)), // positive seconds, no nanoseconds
|
||||
("@ 1234567890", ts(1234567890, 0)), // space after '@', positive seconds, no nanoseconds
|
||||
("@-1234567890", ts(-1234567890, 0)), // negative seconds, no nanoseconds
|
||||
("@ -1234567890", ts(-1234567890, 0)), // space after '@', negative seconds, no nanoseconds
|
||||
("@ - 1234567890", ts(-1234567890, 0)), // space after '@' and after '-', negative seconds, no nanoseconds
|
||||
("@1234567890.12345", ts(1234567890, 123450000)), // positive seconds with nanoseconds, '.' as floating point
|
||||
("@1234567890,12345", ts(1234567890, 123450000)), // positive seconds with nanoseconds, ',' as floating point
|
||||
("@-1234567890.12345", ts(-1234567891, 876550000)), // negative seconds with nanoseconds, '.' as floating point
|
||||
("@1234567890.1234567890123", ts(1234567890, 123456789)), // nanoseconds with more than 9 digits, truncated
|
||||
] {
|
||||
let mut s = input;
|
||||
assert_eq!(parse(&mut s).unwrap(), expected, "{input}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
use std::fmt;
|
||||
|
||||
use winnow::error::{ContextError, ErrMode};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Error {
|
||||
ParseError(String),
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::ParseError(reason) => {
|
||||
write!(f, "{reason}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for Error {
|
||||
fn from(reason: &'static str) -> Self {
|
||||
Error::ParseError(reason.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ErrMode<ContextError>> for Error {
|
||||
fn from(err: ErrMode<ContextError>) -> Self {
|
||||
Error::ParseError(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jiff::Error> for Error {
|
||||
fn from(err: jiff::Error) -> Self {
|
||||
Error::ParseError(err.to_string())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,468 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
//! Parse an offset item.
|
||||
//!
|
||||
//! From the GNU docs:
|
||||
//!
|
||||
//! > A “time zone item” specifies an international time zone, indicated by a
|
||||
//! > small set of letters, e.g., ‘UTC’ or ‘Z’ for Coordinated Universal Time.
|
||||
//! > Any included periods are ignored. By following a non-daylight-saving
|
||||
//! > time zone by the string ‘DST’ in a separate word (that is, separated by
|
||||
//! > some white space), the corresponding daylight saving time zone may be
|
||||
//! > specified. Alternatively, a non-daylight-saving time zone can be
|
||||
//! > followed by a time zone correction, to add the two values. This is
|
||||
//! > normally done only for ‘UTC’; for example, ‘UTC+05:30’ is equivalent to
|
||||
//! > ‘+05:30’.
|
||||
//! >
|
||||
//! > Time zone items other than ‘UTC’ and ‘Z’ are obsolescent and are not
|
||||
//! > recommended, because they are ambiguous; for example, ‘EST’ has a
|
||||
//! > different meaning in Australia than in the United States, and ‘A’ has
|
||||
//! > different meaning as a military time zone than as an obsolete RFC 822
|
||||
//! > time zone. Instead, it's better to use unambiguous numeric time zone
|
||||
//! > corrections like ‘-0500’, as described in the previous section.
|
||||
//! >
|
||||
//! > If neither a time zone item nor a time zone correction is supplied,
|
||||
//! > timestamps are interpreted using the rules of the default time zone.
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use winnow::{
|
||||
combinator::{alt, peek},
|
||||
error::{ContextError, ErrMode},
|
||||
stream::{AsChar, Stream},
|
||||
token::take_while,
|
||||
ModalResult, Parser,
|
||||
};
|
||||
|
||||
use super::{
|
||||
primitive::{colon, ctx_err, dec_uint, dec_uint_str, plus_or_minus, s},
|
||||
relative,
|
||||
};
|
||||
|
||||
/// Represents a time zone offset from UTC.
|
||||
///
|
||||
/// This struct is used to represent a time zone offset in hours and minutes,
|
||||
/// with a boolean indicating whether the offset is negative (i.e., west of
|
||||
/// UTC).
|
||||
#[derive(PartialEq, Debug, Clone, Default)]
|
||||
pub(super) struct Offset {
|
||||
negative: bool,
|
||||
hours: u8,
|
||||
minutes: u8,
|
||||
}
|
||||
|
||||
impl Offset {
|
||||
/// Merge two timezone offsets.
|
||||
///
|
||||
/// Note: when parsing an offset from a string (e.g., "+08:00"), the hours
|
||||
/// and minutes are validated to ensure they fall within valid bounds. In
|
||||
/// contrast, merging two offsets does not perform such validation. This
|
||||
/// behavior is intentional to match GNU date.
|
||||
fn merge(self, offset: Offset) -> Offset {
|
||||
fn combine(a: u16, neg_a: bool, b: u16, neg_b: bool) -> (u16, bool) {
|
||||
if neg_a == neg_b {
|
||||
(a + b, neg_a)
|
||||
} else if a > b {
|
||||
(a - b, neg_a)
|
||||
} else {
|
||||
(b - a, neg_b)
|
||||
}
|
||||
}
|
||||
let (total_minutes, negative) = combine(
|
||||
(self.hours as u16) * 60 + (self.minutes as u16),
|
||||
self.negative,
|
||||
(offset.hours as u16) * 60 + (offset.minutes as u16),
|
||||
offset.negative,
|
||||
);
|
||||
let hours = (total_minutes / 60) as u8;
|
||||
let minutes = (total_minutes % 60) as u8;
|
||||
|
||||
Offset {
|
||||
negative,
|
||||
hours,
|
||||
minutes,
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalize the offset so that the hour field is within the accepted range.
|
||||
///
|
||||
/// - If the hour field is less than 24, or exactly 24 with a zero minute,
|
||||
/// the offset is already normalized, and the function returns the offset
|
||||
/// itself along with a zero hour adjustment.
|
||||
/// - Otherwise, the hour field is reduced to 23 while preserving the minute
|
||||
/// field, and the function returns the normalized offset along with the
|
||||
/// hour adjustment needed to reach the original offset.
|
||||
pub(super) fn normalize(self) -> (Offset, i8) {
|
||||
if self.hours < 24 || (self.hours == 24 && self.minutes == 0) {
|
||||
return (self, 0);
|
||||
}
|
||||
|
||||
let hour_adjustment = (self.hours as i8 - 23) * if self.negative { 1 } else { -1 };
|
||||
(
|
||||
Offset {
|
||||
negative: self.negative,
|
||||
hours: 23,
|
||||
minutes: self.minutes,
|
||||
},
|
||||
hour_adjustment,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(bool, u8, u8)> for Offset {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from((negative, hours, minutes): (bool, u8, u8)) -> Result<Self, Self::Error> {
|
||||
if hours > 24 {
|
||||
return Err("timezone hour must be between 0 and 24");
|
||||
}
|
||||
if minutes > 60 || (hours == 24 && minutes != 0) {
|
||||
return Err("timezone minute must be between 0 and 60");
|
||||
}
|
||||
|
||||
Ok(Offset {
|
||||
negative,
|
||||
hours,
|
||||
minutes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Offset> for jiff::tz::TimeZone {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(
|
||||
Offset {
|
||||
negative,
|
||||
hours,
|
||||
minutes,
|
||||
}: &Offset,
|
||||
) -> Result<Self, Self::Error> {
|
||||
let secs = (*hours as i32) * 3600 + (*minutes as i32) * 60;
|
||||
let secs = if *negative { -secs } else { secs };
|
||||
|
||||
let offset = jiff::tz::Offset::from_seconds(secs).map_err(|_| "offset is invalid")?;
|
||||
Ok(jiff::tz::TimeZone::fixed(offset))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Offset {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
fmt,
|
||||
"{}{:02}:{:02}",
|
||||
if self.negative { "-" } else { "+" },
|
||||
self.hours,
|
||||
self.minutes
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn parse(input: &mut &str) -> ModalResult<Offset> {
|
||||
timezone_name_offset.parse_next(input)
|
||||
}
|
||||
|
||||
/// Parse a timezone starting with `+` or `-`.
|
||||
pub(super) fn timezone_offset(input: &mut &str) -> ModalResult<Offset> {
|
||||
// Strings like "+8 years" are ambiguous, they can either be parsed as a
|
||||
// timezone offset "+8" and a relative time "years", or just a relative time
|
||||
// "+8 years". GNU date parses them the second way, so we do the same here.
|
||||
//
|
||||
// Return early if the input can be parsed as a relative time.
|
||||
if peek(relative::parse).parse_next(input).is_ok() {
|
||||
return Err(ErrMode::Backtrack(ContextError::new()));
|
||||
}
|
||||
|
||||
alt((timezone_offset_colon, timezone_offset_colonless)).parse_next(input)
|
||||
}
|
||||
|
||||
/// Parse a timezone by name, with an optional numeric offset appended.
|
||||
fn timezone_name_offset(input: &mut &str) -> ModalResult<Offset> {
|
||||
/// I'm assuming there are no timezone abbreviations with more
|
||||
/// than 6 charactres
|
||||
const MAX_TZ_SIZE: usize = 6;
|
||||
let nextword = s(take_while(1..=MAX_TZ_SIZE, AsChar::is_alpha)).parse_next(input)?;
|
||||
let tz = timezone_name_to_offset(nextword)?;
|
||||
|
||||
// Strings like "UTC +8 years" are ambiguous, they can either be parsed as
|
||||
// "UTC+8" and "years", or "UTC" and "+8 years". GNU date parses them the
|
||||
// second way, so we do the same here.
|
||||
//
|
||||
// Only process if the input cannot be parsed as a relative time.
|
||||
if peek(relative::parse).parse_next(input).is_err() {
|
||||
let start = input.checkpoint();
|
||||
if let Ok(other_tz) = timezone_offset.parse_next(input) {
|
||||
let new_tz = tz.merge(other_tz);
|
||||
|
||||
return Ok(new_tz);
|
||||
};
|
||||
input.reset(&start);
|
||||
}
|
||||
|
||||
Ok(tz)
|
||||
}
|
||||
|
||||
/// Parse a timezone offset with a colon separating hours and minutes, e.g.,
|
||||
/// `+08:00`, `+8:00`, `+8:0`.
|
||||
fn timezone_offset_colon(input: &mut &str) -> ModalResult<Offset> {
|
||||
(plus_or_minus, s(dec_uint), s(colon), s(dec_uint))
|
||||
.parse_next(input)
|
||||
.and_then(|(sign, hours, _, minutes)| {
|
||||
(sign == '-', hours, minutes)
|
||||
.try_into()
|
||||
.map_err(|e| ErrMode::Cut(ctx_err(e)))
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a timezone offset without colon, e.g., `+0800`, `+800`, `+08`, `+8`.
|
||||
fn timezone_offset_colonless(input: &mut &str) -> ModalResult<Offset> {
|
||||
(plus_or_minus, s(dec_uint_str))
|
||||
.verify_map(|(sign, s)| {
|
||||
// GNU date accepts numeric offset strings with leading zeroes. For
|
||||
// example, `+000000110` is valid. In such cases, the string is
|
||||
// truncated to the last four characters. Thus, `+000000110` becomes
|
||||
// `+0110` (note that one leading zero is kept).
|
||||
let s = if s.len() > 4 && s.trim_start_matches('0').len() <= 4 {
|
||||
&s[s.len() - 4..]
|
||||
} else {
|
||||
s
|
||||
};
|
||||
|
||||
// Hour and minute values are dependent on the length of the string.
|
||||
// For example:
|
||||
//
|
||||
// - "5" -> 05:00
|
||||
// - "05" -> 05:00
|
||||
// - "530" -> 05:30 (the minute is the last two characters here)
|
||||
// - "0530"-> 05:30
|
||||
// - "0000530" -> 05:30
|
||||
let (h_str, m_str) = match s.len() {
|
||||
1 | 2 => (s, "0"),
|
||||
3 => s.split_at(1),
|
||||
4 => s.split_at(2),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let hours = h_str.parse::<u8>().ok()?;
|
||||
let minutes = m_str.parse::<u8>().ok()?;
|
||||
Some((sign, hours, minutes))
|
||||
})
|
||||
.parse_next(input)
|
||||
.and_then(|(sign, hours, minutes)| {
|
||||
(sign == '-', hours, minutes)
|
||||
.try_into()
|
||||
.map_err(|e| ErrMode::Cut(ctx_err(e)))
|
||||
})
|
||||
}
|
||||
|
||||
/// Named timezone list.
|
||||
///
|
||||
/// The full list of timezones can be extracted from
|
||||
/// https://www.timeanddate.com/time/zones/. GNU date only supports a subset of
|
||||
/// these. We support the same subset as GNU date.
|
||||
fn timezone_name_to_offset(input: &str) -> ModalResult<Offset> {
|
||||
let mut offset_str = match input {
|
||||
"z" => Ok("+0"),
|
||||
"y" => Ok("-12"),
|
||||
"x" => Ok("-11"),
|
||||
"wet" => Ok("+0"),
|
||||
"west" => Ok("+1"),
|
||||
"wat" => Ok("+1"),
|
||||
"w" => Ok("-10"),
|
||||
"v" => Ok("-9"),
|
||||
"utc" => Ok("+0"),
|
||||
"u" => Ok("-8"),
|
||||
"t" => Ok("-7"),
|
||||
"sst" => Ok("-11"),
|
||||
"sgt" => Ok("+8"),
|
||||
"sast" => Ok("+2"),
|
||||
"s" => Ok("-6"),
|
||||
"r" => Ok("-5"),
|
||||
"q" => Ok("-4"),
|
||||
"pst" => Ok("-8"),
|
||||
"pdt" => Ok("-7"),
|
||||
"p" => Ok("-3"),
|
||||
"o" => Ok("-2"),
|
||||
"nzst" => Ok("+12"),
|
||||
"nzdt" => Ok("+13"),
|
||||
"nst" => Ok("-3:30"),
|
||||
"ndt" => Ok("-2:30"),
|
||||
"n" => Ok("-1"),
|
||||
"mst" => Ok("-7"),
|
||||
"msk" => Ok("+3"),
|
||||
"msd" => Ok("+4"),
|
||||
"mdt" => Ok("-6"),
|
||||
"m" => Ok("+12"),
|
||||
"l" => Ok("+11"),
|
||||
"k" => Ok("+10"),
|
||||
"jst" => Ok("+9"),
|
||||
"ist" => Ok("+5:30"),
|
||||
"i" => Ok("+9"),
|
||||
"hst" => Ok("-10"),
|
||||
"h" => Ok("+8"),
|
||||
"gst" => Ok("+4"),
|
||||
"gmt" => Ok("+0"),
|
||||
"g" => Ok("+7"),
|
||||
"f" => Ok("+6"),
|
||||
"est" => Ok("-5"),
|
||||
"eet" => Ok("+2"),
|
||||
"eest" => Ok("+3"),
|
||||
"edt" => Ok("-4"),
|
||||
"eat" => Ok("+3"),
|
||||
"e" => Ok("+5"),
|
||||
"d" => Ok("+4"),
|
||||
"cst" => Ok("-6"),
|
||||
"clt" => Ok("-4"),
|
||||
"clst" => Ok("-3"),
|
||||
"cet" => Ok("+1"),
|
||||
"cest" => Ok("+2"),
|
||||
"cdt" => Ok("-5"),
|
||||
"cat" => Ok("+2"),
|
||||
"c" => Ok("+3"),
|
||||
"bst" => Ok("+6"),
|
||||
"brt" => Ok("-3"),
|
||||
"brst" => Ok("-2"),
|
||||
"b" => Ok("+2"),
|
||||
"ast" => Ok("-3"),
|
||||
"art" => Ok("-3"),
|
||||
"akst" => Ok("-9"),
|
||||
"akdt" => Ok("-8"),
|
||||
"adt" => Ok("+4"),
|
||||
"a" => Ok("+1"),
|
||||
_ => Err(ErrMode::Backtrack(ContextError::new())),
|
||||
}?;
|
||||
|
||||
timezone_offset(&mut offset_str)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn off(negative: bool, hours: u8, minutes: u8) -> Offset {
|
||||
Offset {
|
||||
negative,
|
||||
hours,
|
||||
minutes,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timezone_offset_with_colon() {
|
||||
for (input, expected) in [
|
||||
("+00:00", off(false, 0, 0)), // UTC
|
||||
("-00:00", off(true, 0, 0)), // UTC
|
||||
("+01:00", off(false, 1, 0)), // positive offset
|
||||
("-06:00", off(true, 6, 0)), // negative offset
|
||||
("+05:30", off(false, 5, 30)), // positive offset with non-zero minutes
|
||||
("-03:30", off(true, 3, 30)), // negative offset with non-zero minutes
|
||||
("- 06:00", off(true, 6, 0)), // space after sign
|
||||
("- 06 : 00", off(true, 6, 0)), // space around colon
|
||||
("+5:3", off(false, 5, 3)), // single-digit hours and single-digit minutes
|
||||
("+5:03", off(false, 5, 3)), // single-digit hours
|
||||
("+05:3", off(false, 5, 3)), // single-digit minutes
|
||||
("+00005:00030", off(false, 5, 30)), // leading zeroes in hours and minutes
|
||||
("+00:00abc", off(false, 0, 0)), // space separator can be ignored if immediately followed by alphas (GNU date behavior)
|
||||
] {
|
||||
let mut s = input;
|
||||
assert_eq!(timezone_offset(&mut s).unwrap(), expected, "{input}");
|
||||
}
|
||||
|
||||
for input in [
|
||||
"+25:00", // invalid: hours > 24
|
||||
"-23:61", // invalid: minutes > 60
|
||||
"+24:01", // invalid: minutes > 0 when hours == 24
|
||||
] {
|
||||
let mut s = input;
|
||||
assert!(timezone_offset(&mut s).is_err(), "{input}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timezone_offset_without_colon() {
|
||||
for (input, expected) in [
|
||||
("+0000", off(false, 0, 0)), // UTC
|
||||
("-0000", off(true, 0, 0)), // UTC
|
||||
("+0100", off(false, 1, 0)), // positive offset
|
||||
("-0600", off(true, 6, 0)), // negative offset
|
||||
("+0530", off(false, 5, 30)), // positive offset with non-zero minutes
|
||||
("-0330", off(true, 3, 30)), // negative offset with non-zero minutes
|
||||
("- 0330", off(true, 3, 30)), // space after sign
|
||||
("+530", off(false, 5, 30)), // single-digit hours
|
||||
("+05", off(false, 5, 0)), // double-digit hours and no minutes
|
||||
("+5", off(false, 5, 0)), // single-digit hours and no minutes
|
||||
("+00000530", off(false, 5, 30)), // leading zeroes
|
||||
("+0000abc", off(false, 0, 0)), // space separator can be ignored if immediately followed by alphas (GNU date behavior)
|
||||
] {
|
||||
let mut s = input;
|
||||
assert_eq!(timezone_offset(&mut s).unwrap(), expected, "{input}");
|
||||
}
|
||||
|
||||
for input in [
|
||||
"+2500", // invalid: hours > 24
|
||||
"-2361", // invalid: minutes > 60
|
||||
"+2401", // invalid: minutes > 0 when hours == 24
|
||||
"+23 days", // invalid: ambiguous with relative time parsing
|
||||
] {
|
||||
let mut s = input;
|
||||
assert!(timezone_offset(&mut s).is_err(), "{input}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timezone_name_without_offset() {
|
||||
for (input, expected) in [
|
||||
("utc", off(false, 0, 0)), // UTC
|
||||
("gmt", off(false, 0, 0)), // UTC
|
||||
("z", off(false, 0, 0)), // UTC
|
||||
("west", off(false, 1, 0)), // positive offset
|
||||
("cst", off(true, 6, 0)), // negative offset
|
||||
("ist", off(false, 5, 30)), // positive offset with non-zero minutes
|
||||
("nst", off(true, 3, 30)), // negative offset with non-zero minutes
|
||||
("z123", off(false, 0, 0)), // space separator can be ignored if immediately followed by digits (GNU date behavior)
|
||||
] {
|
||||
let mut s = input;
|
||||
assert_eq!(timezone_name_offset(&mut s).unwrap(), expected, "{input}");
|
||||
}
|
||||
|
||||
for input in [
|
||||
"abc", // invalid: non-existent timezone
|
||||
"utcabc", // invalid: non-existent timezone
|
||||
] {
|
||||
let mut s = input;
|
||||
assert!(timezone_name_offset(&mut s).is_err(), "{input}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timezone_name_with_offset() {
|
||||
for (input, expected) in [
|
||||
("utc+5:30", off(false, 5, 30)), // UTC with possitive offset
|
||||
("utc-5:30", off(true, 5, 30)), // UTC with negative offset
|
||||
("utc +5:30", off(false, 5, 30)), // space after timezone name
|
||||
("utc + 5 : 30", off(false, 5, 30)), // spaces
|
||||
("a+5:30", off(false, 6, 30)), // merge two positive offsets (a=+1)
|
||||
("a-5:30", off(true, 4, 30)), // merge positive and negative offsets (a=+1)
|
||||
("n-5:30", off(true, 6, 30)), // merge two negative offsets (n=-1)
|
||||
("n+5:30", off(false, 4, 30)), // merge negative and positive offsets (n=-1)
|
||||
("m+24", off(false, 36, 0)), // maximum possible positive offset (m=+12)
|
||||
("y-24", off(true, 36, 0)), // maximum possible negative offset (y=-12)
|
||||
] {
|
||||
let mut s = input;
|
||||
assert_eq!(timezone_name_offset(&mut s).unwrap(), expected, "{input}");
|
||||
}
|
||||
|
||||
for input in [
|
||||
"abc+08:00", // invalid: non-existent timezone
|
||||
"utc+25", // invalid: invalid offset
|
||||
"utc+23 days", // invalid: ambiguous with relative time parsing
|
||||
] {
|
||||
let mut s = input;
|
||||
assert!(
|
||||
timezone_name_offset(&mut s).is_err() || !s.is_empty(),
|
||||
"{input}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use winnow::{
|
||||
ascii::alpha1,
|
||||
combinator::{alt, opt},
|
||||
ModalResult, Parser,
|
||||
};
|
||||
|
||||
use super::primitive::{dec_uint, s};
|
||||
|
||||
pub(super) 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)
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
//! Primitive combinators.
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use winnow::{
|
||||
ascii::{digit1, multispace0, Uint},
|
||||
combinator::{alt, delimited, not, opt, peek, preceded, repeat, separated},
|
||||
error::{ContextError, ParserError, StrContext, StrContextValue},
|
||||
stream::AsChar,
|
||||
token::{none_of, one_of, take_while},
|
||||
Parser,
|
||||
};
|
||||
|
||||
/// Allow spaces and comments before a parser
|
||||
///
|
||||
/// Every token parser should be wrapped in this to allow spaces and comments.
|
||||
/// It is only preceding, because that allows us to check mandatory whitespace
|
||||
/// after running the parser.
|
||||
pub(super) fn s<'a, O, E>(p: impl Parser<&'a str, O, E>) -> impl Parser<&'a str, O, E>
|
||||
where
|
||||
E: ParserError<&'a str>,
|
||||
{
|
||||
preceded(space, p)
|
||||
}
|
||||
|
||||
/// Parse the space in-between tokens
|
||||
///
|
||||
/// You probably want to use the [`s`] combinator instead.
|
||||
pub(super) fn space<'a, E>(input: &mut &'a str) -> winnow::Result<(), E>
|
||||
where
|
||||
E: ParserError<&'a str>,
|
||||
{
|
||||
separated(0.., multispace0, alt((comment, ignored_hyphen_or_plus))).parse_next(input)
|
||||
}
|
||||
|
||||
/// A hyphen or plus is ignored when it is not followed by a digit
|
||||
///
|
||||
/// This includes being followed by a comment! Compare these inputs:
|
||||
/// ```txt
|
||||
/// - 12 weeks
|
||||
/// - (comment) 12 weeks
|
||||
/// ```
|
||||
/// The last comment should be ignored.
|
||||
///
|
||||
/// The plus is undocumented, but it seems to be ignored.
|
||||
fn ignored_hyphen_or_plus<'a, E>(input: &mut &'a str) -> winnow::Result<(), E>
|
||||
where
|
||||
E: ParserError<&'a str>,
|
||||
{
|
||||
(
|
||||
alt(('-', '+')),
|
||||
multispace0,
|
||||
peek(not(take_while(1, AsChar::is_dec_digit))),
|
||||
)
|
||||
.void()
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
/// Parse a comment
|
||||
///
|
||||
/// A comment is given between parentheses, which must be balanced. Any other
|
||||
/// tokens can be within the comment.
|
||||
fn comment<'a, E>(input: &mut &'a str) -> winnow::Result<(), E>
|
||||
where
|
||||
E: ParserError<&'a str>,
|
||||
{
|
||||
delimited(
|
||||
'(',
|
||||
repeat(0.., alt((none_of(['(', ')']).void(), comment))),
|
||||
')',
|
||||
)
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
/// Parse a signed decimal integer.
|
||||
///
|
||||
/// Rationale for not using `winnow::ascii::dec_int`: When upgrading winnow from
|
||||
/// 0.5 to 0.7, we discovered that `winnow::ascii::dec_int` now accepts only the
|
||||
/// following two forms:
|
||||
///
|
||||
/// - 0
|
||||
/// - [+-]?[1-9][0-9]*
|
||||
///
|
||||
/// Inputs like [+-]?0[0-9]* (e.g., `+012`) are therefore rejected. We provide a
|
||||
/// custom implementation to support such zero-prefixed integers.
|
||||
#[allow(unused)]
|
||||
pub(super) fn dec_int<'a, E>(input: &mut &'a str) -> winnow::Result<i32, E>
|
||||
where
|
||||
E: ParserError<&'a str>,
|
||||
{
|
||||
(opt(one_of(['+', '-'])), digit1)
|
||||
.void()
|
||||
.take()
|
||||
.verify_map(|s: &str| s.parse().ok())
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
/// Parse an unsigned decimal integer.
|
||||
///
|
||||
/// See the rationale for `dec_int` for why we don't use
|
||||
/// `winnow::ascii::dec_uint`.
|
||||
pub(super) fn dec_uint<'a, O, E>(input: &mut &'a str) -> winnow::Result<O, E>
|
||||
where
|
||||
O: Uint + FromStr,
|
||||
E: ParserError<&'a str>,
|
||||
{
|
||||
dec_uint_str
|
||||
.verify_map(|s: &str| s.parse().ok())
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
/// Parse an unsigned decimal integer as a string slice.
|
||||
pub(super) fn dec_uint_str<'a, E>(input: &mut &'a str) -> winnow::Result<&'a str, E>
|
||||
where
|
||||
E: ParserError<&'a str>,
|
||||
{
|
||||
digit1.void().take().parse_next(input)
|
||||
}
|
||||
|
||||
/// Parse a colon preceded by whitespace.
|
||||
pub(super) fn colon<'a, E>(input: &mut &'a str) -> winnow::Result<(), E>
|
||||
where
|
||||
E: ParserError<&'a str>,
|
||||
{
|
||||
s(':').void().parse_next(input)
|
||||
}
|
||||
|
||||
/// Parse a plus or minus character optionally preceeded by whitespace.
|
||||
pub(super) fn plus_or_minus<'a, E>(input: &mut &'a str) -> winnow::Result<char, E>
|
||||
where
|
||||
E: ParserError<&'a str>,
|
||||
{
|
||||
s(alt(('+', '-'))).parse_next(input)
|
||||
}
|
||||
|
||||
/// Create a context error with a reason.
|
||||
pub(super) fn ctx_err(reason: &'static str) -> ContextError {
|
||||
let mut err = ContextError::new();
|
||||
err.push(StrContext::Expected(StrContextValue::Description(reason)));
|
||||
err
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
//! Parse a pure number string.
|
||||
//!
|
||||
//! From the GNU docs:
|
||||
//!
|
||||
//! > The precise interpretation of a pure decimal number depends on the
|
||||
//! > context in the date string.
|
||||
//! >
|
||||
//! > If the decimal number is of the form YYYYMMDD and no other calendar
|
||||
//! > date item (*note Calendar date items::) appears before it in the date
|
||||
//! > string, then YYYY is read as the year, MM as the month number and DD as
|
||||
//! > the day of the month, for the specified calendar date.
|
||||
//! >
|
||||
//! > If the decimal number is of the form HHMM and no other time of day
|
||||
//! > item appears before it in the date string, then HH is read as the hour
|
||||
//! > of the day and MM as the minute of the hour, for the specified time of
|
||||
//! > day. MM can also be omitted.
|
||||
//! >
|
||||
//! > If both a calendar date and a time of day appear to the left of a
|
||||
//! > number in the date string, but no relative item, then the number
|
||||
//! > overrides the year.
|
||||
|
||||
use winnow::{ModalResult, Parser};
|
||||
|
||||
use super::primitive::{dec_uint_str, s};
|
||||
|
||||
/// Parse a pure number string and return it as an owned `String`. We return a
|
||||
/// `String` here because the interpretation of the number depends on the
|
||||
/// parsing context in which it appears. The interpretation is deferred to the
|
||||
/// result building phase.
|
||||
pub(super) fn parse(input: &mut &str) -> ModalResult<String> {
|
||||
s(dec_uint_str)
|
||||
.map(|s: &str| s.to_owned())
|
||||
.parse_next(input)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user