1 Commits

Author SHA1 Message Date
Sylvestre Ledru bf6f14bc09 version 0.2.1 2023-05-12 13:57:25 +02:00
35 changed files with 736 additions and 8575 deletions
+63 -65
View File
@@ -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@v6
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
-37
View File
@@ -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 -986
View File
File diff suppressed because it is too large Load Diff
+6 -18
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2023 Sylvestre Ledru and others
Copyright (c) 2023 uutils
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+30 -70
View File
@@ -1,76 +1,46 @@
# parse_datetime
# humantime_to_duration
[![Crates.io](https://img.shields.io/crates/v/parse_datetime.svg)](https://crates.io/crates/parse_datetime)
[![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/uutils/parse_datetime/blob/main/LICENSE)
[![CodeCov](https://codecov.io/gh/uutils/parse_datetime/branch/main/graph/badge.svg)](https://codecov.io/gh/uutils/parse_datetime)
[![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/uutils/parse_datetime?utm_source=badge)
[![Crates.io](https://img.shields.io/crates/v/humantime_to_duration.svg)](https://crates.io/crates/humantime_to_duration)
[![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/uutils/humantime_to_duration/blob/main/LICENSE)
[![CodeCov](https://codecov.io/gh/uutils/humantime_to_duration/branch/main/graph/badge.svg)](https://codecov.io/gh/uutils/humantime_to_duration)
A Rust crate for parsing human-readable relative time strings and
human-readable datetime strings.
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.
-146
View File
@@ -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
View File
@@ -1 +0,0 @@
corpus
+171 -282
View File
@@ -1,293 +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.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359"
dependencies = [
"jiff-static",
"jiff-tzdb-platform",
"log",
"portable-atomic",
"portable-atomic-util",
"serde_core",
"windows-sys",
]
[[package]]
name = "jiff-static"
version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4"
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.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d"
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 = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "winnow"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
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
View File
@@ -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);
});
-64
View File
@@ -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);
});
-6
View File
@@ -1,6 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
]
}
-757
View File
File diff suppressed because it is too large Load Diff
-1023
View File
File diff suppressed because it is too large Load Diff
-76
View File
@@ -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}")
}
}
}
-640
View File
File diff suppressed because it is too large Load Diff
-137
View File
@@ -1,137 +0,0 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//! 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}");
}
}
}
-38
View File
@@ -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())
}
}
-774
View File
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More