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
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f68a01699e | |||
| 73b4b0a50c | |||
| 002ca41008 | |||
| 818cfa4ad0 | |||
| 5cf8a6fbdd | |||
| 9688db08a8 | |||
| f10a950e23 | |||
| 14bbd4e848 | |||
| dbfdea454f | |||
| 7e528ad609 | |||
| 6306843888 | |||
| d5c441bd3f | |||
| 62cb7d9687 | |||
| 642bbbf214 | |||
| 6396c07d72 | |||
| 377ba0e040 | |||
| ffb1ba35c5 | |||
| eeffa242a1 | |||
| 3a1edfb177 | |||
| 866b6b446f | |||
| 13e038a503 | |||
| 7030df6cf0 | |||
| cfbb973215 | |||
| 88a4a718b0 | |||
| 6462d410d8 | |||
| 92d095b53b | |||
| ea143af391 | |||
| 835e46e6f1 | |||
| 31b8e58cc5 | |||
| 9de30f5fc3 | |||
| d6ab892c29 | |||
| 1e4fc30f7b | |||
| 6ce151fb58 |
@@ -14,7 +14,7 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo check
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo test
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
name: cargo fmt --all -- --check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: rustup component add rustfmt
|
||||
- run: cargo fmt --all -- --check
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: rustup component add clippy
|
||||
- run: cargo clippy --all-targets -- -D warnings
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.RUST_MIN_SRV }}
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
- { os: macos-latest , features: macos }
|
||||
- { os: windows-latest , features: windows }
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Initialize workflow variables
|
||||
id: vars
|
||||
shell: bash
|
||||
@@ -134,17 +134,19 @@ jobs:
|
||||
# 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
|
||||
# generate coverage report
|
||||
grcov . --binary-path="${COVERAGE_REPORT_DIR}" --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"
|
||||
grcov . --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 .
|
||||
echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT
|
||||
- name: Upload coverage results (to Codecov.io)
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
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
|
||||
verbose: true
|
||||
directory: ./
|
||||
root_dir: ./
|
||||
|
||||
fuzz:
|
||||
name: Run the fuzzers
|
||||
@@ -152,7 +154,7 @@ jobs:
|
||||
env:
|
||||
RUN_FOR: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- name: Install `cargo-fuzz`
|
||||
run: cargo install cargo-fuzz
|
||||
|
||||
Generated
+40
-92
@@ -80,9 +80,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.0"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
@@ -96,9 +96,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.16"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35"
|
||||
checksum = "c867c356cc096b33f4981825ab281ecba3db0acefe60329f044c1789d94c6543"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"jiff-tzdb-platform",
|
||||
@@ -111,9 +111,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.16"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69"
|
||||
checksum = "f7946b4325269738f270bb55b3c19ab5c5040525f83fd625259422a9d25d9be5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -158,7 +158,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "parse_datetime"
|
||||
version = "0.13.2"
|
||||
version = "0.14.0"
|
||||
dependencies = [
|
||||
"jiff",
|
||||
"num-traits",
|
||||
@@ -195,9 +195,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.2.0"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
|
||||
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
|
||||
dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
@@ -213,9 +213,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.41"
|
||||
version = "1.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
|
||||
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -327,9 +327,9 @@ checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.108"
|
||||
version = "2.0.110"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917"
|
||||
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -338,112 +338,60 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
checksum = "a197c0ec7d131bfc6f7e82c8442ba1595aeab35da7adbf05b6b73cd06a16b6be"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
version = "0.23.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
checksum = "c2ad0b7ae9cfeef5605163839cb9221f453399f15cfb5c10be9885fcf56611f9"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"toml_parser",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
name = "toml_parser"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.20"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[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.59.0"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[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"
|
||||
version = "0.7.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||
checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "parse_datetime"
|
||||
description = "parsing human-readable time strings and converting them to a DateTime"
|
||||
version = "0.13.2"
|
||||
version = "0.14.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/uutils/parse_datetime"
|
||||
|
||||
Generated
+27
-94
@@ -16,9 +16,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.43"
|
||||
version = "1.2.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2"
|
||||
checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -34,9 +34,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
|
||||
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
|
||||
|
||||
[[package]]
|
||||
name = "fuzz_parse_datetime"
|
||||
@@ -60,24 +60,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.15"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
|
||||
checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"jiff-tzdb-platform",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.15"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
|
||||
checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -117,9 +117,9 @@ checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.10"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404"
|
||||
checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
@@ -148,7 +148,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "parse_datetime"
|
||||
version = "0.13.1"
|
||||
version = "0.13.3"
|
||||
dependencies = [
|
||||
"jiff",
|
||||
"num-traits",
|
||||
@@ -181,9 +181,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.41"
|
||||
version = "1.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
|
||||
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -194,15 +194,6 @@ version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
@@ -231,9 +222,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.108"
|
||||
version = "2.0.110"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917"
|
||||
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -242,9 +233,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.20"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
@@ -255,79 +246,21 @@ 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.59.0"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[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"
|
||||
|
||||
+169
-12
@@ -1,7 +1,7 @@
|
||||
// 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 jiff::{civil, Span, ToSpan, Zoned};
|
||||
|
||||
use super::{date, epoch, error, offset, relative, time, weekday, year, Item};
|
||||
|
||||
@@ -187,9 +187,13 @@ impl DateTimeBuilder {
|
||||
/// - 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()),
|
||||
// If a TZ="..." prefix was parsed, it should override the base's timezone
|
||||
// while keeping the base's timestamp for relative date calculations.
|
||||
let has_timezone = self.timezone.is_some();
|
||||
let base = match (self.base, self.timezone) {
|
||||
(Some(b), Some(tz)) => b.timestamp().to_zoned(tz),
|
||||
(Some(b), None) => b,
|
||||
(None, Some(tz)) => jiff::Timestamp::now().to_zoned(tz),
|
||||
(None, None) => Zoned::now(),
|
||||
};
|
||||
|
||||
@@ -204,7 +208,7 @@ impl DateTimeBuilder {
|
||||
|| self.time.is_some()
|
||||
|| self.weekday.is_some()
|
||||
|| self.offset.is_some()
|
||||
|| self.timezone.is_some();
|
||||
|| has_timezone;
|
||||
|
||||
let mut dt = if need_midnight {
|
||||
base.with().time(civil::time(0, 0, 0, 0)).build()?
|
||||
@@ -276,13 +280,23 @@ impl DateTimeBuilder {
|
||||
|
||||
// 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()?
|
||||
})?;
|
||||
dt = match rel {
|
||||
relative::Relative::Years(_) | relative::Relative::Months(_) => {
|
||||
// GNU way of calculating relative months and years
|
||||
// GNU changes the month and then checks if the target month has
|
||||
// this day. If this day does not exist in the target month it overflows
|
||||
// the difference
|
||||
let original_day_of_month = dt.day();
|
||||
dt = dt.checked_add::<Span>(rel.try_into()?)?;
|
||||
if original_day_of_month != dt.day() {
|
||||
dt = dt.checked_add(
|
||||
(original_day_of_month.checked_sub(dt.day()).unwrap_or(0)).days(),
|
||||
)?;
|
||||
}
|
||||
dt
|
||||
}
|
||||
_ => dt.checked_add::<Span>(rel.try_into()?)?,
|
||||
}
|
||||
}
|
||||
|
||||
// 4e. Apply final fixed offset.
|
||||
@@ -319,3 +333,146 @@ impl TryFrom<Vec<Item>> for DateTimeBuilder {
|
||||
Ok(builder)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// Helper functions to create test items by parsing
|
||||
fn timestamp() -> epoch::Timestamp {
|
||||
let mut input = "@1234567890";
|
||||
epoch::parse(&mut input).unwrap()
|
||||
}
|
||||
|
||||
fn date() -> date::Date {
|
||||
let mut input = "2023-06-15";
|
||||
date::parse(&mut input).unwrap()
|
||||
}
|
||||
|
||||
fn time() -> time::Time {
|
||||
let mut input = "12:30:00";
|
||||
time::parse(&mut input).unwrap()
|
||||
}
|
||||
|
||||
fn time_with_offset() -> time::Time {
|
||||
let mut input = "12:30:00+05:00";
|
||||
time::parse(&mut input).unwrap()
|
||||
}
|
||||
|
||||
fn weekday() -> weekday::Weekday {
|
||||
let mut input = "monday";
|
||||
weekday::parse(&mut input).unwrap()
|
||||
}
|
||||
|
||||
fn offset() -> offset::Offset {
|
||||
let mut input = "+05:00";
|
||||
offset::timezone_offset(&mut input).unwrap()
|
||||
}
|
||||
|
||||
fn relative() -> relative::Relative {
|
||||
let mut input = "1 day";
|
||||
relative::parse(&mut input).unwrap()
|
||||
}
|
||||
|
||||
fn timezone() -> jiff::tz::TimeZone {
|
||||
jiff::tz::TimeZone::UTC
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duplicate_items_errors() {
|
||||
let test_cases = vec![
|
||||
(
|
||||
vec![Item::TimeZone(timezone()), Item::TimeZone(timezone())],
|
||||
"timezone rule cannot appear more than once",
|
||||
),
|
||||
(
|
||||
vec![Item::Timestamp(timestamp()), Item::Timestamp(timestamp())],
|
||||
"timestamp cannot appear more than once",
|
||||
),
|
||||
(
|
||||
vec![Item::Date(date()), Item::Date(date())],
|
||||
"date cannot appear more than once",
|
||||
),
|
||||
(
|
||||
vec![Item::Time(time()), Item::Time(time())],
|
||||
"time cannot appear more than once",
|
||||
),
|
||||
(
|
||||
vec![Item::Weekday(weekday()), Item::Weekday(weekday())],
|
||||
"weekday cannot appear more than once",
|
||||
),
|
||||
(
|
||||
vec![Item::Offset(offset()), Item::Offset(offset())],
|
||||
"time offset cannot appear more than once",
|
||||
),
|
||||
];
|
||||
|
||||
for (items, expected_err) in test_cases {
|
||||
let result = DateTimeBuilder::try_from(items);
|
||||
assert_eq!(result.unwrap_err(), expected_err);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_timestamp_cannot_be_combined_with_other_items() {
|
||||
let test_cases = vec![
|
||||
vec![Item::Date(date()), Item::Timestamp(timestamp())],
|
||||
vec![Item::Time(time()), Item::Timestamp(timestamp())],
|
||||
vec![Item::Weekday(weekday()), Item::Timestamp(timestamp())],
|
||||
vec![Item::Offset(offset()), Item::Timestamp(timestamp())],
|
||||
vec![Item::Relative(relative()), Item::Timestamp(timestamp())],
|
||||
vec![Item::Timestamp(timestamp()), Item::Date(date())],
|
||||
vec![Item::Timestamp(timestamp()), Item::Time(time())],
|
||||
vec![Item::Timestamp(timestamp()), Item::Weekday(weekday())],
|
||||
vec![Item::Timestamp(timestamp()), Item::Relative(relative())],
|
||||
vec![Item::Timestamp(timestamp()), Item::Offset(offset())],
|
||||
vec![Item::Timestamp(timestamp()), Item::Pure("2023".to_string())],
|
||||
];
|
||||
|
||||
for items in test_cases {
|
||||
let result = DateTimeBuilder::try_from(items);
|
||||
assert_eq!(
|
||||
result.unwrap_err(),
|
||||
"timestamp cannot be combined with other date/time items"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_offset_conflicts() {
|
||||
// Time with offset followed by separate Offset item
|
||||
let items1 = vec![Item::Time(time_with_offset()), Item::Offset(offset())];
|
||||
assert_eq!(
|
||||
DateTimeBuilder::try_from(items1).unwrap_err(),
|
||||
"time offset cannot appear more than once"
|
||||
);
|
||||
|
||||
// Offset item followed by Time with offset
|
||||
let items2 = vec![Item::Offset(offset()), Item::Time(time_with_offset())];
|
||||
assert_eq!(
|
||||
DateTimeBuilder::try_from(items2).unwrap_err(),
|
||||
"time offset and timezone are mutually exclusive"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_combination_date_time() {
|
||||
let items = vec![Item::Date(date()), Item::Time(time())];
|
||||
let result = DateTimeBuilder::try_from(items);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_combination_date_weekday() {
|
||||
let items = vec![Item::Date(date()), Item::Weekday(weekday())];
|
||||
let result = DateTimeBuilder::try_from(items);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_timestamp_alone() {
|
||||
let items = vec![Item::Timestamp(timestamp())];
|
||||
let result = DateTimeBuilder::try_from(items);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,3 +142,64 @@ pub(super) fn ctx_err(reason: &'static str) -> ContextError {
|
||||
err.push(StrContext::Expected(StrContextValue::Description(reason)));
|
||||
err
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_dec_int() {
|
||||
for (input, expected) in [
|
||||
("123", 123), // positive without sign
|
||||
("+123", 123), // positive with '+' sign
|
||||
("-123", -123), // negative with '-' sign
|
||||
("0", 0), // zero
|
||||
("+0", 0), // zero with '+' sign
|
||||
("-0", 0), // zero with '-' sign (parses as 0)
|
||||
("012", 12), // zero-prefixed (the main reason for this function)
|
||||
("+012", 12), // zero-prefixed with '+' sign
|
||||
("-012", -12), // zero-prefixed with '-' sign
|
||||
("00123", 123), // multiple leading zeros
|
||||
("2147483647", 2147483647), // i32::MAX
|
||||
("-2147483648", -2147483648), // i32::MIN
|
||||
] {
|
||||
let mut s = input;
|
||||
assert_eq!(
|
||||
dec_int::<ContextError>(&mut s).unwrap(),
|
||||
expected,
|
||||
"{input}"
|
||||
);
|
||||
}
|
||||
|
||||
for input in [
|
||||
"", // empty string
|
||||
"+", // sign without digits
|
||||
"-", // sign without digits
|
||||
"abc", // non-numeric
|
||||
"12a", // starts with digits but has non-digit after (but should parse "12" successfully)
|
||||
] {
|
||||
let mut s = input;
|
||||
let result = dec_int::<ContextError>(&mut s);
|
||||
// Note: "12a" will actually succeed and parse "12", leaving "a" unparsed
|
||||
if input == "12a" {
|
||||
assert_eq!(result.unwrap(), 12, "{input}");
|
||||
assert_eq!(s, "a", "Should leave 'a' unparsed");
|
||||
} else {
|
||||
assert!(result.is_err(), "{input} should fail");
|
||||
}
|
||||
}
|
||||
|
||||
// Test overflow cases
|
||||
for input in [
|
||||
"2147483648", // i32::MAX + 1
|
||||
"-2147483649", // i32::MIN - 1
|
||||
"99999999999", // way too large
|
||||
] {
|
||||
let mut s = input;
|
||||
assert!(
|
||||
dec_int::<ContextError>(&mut s).is_err(),
|
||||
"{input} should overflow"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
-2
@@ -83,7 +83,7 @@ pub(super) fn parse(input: &mut &str) -> ModalResult<Relative> {
|
||||
fn seconds(input: &mut &str) -> ModalResult<Relative> {
|
||||
(
|
||||
opt(alt((s('+').value(1), s('-').value(-1)))),
|
||||
sec_and_nsec,
|
||||
s(sec_and_nsec),
|
||||
s(alpha1).verify(|s: &str| matches!(s, "seconds" | "second" | "sec" | "secs")),
|
||||
ago,
|
||||
)
|
||||
@@ -138,11 +138,13 @@ mod tests {
|
||||
("secs", Relative::Seconds(1, 0)),
|
||||
("second ago", Relative::Seconds(-1, 0)),
|
||||
("3 seconds", Relative::Seconds(3, 0)),
|
||||
("+ 3 seconds", Relative::Seconds(3, 0)),
|
||||
("3.5 seconds", Relative::Seconds(3, 500_000_000)),
|
||||
("-3.5 seconds", Relative::Seconds(-4, 500_000_000)),
|
||||
("+3.5 seconds", Relative::Seconds(3, 500_000_000)),
|
||||
("+ 3.5 seconds", Relative::Seconds(3, 500_000_000)),
|
||||
("3.5 seconds ago", Relative::Seconds(-4, 500_000_000)),
|
||||
("-3.5 seconds ago", Relative::Seconds(3, 500_000_000)),
|
||||
("- 3.5 seconds ago", Relative::Seconds(3, 500_000_000)),
|
||||
// Minutes
|
||||
("minute", Relative::Minutes(1)),
|
||||
("minutes", Relative::Minutes(1)),
|
||||
@@ -150,29 +152,40 @@ mod tests {
|
||||
("mins", Relative::Minutes(1)),
|
||||
("10 minutes", Relative::Minutes(10)),
|
||||
("-10 minutes", Relative::Minutes(-10)),
|
||||
("- 10 minutes", Relative::Minutes(-10)),
|
||||
("10 minutes ago", Relative::Minutes(-10)),
|
||||
("-10 minutes ago", Relative::Minutes(10)),
|
||||
("- 10 minutes ago", Relative::Minutes(10)),
|
||||
("-10 minutes ago", Relative::Minutes(10)),
|
||||
("- 10 minutes ago", Relative::Minutes(10)),
|
||||
// Hours
|
||||
("hour", Relative::Hours(1)),
|
||||
("hours", Relative::Hours(1)),
|
||||
("10 hours", Relative::Hours(10)),
|
||||
("+10 hours", Relative::Hours(10)),
|
||||
("+ 10 hours", Relative::Hours(10)),
|
||||
("-10 hours", Relative::Hours(-10)),
|
||||
("- 10 hours", Relative::Hours(-10)),
|
||||
("10 hours ago", Relative::Hours(-10)),
|
||||
("-10 hours ago", Relative::Hours(10)),
|
||||
("- 10 hours ago", Relative::Hours(10)),
|
||||
// Days
|
||||
("day", Relative::Days(1)),
|
||||
("days", Relative::Days(1)),
|
||||
("10 days", Relative::Days(10)),
|
||||
("+10 days", Relative::Days(10)),
|
||||
("+ 10 days", Relative::Days(10)),
|
||||
("-10 days", Relative::Days(-10)),
|
||||
("- 10 days", Relative::Days(-10)),
|
||||
("10 days ago", Relative::Days(-10)),
|
||||
("-10 days ago", Relative::Days(10)),
|
||||
("- 10 days ago", Relative::Days(10)),
|
||||
// Multiple days
|
||||
("fortnight", Relative::Days(14)),
|
||||
("fortnights", Relative::Days(14)),
|
||||
("2 fortnights ago", Relative::Days(-28)),
|
||||
("+2 fortnights ago", Relative::Days(-28)),
|
||||
("+ 2 fortnights ago", Relative::Days(-28)),
|
||||
("week", Relative::Days(7)),
|
||||
("weeks", Relative::Days(7)),
|
||||
("2 weeks ago", Relative::Days(-14)),
|
||||
|
||||
+36
@@ -307,6 +307,42 @@ mod tests {
|
||||
assert!(parse_datetime(relative_time).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integer_seconds_offset() {
|
||||
let dt = "0 + 0 seconds";
|
||||
assert!(parse_datetime(dt).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn integer_seconds_offset_spaceless() {
|
||||
let dt = "0+0 seconds";
|
||||
assert!(parse_datetime(dt).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn floating_seconds_offset() {
|
||||
let dt = "0 + 0.0 seconds";
|
||||
assert!(parse_datetime(dt).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn floating_seconds_offset_spaceless() {
|
||||
let dt = "0+0.0 seconds";
|
||||
assert!(parse_datetime(dt).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn floating_seconds_offset_from_now() {
|
||||
let dt = "now + 1.5 seconds";
|
||||
assert!(parse_datetime(dt).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn floating_seconds_offset_from_tomorrow_spaceless() {
|
||||
let dt = "tomorrow+1.5 seconds";
|
||||
assert!(parse_datetime(dt).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+190
@@ -95,3 +95,193 @@ fn test_date_omitting_year(#[case] input: &str, #[case] year: u32, #[case] expec
|
||||
.unwrap();
|
||||
check_relative(now, input, expected);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::tz_prefix_est5("TZ=\"EST5\" 1970-01-01 00:00", "1970-01-01 00:00:00-05:00")]
|
||||
#[case::tz_prefix_pst8("TZ=\"PST8\" 1970-01-01 00:00", "1970-01-01 00:00:00-08:00")]
|
||||
#[case::tz_prefix_utc("TZ=\"UTC\" 1970-01-01 12:00", "1970-01-01 12:00:00+00:00")]
|
||||
#[case::tz_prefix_europe_paris(
|
||||
r#"TZ="Europe/Paris" 2025-01-02 03:04:05"#,
|
||||
"2025-01-02 03:04:05+01:00"
|
||||
)]
|
||||
fn test_tz_prefix_with_base_date(#[case] input: &str, #[case] expected: &str) {
|
||||
let base = "2020-06-15 12:00:00"
|
||||
.parse::<DateTime>()
|
||||
.unwrap()
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap();
|
||||
check_relative(base, input, expected);
|
||||
}
|
||||
|
||||
// Test leap year overflow: Feb 29 + years → non-leap year should overflow to March 1
|
||||
// This matches GNU date behavior
|
||||
#[rstest]
|
||||
#[case::feb29_1996_plus_1year("1996-02-29 00:00:00", "1 year", "1997-03-01 00:00:00+00:00")]
|
||||
#[case::feb29_2020_plus_1year("2020-02-29 00:00:00", "1 year", "2021-03-01 00:00:00+00:00")]
|
||||
#[case::feb29_2000_plus_1year("2000-02-29 00:00:00", "1 year", "2001-03-01 00:00:00+00:00")]
|
||||
// Edge case: 0 years should return the same date
|
||||
#[case::zero_years("2024-01-15 12:30:45", "0 years", "2024-01-15 12:30:45+00:00")]
|
||||
#[case::zero_years_feb29("2020-02-29 00:00:00", "0 years", "2020-02-29 00:00:00+00:00")]
|
||||
fn test_leap_year_overflow(#[case] base: &str, #[case] input: &str, #[case] expected: &str) {
|
||||
let now = base
|
||||
.parse::<DateTime>()
|
||||
.unwrap()
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap();
|
||||
check_relative(now, input, expected);
|
||||
}
|
||||
|
||||
// Test month arithmetic with day overflow
|
||||
// Matches GNU date behavior: when adding months causes day clamping,
|
||||
// overflow to next month (e.g., Jan 31 + 1 month = March 2/3, not Feb 28/29)
|
||||
#[rstest]
|
||||
#[case::jan31_plus_1month_leap("2024-01-31 00:00:00", "1 month", "2024-03-02 00:00:00+00:00")]
|
||||
#[case::jan31_plus_1month_nonleap("2023-01-31 00:00:00", "1 month", "2023-03-03 00:00:00+00:00")]
|
||||
#[case::mar31_plus_1month("2024-03-31 00:00:00", "1 month", "2024-05-01 00:00:00+00:00")]
|
||||
#[case::may31_plus_1month("2024-05-31 00:00:00", "1 month", "2024-07-01 00:00:00+00:00")]
|
||||
#[case::rel_2b("1997-01-19 08:17:48", "7 months ago", "1996-06-19 08:17:48+00:00")]
|
||||
// Edge case: 0 months should return the same date
|
||||
#[case::zero_months("2024-01-31 12:30:45", "0 months", "2024-01-31 12:30:45+00:00")]
|
||||
#[case::zero_months_feb29("2020-02-29 00:00:00", "0 months", "2020-02-29 00:00:00+00:00")]
|
||||
fn test_month_overflow(#[case] base: &str, #[case] input: &str, #[case] expected: &str) {
|
||||
let now = base
|
||||
.parse::<DateTime>()
|
||||
.unwrap()
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap();
|
||||
check_relative(now, input, expected);
|
||||
}
|
||||
|
||||
// Test negative year operations with leap year edge cases
|
||||
#[rstest]
|
||||
#[case::feb29_minus_1year("2020-02-29 00:00:00", "1 year ago", "2019-03-01 00:00:00+00:00")]
|
||||
#[case::feb29_minus_4years("2020-02-29 00:00:00", "4 years ago", "2016-02-29 00:00:00+00:00")]
|
||||
#[case::march1_minus_1year("2021-03-01 00:00:00", "1 year ago", "2020-03-01 00:00:00+00:00")]
|
||||
fn test_negative_year_operations(#[case] base: &str, #[case] input: &str, #[case] expected: &str) {
|
||||
let now = base
|
||||
.parse::<DateTime>()
|
||||
.unwrap()
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap();
|
||||
check_relative(now, input, expected);
|
||||
}
|
||||
|
||||
// Test negative month operations with day overflow
|
||||
#[rstest]
|
||||
#[case::march31_minus_1month("2024-03-31 00:00:00", "1 month ago", "2024-03-02 00:00:00+00:00")]
|
||||
#[case::march31_minus_1month_nonleap(
|
||||
"2023-03-31 00:00:00",
|
||||
"1 month ago",
|
||||
"2023-03-03 00:00:00+00:00"
|
||||
)]
|
||||
#[case::may31_minus_1month("2024-05-31 00:00:00", "1 month ago", "2024-05-01 00:00:00+00:00")]
|
||||
#[case::jan31_minus_1month("2024-01-31 00:00:00", "1 month ago", "2023-12-31 00:00:00+00:00")]
|
||||
fn test_negative_month_operations(#[case] base: &str, #[case] input: &str, #[case] expected: &str) {
|
||||
let now = base
|
||||
.parse::<DateTime>()
|
||||
.unwrap()
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap();
|
||||
check_relative(now, input, expected);
|
||||
}
|
||||
|
||||
// Test chained operations (multiple relative adjustments in one parse)
|
||||
// These ensure that year and month overflow logic works correctly when combined
|
||||
#[rstest]
|
||||
// Feb 29, 2020 + 1 year = March 1, 2021; + 1 month = April 1, 2021
|
||||
#[case::feb29_plus_year_plus_month(
|
||||
"2020-02-29 00:00:00",
|
||||
"1 year 1 month",
|
||||
"2021-04-01 00:00:00+00:00"
|
||||
)]
|
||||
// Jan 31, 2024 + 1 month = March 2, 2024; + 1 year = March 2, 2025
|
||||
#[case::jan31_plus_month_plus_year(
|
||||
"2024-01-31 00:00:00",
|
||||
"1 month 1 year",
|
||||
"2025-03-02 00:00:00+00:00"
|
||||
)]
|
||||
// Jan 31 + 2 months + 1 day
|
||||
#[case::jan31_plus_2months_1day(
|
||||
"2024-01-31 00:00:00",
|
||||
"2 months 1 day",
|
||||
"2024-04-01 00:00:00+00:00"
|
||||
)]
|
||||
// Feb 29 - 1 year + 1 month (March 1, 2019 + 1 month = April 1, 2019)
|
||||
#[case::feb29_minus_year_plus_month(
|
||||
"2020-02-29 00:00:00",
|
||||
"1 year ago 1 month",
|
||||
"2019-04-01 00:00:00+00:00"
|
||||
)]
|
||||
// Multiple operations with days
|
||||
#[case::complex_chain(
|
||||
"2024-01-31 12:30:45",
|
||||
"1 year 2 months 3 days 4 hours",
|
||||
"2025-04-03 16:30:45+00:00"
|
||||
)]
|
||||
fn test_chained_operations(#[case] base: &str, #[case] input: &str, #[case] expected: &str) {
|
||||
let now = base
|
||||
.parse::<DateTime>()
|
||||
.unwrap()
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap();
|
||||
check_relative(now, input, expected);
|
||||
}
|
||||
|
||||
// Test multiple month additions
|
||||
// Verifies correct handling when adding multiple months at once
|
||||
#[rstest]
|
||||
// Jan 31 + 2 months: Jan 31 -> March 31 (no clamping, month has 31 days)
|
||||
#[case::jan31_plus_2months("2024-01-31 00:00:00", "2 months", "2024-03-31 00:00:00+00:00")]
|
||||
// Jan 31 + 3 months: Jan 31 -> April 30 (clamps), overflow to May 1
|
||||
#[case::jan31_plus_3months("2024-01-31 00:00:00", "3 months", "2024-05-01 00:00:00+00:00")]
|
||||
// Jan 31 + 6 months: Jan 31 -> July 31 (no overflow)
|
||||
#[case::jan31_plus_6months("2024-01-31 00:00:00", "6 months", "2024-07-31 00:00:00+00:00")]
|
||||
// Jan 31 + 7 months: Jan 31 -> Aug 31 (no overflow)
|
||||
#[case::jan31_plus_7months("2024-01-31 00:00:00", "7 months", "2024-08-31 00:00:00+00:00")]
|
||||
// Aug 31 + 6 months: Aug 31 -> Feb 28 (2025 non-leap), overflow to March 3
|
||||
#[case::aug31_plus_6months("2024-08-31 00:00:00", "6 months", "2025-03-03 00:00:00+00:00")]
|
||||
// May 31 - 3 months: May 31 -> Feb 29 (2024 leap), overflow to March 2
|
||||
#[case::may31_minus_3months_leap(
|
||||
"2024-05-31 00:00:00",
|
||||
"3 months ago",
|
||||
"2024-03-02 00:00:00+00:00"
|
||||
)]
|
||||
// Oct 31 - 8 months: Oct 31 -> Feb 29 (2024 leap), overflow to March 2
|
||||
#[case::oct31_minus_8months_leap(
|
||||
"2024-10-31 00:00:00",
|
||||
"8 months ago",
|
||||
"2024-03-02 00:00:00+00:00"
|
||||
)]
|
||||
fn test_multiple_month_skip(#[case] base: &str, #[case] input: &str, #[case] expected: &str) {
|
||||
let now = base
|
||||
.parse::<DateTime>()
|
||||
.unwrap()
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap();
|
||||
check_relative(now, input, expected);
|
||||
}
|
||||
|
||||
// Test embedded timezone handling (cross-TZ-mishandled)
|
||||
// When TZ="..." is specified in input with a base date, apply the timezone to the base
|
||||
// https://bugs.debian.org/851934#10
|
||||
//
|
||||
// NOTE: These tests were added without implementation changes.
|
||||
// The timezone handling was already working correctly from previous commits.
|
||||
// These tests document and verify the expected behavior for this edge case.
|
||||
#[rstest]
|
||||
#[case::utc_explicit(r#"TZ="UTC0" 1970-01-01 00:00"#, "1970-01-01 00:00:00+00:00")]
|
||||
#[case::with_time(r#"TZ="EST5" 1970-01-01 12:30:45"#, "1970-01-01 12:30:45-05:00")]
|
||||
#[case::iana_timezone(
|
||||
r#"TZ="America/New_York" 1970-01-01 00:00"#,
|
||||
"1970-01-01 00:00:00-05:00"
|
||||
)]
|
||||
// Bug #851934: timezone conversion case
|
||||
// Parse date in Australia/Perth (AWST, UTC+8) and output should reflect that timezone
|
||||
// Input: 2016-08-15 07:00 in Australia/Perth -> expected: 2016-08-15 07:00:00+08:00
|
||||
#[case::perth_to_london(
|
||||
r#"TZ="Australia/Perth" 2016-08-15 07:00"#,
|
||||
"2016-08-15 07:00:00+08:00"
|
||||
)]
|
||||
fn test_embedded_timezone(#[case] input: &str, #[case] expected: &str) {
|
||||
check_absolute(input, expected);
|
||||
}
|
||||
|
||||
+117
@@ -129,6 +129,123 @@ fn test_time_invalid(#[case] input: &str) {
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::months_ago_0("2026-01-12 0 Months ago", "2026-01-12")]
|
||||
#[case::months_ago_negative_1("2026-01-12 -1 Months ago", "2026-02-12")]
|
||||
#[case::months_ago_1("2026-01-12 1 Months ago", "2025-12-12")]
|
||||
#[case::months_ago_2("2026-01-12 2 Months ago", "2025-11-12")]
|
||||
#[case::months_ago_3("2026-01-12 3 Months ago", "2025-10-12")]
|
||||
#[case::months_ago_4("2026-01-12 4 Months ago", "2025-09-12")]
|
||||
#[case::months_ago_5("2026-01-12 5 Months ago", "2025-08-12")]
|
||||
#[case::months_ago_6("2026-01-12 6 Months ago", "2025-07-12")]
|
||||
#[case::months_ago_7("2026-01-12 7 Months ago", "2025-06-12")]
|
||||
#[case::months_ago_8("2026-01-12 8 Months ago", "2025-05-12")]
|
||||
#[case::months_ago_9("2026-01-12 9 Months ago", "2025-04-12")]
|
||||
#[case::months_ago_10("2026-01-12 10 Months ago", "2025-03-12")]
|
||||
#[case::months_ago_11("2026-01-12 11 Months ago", "2025-02-12")]
|
||||
#[case::months_ago_12("2026-01-12 12 Months ago", "2025-01-12")]
|
||||
#[case::months_ago_24("2026-01-12 24 Months ago", "2024-01-12")]
|
||||
#[case::months_ago_36("2026-01-12 36 Months ago", "2023-01-12")]
|
||||
#[case::months_ago_120("2026-01-12 120 Months ago", "2016-01-12")]
|
||||
#[case::months_ago_240("2026-01-12 240 Months ago", "2006-01-12")]
|
||||
fn test_time_months_ago(#[case] input: &str, #[case] expected: &str) {
|
||||
let now = "2026-01-12"
|
||||
.parse::<DateTime>()
|
||||
.unwrap()
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap();
|
||||
check_time(input, expected, "%Y-%m-%d", Some(now));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::from_28_feb_in_1_month("2026-02-28 1 months", "2026-03-28")]
|
||||
#[case::from_28_feb_1_month_ago("2026-02-28 1 months ago", "2026-01-28")]
|
||||
#[case::from_31_jan_in_1_month("2026-01-31 1 months", "2026-03-03")]
|
||||
#[case::from_31_march_1_month_ago("2026-03-31 1 months ago", "2026-03-03")]
|
||||
#[case::from_15_march_1_month_ago("2026-03-15 1 months ago", "2026-02-15")]
|
||||
#[case::from_15_jan_in_1_month("2026-01-15 1 months", "2026-02-15")]
|
||||
fn test_relative_month_time_non_leap_year(#[case] input: &str, #[case] expected: &str) {
|
||||
let now = "2026-01-12"
|
||||
.parse::<DateTime>()
|
||||
.unwrap()
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap();
|
||||
check_time(input, expected, "%Y-%m-%d", Some(now));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::from_31_dec_1_month_ago("2026-12-31 1 months ago", "2026-12-01")]
|
||||
#[case::from_31_dec_3_month_ago("2026-12-31 3 months ago", "2026-10-01")]
|
||||
#[case::from_31_mar_in_1_month("2026-03-31 1 months", "2026-05-01")]
|
||||
#[case::from_31_mar_in_3_month("2026-03-31 3 months", "2026-07-01")]
|
||||
fn test_relative_month_time_dest_month_does_not_have_the_day(
|
||||
#[case] input: &str,
|
||||
#[case] expected: &str,
|
||||
) {
|
||||
let now = "2026-01-12"
|
||||
.parse::<DateTime>()
|
||||
.unwrap()
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap();
|
||||
check_time(input, expected, "%Y-%m-%d", Some(now));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::from_29_feb_in_1_year("2024-02-29 1 year", "2025-03-01")]
|
||||
#[case::from_29_feb_1_year_ago("2024-02-29 1 year ago", "2023-03-01")]
|
||||
fn test_relative_year_time_leap_year(#[case] input: &str, #[case] expected: &str) {
|
||||
let now = "2026-01-12"
|
||||
.parse::<DateTime>()
|
||||
.unwrap()
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap();
|
||||
check_time(input, expected, "%Y-%m-%d", Some(now));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::from_29_feb_in_1_month("2024-02-29 1 months", "2024-03-29")]
|
||||
#[case::from_29_feb_1_month_ago("2024-02-29 1 months ago", "2024-01-29")]
|
||||
#[case::from_31_jan_in_1_month("2024-01-31 1 months", "2024-03-02")]
|
||||
#[case::from_31_march_1_month_ago("2024-03-31 1 months ago", "2024-03-02")]
|
||||
#[case::from_15_march_1_month_ago("2024-03-15 1 months ago", "2024-02-15")]
|
||||
#[case::from_15_jan_in_1_month("2024-01-15 1 months", "2024-02-15")]
|
||||
fn test_relative_month_time_leap_year(#[case] input: &str, #[case] expected: &str) {
|
||||
let now = "2026-01-12"
|
||||
.parse::<DateTime>()
|
||||
.unwrap()
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap();
|
||||
check_time(input, expected, "%Y-%m-%d", Some(now));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::months_in_0_months("2026-01-12 0 months", "2026-01-12")]
|
||||
#[case::months_in_negative_1_months("2026-01-12 -1 months", "2025-12-12")]
|
||||
#[case::months_in_1_months("2026-01-12 1 months", "2026-02-12")]
|
||||
#[case::months_in_2_months("2026-01-12 2 months", "2026-03-12")]
|
||||
#[case::months_in_3_months("2026-01-12 3 months", "2026-04-12")]
|
||||
#[case::months_in_4_months("2026-01-12 4 months", "2026-05-12")]
|
||||
#[case::months_in_5_months("2026-01-12 5 months", "2026-06-12")]
|
||||
#[case::months_in_6_months("2026-01-12 6 months", "2026-07-12")]
|
||||
#[case::months_in_7_months("2026-01-12 7 months", "2026-08-12")]
|
||||
#[case::months_in_8_months("2026-01-12 8 months", "2026-09-12")]
|
||||
#[case::months_in_9_months("2026-01-12 9 months", "2026-10-12")]
|
||||
#[case::months_in_10_months("2026-01-12 10 months", "2026-11-12")]
|
||||
#[case::months_in_11_months("2026-01-12 11 months", "2026-12-12")]
|
||||
#[case::months_in_12_months("2026-01-12 12 months", "2027-01-12")]
|
||||
#[case::months_in_24_months("2026-01-12 24 months", "2028-01-12")]
|
||||
#[case::months_in_36_months("2026-01-12 36 months", "2029-01-12")]
|
||||
#[case::months_in_120_months("2026-01-12 120 months", "2036-01-12")]
|
||||
#[case::months_in_240_months("2026-01-12 240 months", "2046-01-12")]
|
||||
fn test_time_in_months(#[case] input: &str, #[case] expected: &str) {
|
||||
let now = "2026-01-12"
|
||||
.parse::<DateTime>()
|
||||
.unwrap()
|
||||
.to_zoned(TimeZone::UTC)
|
||||
.unwrap();
|
||||
check_time(input, expected, "%Y-%m-%d", Some(now));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::decimal_1_whole("1.123456789 seconds ago")]
|
||||
#[case::decimal_2_whole("12.123456789 seconds ago")]
|
||||
|
||||
Reference in New Issue
Block a user