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
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf6f14bc09 |
@@ -1,15 +0,0 @@
|
||||
name: Security audit
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rustsec/audit-check@v2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
+63
-65
@@ -3,9 +3,7 @@ on: [push, pull_request]
|
||||
name: Basic CI
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: "0"
|
||||
CARGO_TERM_COLOR: always
|
||||
RUST_MIN_SRV: "1.71.1"
|
||||
|
||||
jobs:
|
||||
check:
|
||||
@@ -15,8 +13,15 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- run: cargo check
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
|
||||
test:
|
||||
name: cargo test
|
||||
@@ -25,15 +30,31 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- run: cargo test
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
|
||||
fmt:
|
||||
name: cargo fmt --all -- --check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- run: cargo fmt --all -- --check
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- run: rustup component add rustfmt
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
clippy:
|
||||
name: cargo clippy -- -D warnings
|
||||
@@ -42,21 +63,17 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- 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@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_MIN_SRV }}
|
||||
- run: cargo check
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
- run: rustup component add clippy
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: -- -D warnings
|
||||
|
||||
coverage:
|
||||
name: Code Coverage
|
||||
@@ -69,46 +86,53 @@ jobs:
|
||||
- { os: macos-latest , features: macos }
|
||||
- { os: windows-latest , features: windows }
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: emulate nightly toolchain
|
||||
run: echo "RUSTC_BOOTSTRAP=1" >> "${GITHUB_ENV}"
|
||||
- uses: actions/checkout@v3
|
||||
- name: Initialize workflow variables
|
||||
id: vars
|
||||
shell: bash
|
||||
run: |
|
||||
## VARs setup
|
||||
outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; }
|
||||
# toolchain
|
||||
TOOLCHAIN="nightly" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support
|
||||
# * specify gnu-type TOOLCHAIN for windows; `grcov` requires gnu-style code coverage data files
|
||||
case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac;
|
||||
# * use requested TOOLCHAIN if specified
|
||||
if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi
|
||||
outputs TOOLCHAIN
|
||||
# target-specific options
|
||||
# * CARGO_FEATURES_OPTION
|
||||
CARGO_FEATURES_OPTION='--all -- --check' ; ## default to '--all-features' for code coverage
|
||||
# * CODECOV_FLAGS
|
||||
CODECOV_FLAGS=$( echo "${{ matrix.job.os }}" | sed 's/[^[:alnum:]]/_/g' )
|
||||
outputs CODECOV_FLAGS
|
||||
|
||||
- name: rust toolchain ~ install
|
||||
uses: dtolnay/rust-toolchain@nightly # this job is not reproducible...
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
components: llvm-tools-preview
|
||||
toolchain: ${{ steps.vars.outputs.TOOLCHAIN }}
|
||||
default: true
|
||||
profile: minimal # minimal component installation (ie, no documentation)
|
||||
- name: Test
|
||||
run: cargo test --no-fail-fast
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast
|
||||
env:
|
||||
CARGO_INCREMENTAL: "0"
|
||||
RUSTC_WRAPPER: ""
|
||||
RUSTFLAGS: "-Cinstrument-coverage -Zcoverage-options=branch -Ccodegen-units=1 -Copt-level=0 -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
|
||||
RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
|
||||
RUSTDOCFLAGS: "-Cpanic=abort"
|
||||
LLVM_PROFILE_FILE: "parse_datetime-%p-%m.profraw"
|
||||
- name: "`grcov` ~ install"
|
||||
id: build_grcov
|
||||
shell: bash
|
||||
run: |
|
||||
git clone --depth=1 https://github.com/mozilla/grcov.git ~/grcov/
|
||||
git clone https://github.com/mozilla/grcov.git ~/grcov/
|
||||
cd ~/grcov
|
||||
# Hardcode the version of crossbeam-epoch. See
|
||||
# https://github.com/uutils/coreutils/issues/3680
|
||||
sed -i -e "s|tempfile =|crossbeam-epoch = \"=0.9.8\"\ntempfile =|" Cargo.toml
|
||||
cargo install --path . --locked
|
||||
cargo install --path .
|
||||
cd -
|
||||
# Uncomment when the upstream issue
|
||||
# https://github.com/mozilla/grcov/issues/849 is fixed
|
||||
@@ -126,43 +150,17 @@ jobs:
|
||||
COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info"
|
||||
mkdir -p "${COVERAGE_REPORT_DIR}"
|
||||
# display coverage files
|
||||
grcov . --binary-path="${COVERAGE_REPORT_DIR}" --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique
|
||||
grcov . --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique
|
||||
# generate coverage report
|
||||
grcov . --binary-path="${COVERAGE_REPORT_DIR}" --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" --source-dir .
|
||||
grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"
|
||||
echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT
|
||||
- name: Upload coverage results (to Codecov.io)
|
||||
uses: codecov/codecov-action@v7
|
||||
uses: codecov/codecov-action@v3
|
||||
# if: steps.vars.outputs.HAS_CODECOV_TOKEN
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ${{ steps.coverage.outputs.report }}
|
||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||
file: ${{ steps.coverage.outputs.report }}
|
||||
## flags: IntegrationTests, UnitTests, ${{ steps.vars.outputs.CODECOV_FLAGS }}
|
||||
flags: ${{ steps.vars.outputs.CODECOV_FLAGS }}
|
||||
name: codecov-umbrella
|
||||
fail_ci_if_error: false
|
||||
verbose: true
|
||||
directory: ./
|
||||
root_dir: ./
|
||||
|
||||
fuzz:
|
||||
name: Run the fuzzers
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUN_FOR: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: emulate nightly toolchain
|
||||
run: echo "RUSTC_BOOTSTRAP=1" >> "${GITHUB_ENV}"
|
||||
- name: Install `cargo-fuzz`
|
||||
run: cargo install cargo-fuzz --locked
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Run fuzz_parse_datetime for XX seconds
|
||||
shell: bash
|
||||
run: |
|
||||
## Run it
|
||||
cd fuzz
|
||||
cargo fuzz run fuzz_parse_datetime -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
|
||||
- name: Run fuzz_large_year for XX seconds
|
||||
shell: bash
|
||||
run: |
|
||||
## Run it
|
||||
cd fuzz
|
||||
cargo fuzz run fuzz_large_year -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
name: CodSpeed
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
pull_request:
|
||||
# `workflow_dispatch` allows CodSpeed to trigger backtest
|
||||
# performance analysis in order to generate initial data.
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
codspeed:
|
||||
name: Run benchmarks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Setup rust toolchain, cache and cargo-codspeed binary
|
||||
uses: moonrepo/setup-rust@v1
|
||||
with:
|
||||
channel: stable
|
||||
cache-target: release
|
||||
bins: cargo-codspeed
|
||||
|
||||
- name: Build the benchmark target(s)
|
||||
run: cargo codspeed build
|
||||
|
||||
- name: Run the benchmarks
|
||||
uses: CodSpeedHQ/action@v4
|
||||
with:
|
||||
mode: simulation
|
||||
run: cargo codspeed run
|
||||
Generated
+19
-977
File diff suppressed because it is too large
Load Diff
+6
-18
@@ -1,24 +1,12 @@
|
||||
[package]
|
||||
name = "parse_datetime"
|
||||
description = "parsing human-readable time strings and converting them to a Zoned datetime"
|
||||
version = "0.15.0"
|
||||
name = "humantime_to_duration"
|
||||
description = " parsing human-readable relative time strings and converting them to a Duration"
|
||||
version = "0.2.1"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/uutils/parse_datetime"
|
||||
repository = "https://github.com/uutils/humantime_to_duration"
|
||||
readme = "README.md"
|
||||
rust-version = "1.71.1"
|
||||
|
||||
[dependencies]
|
||||
winnow = "1.0.0"
|
||||
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"
|
||||
|
||||
[dev-dependencies.codspeed-criterion-compat]
|
||||
version = "4.0"
|
||||
|
||||
[[bench]]
|
||||
name = "parse_datetime"
|
||||
harness = false
|
||||
regex = "1.7"
|
||||
time = "0.3.20"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Sylvestre Ledru and others
|
||||
Copyright (c) 2023 uutils
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,76 +1,46 @@
|
||||
# parse_datetime
|
||||
# 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)
|
||||
[](https://codspeed.io/uutils/parse_datetime?utm_source=badge)
|
||||
[](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)
|
||||
|
||||
A Rust crate for parsing human-readable relative time strings and
|
||||
human-readable datetime strings.
|
||||
A Rust crate for parsing human-readable relative time strings and converting them to a `Duration`.
|
||||
|
||||
## Features
|
||||
|
||||
- Parses a variety of human-readable and standard time formats.
|
||||
- Parses a variety of human-readable time formats.
|
||||
- Supports positive and negative durations.
|
||||
- Allows for chaining time units (e.g., "1 hour 2 minutes" or "2 days 2 hours ago").
|
||||
- Allows for chaining time units (e.g., "1 hour 2 minutes" or "2 days and 2 hours").
|
||||
- Calculate durations relative to a specified date.
|
||||
- Relies on Jiff
|
||||
|
||||
## Usage
|
||||
|
||||
Add `parse_datetime` to your `Cargo.toml` with:
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```
|
||||
cargo add parse_datetime
|
||||
```toml
|
||||
[dependencies]
|
||||
humantime_to_duration = "0.2.1"
|
||||
```
|
||||
|
||||
Then, import the crate and use the `parse_datetime_at_date` function:
|
||||
|
||||
```rs
|
||||
use jiff::{ToSpan, Zoned};
|
||||
use parse_datetime::{parse_datetime_at_date, ParsedDateTime};
|
||||
|
||||
let now = Zoned::now();
|
||||
let after = parse_datetime_at_date(now.clone(), "+3 days");
|
||||
|
||||
match after.unwrap() {
|
||||
ParsedDateTime::InRange(z) => assert_eq!(now.checked_add(3.days()).unwrap(), z),
|
||||
ParsedDateTime::Extended(_) => unreachable!("unexpected for this input"),
|
||||
}
|
||||
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;
|
||||
|
||||
For DateTime parsing, import the `parse_datetime` function:
|
||||
let duration = from_str("+3 days");
|
||||
assert_eq!(duration.unwrap(), Duration::days(3));
|
||||
|
||||
```rs
|
||||
use jiff::{civil::{date, time} ,Zoned};
|
||||
use parse_datetime::{parse_datetime, ParsedDateTime};
|
||||
|
||||
let dt = parse_datetime("2021-02-14 06:37:47");
|
||||
match dt.unwrap() {
|
||||
ParsedDateTime::InRange(z) => assert_eq!(z, Zoned::now().with().date(date(2021, 2, 14)).time(time(6, 37, 47, 0)).build().unwrap()),
|
||||
ParsedDateTime::Extended(_) => unreachable!("unexpected for this input"),
|
||||
}
|
||||
```
|
||||
|
||||
For years beyond jiff's representable range (e.g., year 10000+), the result is an `ExtendedDateTime`:
|
||||
|
||||
```rs
|
||||
use parse_datetime::{parse_datetime, ParsedDateTime};
|
||||
|
||||
let dt = parse_datetime("12000-01-01").unwrap();
|
||||
match dt {
|
||||
ParsedDateTime::Extended(ext) => {
|
||||
assert_eq!(ext.year, 12000);
|
||||
assert_eq!(ext.month, 1);
|
||||
assert_eq!(ext.day, 1);
|
||||
}
|
||||
ParsedDateTime::InRange(_) => unreachable!("year 12000 is out of jiff range"),
|
||||
}
|
||||
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)
|
||||
);
|
||||
```
|
||||
|
||||
### Supported Formats
|
||||
|
||||
The `parse_datetime` and `parse_datetime_at_date` functions support absolute datetime and the following relative times:
|
||||
The `from_str` and `from_str_at_date` functions support the following formats for relative time:
|
||||
|
||||
- `num` `unit` (e.g., "-1 hour", "+3 days")
|
||||
- `unit` (e.g., "hour", "day")
|
||||
@@ -78,38 +48,28 @@ The `parse_datetime` and `parse_datetime_at_date` functions support absolute dat
|
||||
- "yesterday"
|
||||
- "tomorrow"
|
||||
- use "ago" for the past
|
||||
- use "next" or "last" with `unit` (e.g., "next week", "last year")
|
||||
- unix timestamps (for example "@0" "@1344000")
|
||||
- combined units with "and" or "," (e.g., "2 years and 1 month", "1 day, 2 hours" or "2 weeks 1 second")
|
||||
|
||||
`num` can be a positive or negative integer.
|
||||
`unit` can be one of the following: "fortnight", "week", "day", "hour", "minute", "min", "second", "sec" and their plural forms.
|
||||
|
||||
## Return Values
|
||||
|
||||
### parse_datetime and parse_datetime_at_date
|
||||
The `from_str` and `from_str_at_date` functions return:
|
||||
|
||||
The `parse_datetime` and `parse_datetime_at_date` function return:
|
||||
- `Ok(Duration)` - If the input string can be parsed as a relative time
|
||||
- `Err(ParseDurationError)` - If the input string cannot be parsed as a relative time
|
||||
|
||||
- `Ok(ParsedDateTime)` - If the input string can be parsed
|
||||
- `ParsedDateTime::InRange(Zoned)` for years supported by `jiff::Zoned`
|
||||
- `ParsedDateTime::Extended(ExtendedDateTime)` for out-of-range years (for example `>9999`)
|
||||
- `Err(ParseDateTimeError::InvalidInput)` - If the input string cannot be parsed
|
||||
This function will return `Err(ParseDurationError::InvalidInput)` if the input string
|
||||
cannot be parsed as a relative time.
|
||||
|
||||
## Fuzzer
|
||||
|
||||
To run the fuzzer:
|
||||
|
||||
```
|
||||
$ cd fuzz
|
||||
$ cargo install cargo-fuzz
|
||||
$ cargo +nightly fuzz run fuzz_parse_datetime
|
||||
$ cargo fuzz run fuzz_from_str
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT License](LICENSE).
|
||||
|
||||
## Note
|
||||
|
||||
At some point, this crate was called humantime_to_duration.
|
||||
It has been renamed to cover more cases.
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
use codspeed_criterion_compat::{criterion_group, criterion_main, Criterion};
|
||||
use jiff::Zoned;
|
||||
use parse_datetime::{parse_datetime, parse_datetime_at_date};
|
||||
|
||||
fn bench_iso_datetime(c: &mut Criterion) {
|
||||
c.bench_function("parse_iso_datetime", |b| {
|
||||
b.iter(|| parse_datetime("2021-02-14 06:37:47 +0000"))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_iso_datetime_t_sep(c: &mut Criterion) {
|
||||
c.bench_function("parse_iso_datetime_t_separator", |b| {
|
||||
b.iter(|| parse_datetime("2021-02-14T22:37:47-0800"))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_date_only(c: &mut Criterion) {
|
||||
c.bench_function("parse_date_only", |b| {
|
||||
b.iter(|| parse_datetime("1997-01-01"))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_date_slash_format(c: &mut Criterion) {
|
||||
c.bench_function("parse_date_slash_format", |b| {
|
||||
b.iter(|| parse_datetime("05/07/1987"))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_epoch_timestamp(c: &mut Criterion) {
|
||||
c.bench_function("parse_epoch_timestamp", |b| {
|
||||
b.iter(|| parse_datetime("@1613371067"))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_relative_time(c: &mut Criterion) {
|
||||
let now = Zoned::now();
|
||||
c.bench_function("parse_relative_time", |b| {
|
||||
b.iter(|| parse_datetime_at_date(now.clone(), "+3 days"))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_relative_time_complex(c: &mut Criterion) {
|
||||
let now = Zoned::now();
|
||||
c.bench_function("parse_relative_time_complex", |b| {
|
||||
b.iter(|| parse_datetime_at_date(now.clone(), "1 year 3 months 2 days ago"))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_relative_keywords(c: &mut Criterion) {
|
||||
c.bench_function("parse_yesterday", |b| {
|
||||
b.iter(|| parse_datetime("yesterday"))
|
||||
});
|
||||
c.bench_function("parse_tomorrow", |b| b.iter(|| parse_datetime("tomorrow")));
|
||||
c.bench_function("parse_now", |b| b.iter(|| parse_datetime("now")));
|
||||
}
|
||||
|
||||
fn bench_weekday(c: &mut Criterion) {
|
||||
c.bench_function("parse_weekday", |b| b.iter(|| parse_datetime("wednesday")));
|
||||
}
|
||||
|
||||
fn bench_timezone_offset(c: &mut Criterion) {
|
||||
c.bench_function("parse_timezone_offset", |b| {
|
||||
b.iter(|| parse_datetime("UTC+07:00"))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_datetime_with_delta(c: &mut Criterion) {
|
||||
c.bench_function("parse_datetime_with_delta", |b| {
|
||||
b.iter(|| parse_datetime("1997-01-01 00:00:00 +0000 +1 year"))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_ctime_format(c: &mut Criterion) {
|
||||
c.bench_function("parse_ctime_format", |b| {
|
||||
b.iter(|| parse_datetime("Wed Jan 1 00:00:00 1997"))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_datetime_with_timezone_name(c: &mut Criterion) {
|
||||
c.bench_function("parse_datetime_with_tz_name", |b| {
|
||||
b.iter(|| parse_datetime("1997-01-19 08:17:48 BRT"))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_datetime_ending_in_z(c: &mut Criterion) {
|
||||
c.bench_function("parse_datetime_ending_in_z", |b| {
|
||||
b.iter(|| parse_datetime("2023-06-03 12:00:01Z"))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_invalid_input(c: &mut Criterion) {
|
||||
c.bench_function("parse_invalid_input", |b| {
|
||||
b.iter(|| parse_datetime("NotADate"))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_extended_year(c: &mut Criterion) {
|
||||
c.bench_function("parse_extended_year", |b| {
|
||||
b.iter(|| parse_datetime("10000-01-01"))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_extended_year_rollover(c: &mut Criterion) {
|
||||
c.bench_function("parse_extended_year_rollover", |b| {
|
||||
b.iter(|| parse_datetime("9999-12-31 +1 day"))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_extended_year_relative(c: &mut Criterion) {
|
||||
let base = jiff::civil::DateTime::from(jiff::civil::date(2000, 1, 1))
|
||||
.to_zoned(jiff::tz::TimeZone::UTC)
|
||||
.unwrap();
|
||||
c.bench_function("parse_extended_year_relative", |b| {
|
||||
b.iter(|| parse_datetime_at_date(base.clone(), "10000-01-01 +1 day"))
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_extended_large_year(c: &mut Criterion) {
|
||||
c.bench_function("parse_extended_large_year", |b| {
|
||||
b.iter(|| parse_datetime("999999-06-15"))
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_iso_datetime,
|
||||
bench_iso_datetime_t_sep,
|
||||
bench_date_only,
|
||||
bench_date_slash_format,
|
||||
bench_epoch_timestamp,
|
||||
bench_relative_time,
|
||||
bench_relative_time_complex,
|
||||
bench_relative_keywords,
|
||||
bench_weekday,
|
||||
bench_timezone_offset,
|
||||
bench_datetime_with_delta,
|
||||
bench_ctime_format,
|
||||
bench_datetime_with_timezone_name,
|
||||
bench_datetime_ending_in_z,
|
||||
bench_invalid_input,
|
||||
bench_extended_year,
|
||||
bench_extended_year_rollover,
|
||||
bench_extended_year_relative,
|
||||
bench_extended_large_year,
|
||||
);
|
||||
criterion_main!(benches);
|
||||
@@ -1 +0,0 @@
|
||||
corpus
|
||||
Generated
+171
-273
@@ -1,284 +1,182 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.2"
|
||||
name = "aho-corasick"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||
|
||||
[[package]]
|
||||
name = "fuzz_parse_datetime"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"jiff",
|
||||
"libfuzzer-sys",
|
||||
"parse_datetime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"jiff-tzdb-platform",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-tzdb"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c900ef84826f1338a557697dc8fc601df9ca9af4ac137c7fb61d4c6f2dfd3076"
|
||||
|
||||
[[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.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.183"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9fd2f41a1cba099f79a0b6b6c35656cf7c03351a7bae8ff0f28f25270f929d2"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[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.15.0"
|
||||
dependencies = [
|
||||
"jiff",
|
||||
"num-traits",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
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 = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
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.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
|
||||
checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
name = "arbitrary"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "fuzz_from_str"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"humantime_to_duration",
|
||||
"libfuzzer-sys",
|
||||
"rand",
|
||||
"regex",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime_to_duration"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.142"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "beb09950ae85a0a94b27676cccf37da5ff13f27076aa1adbc6545dd0d0e1bd4e"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.17.1"
|
||||
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"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.160"
|
||||
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"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
+10
-15
@@ -1,27 +1,22 @@
|
||||
[package]
|
||||
name = "fuzz_parse_datetime"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
name = "fuzz_from_str"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4.7"
|
||||
arbitrary = { version = "1", features = ["derive"] }
|
||||
jiff = "0.2"
|
||||
rand = "0.8.4"
|
||||
libfuzzer-sys = "0.4"
|
||||
regex = "1.8.1"
|
||||
time = "0.3.20"
|
||||
|
||||
[dependencies.parse_datetime]
|
||||
[dependencies.humantime_to_duration]
|
||||
path = "../"
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_parse_datetime"
|
||||
path = "fuzz_targets/parse_datetime.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_large_year"
|
||||
path = "fuzz_targets/large_year.rs"
|
||||
name = "fuzz_from_str"
|
||||
path = "fuzz_targets/from_str.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
@@ -4,5 +4,5 @@ use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let s = std::str::from_utf8(data).unwrap_or("");
|
||||
let _ = parse_datetime::parse_datetime(s);
|
||||
let _ = humantime_to_duration::from_str(s);
|
||||
});
|
||||
@@ -1,64 +0,0 @@
|
||||
#![no_main]
|
||||
|
||||
use arbitrary::Arbitrary;
|
||||
use jiff::{civil::DateTime, tz::TimeZone};
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
#[derive(Arbitrary, Debug)]
|
||||
struct Input {
|
||||
/// Year for the base date (biased toward boundary years).
|
||||
base_year_selector: u8,
|
||||
/// Year to embed in a constructed large-year input string.
|
||||
input_year: u32,
|
||||
month: u8,
|
||||
day: u8,
|
||||
/// Suffix appended after the constructed date (e.g. relative items).
|
||||
suffix: String,
|
||||
/// Whether to also call parse_datetime (no base).
|
||||
try_no_base: bool,
|
||||
}
|
||||
|
||||
fn base_year(selector: u8) -> i16 {
|
||||
match selector % 6 {
|
||||
0 => 2024,
|
||||
1 => 9998,
|
||||
2 => 9999,
|
||||
3 => 1,
|
||||
4 => 100,
|
||||
_ => (selector as i16) * 40,
|
||||
}
|
||||
}
|
||||
|
||||
fn clamp_year(y: u32) -> u32 {
|
||||
// Focus on the interesting range: 9990..=100_000 and 0..=20_000
|
||||
match y % 4 {
|
||||
0 => 9990 + (y % 20), // near boundary
|
||||
1 => 10000 + (y % 90_000), // large years
|
||||
2 => y % 20_000, // general range
|
||||
_ => 2_147_485_540 + (y % 10), // near GNU_MAX_YEAR
|
||||
}
|
||||
}
|
||||
|
||||
fuzz_target!(|input: Input| {
|
||||
let year = clamp_year(input.input_year);
|
||||
let month = (input.month % 12) + 1;
|
||||
let day = (input.day % 28) + 1;
|
||||
let date_str = format!("{year:04}-{month:02}-{day:02} {}", input.suffix);
|
||||
|
||||
// Test parse_datetime (uses current time as base).
|
||||
if input.try_no_base {
|
||||
let _ = parse_datetime::parse_datetime(&date_str);
|
||||
}
|
||||
|
||||
// Test parse_datetime_at_date with a controlled base.
|
||||
let by = base_year(input.base_year_selector);
|
||||
if let Ok(base) = DateTime::new(by, 1, 1, 0, 0, 0, 0) {
|
||||
if let Ok(base) = base.to_zoned(TimeZone::UTC) {
|
||||
let _ = parse_datetime::parse_datetime_at_date(base, &date_str);
|
||||
}
|
||||
}
|
||||
|
||||
// Also try a bare large year as a pure number.
|
||||
let bare = format!("{year}");
|
||||
let _ = parse_datetime::parse_datetime(&bare);
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended"
|
||||
]
|
||||
}
|
||||
-757
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,76 +0,0 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
//! Parse an ISO 8601 date and time item
|
||||
//!
|
||||
//! The GNU docs state:
|
||||
//!
|
||||
//! > The ISO 8601 date and time of day extended format consists of an ISO 8601
|
||||
//! > date, a ‘T’ character separator, and an ISO 8601 time of day. This format
|
||||
//! > is also recognized if the ‘T’ is replaced by a space.
|
||||
//! >
|
||||
//! > In this format, the time of day should use 24-hour notation. Fractional
|
||||
//! > seconds are allowed, with either comma or period preceding the fraction.
|
||||
//! > ISO 8601 fractional minutes and hours are not supported. Typically, hosts
|
||||
//! > support nanosecond timestamp resolution; excess precision is silently
|
||||
//! > discarded.
|
||||
use winnow::{
|
||||
combinator::{alt, trace},
|
||||
seq, ModalResult, Parser,
|
||||
};
|
||||
|
||||
use crate::items::space;
|
||||
|
||||
use super::{date, 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
@@ -1,137 +0,0 @@
|
||||
// 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, Clone, Copy)]
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
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())
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user