diff --git a/.bumpversion.toml b/.bumpversion.toml index d8c8877..81bb1d9 100644 --- a/.bumpversion.toml +++ b/.bumpversion.toml @@ -3,10 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 OR MIT [tool.bumpversion] -current_version = "0.10.6" - -[[tool.bumpversion.files]] -filename = "README.md" +current_version = "0.12.1" [[tool.bumpversion.files]] filename = "src/lib.rs" diff --git a/.github/pull_request_template.md.license b/.github/pull_request_template.md.license index 90382fe..06889ab 100644 --- a/.github/pull_request_template.md.license +++ b/.github/pull_request_template.md.license @@ -1,3 +1,3 @@ SPDX-FileCopyrightText: 2023 Shun Sakai -SPDX-License-Identifier: Apache-2.0 OR MIT +SPDX-License-Identifier: CC-BY-4.0 diff --git a/.github/workflows/CI.yaml b/.github/workflows/CI.yaml index 0afe8fd..e3fe7cd 100644 --- a/.github/workflows/CI.yaml +++ b/.github/workflows/CI.yaml @@ -37,7 +37,7 @@ jobs: os: windows-2022 target: x86_64-pc-windows-msvc - toolchain-alias: msrv - toolchain: 1.67.1 + toolchain: 1.85.0 - toolchain-alias: stable toolchain: stable steps: @@ -49,7 +49,7 @@ jobs: toolchain: ${{ matrix.toolchain }} targets: ${{ matrix.target }} - name: Cache build artifacts - uses: Swatinem/rust-cache@v2.7.7 + uses: Swatinem/rust-cache@v2.8.0 with: key: ${{ matrix.target }} - name: Check a package @@ -90,7 +90,7 @@ jobs: os: windows-2022 target: x86_64-pc-windows-msvc - toolchain-alias: msrv - toolchain: 1.67.1 + toolchain: 1.85.0 - toolchain-alias: stable toolchain: stable steps: @@ -102,7 +102,7 @@ jobs: toolchain: ${{ matrix.toolchain }} targets: ${{ matrix.target }} - name: Cache build artifacts - uses: Swatinem/rust-cache@v2.7.7 + uses: Swatinem/rust-cache@v2.8.0 with: key: ${{ matrix.target }} - name: Run tests @@ -132,7 +132,7 @@ jobs: toolchain: stable components: rustfmt - name: Cache build artifacts - uses: Swatinem/rust-cache@v2.7.7 + uses: Swatinem/rust-cache@v2.8.0 - name: Check code formatted run: cargo fmt -- --check @@ -148,7 +148,7 @@ jobs: toolchain: stable components: clippy - name: Cache build artifacts - uses: Swatinem/rust-cache@v2.7.7 + uses: Swatinem/rust-cache@v2.8.0 - name: Check no lint warnings run: cargo clippy -- -D warnings - name: Check no lint warnings (all features) @@ -167,7 +167,7 @@ jobs: with: toolchain: stable - name: Cache build artifacts - uses: Swatinem/rust-cache@v2.7.7 + uses: Swatinem/rust-cache@v2.8.0 - name: Check no `rustdoc` lint warnings run: RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items --all-features @@ -182,6 +182,6 @@ jobs: with: toolchain: nightly - name: Cache build artifacts - uses: Swatinem/rust-cache@v2.7.7 + uses: Swatinem/rust-cache@v2.8.0 - name: Run benchmarks run: cargo bench --all-features diff --git a/.github/workflows/SemverChecks.yaml b/.github/workflows/SemverChecks.yaml index fd71b82..d408144 100644 --- a/.github/workflows/SemverChecks.yaml +++ b/.github/workflows/SemverChecks.yaml @@ -20,4 +20,4 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - name: Check Semantic Versioning - uses: obi1kenobi/cargo-semver-checks-action@v2.6 + uses: obi1kenobi/cargo-semver-checks-action@v2.8 diff --git a/.github/workflows/dependabot_auto_merge.yaml b/.github/workflows/dependabot_auto_merge.yaml index 1efd5ae..6225383 100644 --- a/.github/workflows/dependabot_auto_merge.yaml +++ b/.github/workflows/dependabot_auto_merge.yaml @@ -18,7 +18,7 @@ jobs: steps: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v2.2.0 + uses: dependabot/fetch-metadata@v2.4.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} - name: Approve a PR diff --git a/.github/workflows/mirror.yaml b/.github/workflows/mirror.yaml deleted file mode 100644 index fc0ba12..0000000 --- a/.github/workflows/mirror.yaml +++ /dev/null @@ -1,49 +0,0 @@ -# SPDX-FileCopyrightText: 2024 Shun Sakai -# -# SPDX-License-Identifier: Apache-2.0 OR MIT - -name: Mirror to mirror repositories - -on: - push: - branches: - - "develop" - - "master" - schedule: - - cron: "0 0 * * FRI" - workflow_dispatch: - -jobs: - gitlab: - name: Mirror to GitLab - if: (github.actor == 'sorairolake' || github.event_name == 'schedule') && github.repository_owner == 'sorairolake' - runs-on: ubuntu-24.04 - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Mirror to GitLab - uses: yesolutions/mirror-action@v0.7.0 - with: - REMOTE: "https://gitlab.com/sorairolake/nt-time.git" - GIT_USERNAME: ${{ github.actor }} - GIT_PASSWORD: ${{ secrets.GITLAB_TOKEN }} - PUSH_ALL_REFS: "false" - - codeberg: - name: Mirror to Codeberg - if: (github.actor == 'sorairolake' || github.event_name == 'schedule') && github.repository_owner == 'sorairolake' - runs-on: ubuntu-24.04 - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Mirror to Codeberg - uses: yesolutions/mirror-action@v0.7.0 - with: - REMOTE: "https://codeberg.org/sorairolake/nt-time.git" - GIT_USERNAME: ${{ github.actor }} - GIT_PASSWORD: ${{ secrets.CODEBERG_TOKEN }} - PUSH_ALL_REFS: "false" diff --git a/AUTHORS.adoc b/AUTHORS.adoc index f8a12ea..0960605 100644 --- a/AUTHORS.adoc +++ b/AUTHORS.adoc @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 Shun Sakai // -// SPDX-License-Identifier: Apache-2.0 OR MIT +// SPDX-License-Identifier: CC-BY-4.0 = List of Authors diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index a332e06..cb5b8da 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 Shun Sakai // -// SPDX-License-Identifier: Apache-2.0 OR MIT +// SPDX-License-Identifier: CC-BY-4.0 = Changelog :toc: preamble @@ -14,6 +14,47 @@ All notable changes to this project will be documented in this file. The format is based on https://keepachangelog.com/[Keep a Changelog], and this project adheres to https://semver.org/[Semantic Versioning]. +== {compare-url}/v0.12.0\...v0.12.1[0.12.1] - 2025-08-02 + +=== Removed + +* Remove `macros` feature from `time` ({pull-request-url}/340[#340]) +* Remove `derive` feature from `serde` ({pull-request-url}/341[#341]) + +== {compare-url}/v0.11.2\...v0.12.0[0.12.0] - 2025-07-29 + +=== Changed + +* Change MS-DOS date and time conversion methods to handle only the local date + and time with the resolution of 2 seconds ({pull-request-url}/338[#338]) + +== {compare-url}/v0.11.1\...v0.11.2[0.11.2] - 2025-07-28 + +=== Changed + +* Change the license for documents to CC BY 4.0 ({pull-request-url}/319[#319]) + +== {compare-url}/v0.11.0\...v0.11.1[0.11.1] - 2025-03-12 + +=== Added + +* Supports `jiff` crate ({pull-request-url}/303[#303]) + +=== Changed + +* Change `FileTime::from_str_radix` to `const fn` ({pull-request-url}/300[#300]) + +== {compare-url}/v0.10.6\...v0.11.0[0.11.0] - 2025-02-26 + +=== Added + +* Add `FileTime::from_str_radix` ({pull-request-url}/296[#296]) + +=== Changed + +* Change MSRV to 1.85.0 ({pull-request-url}/294[#294]) +* Make `Error` trait available in `no_std` mode ({pull-request-url}/294[#294]) + == {compare-url}/v0.10.5\...v0.10.6[0.10.6] - 2025-01-14 === Changed diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc index 830bd89..77ce00d 100644 --- a/CONTRIBUTING.adoc +++ b/CONTRIBUTING.adoc @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 Shun Sakai // -// SPDX-License-Identifier: Apache-2.0 OR MIT +// SPDX-License-Identifier: CC-BY-4.0 = Contribution Guide :git-flow-url: https://nvie.com/posts/a-successful-git-branching-model/ @@ -28,8 +28,8 @@ Please see the {commit-messages-guide-url}[Commit messages guide] and the . Create your patch. If your change is a feature or a bugfix, please add a test case if possible. Note that the change must pass the CI. . Please update the copyright information if possible. This project is - compliant with version 3.2 of the - https://reuse.software/spec/[_REUSE Specification_]. + compliant with version 3.3 of the + https://reuse.software/spec-3.3/[_REUSE Specification_]. https://github.com/fsfe/reuse-tool[`reuse`] is useful for updating the copyright information. . Please update the link:CHANGELOG.adoc[Changelog] if possible. diff --git a/Cargo.lock b/Cargo.lock index ee07014..5fd3734 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,67 +1,68 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "anstream" -version = "0.3.2" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", - "is-terminal", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "1.0.2" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "once_cell_polyfill", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.95" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bit-set" @@ -80,47 +81,40 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" - -[[package]] -name = "byteorder" -version = "1.5.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "num-traits", ] [[package]] name = "clap" -version = "4.3.24" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb690e81c7840c0d7aade59f242ea3b41b9bc27bcd5997890e7702ae4b32e487" +checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" dependencies = [ "clap_builder", "clap_derive", - "once_cell", ] [[package]] name = "clap_builder" -version = "4.3.24" +version = "4.5.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ed2e96bc16d8d740f6f48d663eddf4b8a0983e79210fd55479b7bcd0a69860e" +checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" dependencies = [ "anstream", "anstyle", @@ -130,9 +124,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.3.12" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ "heck", "proc-macro2", @@ -142,34 +136,46 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.5.0" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", "serde", ] +[[package]] +name = "derive-ex" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bba95f299f6b9cd47f68a847eca2ae9060a2713af532dc35c342065544845407" +dependencies = [ + "proc-macro2", + "quote", + "structmeta", + "syn", +] + [[package]] name = "errno" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -186,43 +192,57 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "getrandom" -version = "0.2.15" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", "libc", + "r-efi", "wasi", ] [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "hermit-abi" -version = "0.4.0" +name = "is_terminal_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] -name = "is-terminal" -version = "0.4.13" +name = "itoa" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", ] [[package]] -name = "itoa" -version = "1.0.14" +name = "jiff-static" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "lazy_static" @@ -232,32 +252,40 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.169" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "linux-raw-sys" -version = "0.4.15" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "nt-time" -version = "0.10.6" +version = "0.12.1" dependencies = [ "anyhow", "chrono", "clap", + "jiff", "proptest", "proptest-derive", "rand", + "rand_pcg", "serde", "serde_json", "serde_test", @@ -282,9 +310,30 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] [[package]] name = "powerfmt" @@ -294,27 +343,27 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" +checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" dependencies = [ "bit-set", "bit-vec", @@ -332,9 +381,9 @@ dependencies = [ [[package]] name = "proptest-derive" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" +checksum = "095a99f75c69734802359b682be8daaf8980296731f6470434ea2c652af1dd30" dependencies = [ "proc-macro2", "quote", @@ -349,29 +398,34 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" -version = "0.8.5" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", @@ -379,18 +433,27 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom", ] +[[package]] +name = "rand_pcg" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b48ac3f7ffaab7fac4d2376632268aa5f89abdb55f7ebf8f4d11fffccb2320f7" +dependencies = [ + "rand_core", +] + [[package]] name = "rand_xorshift" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ "rand_core", ] @@ -403,15 +466,15 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustix" -version = "0.38.43" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -428,24 +491,24 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "serde" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -454,9 +517,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.135" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "itoa", "memchr", @@ -475,15 +538,15 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "structmeta" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ad9e09554f0456d67a69c1584c9798ba733a5b50349a6c0d0948710523922d" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" dependencies = [ "proc-macro2", "quote", @@ -493,9 +556,9 @@ dependencies = [ [[package]] name = "structmeta-derive" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" dependencies = [ "proc-macro2", "quote", @@ -504,9 +567,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.96" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -515,11 +578,10 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.15.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ - "cfg-if", "fastrand", "getrandom", "once_cell", @@ -529,10 +591,11 @@ dependencies = [ [[package]] name = "test-strategy" -version = "0.3.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8361c808554228ad09bfed70f5c823caf8a3450b6881cc3a38eb57e8c08c1d9" +checksum = "43b12f9683de37f9980e485167ee624bfaa0b6b04da661e98e25ef9c2669bc1b" dependencies = [ + "derive-ex", "proc-macro2", "quote", "structmeta", @@ -541,9 +604,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -556,15 +619,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -578,9 +641,9 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "utf8parse" @@ -590,36 +653,27 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "wait-timeout" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "windows-sys" -version = "0.48.0" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ - "windows-targets 0.48.5", + "wit-bindgen-rt", ] [[package]] -name = "windows-sys" -version = "0.52.0" +name = "windows-link" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-sys" @@ -631,18 +685,12 @@ dependencies = [ ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets 0.53.3", ] [[package]] @@ -654,7 +702,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", @@ -662,10 +710,21 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" +name = "windows-targets" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] [[package]] name = "windows_aarch64_gnullvm" @@ -674,10 +733,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" +name = "windows_aarch64_gnullvm" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" @@ -686,10 +745,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "windows_i686_gnu" -version = "0.48.5" +name = "windows_aarch64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" @@ -697,6 +756,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" @@ -704,10 +769,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "windows_i686_msvc" -version = "0.48.5" +name = "windows_i686_gnullvm" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" @@ -716,10 +781,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +name = "windows_i686_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" @@ -728,10 +793,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" +name = "windows_x86_64_gnu" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" @@ -740,10 +805,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "windows_x86_64_gnullvm" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" @@ -751,21 +816,35 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 28be054..eb23c76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,10 +4,10 @@ [package] name = "nt-time" -version = "0.10.6" +version = "0.12.1" authors = ["Shun Sakai "] -edition = "2021" -rust-version = "1.67.1" +edition = "2024" +rust-version = "1.85.0" description = "A Windows file time library" documentation = "https://docs.rs/nt-time" readme = "README.md" @@ -20,8 +20,6 @@ include = ["/LICENSES", "/README.md", "/src"] [package.metadata.docs.rs] all-features = true -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [[example]] name = "dos2ft" path = "examples/dos2ft.rs" @@ -53,34 +51,39 @@ path = "examples/unix2ft.rs" required-features = ["std"] [dependencies] -chrono = { version = "0.4.39", default-features = false, optional = true } -rand = { version = "0.8.5", default-features = false, optional = true } -serde = { version = "1.0.217", default-features = false, features = ["derive"], optional = true } -time = { version = "0.3.37", default-features = false, features = ["macros"] } +chrono = { version = "0.4.41", default-features = false, optional = true } +jiff = { version = "0.2.15", default-features = false, optional = true } +rand = { version = "0.9.2", default-features = false, optional = true } +serde = { version = "1.0.219", default-features = false, optional = true } +time = { version = "0.3.41", default-features = false } [dev-dependencies] -anyhow = "1.0.95" -clap = { version = "4.3.24", features = ["derive"] } -proptest = "1.6.0" -proptest-derive = "0.5.1" -serde_json = "1.0.135" +anyhow = "1.0.98" +clap = { version = "4.5.42", features = ["derive"] } +proptest = "1.7.0" +proptest-derive = "0.6.0" +rand_pcg = "0.9.0" +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.142" serde_test = "1.0.177" -test-strategy = "0.3.1" -time = { version = "0.3.37", features = ["parsing"] } +test-strategy = "0.4.3" +time = { version = "0.3.41", features = ["macros", "parsing"] } [features] default = ["std"] chrono = ["dep:chrono"] +jiff = ["dep:jiff"] +large-dates = ["time/large-dates"] rand = ["dep:rand"] serde = ["dep:serde"] -std = ["chrono?/std", "rand?/std", "time/std"] -large-dates = ["time/large-dates"] serde-human-readable = ["serde", "time/serde-human-readable"] +std = ["chrono?/std", "jiff?/std", "rand?/std", "time/std"] [lints.clippy] -cargo = "warn" -nursery = "warn" -pedantic = "warn" +cargo = { level = "warn", priority = -1 } +multiple_crate_versions = "allow" +nursery = { level = "warn", priority = -1 } +pedantic = { level = "warn", priority = -1 } [lints.rust] missing_debug_implementations = "deny" diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..42b3a15 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Shun Sakai + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 93f63d0..d864768 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # nt-time @@ -11,6 +11,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT ![MSRV][msrv-badge] [![Docs][docs-badge]][docs-url] ![License][license-badge] +[![REUSE status][reuse-badge]][reuse-url] **nt-time** is a [Windows file time] library for [Rust]. @@ -19,35 +20,34 @@ number of 100-nanosecond intervals that have elapsed since "1601-01-01 00:00:00 UTC", and is used as timestamps such as [NTFS] and [7z]. Windows uses a file time to record when an application creates, accesses, or writes to a file. -Note that many environments, such as the [Win32 API], may limit the largest -value of the file time to "+30828-09-14 02:48:05.477580700 UTC", which is equal -to the largest value of a 64-bit signed integer type when represented as an -underlying integer value. This is the largest file time accepted by the -[`FileTimeToSystemTime`] function of the Win32 API. +> [!IMPORTANT] +> Note that many environments, such as the [Win32 API], may limit the largest +> value of the file time to "+30828-09-14 02:48:05.477580700 UTC", which is +> equal to the largest value of a 64-bit signed integer type when represented +> as an underlying integer value. This is the largest file time accepted by the +> [`FileTimeToSystemTime`] function of the Win32 API. ## Usage -Add this to your `Cargo.toml`: +Run the following command in your project directory: -```toml -[dependencies] -nt-time = "0.10.6" +```sh +cargo add nt-time ``` ### Crate features -#### `std` +#### `chrono` -Enables features that depend on the standard library. This is enabled by -default. +Enables the [`chrono`] crate. -#### `large-dates` +#### `jiff` -Enables the `large-dates` feature of the [`time`] crate. +Enables the [`jiff`] crate. -#### `chrono` +#### `large-dates` -Enables the [`chrono`] crate. +Enables the `large-dates` feature of the [`time`] crate. #### `rand` @@ -62,6 +62,11 @@ Enables the [`serde`] crate. Allows Serde representations to use a human-readable format. This implicitly enables the `serde` feature. +#### `std` + +Enables features that depend on the standard library. This is enabled by +default. + ### `no_std` support This supports `no_std` mode. Disables the `default` feature to enable this. @@ -72,18 +77,13 @@ See the [documentation][docs-url] for more details. ## Minimum supported Rust version -The minimum supported Rust version (MSRV) of this library is v1.67.1. +The minimum supported Rust version (MSRV) of this library is v1.85.0. ## Source code The upstream repository is available at . -The source code is also available at: - -- -- - ## Changelog Please see [CHANGELOG.adoc]. @@ -99,7 +99,7 @@ Copyright (C) 2023 Shun Sakai (see [AUTHORS.adoc]) This library is distributed under the terms of either the _Apache License 2.0_ or the _MIT License_. -This project is compliant with version 3.2 of the [_REUSE Specification_]. See +This project is compliant with version 3.3 of the [_REUSE Specification_]. See copyright notices of individual files for more details on copyright and licensing information. @@ -111,6 +111,8 @@ licensing information. [docs-badge]: https://img.shields.io/docsrs/nt-time?style=for-the-badge&logo=docsdotrs&label=Docs.rs [docs-url]: https://docs.rs/nt-time [license-badge]: https://img.shields.io/crates/l/nt-time?style=for-the-badge +[reuse-badge]: https://img.shields.io/reuse/compliance/github.com%2Fsorairolake%2Fnt-time?style=for-the-badge +[reuse-url]: https://api.reuse.software/info/github.com/sorairolake/nt-time [Windows file time]: https://docs.microsoft.com/en-us/windows/win32/sysinfo/file-times [Rust]: https://www.rust-lang.org/ [NTFS]: https://en.wikipedia.org/wiki/NTFS @@ -119,9 +121,10 @@ licensing information. [`FileTimeToSystemTime`]: https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/nf-timezoneapi-filetimetosystemtime [`time`]: https://crates.io/crates/time [`chrono`]: https://crates.io/crates/chrono +[`jiff`]: https://crates.io/crates/jiff [`rand`]: https://crates.io/crates/rand [`serde`]: https://serde.rs/ [CHANGELOG.adoc]: CHANGELOG.adoc [CONTRIBUTING.adoc]: CONTRIBUTING.adoc [AUTHORS.adoc]: AUTHORS.adoc -[_REUSE Specification_]: https://reuse.software/spec/ +[_REUSE Specification_]: https://reuse.software/spec-3.3/ diff --git a/benches/cmp.rs b/benches/cmp.rs index 8e42713..ac25f0a 100644 --- a/benches/cmp.rs +++ b/benches/cmp.rs @@ -7,8 +7,8 @@ extern crate test; use nt_time::{ - time::{macros::datetime, OffsetDateTime}, FileTime, + time::{OffsetDateTime, macros::datetime}, }; use test::Bencher; @@ -68,6 +68,22 @@ fn equality_file_time_and_chrono_date_time(b: &mut Bencher) { }); } +#[cfg(feature = "jiff")] +#[bench] +fn equality_jiff_timestamp_and_file_time(b: &mut Bencher) { + use jiff::Timestamp; + + b.iter(|| Timestamp::from_second(-11_644_473_600).unwrap() == FileTime::NT_TIME_EPOCH); +} + +#[cfg(feature = "jiff")] +#[bench] +fn equality_file_time_and_jiff_timestamp(b: &mut Bencher) { + use jiff::Timestamp; + + b.iter(|| FileTime::NT_TIME_EPOCH == Timestamp::from_second(-11_644_473_600).unwrap()); +} + #[cfg(feature = "std")] #[bench] fn order_system_time_and_file_time(b: &mut Bencher) { @@ -112,3 +128,19 @@ fn order_file_time_and_chrono_date_time(b: &mut Bencher) { b.iter(|| FileTime::UNIX_EPOCH > "1601-01-01 00:00:00 UTC".parse::>().unwrap()); } + +#[cfg(feature = "jiff")] +#[bench] +fn order_jiff_timestamp_and_file_time(b: &mut Bencher) { + use jiff::Timestamp; + + b.iter(|| Timestamp::UNIX_EPOCH > FileTime::NT_TIME_EPOCH); +} + +#[cfg(feature = "jiff")] +#[bench] +fn order_file_time_and_jiff_timestamp(b: &mut Bencher) { + use jiff::Timestamp; + + b.iter(|| FileTime::UNIX_EPOCH > Timestamp::from_second(-11_644_473_600).unwrap()); +} diff --git a/benches/convert.rs b/benches/convert.rs index 7b35de4..e6f8eff 100644 --- a/benches/convert.rs +++ b/benches/convert.rs @@ -6,7 +6,7 @@ extern crate test; -use nt_time::{time::OffsetDateTime, FileTime}; +use nt_time::{FileTime, time::OffsetDateTime}; use test::Bencher; #[bench] @@ -40,6 +40,14 @@ fn from_file_time_to_chrono_date_time(b: &mut Bencher) { b.iter(|| DateTime::::from(FileTime::UNIX_EPOCH)); } +#[cfg(feature = "jiff")] +#[bench] +fn try_from_file_time_to_jiff_timestamp(b: &mut Bencher) { + use jiff::Timestamp; + + b.iter(|| Timestamp::try_from(FileTime::UNIX_EPOCH).unwrap()); +} + #[bench] fn from_u64_to_file_time(b: &mut Bencher) { b.iter(|| FileTime::from(u64::MIN)); @@ -70,3 +78,11 @@ fn try_from_chrono_date_time_to_file_time(b: &mut Bencher) { b.iter(|| FileTime::try_from(DateTime::::UNIX_EPOCH).unwrap()); } + +#[cfg(feature = "jiff")] +#[bench] +fn try_from_jiff_timestamp_to_file_time(b: &mut Bencher) { + use jiff::Timestamp; + + b.iter(|| FileTime::try_from(Timestamp::UNIX_EPOCH).unwrap()); +} diff --git a/benches/dos_date_time.rs b/benches/dos_date_time.rs index c413011..7da29eb 100644 --- a/benches/dos_date_time.rs +++ b/benches/dos_date_time.rs @@ -13,12 +13,12 @@ use test::Bencher; fn to_dos_date_time(b: &mut Bencher) { b.iter(|| { FileTime::new(119_600_064_000_000_000) - .to_dos_date_time(None) + .to_dos_date_time() .unwrap() }); } #[bench] fn from_dos_date_time(b: &mut Bencher) { - b.iter(|| FileTime::from_dos_date_time(0x0021, u16::MIN, None, None).unwrap()); + b.iter(|| FileTime::from_dos_date_time(0x0021, u16::MIN).unwrap()); } diff --git a/benches/file_time.rs b/benches/file_time.rs index 310e034..69c653a 100644 --- a/benches/file_time.rs +++ b/benches/file_time.rs @@ -6,8 +6,6 @@ extern crate test; -use core::str::FromStr; - use nt_time::FileTime; use test::Bencher; @@ -71,8 +69,3 @@ fn from_high_low(b: &mut Bencher) { fn default(b: &mut Bencher) { b.iter(FileTime::default); } - -#[bench] -fn from_str(b: &mut Bencher) { - b.iter(|| FileTime::from_str("116444736000000000").unwrap()); -} diff --git a/benches/ops.rs b/benches/ops.rs index 55fe8dd..abd5f0b 100644 --- a/benches/ops.rs +++ b/benches/ops.rs @@ -6,7 +6,7 @@ extern crate test; -use nt_time::{time::macros::datetime, FileTime}; +use nt_time::{FileTime, time::macros::datetime}; use test::Bencher; #[bench] @@ -74,6 +74,22 @@ fn add_negative_chrono_time_delta(b: &mut Bencher) { b.iter(|| FileTime::MAX + TimeDelta::nanoseconds(-100)); } +#[cfg(feature = "jiff")] +#[bench] +fn add_positive_jiff_span(b: &mut Bencher) { + use jiff::ToSpan; + + b.iter(|| FileTime::NT_TIME_EPOCH + 100.nanoseconds()); +} + +#[cfg(feature = "jiff")] +#[bench] +fn add_negative_jiff_span(b: &mut Bencher) { + use jiff::ToSpan; + + b.iter(|| FileTime::MAX + (-100).nanoseconds()); +} + #[bench] fn sub_file_time(b: &mut Bencher) { b.iter(|| FileTime::MAX - FileTime::NT_TIME_EPOCH); @@ -116,6 +132,22 @@ fn sub_negative_chrono_time_delta(b: &mut Bencher) { b.iter(|| FileTime::NT_TIME_EPOCH - TimeDelta::nanoseconds(-100)); } +#[cfg(feature = "jiff")] +#[bench] +fn sub_positive_jiff_span(b: &mut Bencher) { + use jiff::ToSpan; + + b.iter(|| FileTime::MAX - 100.nanoseconds()); +} + +#[cfg(feature = "jiff")] +#[bench] +fn sub_negative_jiff_span(b: &mut Bencher) { + use jiff::ToSpan; + + b.iter(|| FileTime::NT_TIME_EPOCH - (-100).nanoseconds()); +} + #[cfg(feature = "std")] #[bench] fn sub_file_time_from_system_time(b: &mut Bencher) { @@ -168,3 +200,21 @@ fn sub_chrono_date_time_from_file_time(b: &mut Bencher) { b.iter(|| FileTime::MAX - "1601-01-01 00:00:00 UTC".parse::>().unwrap()); } + +#[cfg(feature = "jiff")] +#[bench] +fn sub_file_time_from_jiff_timestamp(b: &mut Bencher) { + use jiff::{Timestamp, ToSpan}; + + b.iter(|| (Timestamp::MAX - 99.nanoseconds()) - FileTime::NT_TIME_EPOCH); +} + +#[cfg(feature = "jiff")] +#[bench] +fn sub_jiff_timestamp_from_file_time(b: &mut Bencher) { + use jiff::Timestamp; + + b.iter(|| { + FileTime::new(2_650_466_808_009_999_999) - Timestamp::from_second(-11_644_473_600).unwrap() + }); +} diff --git a/benches/rand.rs b/benches/rand.rs index c079a09..c81193a 100644 --- a/benches/rand.rs +++ b/benches/rand.rs @@ -7,14 +7,12 @@ extern crate test; -use nt_time::{ - rand::{rngs::mock::StepRng, Rng}, - FileTime, -}; +use nt_time::{FileTime, rand::Rng}; +use rand_pcg::{Pcg64Mcg, rand_core::SeedableRng}; use test::Bencher; #[bench] fn sample(b: &mut Bencher) { - let mut rng = StepRng::new(0, 1); - b.iter(|| rng.gen::()); + let mut rng = Pcg64Mcg::from_seed(Default::default()); + b.iter(|| rng.random::()); } diff --git a/benches/str.rs b/benches/str.rs new file mode 100644 index 0000000..075347f --- /dev/null +++ b/benches/str.rs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2024 Shun Sakai +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +#![feature(test)] + +extern crate test; + +use core::str::FromStr; + +use nt_time::FileTime; +use test::Bencher; + +#[bench] +fn from_str_radix(b: &mut Bencher) { + b.iter(|| FileTime::from_str_radix("6355435732517500000", 8).unwrap()); +} + +#[bench] +fn from_str(b: &mut Bencher) { + b.iter(|| FileTime::from_str("116444736000000000").unwrap()); +} diff --git a/clippy.toml b/clippy.toml index 956257c..f9d15ad 100644 --- a/clippy.toml +++ b/clippy.toml @@ -2,4 +2,4 @@ # # SPDX-License-Identifier: Apache-2.0 OR MIT -msrv = "1.67.1" +msrv = "1.85.0" diff --git a/examples/README.adoc b/examples/README.adoc index a3c7cd1..c5bb982 100644 --- a/examples/README.adoc +++ b/examples/README.adoc @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2023 Shun Sakai // -// SPDX-License-Identifier: Apache-2.0 OR MIT +// SPDX-License-Identifier: CC-BY-4.0 = Examples diff --git a/examples/dos2ft.rs b/examples/dos2ft.rs index deac3ff..824d515 100644 --- a/examples/dos2ft.rs +++ b/examples/dos2ft.rs @@ -6,21 +6,11 @@ use anyhow::Context; use clap::Parser; -use nt_time::{time::UtcOffset, FileTime}; +use nt_time::FileTime; #[derive(Debug, Parser)] #[command(version, about)] struct Opt { - /// Additional finer resolution of MS-DOS date and time. - #[arg(short, long)] - resolution: Option, - - /// UTC offset of MS-DOS date and time. - /// - /// takes the UTC offset in 15 minute intervals. - #[arg(short, long, allow_hyphen_values(true))] - offset: Option, - /// The MS-DOS date to convert. date: u16, @@ -31,12 +21,7 @@ struct Opt { fn main() -> anyhow::Result<()> { let opt = Opt::parse(); - let offset = opt - .offset - .map(|o| UtcOffset::from_whole_seconds(i32::from(o) * 900)) - .transpose() - .context("could not create the UTC offset")?; - let ft = FileTime::from_dos_date_time(opt.date, opt.time, opt.resolution, offset) + let ft = FileTime::from_dos_date_time(opt.date, opt.time) .context("could not convert date and time")?; println!("{ft}"); Ok(()) diff --git a/examples/format.rs b/examples/format.rs index 4c925bc..60f5d67 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -6,7 +6,7 @@ use anyhow::Context; use clap::Parser; -use nt_time::{time::OffsetDateTime, FileTime}; +use nt_time::{FileTime, time::OffsetDateTime}; #[derive(Debug, Parser)] #[command(version, about)] diff --git a/examples/ft2dos.rs b/examples/ft2dos.rs index 13e1685..8355961 100644 --- a/examples/ft2dos.rs +++ b/examples/ft2dos.rs @@ -6,17 +6,11 @@ use anyhow::Context; use clap::Parser; -use nt_time::{time::UtcOffset, FileTime}; +use nt_time::FileTime; #[derive(Debug, Parser)] #[command(version, about)] struct Opt { - /// UTC offset of MS-DOS date and time. - /// - /// takes the UTC offset in 15 minute intervals. - #[arg(short, long, allow_hyphen_values(true))] - offset: Option, - /// File time to convert. time: FileTime, } @@ -24,14 +18,9 @@ struct Opt { fn main() -> anyhow::Result<()> { let opt = Opt::parse(); - let offset = opt - .offset - .map(|o| UtcOffset::from_whole_seconds(i32::from(o) * 900)) - .transpose() - .context("could not create the UTC offset")?; let dt = opt .time - .to_dos_date_time(offset) + .to_dos_date_time() .context("could not convert time")?; println!("{dt:?}"); Ok(()) diff --git a/examples/parse.rs b/examples/parse.rs index c983530..df0b35c 100644 --- a/examples/parse.rs +++ b/examples/parse.rs @@ -4,41 +4,36 @@ //! An example of printing a human-readable date and time as the file time. +use std::{ops::Deref, str::FromStr}; + use anyhow::Context; use clap::{Parser, ValueEnum}; use nt_time::{ + FileTime, time::{ + OffsetDateTime, error::Parse, format_description::well_known::{Iso8601, Rfc2822, Rfc3339}, - OffsetDateTime, }, - FileTime, }; #[derive(Debug, Parser)] #[command(version, about)] struct Opt { /// The format of the output. - #[arg( - short, - long, - value_enum, - default_value_t, - value_name("FORMAT"), - ignore_case(true) - )] + #[arg(short, long, value_enum, default_value_t, ignore_case(true))] format: Format, /// Date and time to print. /// /// is a string representing a date and time in either ISO 8601, RFC /// 2822, or RFC 3339 format. - #[arg(value_parser(parse_offset_date_time), value_name("DATE"))] - dt: OffsetDateTime, + #[arg(value_name("DATE"))] + dt: DateTime, } #[derive(Clone, Debug, Default, ValueEnum)] -pub enum Format { +enum Format { /// Underlying 64-bit unsigned integer value. #[default] Raw, @@ -56,16 +51,32 @@ pub enum Format { HighLow, } -fn parse_offset_date_time(dt: &str) -> Result { - OffsetDateTime::parse(dt, &Iso8601::DEFAULT) - .or_else(|_| OffsetDateTime::parse(dt, &Rfc2822)) - .or_else(|_| OffsetDateTime::parse(dt, &Rfc3339)) +#[derive(Clone, Debug)] +struct DateTime(OffsetDateTime); + +impl Deref for DateTime { + type Target = OffsetDateTime; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl FromStr for DateTime { + type Err = Parse; + + fn from_str(dt: &str) -> Result { + OffsetDateTime::parse(dt, &Iso8601::DEFAULT) + .or_else(|_| OffsetDateTime::parse(dt, &Rfc2822)) + .or_else(|_| OffsetDateTime::parse(dt, &Rfc3339)) + .map(Self) + } } fn main() -> anyhow::Result<()> { let opt = Opt::parse(); - let ft = FileTime::try_from(opt.dt).context("could not convert time")?; + let ft = FileTime::try_from(*opt.dt).context("could not convert time")?; match opt.format { Format::Raw => println!("{}", ft.to_raw()), Format::BeBytes => println!("{:#04x?}", ft.to_be_bytes()), diff --git a/justfile b/justfile index 3abd627..62f7e72 100644 --- a/justfile +++ b/justfile @@ -2,53 +2,61 @@ # # SPDX-License-Identifier: Apache-2.0 OR MIT -alias all := default alias lint := clippy # Run default recipe -default: build +_default: + just -l # Build a package -@build: +build: cargo build # Remove generated artifacts -@clean: +clean: cargo clean # Check a package -@check: +check: cargo check # Run tests -@test: +test: cargo test +# Run benchmarks +bench: + cargo +nightly bench + # Run the formatter -@fmt: +fmt: cargo fmt # Run the formatter with options -@fmt-with-options: +fmt-with-options: cargo +nightly fmt # Run the linter -@clippy: +clippy: cargo clippy -- -D warnings # Apply lint suggestions -@clippy-fix: +clippy-fix: cargo +nightly clippy --fix --allow-dirty --allow-staged -- -D warnings +# Build the package documentation +doc $RUSTDOCFLAGS="--cfg docsrs": + cargo +nightly doc --all-features + # Run the linter for GitHub Actions workflow files -@lint-github-actions: +lint-github-actions: actionlint -verbose # Run the formatter for the README -@fmt-readme: +fmt-readme: npx prettier -w README.md # Increment the version -@bump part: - bump-my-version bump {{part}} - cargo set-version --bump {{part}} +bump part: + bump-my-version bump {{ part }} + cargo set-version --bump {{ part }} diff --git a/rustfmt.toml b/rustfmt.toml index f55a8b6..628fa22 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -2,9 +2,10 @@ # # SPDX-License-Identifier: Apache-2.0 OR MIT -edition = "2021" +edition = "2024" format_code_in_doc_comments = true format_macro_matchers = true group_imports = "StdExternalCrate" imports_granularity = "Crate" +style_edition = "2024" wrap_comments = true diff --git a/src/error.rs b/src/error.rs index fdeaed7..4bba0fb 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,6 +5,7 @@ //! Error types for this crate. use core::{ + error::Error, fmt, num::{IntErrorKind, ParseIntError}, }; @@ -27,19 +28,13 @@ impl DosDateTimeRangeError { /// # Examples /// /// ``` - /// # use nt_time::{error::DosDateTimeRangeErrorKind, FileTime}; + /// # use nt_time::{FileTime, error::DosDateTimeRangeErrorKind}; /// # - /// assert_eq!( - /// FileTime::NT_TIME_EPOCH - /// .to_dos_date_time(None) - /// .unwrap_err() - /// .kind(), - /// DosDateTimeRangeErrorKind::Negative - /// ); - /// assert_eq!( - /// FileTime::MAX.to_dos_date_time(None).unwrap_err().kind(), - /// DosDateTimeRangeErrorKind::Overflow - /// ); + /// let err = FileTime::NT_TIME_EPOCH.to_dos_date_time().unwrap_err(); + /// assert_eq!(err.kind(), DosDateTimeRangeErrorKind::Negative); + /// + /// let err = FileTime::MAX.to_dos_date_time().unwrap_err(); + /// assert_eq!(err.kind(), DosDateTimeRangeErrorKind::Overflow); /// ``` #[must_use] #[inline] @@ -55,8 +50,7 @@ impl fmt::Display for DosDateTimeRangeError { } } -#[cfg(feature = "std")] -impl std::error::Error for DosDateTimeRangeError {} +impl Error for DosDateTimeRangeError {} impl From for DosDateTimeRangeError { #[inline] @@ -75,7 +69,7 @@ pub enum DosDateTimeRangeErrorKind { /// Value was too big to be represented as [MS-DOS date and time]. /// - /// This means the date and time was after "2107-12-31 23:59:59.990000000". + /// This means the date and time was after "2107-12-31 23:59:58". /// /// [MS-DOS date and time]: https://learn.microsoft.com/en-us/windows/win32/sysinfo/ms-dos-date-and-time Overflow, @@ -85,12 +79,8 @@ impl fmt::Display for DosDateTimeRangeErrorKind { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Negative => { - write!(f, "date and time is before `1980-01-01 00:00:00`") - } - Self::Overflow => { - write!(f, "date and time is after `2107-12-31 23:59:59.990000000`") - } + Self::Negative => write!(f, "date and time is before `1980-01-01 00:00:00`"), + Self::Overflow => write!(f, "date and time is after `2107-12-31 23:59:58`"), } } } @@ -112,16 +102,13 @@ impl FileTimeRangeError { /// # Examples /// /// ``` - /// # use nt_time::{error::FileTimeRangeErrorKind, FileTime}; + /// # use nt_time::{FileTime, error::FileTimeRangeErrorKind}; /// # - /// assert_eq!( - /// FileTime::from_unix_time_secs(i64::MIN).unwrap_err().kind(), - /// FileTimeRangeErrorKind::Negative - /// ); - /// assert_eq!( - /// FileTime::from_unix_time_secs(i64::MAX).unwrap_err().kind(), - /// FileTimeRangeErrorKind::Overflow - /// ); + /// let err = FileTime::from_unix_time_secs(i64::MIN).unwrap_err(); + /// assert_eq!(err.kind(), FileTimeRangeErrorKind::Negative); + /// + /// let err = FileTime::from_unix_time_secs(i64::MAX).unwrap_err(); + /// assert_eq!(err.kind(), FileTimeRangeErrorKind::Overflow); /// ``` #[must_use] #[inline] @@ -137,8 +124,7 @@ impl fmt::Display for FileTimeRangeError { } } -#[cfg(feature = "std")] -impl std::error::Error for FileTimeRangeError {} +impl Error for FileTimeRangeError {} impl From for FileTimeRangeError { #[inline] @@ -166,15 +152,11 @@ impl fmt::Display for FileTimeRangeErrorKind { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Negative => { - write!(f, "date and time is before `1601-01-01 00:00:00 UTC`") - } - Self::Overflow => { - write!( - f, - "date and time is after `+60056-05-28 05:36:10.955161500 UTC`" - ) - } + Self::Negative => write!(f, "date and time is before `1601-01-01 00:00:00 UTC`"), + Self::Overflow => write!( + f, + "date and time is after `+60056-05-28 05:36:10.955161500 UTC`" + ), } } } @@ -206,10 +188,9 @@ impl fmt::Display for ParseFileTimeError { } } -#[cfg(feature = "std")] -impl std::error::Error for ParseFileTimeError { +impl Error for ParseFileTimeError { #[inline] - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + fn source(&self) -> Option<&(dyn Error + 'static)> { Some(&self.0) } } @@ -317,15 +298,12 @@ mod tests { "{}", DosDateTimeRangeError::new(DosDateTimeRangeErrorKind::Overflow) ), - "date and time is after `2107-12-31 23:59:59.990000000`" + "date and time is after `2107-12-31 23:59:58`" ); } - #[cfg(feature = "std")] #[test] fn source_dos_date_time_range_error() { - use std::error::Error; - assert!( DosDateTimeRangeError::new(DosDateTimeRangeErrorKind::Negative) .source() @@ -417,7 +395,7 @@ mod tests { ); assert_eq!( format!("{}", DosDateTimeRangeErrorKind::Overflow), - "date and time is after `2107-12-31 23:59:59.990000000`" + "date and time is after `2107-12-31 23:59:58`" ); } @@ -522,17 +500,18 @@ mod tests { ); } - #[cfg(feature = "std")] #[test] fn source_file_time_range_error() { - use std::error::Error; - - assert!(FileTimeRangeError::new(FileTimeRangeErrorKind::Negative) - .source() - .is_none()); - assert!(FileTimeRangeError::new(FileTimeRangeErrorKind::Overflow) - .source() - .is_none()); + assert!( + FileTimeRangeError::new(FileTimeRangeErrorKind::Negative) + .source() + .is_none() + ); + assert!( + FileTimeRangeError::new(FileTimeRangeErrorKind::Overflow) + .source() + .is_none() + ); } #[test] @@ -708,11 +687,8 @@ mod tests { ); } - #[cfg(feature = "std")] #[test] fn source_parse_file_time_error() { - use std::error::Error; - assert_eq!( ParseFileTimeError::new(u64::from_str("").unwrap_err()) .source() diff --git a/src/file_time.rs b/src/file_time.rs index 990f539..a8d4c09 100644 --- a/src/file_time.rs +++ b/src/file_time.rs @@ -16,11 +16,10 @@ mod ops; mod rand; #[cfg(feature = "serde")] mod serde; +mod str; mod unix_time; -use core::{mem, str::FromStr}; - -use crate::error::ParseFileTimeError; +use core::mem; const FILE_TIMES_PER_SEC: u64 = 10_000_000; @@ -34,12 +33,16 @@ const FILE_TIMES_PER_SEC: u64 = 10_000_000; /// This represents the same value as the [`FILETIME`] structure of the [Win32 /// API], which represents a 64-bit unsigned integer value. /// +///
+/// /// Note that many environments, such as the Win32 API, may limit the largest /// value of the file time to "+30828-09-14 02:48:05.477580700 UTC", which is /// equal to [`i64::MAX`], the largest value of a 64-bit signed integer type /// when represented as an underlying integer value. This is the largest file /// time accepted by the [`FileTimeToSystemTime`] function of the Win32 API. /// +///
+/// /// Also, the file time is sometimes represented as an [`i64`] value, such as in /// the [`DateTime.FromFileTimeUtc`] method and the [`DateTime.ToFileTimeUtc`] /// method in [.NET]. @@ -175,10 +178,14 @@ impl FileTime { /// Returns the memory representation of this `FileTime` as a byte array in /// native byte order. /// + ///
+ /// /// As the target platform's native endianness is used, portable code should /// use [`FileTime::to_be_bytes`] or [`FileTime::to_le_bytes`], as /// appropriate, instead. /// + ///
+ /// /// # Examples /// /// ``` @@ -268,10 +275,14 @@ impl FileTime { /// Creates a native endian `FileTime` value from its memory representation /// as a byte array in native endianness. /// + ///
+ /// /// As the target platform's native endianness is used, portable code likely /// wants to use [`FileTime::from_be_bytes`] or [`FileTime::from_le_bytes`], /// as appropriate instead. /// + ///
+ /// /// # Examples /// /// ``` @@ -404,55 +415,6 @@ impl Default for FileTime { } } -impl FromStr for FileTime { - type Err = ParseFileTimeError; - - /// Parses a string `s` to return a value of `FileTime`. - /// - /// The string is expected to be a decimal non-negative integer. If the - /// string is not a decimal integer, use [`u64::from_str_radix`] and - /// [`FileTime::new`] instead. - /// - /// # Errors - /// - /// Returns [`Err`] if [`u64::from_str`] returns an error. - /// - /// # Examples - /// - /// ``` - /// # use core::str::FromStr; - /// # - /// # use nt_time::FileTime; - /// # - /// assert_eq!(FileTime::from_str("0").unwrap(), FileTime::NT_TIME_EPOCH); - /// assert_eq!( - /// FileTime::from_str("116444736000000000").unwrap(), - /// FileTime::UNIX_EPOCH - /// ); - /// assert_eq!( - /// FileTime::from_str("+9223372036854775807").unwrap(), - /// FileTime::SIGNED_MAX - /// ); - /// assert_eq!( - /// FileTime::from_str("+18446744073709551615").unwrap(), - /// FileTime::MAX - /// ); - /// - /// assert!(FileTime::from_str("").is_err()); - /// - /// assert!(FileTime::from_str("a").is_err()); - /// assert!(FileTime::from_str("-1").is_err()); - /// assert!(FileTime::from_str("+").is_err()); - /// assert!(FileTime::from_str("0 ").is_err()); - /// - /// assert!(FileTime::from_str("18446744073709551616").is_err()); - /// ``` - #[inline] - fn from_str(s: &str) -> Result { - s.parse().map_err(ParseFileTimeError::new).map(Self::new) - } -} - #[cfg(test)] mod tests { use super::*; @@ -790,163 +752,4 @@ mod tests { fn default() { assert_eq!(FileTime::default(), FileTime::NT_TIME_EPOCH); } - - #[test] - fn from_str() { - assert_eq!(FileTime::from_str("0").unwrap(), FileTime::NT_TIME_EPOCH); - assert_eq!(FileTime::from_str("+0").unwrap(), FileTime::NT_TIME_EPOCH); - assert_eq!( - FileTime::from_str("116444736000000000").unwrap(), - FileTime::UNIX_EPOCH - ); - assert_eq!( - FileTime::from_str("+116444736000000000").unwrap(), - FileTime::UNIX_EPOCH - ); - assert_eq!( - FileTime::from_str("9223372036854775807").unwrap(), - FileTime::SIGNED_MAX - ); - assert_eq!( - FileTime::from_str("+9223372036854775807").unwrap(), - FileTime::SIGNED_MAX - ); - assert_eq!( - FileTime::from_str("18446744073709551615").unwrap(), - FileTime::MAX - ); - assert_eq!( - FileTime::from_str("+18446744073709551615").unwrap(), - FileTime::MAX - ); - } - - #[cfg(feature = "std")] - #[test_strategy::proptest] - fn from_str_roundtrip(#[strategy(r"\+?[0-9]{1,19}")] s: std::string::String) { - use proptest::prop_assert_eq; - - let ft = s.parse().unwrap(); - prop_assert_eq!(FileTime::from_str(&s).unwrap(), FileTime::new(ft)); - } - - #[cfg(feature = "std")] - #[test] - fn from_str_when_empty() { - use std::{ - error::Error, - num::{IntErrorKind, ParseIntError}, - }; - - assert_eq!( - FileTime::from_str("") - .unwrap_err() - .source() - .unwrap() - .downcast_ref::() - .unwrap() - .kind(), - &IntErrorKind::Empty - ); - } - - #[cfg(feature = "std")] - #[test] - fn from_str_with_invalid_digit() { - use std::{ - error::Error, - num::{IntErrorKind, ParseIntError}, - }; - - assert_eq!( - FileTime::from_str("a") - .unwrap_err() - .source() - .unwrap() - .downcast_ref::() - .unwrap() - .kind(), - &IntErrorKind::InvalidDigit - ); - assert_eq!( - FileTime::from_str("-1") - .unwrap_err() - .source() - .unwrap() - .downcast_ref::() - .unwrap() - .kind(), - &IntErrorKind::InvalidDigit - ); - assert_eq!( - FileTime::from_str("+") - .unwrap_err() - .source() - .unwrap() - .downcast_ref::() - .unwrap() - .kind(), - &IntErrorKind::InvalidDigit - ); - assert_eq!( - FileTime::from_str("-") - .unwrap_err() - .source() - .unwrap() - .downcast_ref::() - .unwrap() - .kind(), - &IntErrorKind::InvalidDigit - ); - assert_eq!( - FileTime::from_str(" 0") - .unwrap_err() - .source() - .unwrap() - .downcast_ref::() - .unwrap() - .kind(), - &IntErrorKind::InvalidDigit - ); - assert_eq!( - FileTime::from_str("0 ") - .unwrap_err() - .source() - .unwrap() - .downcast_ref::() - .unwrap() - .kind(), - &IntErrorKind::InvalidDigit - ); - } - - #[cfg(feature = "std")] - #[test_strategy::proptest] - fn from_str_with_invalid_digit_roundtrip( - #[strategy(r"-[0-9]+|[^0-9]+")] s: std::string::String, - ) { - use proptest::prop_assert; - - prop_assert!(FileTime::from_str(&s).is_err()); - } - - #[cfg(feature = "std")] - #[test] - fn from_str_when_positive_overflow() { - use std::{ - error::Error, - num::{IntErrorKind, ParseIntError}, - }; - - assert_eq!( - FileTime::from_str("18446744073709551616") - .unwrap_err() - .source() - .unwrap() - .downcast_ref::() - .unwrap() - .kind(), - &IntErrorKind::PosOverflow - ); - } } diff --git a/src/file_time/cmp.rs b/src/file_time/cmp.rs index c7b767a..71ec0fd 100644 --- a/src/file_time/cmp.rs +++ b/src/file_time/cmp.rs @@ -61,6 +61,24 @@ impl PartialEq> for FileTime { } } +#[cfg(feature = "jiff")] +impl PartialEq for jiff::Timestamp { + #[inline] + fn eq(&self, other: &FileTime) -> bool { + self == &Self::try_from(*other).expect("`other` is out of range for `Timestamp`") + } +} + +#[cfg(feature = "jiff")] +impl PartialEq for FileTime { + #[inline] + fn eq(&self, other: &jiff::Timestamp) -> bool { + use jiff::Timestamp; + + &Timestamp::try_from(*self).expect("`self` is out of range for `Timestamp`") == other + } +} + #[cfg(feature = "std")] impl PartialOrd for std::time::SystemTime { #[inline] @@ -115,6 +133,26 @@ impl PartialOrd> for FileTime { } } +#[cfg(feature = "jiff")] +impl PartialOrd for jiff::Timestamp { + #[inline] + fn partial_cmp(&self, other: &FileTime) -> Option { + self.partial_cmp(&Self::try_from(*other).expect("`other` is out of range for `Timestamp`")) + } +} + +#[cfg(feature = "jiff")] +impl PartialOrd for FileTime { + #[inline] + fn partial_cmp(&self, other: &jiff::Timestamp) -> Option { + use jiff::Timestamp; + + Timestamp::try_from(*self) + .expect("`self` is out of range for `Timestamp`") + .partial_cmp(other) + } +} + #[cfg(test)] mod tests { use time::macros::datetime; @@ -266,6 +304,46 @@ mod tests { ); } + #[cfg(feature = "jiff")] + #[test] + fn equality_jiff_timestamp_and_file_time() { + use jiff::{Timestamp, ToSpan}; + + assert_eq!( + Timestamp::MAX - 99.nanoseconds(), + FileTime::new(2_650_466_808_009_999_999) + ); + assert_ne!(Timestamp::MAX - 99.nanoseconds(), FileTime::NT_TIME_EPOCH); + assert_ne!( + Timestamp::from_second(-11_644_473_600).unwrap(), + FileTime::new(2_650_466_808_009_999_999) + ); + assert_eq!( + Timestamp::from_second(-11_644_473_600).unwrap(), + FileTime::NT_TIME_EPOCH + ); + } + + #[cfg(feature = "jiff")] + #[test] + fn equality_file_time_and_jiff_timestamp() { + use jiff::{Timestamp, ToSpan}; + + assert_eq!( + FileTime::new(2_650_466_808_009_999_999), + Timestamp::MAX - 99.nanoseconds() + ); + assert_ne!(FileTime::NT_TIME_EPOCH, Timestamp::MAX - 99.nanoseconds()); + assert_ne!( + FileTime::new(2_650_466_808_009_999_999), + Timestamp::from_second(-11_644_473_600).unwrap() + ); + assert_eq!( + FileTime::NT_TIME_EPOCH, + Timestamp::from_second(-11_644_473_600).unwrap() + ); + } + #[cfg(feature = "std")] #[test] fn order_system_time_and_file_time() { @@ -348,4 +426,30 @@ mod tests { ); assert!(FileTime::UNIX_EPOCH > "1601-01-01 00:00:00 UTC".parse::>().unwrap()); } + + #[cfg(feature = "jiff")] + #[test] + fn order_jiff_timestamp_and_file_time() { + use jiff::Timestamp; + + assert!(Timestamp::UNIX_EPOCH < FileTime::new(2_650_466_808_009_999_999)); + assert_eq!( + Timestamp::UNIX_EPOCH.partial_cmp(&FileTime::UNIX_EPOCH), + Some(Ordering::Equal) + ); + assert!(Timestamp::UNIX_EPOCH > FileTime::NT_TIME_EPOCH); + } + + #[cfg(feature = "jiff")] + #[test] + fn order_file_time_and_jiff_timestamp() { + use jiff::Timestamp; + + assert!(FileTime::UNIX_EPOCH < Timestamp::MAX); + assert_eq!( + FileTime::UNIX_EPOCH.partial_cmp(&Timestamp::UNIX_EPOCH), + Some(Ordering::Equal) + ); + assert!(FileTime::UNIX_EPOCH > Timestamp::from_second(-11_644_473_600).unwrap()); + } } diff --git a/src/file_time/consts.rs b/src/file_time/consts.rs index 1cd42c1..c814436 100644 --- a/src/file_time/consts.rs +++ b/src/file_time/consts.rs @@ -4,7 +4,7 @@ //! Constants for [`FileTime`]. -use super::{FileTime, FILE_TIMES_PER_SEC}; +use super::{FILE_TIMES_PER_SEC, FileTime}; impl FileTime { /// The [NT time epoch]. @@ -16,7 +16,7 @@ impl FileTime { /// # Examples /// /// ``` - /// # use nt_time::{time::macros::datetime, FileTime}; + /// # use nt_time::{FileTime, time::macros::datetime}; /// # /// assert_eq!(FileTime::NT_TIME_EPOCH, datetime!(1601-01-01 00:00 UTC)); /// ``` @@ -32,7 +32,7 @@ impl FileTime { /// # Examples /// /// ``` - /// # use nt_time::{time::OffsetDateTime, FileTime}; + /// # use nt_time::{FileTime, time::OffsetDateTime}; /// # /// assert_eq!(FileTime::UNIX_EPOCH, OffsetDateTime::UNIX_EPOCH); /// ``` @@ -56,6 +56,8 @@ impl FileTime { /// environments, it is generally recommended that you use this constant as /// the largest value instead of [`FileTime::MAX`]. /// + ///
+ /// /// Note that the actual largest value of the [`SYSTEMTIME`] structure of /// the Win32 API is "+30827-12-31 23:59:59.999000000" (which is either in /// UTC or local time, depending on the function that is being called), @@ -63,12 +65,14 @@ impl FileTime { /// function accepts the value of this constant, but it is an invalid date /// and time for the `SYSTEMTIME` structure. /// + ///
+ /// /// # Examples /// /// ``` /// # #[cfg(feature = "large-dates")] /// # { - /// # use nt_time::{time::macros::datetime, FileTime}; + /// # use nt_time::{FileTime, time::macros::datetime}; /// # /// assert_eq!( /// FileTime::SIGNED_MAX, @@ -106,7 +110,7 @@ impl FileTime { /// ``` /// # #[cfg(feature = "large-dates")] /// # { - /// # use nt_time::{time::macros::datetime, FileTime}; + /// # use nt_time::{FileTime, time::macros::datetime}; /// # /// assert_eq!( /// FileTime::MAX, @@ -125,7 +129,7 @@ impl FileTime { #[cfg(test)] mod tests { - use time::{macros::datetime, OffsetDateTime}; + use time::{OffsetDateTime, macros::datetime}; use super::*; diff --git a/src/file_time/convert.rs b/src/file_time/convert.rs index 0428897..a692e84 100644 --- a/src/file_time/convert.rs +++ b/src/file_time/convert.rs @@ -6,7 +6,7 @@ use core::num::TryFromIntError; -use time::{error::ComponentRange, OffsetDateTime}; +use time::{OffsetDateTime, error::ComponentRange}; use super::FileTime; use crate::error::{FileTimeRangeError, FileTimeRangeErrorKind}; @@ -51,12 +51,12 @@ impl TryFrom for i64 { /// ``` /// # use nt_time::FileTime; /// # - /// assert_eq!(i64::try_from(FileTime::NT_TIME_EPOCH).unwrap(), 0); + /// assert_eq!(i64::try_from(FileTime::NT_TIME_EPOCH), Ok(0)); /// assert_eq!( - /// i64::try_from(FileTime::UNIX_EPOCH).unwrap(), - /// 116_444_736_000_000_000 + /// i64::try_from(FileTime::UNIX_EPOCH), + /// Ok(116_444_736_000_000_000) /// ); - /// assert_eq!(i64::try_from(FileTime::SIGNED_MAX).unwrap(), i64::MAX); + /// assert_eq!(i64::try_from(FileTime::SIGNED_MAX), Ok(i64::MAX)); /// /// assert!(i64::try_from(FileTime::MAX).is_err()); /// ``` @@ -117,23 +117,23 @@ impl TryFrom for OffsetDateTime { /// /// # Errors /// - /// Returns [`Err`] if `time` is out of range for [`OffsetDateTime`]. + /// Returns [`Err`] if `ft` is out of range for [`OffsetDateTime`]. /// /// # Examples /// /// ``` /// # use nt_time::{ - /// # time::{macros::datetime, OffsetDateTime}, /// # FileTime, + /// # time::{OffsetDateTime, macros::datetime}, /// # }; /// # /// assert_eq!( - /// OffsetDateTime::try_from(FileTime::NT_TIME_EPOCH).unwrap(), - /// datetime!(1601-01-01 00:00 UTC) + /// OffsetDateTime::try_from(FileTime::NT_TIME_EPOCH), + /// Ok(datetime!(1601-01-01 00:00 UTC)) /// ); /// assert_eq!( - /// OffsetDateTime::try_from(FileTime::UNIX_EPOCH).unwrap(), - /// OffsetDateTime::UNIX_EPOCH + /// OffsetDateTime::try_from(FileTime::UNIX_EPOCH), + /// Ok(OffsetDateTime::UNIX_EPOCH) /// ); /// ``` /// @@ -143,7 +143,7 @@ impl TryFrom for OffsetDateTime { /// ``` /// # #[cfg(not(feature = "large-dates"))] /// # { - /// # use nt_time::{time::OffsetDateTime, FileTime}; + /// # use nt_time::{FileTime, time::OffsetDateTime}; /// # /// assert!(OffsetDateTime::try_from(FileTime::new(2_650_467_744_000_000_000)).is_err()); /// # } @@ -155,21 +155,21 @@ impl TryFrom for OffsetDateTime { /// # #[cfg(feature = "large-dates")] /// # { /// # use nt_time::{ - /// # time::{macros::datetime, OffsetDateTime}, /// # FileTime, + /// # time::{OffsetDateTime, macros::datetime}, /// # }; /// # /// assert_eq!( - /// OffsetDateTime::try_from(FileTime::new(2_650_467_744_000_000_000)).unwrap(), - /// datetime!(+10000-01-01 00:00 UTC) + /// OffsetDateTime::try_from(FileTime::new(2_650_467_744_000_000_000)), + /// Ok(datetime!(+10000-01-01 00:00 UTC)) /// ); /// assert_eq!( - /// OffsetDateTime::try_from(FileTime::SIGNED_MAX).unwrap(), - /// datetime!(+30828-09-14 02:48:05.477_580_700 UTC) + /// OffsetDateTime::try_from(FileTime::SIGNED_MAX), + /// Ok(datetime!(+30828-09-14 02:48:05.477_580_700 UTC)) /// ); /// assert_eq!( - /// OffsetDateTime::try_from(FileTime::MAX).unwrap(), - /// datetime!(+60056-05-28 05:36:10.955_161_500 UTC) + /// OffsetDateTime::try_from(FileTime::MAX), + /// Ok(datetime!(+60056-05-28 05:36:10.955_161_500 UTC)) /// ); /// # } /// ``` @@ -187,8 +187,8 @@ impl From for chrono::DateTime { /// /// ``` /// # use nt_time::{ - /// # chrono::{DateTime, Utc}, /// # FileTime, + /// # chrono::{DateTime, Utc}, /// # }; /// # /// assert_eq!( @@ -208,6 +208,39 @@ impl From for chrono::DateTime { } } +#[cfg(feature = "jiff")] +impl TryFrom for jiff::Timestamp { + type Error = jiff::Error; + + /// Converts a `FileTime` to a [`Timestamp`](jiff::Timestamp). + /// + /// # Errors + /// + /// Returns [`Err`] if `ft` is out of range for + /// [`Timestamp`](jiff::Timestamp). + /// + /// # Examples + /// + /// ``` + /// # use nt_time::{FileTime, jiff::Timestamp}; + /// # + /// assert_eq!( + /// Timestamp::try_from(FileTime::NT_TIME_EPOCH).unwrap(), + /// Timestamp::from_second(-11_644_473_600).unwrap() + /// ); + /// assert_eq!( + /// Timestamp::try_from(FileTime::UNIX_EPOCH).unwrap(), + /// Timestamp::UNIX_EPOCH + /// ); + /// + /// assert!(Timestamp::try_from(FileTime::MAX).is_err()); + /// ``` + #[inline] + fn try_from(ft: FileTime) -> Result { + Self::from_nanosecond(ft.to_unix_time_nanos()) + } +} + impl From for FileTime { /// Converts the file time to a `FileTime`. /// @@ -251,12 +284,12 @@ impl TryFrom for FileTime { /// ``` /// # use nt_time::FileTime; /// # - /// assert_eq!(FileTime::try_from(0_i64).unwrap(), FileTime::NT_TIME_EPOCH); + /// assert_eq!(FileTime::try_from(0_i64), Ok(FileTime::NT_TIME_EPOCH)); /// assert_eq!( - /// FileTime::try_from(116_444_736_000_000_000_i64).unwrap(), - /// FileTime::UNIX_EPOCH + /// FileTime::try_from(116_444_736_000_000_000_i64), + /// Ok(FileTime::UNIX_EPOCH) /// ); - /// assert_eq!(FileTime::try_from(i64::MAX).unwrap(), FileTime::SIGNED_MAX); + /// assert_eq!(FileTime::try_from(i64::MAX), Ok(FileTime::SIGNED_MAX)); /// /// assert!(FileTime::try_from(i64::MIN).is_err()); /// ``` @@ -280,7 +313,7 @@ impl TryFrom for FileTime { /// /// # Errors /// - /// Returns [`Err`] if `time` is out of range for the file time. + /// Returns [`Err`] if `st` is out of range for the file time. /// /// # Examples /// @@ -290,25 +323,27 @@ impl TryFrom for FileTime { /// # use nt_time::FileTime; /// # /// assert_eq!( - /// FileTime::try_from(SystemTime::UNIX_EPOCH - Duration::from_secs(11_644_473_600)).unwrap(), - /// FileTime::NT_TIME_EPOCH + /// FileTime::try_from(SystemTime::UNIX_EPOCH - Duration::from_secs(11_644_473_600)), + /// Ok(FileTime::NT_TIME_EPOCH) /// ); /// assert_eq!( - /// FileTime::try_from(SystemTime::UNIX_EPOCH).unwrap(), - /// FileTime::UNIX_EPOCH + /// FileTime::try_from(SystemTime::UNIX_EPOCH), + /// Ok(FileTime::UNIX_EPOCH) /// ); /// /// // Before `1601-01-01 00:00:00 UTC`. - /// assert!(FileTime::try_from( - /// SystemTime::UNIX_EPOCH - Duration::from_nanos(11_644_473_600_000_000_100) - /// ) - /// .is_err()); + /// assert!( + /// FileTime::try_from( + /// SystemTime::UNIX_EPOCH - Duration::from_nanos(11_644_473_600_000_000_100) + /// ) + /// .is_err() + /// ); /// // After `+60056-05-28 05:36:10.955161500 UTC`. /// #[cfg(not(windows))] - /// assert!(FileTime::try_from( - /// SystemTime::UNIX_EPOCH + Duration::new(1_833_029_933_770, 955_161_600) - /// ) - /// .is_err()); + /// assert!( + /// FileTime::try_from(SystemTime::UNIX_EPOCH + Duration::new(1_833_029_933_770, 955_161_600)) + /// .is_err() + /// ); /// ``` #[inline] fn try_from(st: std::time::SystemTime) -> Result { @@ -336,17 +371,17 @@ impl TryFrom for FileTime { /// /// ``` /// # use nt_time::{ - /// # time::{macros::datetime, Duration, OffsetDateTime}, /// # FileTime, + /// # time::{Duration, OffsetDateTime, macros::datetime}, /// # }; /// # /// assert_eq!( - /// FileTime::try_from(datetime!(1601-01-01 00:00 UTC)).unwrap(), - /// FileTime::NT_TIME_EPOCH + /// FileTime::try_from(datetime!(1601-01-01 00:00 UTC)), + /// Ok(FileTime::NT_TIME_EPOCH) /// ); /// assert_eq!( - /// FileTime::try_from(OffsetDateTime::UNIX_EPOCH).unwrap(), - /// FileTime::UNIX_EPOCH + /// FileTime::try_from(OffsetDateTime::UNIX_EPOCH), + /// Ok(FileTime::UNIX_EPOCH) /// ); /// /// // Before `1601-01-01 00:00:00 UTC`. @@ -363,14 +398,16 @@ impl TryFrom for FileTime { /// # #[cfg(feature = "large-dates")] /// # { /// # use nt_time::{ - /// # time::{macros::datetime, Duration}, /// # FileTime, + /// # time::{Duration, macros::datetime}, /// # }; /// # - /// assert!(FileTime::try_from( - /// datetime!(+60056-05-28 05:36:10.955_161_500 UTC) + Duration::nanoseconds(100) - /// ) - /// .is_err()); + /// assert!( + /// FileTime::try_from( + /// datetime!(+60056-05-28 05:36:10.955_161_500 UTC) + Duration::nanoseconds(100) + /// ) + /// .is_err() + /// ); /// # } /// ``` #[inline] @@ -393,32 +430,37 @@ impl TryFrom> for FileTime { /// /// ``` /// # use nt_time::{ - /// # chrono::{DateTime, TimeDelta, Utc}, /// # FileTime, + /// # chrono::{DateTime, TimeDelta, Utc}, /// # }; /// # /// assert_eq!( - /// FileTime::try_from("1601-01-01 00:00:00 UTC".parse::>().unwrap()).unwrap(), - /// FileTime::NT_TIME_EPOCH + /// FileTime::try_from("1601-01-01 00:00:00 UTC".parse::>().unwrap()), + /// Ok(FileTime::NT_TIME_EPOCH) /// ); /// assert_eq!( - /// FileTime::try_from(DateTime::::UNIX_EPOCH).unwrap(), - /// FileTime::UNIX_EPOCH + /// FileTime::try_from(DateTime::::UNIX_EPOCH), + /// Ok(FileTime::UNIX_EPOCH) /// ); /// /// // Before `1601-01-01 00:00:00 UTC`. - /// assert!(FileTime::try_from( - /// "1601-01-01 00:00:00 UTC".parse::>().unwrap() - TimeDelta::nanoseconds(100) - /// ) - /// .is_err()); + /// assert!( + /// FileTime::try_from( + /// "1601-01-01 00:00:00 UTC".parse::>().unwrap() + /// - TimeDelta::nanoseconds(100) + /// ) + /// .is_err() + /// ); /// // After `+60056-05-28 05:36:10.955161500 UTC`. - /// assert!(FileTime::try_from( - /// "+60056-05-28 05:36:10.955161500 UTC" - /// .parse::>() - /// .unwrap() - /// + TimeDelta::nanoseconds(100) - /// ) - /// .is_err()); + /// assert!( + /// FileTime::try_from( + /// "+60056-05-28 05:36:10.955161500 UTC" + /// .parse::>() + /// .unwrap() + /// + TimeDelta::nanoseconds(100) + /// ) + /// .is_err() + /// ); /// ``` #[inline] fn try_from(dt: chrono::DateTime) -> Result { @@ -426,6 +468,42 @@ impl TryFrom> for FileTime { } } +#[cfg(feature = "jiff")] +impl TryFrom for FileTime { + type Error = FileTimeRangeError; + + /// Converts a [`Timestamp`](jiff::Timestamp) to a `FileTime`. + /// + /// # Errors + /// + /// Returns [`Err`] if `ts` is out of range for the file time. + /// + /// # Examples + /// + /// ``` + /// # use nt_time::{FileTime, jiff::Timestamp}; + /// # + /// assert_eq!( + /// FileTime::try_from(Timestamp::from_second(-11_644_473_600).unwrap()), + /// Ok(FileTime::NT_TIME_EPOCH) + /// ); + /// assert_eq!( + /// FileTime::try_from(Timestamp::UNIX_EPOCH), + /// Ok(FileTime::UNIX_EPOCH) + /// ); + /// + /// // Before `1601-01-01 00:00:00 UTC`. + /// assert!( + /// FileTime::try_from(Timestamp::from_nanosecond(-11_644_473_600_000_000_001).unwrap()) + /// .is_err() + /// ); + /// ``` + #[inline] + fn try_from(ts: jiff::Timestamp) -> Result { + Self::from_unix_time_nanos(ts.as_nanosecond()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -593,6 +671,33 @@ mod tests { ); } + #[cfg(feature = "jiff")] + #[test] + fn try_from_file_time_to_jiff_timestamp() { + use jiff::{Timestamp, ToSpan}; + + assert_eq!( + Timestamp::try_from(FileTime::NT_TIME_EPOCH).unwrap(), + Timestamp::from_second(-11_644_473_600).unwrap() + ); + assert_eq!( + Timestamp::try_from(FileTime::UNIX_EPOCH).unwrap(), + Timestamp::UNIX_EPOCH + ); + assert_eq!( + Timestamp::try_from(FileTime::new(2_650_466_808_009_999_999)).unwrap(), + Timestamp::MAX - 99.nanoseconds() + ); + } + + #[cfg(feature = "jiff")] + #[test] + fn try_from_file_time_to_jiff_timestamp_with_invalid_file_time() { + use jiff::Timestamp; + + assert!(Timestamp::try_from(FileTime::new(2_650_466_808_010_000_000)).is_err()); + } + #[test] fn from_u64_to_file_time() { assert_eq!(FileTime::from(u64::MIN), FileTime::NT_TIME_EPOCH); @@ -727,9 +832,11 @@ mod tests { use std::time::{Duration, SystemTime}; if cfg!(windows) { - assert!(SystemTime::UNIX_EPOCH - .checked_add(Duration::new(910_692_730_085, 477_580_800)) - .is_none()); + assert!( + SystemTime::UNIX_EPOCH + .checked_add(Duration::new(910_692_730_085, 477_580_800)) + .is_none() + ); } else { assert_eq!( FileTime::try_from( @@ -743,7 +850,7 @@ mod tests { #[test] fn try_from_offset_date_time_to_file_time_before_nt_time_epoch() { - use time::{macros::datetime, Duration}; + use time::{Duration, macros::datetime}; assert_eq!( FileTime::try_from(datetime!(1601-01-01 00:00 UTC) - Duration::nanoseconds(100)) @@ -792,7 +899,7 @@ mod tests { #[cfg(feature = "large-dates")] #[test] fn try_from_offset_date_time_to_file_time_with_too_big_date_time() { - use time::{macros::datetime, Duration}; + use time::{Duration, macros::datetime}; assert_eq!( FileTime::try_from( @@ -886,4 +993,35 @@ mod tests { FileTimeRangeErrorKind::Overflow.into() ); } + + #[cfg(feature = "jiff")] + #[test] + fn try_from_jiff_timestamp_to_file_time_before_nt_time_epoch() { + use jiff::Timestamp; + + assert_eq!( + FileTime::try_from(Timestamp::from_nanosecond(-11_644_473_600_000_000_001).unwrap()) + .unwrap_err(), + FileTimeRangeErrorKind::Negative.into() + ); + } + + #[cfg(feature = "jiff")] + #[test] + fn try_from_jiff_timestamp_to_file_time() { + use jiff::Timestamp; + + assert_eq!( + FileTime::try_from(Timestamp::from_second(-11_644_473_600).unwrap()).unwrap(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::try_from(Timestamp::UNIX_EPOCH).unwrap(), + FileTime::UNIX_EPOCH + ); + assert_eq!( + FileTime::try_from(Timestamp::MAX).unwrap(), + FileTime::new(2_650_466_808_009_999_999) + ); + } } diff --git a/src/file_time/dos_date_time.rs b/src/file_time/dos_date_time.rs index 92c0bf9..1b6d14e 100644 --- a/src/file_time/dos_date_time.rs +++ b/src/file_time/dos_date_time.rs @@ -7,289 +7,132 @@ //! //! [MS-DOS date and time]: https://learn.microsoft.com/en-us/windows/win32/sysinfo/ms-dos-date-and-time -use time::{error::ComponentRange, macros::offset, Date, OffsetDateTime, Time, UtcOffset}; +use time::{Date, OffsetDateTime, Time, error::ComponentRange}; use super::FileTime; use crate::error::{DosDateTimeRangeError, DosDateTimeRangeErrorKind}; impl FileTime { + #[allow(clippy::missing_panics_doc)] /// Returns [MS-DOS date and time] which represents the same date and time /// as this `FileTime`. This date and time is used as the timestamp such as - /// [FAT], [exFAT] or [ZIP] file format. + /// [FAT] or [ZIP] file format. /// - /// This method returns a `(date, time, resolution, offset)` tuple if the - /// result is [`Ok`]. + /// This method returns a `(date, time)` tuple if the result is [`Ok`]. /// - /// `date` and `time` represents the local date and time. This date and time - /// has no notion of time zone. The resolution of MS-DOS date and time is 2 - /// seconds, but additional [finer resolution] (10 ms units) can be - /// provided. `resolution` represents this additional finer resolution. + /// `date` and `time` represents the local date and time, and has no notion + /// of time zone. /// - /// When the `offset` parameter is [`Some`], converts `date` and `time` from - /// UTC to the local date and time in the provided time zone and returns it - /// with the [UTC offset]. When the `offset` parameter is [`None`] or is not - /// a multiple of 15 minute intervals, returns the UTC date and time as a - /// date and time and [`None`] as the UTC offset. + ///
/// - /// Note that exFAT supports `resolution` for creation and last modified - /// times, and the `offset` return value for these times and last access - /// time, but other file systems and file formats may not support these. For - /// example, the built-in timestamp of ZIP used for last modified time only - /// records `date` and `time`, not `resolution` and the `offset` return - /// value. + /// The resolution of MS-DOS date and time is 2 seconds. So this method + /// rounds towards zero, truncating any fractional part of the exact result + /// of dividing seconds by 2. + /// + ///
/// /// # Errors /// /// Returns [`Err`] if the resulting date and time is out of range for /// MS-DOS date and time. /// - /// # Panics - /// - /// Panics if the `offset` parameter is out of range for the [OffsetFromUtc - /// field]. - /// /// # Examples /// /// ``` - /// # use nt_time::{time::macros::offset, FileTime}; + /// # use nt_time::FileTime; /// # - /// // `1980-01-01 00:00:00 UTC`. + /// // From `1980-01-01 00:00:00 UTC` to `1980-01-01 00:00:00`. /// assert_eq!( - /// FileTime::new(119_600_064_000_000_000) - /// .to_dos_date_time(None) - /// .unwrap(), - /// (0x0021, u16::MIN, u8::MIN, None) + /// FileTime::new(119_600_064_000_000_000).to_dos_date_time(), + /// Ok((0x0021, u16::MIN)) /// ); - /// // `2107-12-31 23:59:59 UTC`. + /// // From `2107-12-31 23:59:59 UTC` to `2107-12-31 23:59:58`. /// assert_eq!( - /// FileTime::new(159_992_927_990_000_000) - /// .to_dos_date_time(None) - /// .unwrap(), - /// (0xff9f, 0xbf7d, 100, None) + /// FileTime::new(159_992_927_990_000_000).to_dos_date_time(), + /// Ok((0xff9f, 0xbf7d)) /// ); /// /// // Before `1980-01-01 00:00:00 UTC`. - /// assert!(FileTime::new(119_600_063_990_000_000) - /// .to_dos_date_time(None) - /// .is_err()); - /// // After `2107-12-31 23:59:59.990000000 UTC`. - /// assert!(FileTime::new(159_992_928_000_000_000) - /// .to_dos_date_time(None) - /// .is_err()); - /// - /// // From `2002-11-27 03:25:00 UTC` to `2002-11-26 19:25:00 -08:00`. - /// assert_eq!( - /// FileTime::new(126_828_411_000_000_000) - /// .to_dos_date_time(Some(offset!(-08:00))) - /// .unwrap(), - /// (0x2d7a, 0x9b20, u8::MIN, Some(offset!(-08:00))) + /// assert!( + /// FileTime::new(119_600_063_990_000_000) + /// .to_dos_date_time() + /// .is_err() /// ); - /// ``` - /// - /// When the UTC offset is not a multiple of 15 minute intervals, returns - /// the UTC date and time: - /// - /// ``` - /// # use nt_time::{time::macros::offset, FileTime}; - /// # - /// // `2002-11-27 03:25:00 UTC`. - /// assert_eq!( - /// FileTime::new(126_828_411_000_000_000) - /// .to_dos_date_time(Some(offset!(-08:01))) - /// .unwrap(), - /// (0x2d7b, 0x1b20, u8::MIN, None) - /// ); - /// // `2002-11-27 03:25:00 UTC`. - /// assert_eq!( - /// FileTime::new(126_828_411_000_000_000) - /// .to_dos_date_time(Some(offset!(-08:14))) - /// .unwrap(), - /// (0x2d7b, 0x1b20, u8::MIN, None) - /// ); - /// - /// // From `2002-11-27 03:25:00 UTC` to `2002-11-26 19:10:00 -08:15`. - /// assert_eq!( - /// FileTime::new(126_828_411_000_000_000) - /// .to_dos_date_time(Some(offset!(-08:15))) - /// .unwrap(), - /// (0x2d7a, 0x9940, u8::MIN, Some(offset!(-08:15))) + /// // After `2107-12-31 23:59:59.999999900 UTC`. + /// assert!( + /// FileTime::new(159_992_928_000_000_000) + /// .to_dos_date_time() + /// .is_err() /// ); /// ``` /// /// [MS-DOS date and time]: https://learn.microsoft.com/en-us/windows/win32/sysinfo/ms-dos-date-and-time /// [FAT]: https://en.wikipedia.org/wiki/File_Allocation_Table - /// [exFAT]: https://en.wikipedia.org/wiki/ExFAT /// [ZIP]: https://en.wikipedia.org/wiki/ZIP_(file_format) - /// [finer resolution]: https://learn.microsoft.com/en-us/windows/win32/fileio/exfat-specification#749-10msincrement-fields - /// [UTC offset]: https://learn.microsoft.com/en-us/windows/win32/fileio/exfat-specification#7410-utcoffset-fields - /// [OffsetFromUtc field]: https://learn.microsoft.com/en-us/windows/win32/fileio/exfat-specification#74101-offsetfromutc-field - pub fn to_dos_date_time( - self, - offset: Option, - ) -> Result<(u16, u16, u8, Option), DosDateTimeRangeError> { - let offset = offset.filter(|o| o.whole_seconds() % 900 == 0); - if let Some(o) = offset { - // The UTC offset must be in the range of a 7-bit signed integer. - assert!((offset!(-16:00)..=offset!(+15:45)).contains(&o)); - } - - let dt = OffsetDateTime::try_from(self) - .ok() - .and_then(|dt| dt.checked_to_offset(offset.unwrap_or(UtcOffset::UTC))) - .ok_or(DosDateTimeRangeErrorKind::Overflow)?; + pub fn to_dos_date_time(self) -> Result<(u16, u16), DosDateTimeRangeError> { + let dt = OffsetDateTime::try_from(self).map_err(|_| DosDateTimeRangeErrorKind::Overflow)?; match dt.year() { ..=1979 => Err(DosDateTimeRangeErrorKind::Negative.into()), 2108.. => Err(DosDateTimeRangeErrorKind::Overflow.into()), - _ => { - let (date, time) = (dt.date(), dt.time()); - + year => { let (year, month, day) = ( - date.year() - 1980, - i32::from(u8::from(date.month())), - i32::from(date.day()), + u16::try_from(year - 1980).expect("year should be in the range of `u16`"), + u16::from(u8::from(dt.month())), + u16::from(dt.day()), ); - let dos_date = ((year << 9) + (month << 5) + day) - .try_into() - .expect("the MS-DOS date should be in the range of `u16`"); - - let (hour, minute, second) = (time.hour(), time.minute(), time.second() / 2); - let dos_time = - (u16::from(hour) << 11) + (u16::from(minute) << 5) + u16::from(second); + let date = (year << 9) | (month << 5) | day; - let resolution = ((time - - Time::from_hms(hour, minute, second * 2) - .expect("the MS-DOS time should be in the range of `Time`")) - .whole_milliseconds() - / 10) - .try_into() - .expect("resolution should be in the range of `u8`"); - debug_assert!(resolution <= 199); + let (hour, minute, second) = (dt.hour(), dt.minute(), dt.second() / 2); + let time = (u16::from(hour) << 11) | (u16::from(minute) << 5) | u16::from(second); - Ok((dos_date, dos_time, resolution, offset)) + Ok((date, time)) } } } + #[allow(clippy::missing_panics_doc)] /// Creates a `FileTime` with the given [MS-DOS date and time]. This date - /// and time is used as the timestamp such as [FAT], [exFAT] or [ZIP] file - /// format. + /// and time is used as the timestamp such as [FAT] or [ZIP] file format. /// - /// When `resolution` is [`Some`], additional [finer resolution] (10 ms - /// units) is added to `time`. + ///
/// - /// When `offset` is [`Some`], converts `date` and `time` from the local - /// date and time in the provided time zone to UTC. When `offset` is - /// [`None`] or is not a multiple of 15 minute intervals, assumes the - /// provided date and time is in UTC. + /// The time zone for the local date and time is assumed to be UTC. /// - /// Note that exFAT supports `resolution` for creation and last modified - /// times, and `offset` for these times and last access time, but other file - /// systems and file formats may not support these. For example, the - /// built-in timestamp of ZIP used for last modified time only records - /// `date` and `time`, not `resolution` and `offset`. + ///
/// /// # Errors /// /// Returns [`Err`] if `date` or `time` is an invalid date and time. /// - /// # Panics - /// - /// Panics if any of the following are true: - /// - /// - `resolution` is greater than 199. - /// - `offset` is out of range for the [OffsetFromUtc field]. - /// /// # Examples /// /// ``` - /// # use nt_time::{time::macros::offset, FileTime}; + /// # use nt_time::FileTime; /// # - /// // `1980-01-01 00:00:00 UTC`. + /// // From `1980-01-01 00:00:00` to `1980-01-01 00:00:00 UTC`. /// assert_eq!( - /// FileTime::from_dos_date_time(0x0021, u16::MIN, None, None).unwrap(), - /// FileTime::new(119_600_064_000_000_000) + /// FileTime::from_dos_date_time(0x0021, u16::MIN), + /// Ok(FileTime::new(119_600_064_000_000_000)) /// ); - /// // `2107-12-31 23:59:59 UTC`. + /// // From `2107-12-31 23:59:58` to `2107-12-31 23:59:58 UTC`. /// assert_eq!( - /// FileTime::from_dos_date_time(0xff9f, 0xbf7d, Some(100), None).unwrap(), - /// FileTime::new(159_992_927_990_000_000) - /// ); - /// - /// // From `2002-11-26 19:25:00 -08:00` to `2002-11-27 03:25:00 UTC`. - /// assert_eq!( - /// FileTime::from_dos_date_time(0x2d7a, 0x9b20, None, Some(offset!(-08:00))).unwrap(), - /// FileTime::new(126_828_411_000_000_000) + /// FileTime::from_dos_date_time(0xff9f, 0xbf7d), + /// Ok(FileTime::new(159_992_927_980_000_000)) /// ); /// /// // The Day field is 0. - /// assert!(FileTime::from_dos_date_time(0x0020, u16::MIN, None, None).is_err()); + /// assert!(FileTime::from_dos_date_time(0x0020, u16::MIN).is_err()); /// // The DoubleSeconds field is 30. - /// assert!(FileTime::from_dos_date_time(0x0021, 0x001e, None, None).is_err()); - /// ``` - /// - /// When the [UTC offset] is not a multiple of 15 minute intervals, assumes - /// the provided date and time is in UTC: - /// - /// ``` - /// # use nt_time::{time::macros::offset, FileTime}; - /// # - /// // From `2002-11-26 19:25:00 -08:01` to `2002-11-26 19:25:00 UTC`. - /// assert_eq!( - /// FileTime::from_dos_date_time(0x2d7a, 0x9b20, None, Some(offset!(-08:01))).unwrap(), - /// FileTime::new(126_828_123_000_000_000) - /// ); - /// // From `2002-11-26 19:25:00 -08:14` to `2002-11-26 19:25:00 UTC`. - /// assert_eq!( - /// FileTime::from_dos_date_time(0x2d7a, 0x9b20, None, Some(offset!(-08:14))).unwrap(), - /// FileTime::new(126_828_123_000_000_000) - /// ); - /// - /// // From `2002-11-26 19:25:00 -08:15` to `2002-11-27 03:40:00 UTC`. - /// assert_eq!( - /// FileTime::from_dos_date_time(0x2d7a, 0x9b20, None, Some(offset!(-08:15))).unwrap(), - /// FileTime::new(126_828_420_000_000_000) - /// ); - /// ``` - /// - /// Additional finer resolution must be in the range 0 to 199: - /// - /// ```should_panic - /// # use nt_time::FileTime; - /// # - /// let _ = FileTime::from_dos_date_time(0x0021, u16::MIN, Some(200), None).unwrap(); + /// assert!(FileTime::from_dos_date_time(0x0021, 0x001e).is_err()); /// ``` /// /// [MS-DOS date and time]: https://learn.microsoft.com/en-us/windows/win32/sysinfo/ms-dos-date-and-time /// [FAT]: https://en.wikipedia.org/wiki/File_Allocation_Table - /// [exFAT]: https://en.wikipedia.org/wiki/ExFAT /// [ZIP]: https://en.wikipedia.org/wiki/ZIP_(file_format) - /// [finer resolution]: https://learn.microsoft.com/en-us/windows/win32/fileio/exfat-specification#749-10msincrement-fields - /// [OffsetFromUtc field]: https://learn.microsoft.com/en-us/windows/win32/fileio/exfat-specification#74101-offsetfromutc-field - /// [UTC offset]: https://learn.microsoft.com/en-us/windows/win32/fileio/exfat-specification#7410-utcoffset-fields - pub fn from_dos_date_time( - date: u16, - time: u16, - resolution: Option, - offset: Option, - ) -> Result { - use core::time::Duration; - - if let Some(res) = resolution { - assert!(res <= 199); - } - let resolution = resolution.map_or(Duration::ZERO, |res| { - Duration::from_millis(u64::from(res) * 10) - }); - - let offset = offset.filter(|o| o.whole_seconds() % 900 == 0); - if let Some(o) = offset { - // The UTC offset must be in the range of a 7-bit signed integer. - assert!((offset!(-16:00)..=offset!(+15:45)).contains(&o)); - } - + pub fn from_dos_date_time(date: u16, time: u16) -> Result { let (year, month, day) = ( - ((date >> 9) + 1980).into(), + (1980 + (date >> 9)).into(), u8::try_from((date >> 5) & 0x0f) .expect("month should be in the range of `u8`") .try_into()?, @@ -310,9 +153,9 @@ impl FileTime { .try_into() .expect("second should be in the range of `u8`"), ); - let time = Time::from_hms(hour, minute, second)? + resolution; + let time = Time::from_hms(hour, minute, second)?; - let ft = OffsetDateTime::new_in_offset(date, time, offset.unwrap_or(UtcOffset::UTC)) + let ft = OffsetDateTime::new_utc(date, time) .try_into() .expect("MS-DOS date and time should be in the range of `FileTime`"); Ok(ft) @@ -328,28 +171,14 @@ mod tests { // `1979-12-31 23:59:58 UTC`. assert_eq!( FileTime::new(119_600_063_980_000_000) - .to_dos_date_time(None) + .to_dos_date_time() .unwrap_err(), DosDateTimeRangeErrorKind::Negative.into() ); // `1979-12-31 23:59:59 UTC`. assert_eq!( FileTime::new(119_600_063_990_000_000) - .to_dos_date_time(None) - .unwrap_err(), - DosDateTimeRangeErrorKind::Negative.into() - ); - // `1980-01-01 00:59:58 UTC`. - assert_eq!( - FileTime::new(119_600_099_980_000_000) - .to_dos_date_time(Some(offset!(-01:00))) - .unwrap_err(), - DosDateTimeRangeErrorKind::Negative.into() - ); - // `1980-01-01 00:59:59 UTC`. - assert_eq!( - FileTime::new(119_600_099_990_000_000) - .to_dos_date_time(Some(offset!(-01:00))) + .to_dos_date_time() .unwrap_err(), DosDateTimeRangeErrorKind::Negative.into() ); @@ -363,171 +192,58 @@ mod tests { use proptest::prop_assert_eq; prop_assert_eq!( - FileTime::new(ft).to_dos_date_time(None).unwrap_err(), + FileTime::new(ft).to_dos_date_time().unwrap_err(), DosDateTimeRangeErrorKind::Negative.into() ); } #[test] fn to_dos_date_time() { - // `1980-01-01 00:00:00 UTC`. + // From `1980-01-01 00:00:00 UTC` to `1980-01-01 00:00:00`. assert_eq!( FileTime::new(119_600_064_000_000_000) - .to_dos_date_time(None) + .to_dos_date_time() .unwrap(), - (0x0021, u16::MIN, u8::MIN, None) + (0x0021, u16::MIN) ); - // `1980-01-01 00:00:01 UTC`. + // From `1980-01-01 00:00:01 UTC` to `1980-01-01 00:00:00`. assert_eq!( FileTime::new(119_600_064_010_000_000) - .to_dos_date_time(None) + .to_dos_date_time() .unwrap(), - (0x0021, u16::MIN, 100, None) + (0x0021, u16::MIN) ); - // . - // - // `2018-11-17 10:38:30 UTC`. - assert_eq!( - FileTime::new(131_869_247_100_000_000) - .to_dos_date_time(None) - .unwrap(), - (0x4d71, 0x54cf, u8::MIN, None) - ); - // `2107-12-31 23:59:58 UTC`. - assert_eq!( - FileTime::new(159_992_927_980_000_000) - .to_dos_date_time(None) - .unwrap(), - (0xff9f, 0xbf7d, u8::MIN, None) - ); - // `2107-12-31 23:59:59 UTC`. - assert_eq!( - FileTime::new(159_992_927_990_000_000) - .to_dos_date_time(None) - .unwrap(), - (0xff9f, 0xbf7d, 100, None) - ); - - // `1980-01-01 00:00:00.010000000 UTC`. - assert_eq!( - FileTime::new(119_600_064_000_100_000) - .to_dos_date_time(None) - .unwrap(), - (0x0021, u16::MIN, 1, None) - ); - // `1980-01-01 00:00:00.100000000 UTC`. - assert_eq!( - FileTime::new(119_600_064_001_000_000) - .to_dos_date_time(None) - .unwrap(), - (0x0021, u16::MIN, 10, None) - ); - // `1980-01-01 00:00:01.990000000 UTC`. - assert_eq!( - FileTime::new(119_600_064_019_900_000) - .to_dos_date_time(None) - .unwrap(), - (0x0021, u16::MIN, 199, None) - ); - // `1980-01-01 00:00:02 UTC`. - assert_eq!( - FileTime::new(119_600_064_020_000_000) - .to_dos_date_time(None) - .unwrap(), - (0x0021, 0x0001, u8::MIN, None) - ); - // . // - // From `2002-11-27 03:25:00 UTC` to `2002-11-26 19:25:00 -08:00`. - assert_eq!( - FileTime::new(126_828_411_000_000_000) - .to_dos_date_time(Some(offset!(-08:00))) - .unwrap(), - (0x2d7a, 0x9b20, u8::MIN, Some(offset!(-08:00))) - ); - // From `2002-11-27 03:25:00 UTC` to `2002-11-26 19:25:00 -08:00`. - assert_eq!( - FileTime::new(126_828_411_000_000_000) - .to_dos_date_time(Some(offset!(-08))) - .unwrap(), - (0x2d7a, 0x9b20, u8::MIN, Some(offset!(-08))) - ); - // `2002-11-27 03:25:00 UTC`. - // - // When the UTC offset is not a multiple of 15 minute intervals, returns the UTC - // date and time. - assert_eq!( - FileTime::new(126_828_411_000_000_000) - .to_dos_date_time(Some(offset!(-08:00:01))) - .unwrap(), - (0x2d7b, 0x1b20, u8::MIN, None) - ); - // `2002-11-27 03:25:00 UTC`. - // - // When the UTC offset is not a multiple of 15 minute intervals, returns the UTC - // date and time. - assert_eq!( - FileTime::new(126_828_411_000_000_000) - .to_dos_date_time(Some(offset!(-08:01))) - .unwrap(), - (0x2d7b, 0x1b20, u8::MIN, None) - ); - // `2002-11-27 03:25:00 UTC`. - // - // When the UTC offset is not a multiple of 15 minute intervals, returns the UTC - // date and time. + // From `2002-11-27 03:25:00 UTC` to `2002-11-27 03:25:00`. assert_eq!( FileTime::new(126_828_411_000_000_000) - .to_dos_date_time(Some(offset!(-08:14))) + .to_dos_date_time() .unwrap(), - (0x2d7b, 0x1b20, u8::MIN, None) + (0x2d7b, 0x1b20) ); - // `2002-11-27 03:25:00 UTC`. + // . // - // When the UTC offset is not a multiple of 15 minute intervals, returns the UTC - // date and time. - assert_eq!( - FileTime::new(126_828_411_000_000_000) - .to_dos_date_time(Some(offset!(-08:14:59))) - .unwrap(), - (0x2d7b, 0x1b20, u8::MIN, None) - ); - // From `2002-11-27 03:25:00 UTC` to `2002-11-26 19:10:00 -08:15`. + // From `2018-11-17 10:38:30 UTC` to `2018-11-17 10:38:30`. assert_eq!( - FileTime::new(126_828_411_000_000_000) - .to_dos_date_time(Some(offset!(-08:15))) - .unwrap(), - (0x2d7a, 0x9940, u8::MIN, Some(offset!(-08:15))) - ); - // `2002-11-27 03:25:00 UTC`. - assert_eq!( - FileTime::new(126_828_411_000_000_000) - .to_dos_date_time(Some(UtcOffset::UTC)) - .unwrap(), - (0x2d7b, 0x1b20, u8::MIN, Some(UtcOffset::UTC)) - ); - // `2002-11-27 03:25:00 UTC`. - assert_eq!( - FileTime::new(126_828_411_000_000_000) - .to_dos_date_time(Some(offset!(+00:00))) + FileTime::new(131_869_247_100_000_000) + .to_dos_date_time() .unwrap(), - (0x2d7b, 0x1b20, u8::MIN, Some(UtcOffset::UTC)) + (0x4d71, 0x54cf) ); - - // From `1980-01-01 00:00:00 UTC` to `1980-01-01 15:45:00 +15:45`. + // From `2107-12-31 23:59:58 UTC` to `2107-12-31 23:59:58`. assert_eq!( - FileTime::new(119_600_064_000_000_000) - .to_dos_date_time(Some(offset!(+15:45))) + FileTime::new(159_992_927_980_000_000) + .to_dos_date_time() .unwrap(), - (0x0021, 0x7da0, u8::MIN, Some(offset!(+15:45))) + (0xff9f, 0xbf7d) ); - // From `2107-12-31 23:59:58 UTC` to `2107-12-31 07:59:58 -16:00`. + // From `2107-12-31 23:59:59 UTC` to `2107-12-31 23:59:58`. assert_eq!( - FileTime::new(159_992_927_980_000_000) - .to_dos_date_time(Some(offset!(-16:00))) + FileTime::new(159_992_927_990_000_000) + .to_dos_date_time() .unwrap(), - (0xff9f, 0x3f7d, u8::MIN, Some(offset!(-16:00))) + (0xff9f, 0xbf7d) ); } @@ -538,7 +254,7 @@ mod tests { ) { use proptest::prop_assert; - prop_assert!(FileTime::new(ft).to_dos_date_time(None).is_ok()); + prop_assert!(FileTime::new(ft).to_dos_date_time().is_ok()); } #[test] @@ -546,14 +262,7 @@ mod tests { // `2108-01-01 00:00:00 UTC`. assert_eq!( FileTime::new(159_992_928_000_000_000) - .to_dos_date_time(None) - .unwrap_err(), - DosDateTimeRangeErrorKind::Overflow.into() - ); - // `2107-12-31 23:00:00 UTC`. - assert_eq!( - FileTime::new(159_992_892_000_000_000) - .to_dos_date_time(Some(offset!(+01:00))) + .to_dos_date_time() .unwrap_err(), DosDateTimeRangeErrorKind::Overflow.into() ); @@ -567,144 +276,36 @@ mod tests { use proptest::prop_assert_eq; prop_assert_eq!( - FileTime::new(ft).to_dos_date_time(None).unwrap_err(), + FileTime::new(ft).to_dos_date_time().unwrap_err(), DosDateTimeRangeErrorKind::Overflow.into() ); } - #[test] - #[should_panic] - fn to_dos_date_time_with_invalid_positive_offset() { - // From `1980-01-01 00:00:00 UTC` to `1980-01-01 16:00:00 +16:00`. - let _ = FileTime::new(119_600_064_000_000_000) - .to_dos_date_time(Some(offset!(+16:00))) - .unwrap(); - } - - #[test] - #[should_panic] - fn to_dos_date_time_with_invalid_negative_offset() { - // From `2107-12-31 23:59:58 UTC` to `2107-12-31 07:44:58 -16:15`. - let _ = FileTime::new(159_992_927_980_000_000) - .to_dos_date_time(Some(offset!(-16:15))) - .unwrap(); - } - #[test] fn from_dos_date_time() { - // `1980-01-01 00:00:00 UTC`. + // From `1980-01-01 00:00:00` to `1980-01-01 00:00:00 UTC`. assert_eq!( - FileTime::from_dos_date_time(0x0021, u16::MIN, None, None).unwrap(), + FileTime::from_dos_date_time(0x0021, u16::MIN).unwrap(), FileTime::new(119_600_064_000_000_000) ); - // `1980-01-01 00:00:01 UTC`. - assert_eq!( - FileTime::from_dos_date_time(0x0021, u16::MIN, Some(100), None).unwrap(), - FileTime::new(119_600_064_010_000_000) - ); - // . - // - // `2018-11-17 10:38:30 UTC`. - assert_eq!( - FileTime::from_dos_date_time(0x4d71, 0x54cf, None, None).unwrap(), - FileTime::new(131_869_247_100_000_000) - ); - // `2107-12-31 23:59:58 UTC`. - assert_eq!( - FileTime::from_dos_date_time(0xff9f, 0xbf7d, None, None).unwrap(), - FileTime::new(159_992_927_980_000_000) - ); - // `2107-12-31 23:59:59 UTC`. - assert_eq!( - FileTime::from_dos_date_time(0xff9f, 0xbf7d, Some(100), None).unwrap(), - FileTime::new(159_992_927_990_000_000) - ); - - // `1980-01-01 00:00:00.010000000 UTC`. - assert_eq!( - FileTime::from_dos_date_time(0x0021, u16::MIN, Some(1), None).unwrap(), - FileTime::new(119_600_064_000_100_000) - ); - // `1980-01-01 00:00:00.100000000 UTC`. - assert_eq!( - FileTime::from_dos_date_time(0x0021, u16::MIN, Some(10), None).unwrap(), - FileTime::new(119_600_064_001_000_000) - ); - // `1980-01-01 00:00:01.990000000 UTC`. - assert_eq!( - FileTime::from_dos_date_time(0x0021, u16::MIN, Some(199), None).unwrap(), - FileTime::new(119_600_064_019_900_000) - ); - // . // - // From `2002-11-26 19:25:00 -08:00` to `2002-11-27 03:25:00 UTC`. - assert_eq!( - FileTime::from_dos_date_time(0x2d7a, 0x9b20, None, Some(offset!(-08:00))).unwrap(), - FileTime::new(126_828_411_000_000_000) - ); - // From `2002-11-26 19:25:00 -08:00` to `2002-11-27 03:25:00 UTC`. - assert_eq!( - FileTime::from_dos_date_time(0x2d7a, 0x9b20, None, Some(offset!(-08))).unwrap(), - FileTime::new(126_828_411_000_000_000) - ); - // From `2002-11-26 19:25:00 -08:00:01` to `2002-11-26 19:25:00 UTC`. - // - // When the UTC offset is not a multiple of 15 minute intervals, assumes the - // provided date and time is in UTC. - assert_eq!( - FileTime::from_dos_date_time(0x2d7a, 0x9b20, None, Some(offset!(-08:00:01))).unwrap(), - FileTime::new(126_828_123_000_000_000) - ); - // From `2002-11-26 19:25:00 -08:01` to `2002-11-26 19:25:00 UTC`. - // - // When the UTC offset is not a multiple of 15 minute intervals, assumes the - // provided date and time is in UTC. - assert_eq!( - FileTime::from_dos_date_time(0x2d7a, 0x9b20, None, Some(offset!(-08:01))).unwrap(), - FileTime::new(126_828_123_000_000_000) - ); - // From `2002-11-26 19:25:00 -08:14` to `2002-11-26 19:25:00 UTC`. - // - // When the UTC offset is not a multiple of 15 minute intervals, assumes the - // provided date and time is in UTC. + // From `2002-11-26 19:25:00` to `2002-11-26 19:25:00 UTC`. assert_eq!( - FileTime::from_dos_date_time(0x2d7a, 0x9b20, None, Some(offset!(-08:14))).unwrap(), + FileTime::from_dos_date_time(0x2d7a, 0x9b20).unwrap(), FileTime::new(126_828_123_000_000_000) ); - // From `2002-11-26 19:25:00 -08:14:59` to `2002-11-26 19:25:00 UTC`. + // . // - // When the UTC offset is not a multiple of 15 minute intervals, assumes the - // provided date and time is in UTC. - assert_eq!( - FileTime::from_dos_date_time(0x2d7a, 0x9b20, None, Some(offset!(-08:14:59))).unwrap(), - FileTime::new(126_828_123_000_000_000) - ); - // From `2002-11-26 19:25:00 -08:15` to `2002-11-27 03:40:00 UTC`. - assert_eq!( - FileTime::from_dos_date_time(0x2d7a, 0x9b20, None, Some(offset!(-08:15))).unwrap(), - FileTime::new(126_828_420_000_000_000) - ); - // `2002-11-26 19:25:00 UTC`. - assert_eq!( - FileTime::from_dos_date_time(0x2d7a, 0x9b20, None, Some(UtcOffset::UTC)).unwrap(), - FileTime::new(126_828_123_000_000_000) - ); - // `2002-11-26 19:25:00 UTC`. - assert_eq!( - FileTime::from_dos_date_time(0x2d7a, 0x9b20, None, Some(offset!(+00:00))).unwrap(), - FileTime::new(126_828_123_000_000_000) - ); - - // From `2107-12-31 23:59:58 +15:45` to `2107-12-31 08:14:58 UTC`. + // From `2018-11-17 10:38:30` to `2018-11-17 10:38:30 UTC`. assert_eq!( - FileTime::from_dos_date_time(0xff9f, 0xbf7d, None, Some(offset!(+15:45))).unwrap(), - FileTime::new(159_992_360_980_000_000) + FileTime::from_dos_date_time(0x4d71, 0x54cf).unwrap(), + FileTime::new(131_869_247_100_000_000) ); - // From `1980-01-01 00:00:00 -16:00` to `1980-01-01 16:00:00 UTC`. + // From `2107-12-31 23:59:58` to `2107-12-31 23:59:58 UTC`. assert_eq!( - FileTime::from_dos_date_time(0x0021, u16::MIN, None, Some(offset!(-16:00))).unwrap(), - FileTime::new(119_600_640_000_000_000) + FileTime::from_dos_date_time(0xff9f, 0xbf7d).unwrap(), + FileTime::new(159_992_927_980_000_000) ); } @@ -725,46 +326,25 @@ mod tests { let date = u16::from(day) + (u16::from(month) << 5) + ((year - 1980) << 9); let time = u16::from(second / 2) + (u16::from(minute) << 5) + (u16::from(hour) << 11); - prop_assert!(FileTime::from_dos_date_time(date, time, None, None).is_ok()); + prop_assert!(FileTime::from_dos_date_time(date, time).is_ok()); } #[test] fn from_dos_date_time_with_invalid_date_time() { // The Day field is 0. - assert!(FileTime::from_dos_date_time(0x0020, u16::MIN, None, None).is_err()); + assert!(FileTime::from_dos_date_time(0x0020, u16::MIN).is_err()); // The Day field is 30, which is after the last day of February. - assert!(FileTime::from_dos_date_time(0x005e, u16::MIN, None, None).is_err()); + assert!(FileTime::from_dos_date_time(0x005e, u16::MIN).is_err()); // The Month field is 0. - assert!(FileTime::from_dos_date_time(0x0001, u16::MIN, None, None).is_err()); + assert!(FileTime::from_dos_date_time(0x0001, u16::MIN).is_err()); // The Month field is 13. - assert!(FileTime::from_dos_date_time(0x01a1, u16::MIN, None, None).is_err()); + assert!(FileTime::from_dos_date_time(0x01a1, u16::MIN).is_err()); // The DoubleSeconds field is 30. - assert!(FileTime::from_dos_date_time(0x0021, 0x001e, None, None).is_err()); + assert!(FileTime::from_dos_date_time(0x0021, 0x001e).is_err()); // The Minute field is 60. - assert!(FileTime::from_dos_date_time(0x0021, 0x0780, None, None).is_err()); + assert!(FileTime::from_dos_date_time(0x0021, 0x0780).is_err()); // The Hour field is 24. - assert!(FileTime::from_dos_date_time(0x0021, 0xc000, None, None).is_err()); - } - - #[test] - #[should_panic] - fn from_dos_date_time_with_invalid_resolution() { - let _ = FileTime::from_dos_date_time(0x0021, u16::MIN, Some(200), None).unwrap(); - } - - #[test] - #[should_panic] - fn from_dos_date_time_with_invalid_positive_offset() { - // From `2107-12-31 23:59:58 +16:00` to `2107-12-31 07:59:58 UTC`. - let _ = FileTime::from_dos_date_time(0xff9f, 0xbf7d, None, Some(offset!(+16:00))).unwrap(); - } - - #[test] - #[should_panic] - fn from_dos_date_time_with_invalid_negative_offset() { - // From `1980-01-01 00:00:00 -16:15` to `1980-01-01 16:15:00 UTC`. - let _ = - FileTime::from_dos_date_time(0x0021, u16::MIN, None, Some(offset!(-16:15))).unwrap(); + assert!(FileTime::from_dos_date_time(0x0021, 0xc000).is_err()); } } diff --git a/src/file_time/ops.rs b/src/file_time/ops.rs index 2a5d3a0..80aee4d 100644 --- a/src/file_time/ops.rs +++ b/src/file_time/ops.rs @@ -8,7 +8,7 @@ use core::ops::{Add, AddAssign, Sub, SubAssign}; use time::OffsetDateTime; -use super::{FileTime, FILE_TIMES_PER_SEC}; +use super::{FILE_TIMES_PER_SEC, FileTime}; impl FileTime { /// Computes `self + rhs`, returning [`None`] if overflow occurred. The part @@ -172,6 +172,22 @@ impl Add for FileTime { } } +#[cfg(feature = "jiff")] +impl Add for FileTime { + type Output = Self; + + #[inline] + fn add(self, rhs: jiff::Span) -> Self::Output { + use core::time::Duration; + + if rhs.is_positive() { + self + Duration::try_from(rhs.abs()).expect("duration is less than zero") + } else { + self - Duration::try_from(rhs.abs()).expect("duration is less than zero") + } + } +} + impl AddAssign for FileTime { #[inline] fn add_assign(&mut self, rhs: core::time::Duration) { @@ -194,6 +210,14 @@ impl AddAssign for FileTime { } } +#[cfg(feature = "jiff")] +impl AddAssign for FileTime { + #[inline] + fn add_assign(&mut self, rhs: jiff::Span) { + *self = *self + rhs; + } +} + impl Sub for FileTime { type Output = core::time::Duration; @@ -247,6 +271,22 @@ impl Sub for FileTime { } } +#[cfg(feature = "jiff")] +impl Sub for FileTime { + type Output = Self; + + #[inline] + fn sub(self, rhs: jiff::Span) -> Self::Output { + use core::time::Duration; + + if rhs.is_positive() { + self - Duration::try_from(rhs.abs()).expect("duration is less than zero") + } else { + self + Duration::try_from(rhs.abs()).expect("duration is less than zero") + } + } +} + #[cfg(feature = "std")] impl Sub for std::time::SystemTime { type Output = std::time::Duration; @@ -312,6 +352,28 @@ impl Sub> for FileTime { } } +#[cfg(feature = "jiff")] +impl Sub for jiff::Timestamp { + type Output = jiff::Span; + + #[inline] + fn sub(self, rhs: FileTime) -> Self::Output { + self - Self::try_from(rhs).expect("RHS is out of range for `Timestamp`") + } +} + +#[cfg(feature = "jiff")] +impl Sub for FileTime { + type Output = jiff::Span; + + #[inline] + fn sub(self, rhs: jiff::Timestamp) -> Self::Output { + use jiff::Timestamp; + + Timestamp::try_from(self).expect("LHS is out of range for `Timestamp`") - rhs + } +} + impl SubAssign for FileTime { #[inline] fn sub_assign(&mut self, rhs: core::time::Duration) { @@ -334,6 +396,14 @@ impl SubAssign for FileTime { } } +#[cfg(feature = "jiff")] +impl SubAssign for FileTime { + #[inline] + fn sub_assign(&mut self, rhs: jiff::Span) { + *self = *self - rhs; + } +} + #[cfg(test)] mod tests { use time::macros::datetime; @@ -717,6 +787,78 @@ mod tests { let _ = FileTime::NT_TIME_EPOCH + TimeDelta::nanoseconds(-100); } + #[cfg(feature = "jiff")] + #[test] + fn add_positive_jiff_span() { + use jiff::{Span, ToSpan}; + + assert_eq!( + FileTime::NT_TIME_EPOCH + Span::new(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::NT_TIME_EPOCH + 1.nanosecond(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::NT_TIME_EPOCH + 99.nanoseconds(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::NT_TIME_EPOCH + 100.nanoseconds(), + FileTime::new(1) + ); + + assert_eq!(FileTime::MAX + Span::new(), FileTime::MAX); + assert_eq!(FileTime::MAX + 1.nanosecond(), FileTime::MAX); + assert_eq!(FileTime::MAX + 99.nanoseconds(), FileTime::MAX); + } + + #[cfg(feature = "jiff")] + #[test] + #[should_panic(expected = "overflow when adding duration to date and time")] + fn add_positive_jiff_span_with_overflow() { + use jiff::ToSpan; + + let _ = FileTime::MAX + 100.nanoseconds(); + } + + #[cfg(feature = "jiff")] + #[test] + fn add_negative_jiff_span() { + use jiff::{Span, ToSpan}; + + assert_eq!(FileTime::MAX + -Span::new(), FileTime::MAX); + assert_eq!(FileTime::MAX + (-1).nanosecond(), FileTime::MAX); + assert_eq!(FileTime::MAX + (-99).nanoseconds(), FileTime::MAX); + assert_eq!( + FileTime::MAX + (-100).nanoseconds(), + FileTime::new(u64::MAX - 1) + ); + + assert_eq!( + FileTime::NT_TIME_EPOCH + -Span::new(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::NT_TIME_EPOCH + (-1).nanosecond(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::NT_TIME_EPOCH + (-99).nanoseconds(), + FileTime::NT_TIME_EPOCH + ); + } + + #[cfg(feature = "jiff")] + #[test] + #[should_panic(expected = "overflow when subtracting duration from date and time")] + fn add_negative_jiff_span_with_overflow() { + use jiff::ToSpan; + + let _ = FileTime::NT_TIME_EPOCH + (-100).nanoseconds(); + } + #[test] fn add_assign_std_duration() { use core::time::Duration; @@ -976,6 +1118,112 @@ mod tests { ft += TimeDelta::nanoseconds(-100); } + #[cfg(feature = "jiff")] + #[test] + fn add_assign_positive_jiff_span() { + use jiff::{Span, ToSpan}; + + { + let mut ft = FileTime::NT_TIME_EPOCH; + ft += Span::new(); + assert_eq!(ft, FileTime::NT_TIME_EPOCH); + } + { + let mut ft = FileTime::NT_TIME_EPOCH; + ft += 1.nanosecond(); + assert_eq!(ft, FileTime::NT_TIME_EPOCH); + } + { + let mut ft = FileTime::NT_TIME_EPOCH; + ft += 99.nanoseconds(); + assert_eq!(ft, FileTime::NT_TIME_EPOCH); + } + { + let mut ft = FileTime::NT_TIME_EPOCH; + ft += 100.nanoseconds(); + assert_eq!(ft, FileTime::new(1)); + } + + { + let mut ft = FileTime::MAX; + ft += Span::new(); + assert_eq!(ft, FileTime::MAX); + } + { + let mut ft = FileTime::MAX; + ft += 1.nanosecond(); + assert_eq!(ft, FileTime::MAX); + } + { + let mut ft = FileTime::MAX; + ft += 99.nanoseconds(); + assert_eq!(ft, FileTime::MAX); + } + } + + #[cfg(feature = "jiff")] + #[test] + #[should_panic(expected = "overflow when adding duration to date and time")] + fn add_assign_positive_jiff_span_with_overflow() { + use jiff::ToSpan; + + let mut ft = FileTime::MAX; + ft += 100.nanoseconds(); + } + + #[cfg(feature = "jiff")] + #[test] + fn add_assign_negative_jiff_span() { + use jiff::{Span, ToSpan}; + + { + let mut ft = FileTime::MAX; + ft += -Span::new(); + assert_eq!(ft, FileTime::MAX); + } + { + let mut ft = FileTime::MAX; + ft += (-1).nanosecond(); + assert_eq!(ft, FileTime::MAX); + } + { + let mut ft = FileTime::MAX; + ft += (-99).nanoseconds(); + assert_eq!(ft, FileTime::MAX); + } + { + let mut ft = FileTime::MAX; + ft += (-100).nanoseconds(); + assert_eq!(ft, FileTime::new(u64::MAX - 1)); + } + + { + let mut ft = FileTime::NT_TIME_EPOCH; + ft += -Span::new(); + assert_eq!(ft, FileTime::NT_TIME_EPOCH); + } + { + let mut ft = FileTime::NT_TIME_EPOCH; + ft += (-1).nanosecond(); + assert_eq!(ft, FileTime::NT_TIME_EPOCH); + } + { + let mut ft = FileTime::NT_TIME_EPOCH; + ft += (-99).nanoseconds(); + assert_eq!(ft, FileTime::NT_TIME_EPOCH); + } + } + + #[cfg(feature = "jiff")] + #[test] + #[should_panic(expected = "overflow when subtracting duration from date and time")] + fn add_assign_negative_jiff_span_with_overflow() { + use jiff::ToSpan; + + let mut ft = FileTime::NT_TIME_EPOCH; + ft += (-100).nanoseconds(); + } + #[test] fn sub_file_time() { use core::time::Duration; @@ -1173,6 +1421,78 @@ mod tests { let _ = FileTime::MAX - TimeDelta::nanoseconds(-100); } + #[cfg(feature = "jiff")] + #[test] + fn sub_positive_jiff_span() { + use jiff::{Span, ToSpan}; + + assert_eq!(FileTime::MAX - Span::new(), FileTime::MAX); + assert_eq!(FileTime::MAX - 1.nanosecond(), FileTime::MAX); + assert_eq!(FileTime::MAX - 99.nanoseconds(), FileTime::MAX); + assert_eq!( + FileTime::MAX - 100.nanoseconds(), + FileTime::new(u64::MAX - 1) + ); + + assert_eq!( + FileTime::NT_TIME_EPOCH - Span::new(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::NT_TIME_EPOCH - 1.nanosecond(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::NT_TIME_EPOCH - 99.nanoseconds(), + FileTime::NT_TIME_EPOCH + ); + } + + #[cfg(feature = "jiff")] + #[test] + #[should_panic(expected = "overflow when subtracting duration from date and time")] + fn sub_positive_jiff_span_with_overflow() { + use jiff::ToSpan; + + let _ = FileTime::NT_TIME_EPOCH - 100.nanoseconds(); + } + + #[cfg(feature = "jiff")] + #[test] + fn sub_negative_jiff_span() { + use jiff::{Span, ToSpan}; + + assert_eq!( + FileTime::NT_TIME_EPOCH - -Span::new(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::NT_TIME_EPOCH - (-1).nanosecond(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::NT_TIME_EPOCH - (-99).nanoseconds(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::NT_TIME_EPOCH - (-100).nanoseconds(), + FileTime::new(1) + ); + + assert_eq!(FileTime::MAX - -Span::new(), FileTime::MAX); + assert_eq!(FileTime::MAX - (-1).nanosecond(), FileTime::MAX); + assert_eq!(FileTime::MAX - (-99).nanoseconds(), FileTime::MAX); + } + + #[cfg(feature = "jiff")] + #[test] + #[should_panic(expected = "overflow when adding duration to date and time")] + fn sub_negative_jiff_span_with_overflow() { + use jiff::ToSpan; + + let _ = FileTime::MAX - (-100).nanoseconds(); + } + #[cfg(feature = "std")] #[test] fn sub_file_time_from_system_time() { @@ -1368,6 +1688,67 @@ mod tests { ); } + #[cfg(feature = "jiff")] + #[test] + fn sub_file_time_from_jiff_timestamp() { + use jiff::{Span, Timestamp, ToSpan}; + + assert_eq!( + (Timestamp::MAX - 99.nanoseconds()) - FileTime::new(2_650_466_808_009_999_999), + Span::new().fieldwise() + ); + assert_eq!( + (Timestamp::MAX - 99.nanoseconds()) + - (FileTime::new(2_650_466_808_009_999_999) - 100.nanoseconds()), + 100.nanoseconds().fieldwise() + ); + assert_eq!( + (Timestamp::MAX - 99.nanoseconds()) - FileTime::NT_TIME_EPOCH, + 265_046_680_800_i64 + .seconds() + .milliseconds(999) + .microseconds(999) + .nanoseconds(900) + .fieldwise() + ); + } + + #[cfg(feature = "jiff")] + #[test] + fn sub_jiff_timestamp_from_file_time() { + use jiff::{Span, Timestamp, ToSpan}; + + assert_eq!( + FileTime::new(2_650_466_808_009_999_999) - (Timestamp::MAX - 99.nanoseconds()), + Span::new().fieldwise() + ); + assert_eq!( + FileTime::new(2_650_466_808_009_999_999) + - ((Timestamp::MAX - 99.nanoseconds()) - 1.nanosecond()), + 1.nanosecond().fieldwise() + ); + assert_eq!( + FileTime::new(2_650_466_808_009_999_999) + - ((Timestamp::MAX - 99.nanoseconds()) - 99.nanoseconds()), + 99.nanoseconds().fieldwise() + ); + assert_eq!( + FileTime::new(2_650_466_808_009_999_999) + - ((Timestamp::MAX - 99.nanoseconds()) - 100.nanoseconds()), + 100.nanoseconds().fieldwise() + ); + assert_eq!( + FileTime::new(2_650_466_808_009_999_999) + - Timestamp::from_second(-11_644_473_600).unwrap(), + 265_046_680_800_i64 + .seconds() + .milliseconds(999) + .microseconds(999) + .nanoseconds(900) + .fieldwise() + ); + } + #[test] fn sub_assign_std_duration() { use core::time::Duration; @@ -1626,4 +2007,110 @@ mod tests { let mut ft = FileTime::MAX; ft -= TimeDelta::nanoseconds(-100); } + + #[cfg(feature = "jiff")] + #[test] + fn sub_assign_positive_jiff_span() { + use jiff::{Span, ToSpan}; + + { + let mut ft = FileTime::MAX; + ft -= Span::new(); + assert_eq!(ft, FileTime::MAX); + } + { + let mut ft = FileTime::MAX; + ft -= 1.nanosecond(); + assert_eq!(ft, FileTime::MAX); + } + { + let mut ft = FileTime::MAX; + ft -= 99.nanoseconds(); + assert_eq!(ft, FileTime::MAX); + } + { + let mut ft = FileTime::MAX; + ft -= 100.nanoseconds(); + assert_eq!(ft, FileTime::new(u64::MAX - 1)); + } + + { + let mut ft = FileTime::NT_TIME_EPOCH; + ft -= Span::new(); + assert_eq!(ft, FileTime::NT_TIME_EPOCH); + } + { + let mut ft = FileTime::NT_TIME_EPOCH; + ft -= 1.nanosecond(); + assert_eq!(ft, FileTime::NT_TIME_EPOCH); + } + { + let mut ft = FileTime::NT_TIME_EPOCH; + ft -= 99.nanoseconds(); + assert_eq!(ft, FileTime::NT_TIME_EPOCH); + } + } + + #[cfg(feature = "jiff")] + #[test] + #[should_panic(expected = "overflow when subtracting duration from date and time")] + fn sub_assign_positive_jiff_span_with_overflow() { + use jiff::ToSpan; + + let mut ft = FileTime::NT_TIME_EPOCH; + ft -= 100.nanoseconds(); + } + + #[cfg(feature = "jiff")] + #[test] + fn sub_assign_negative_jiff_span() { + use jiff::{Span, ToSpan}; + + { + let mut ft = FileTime::NT_TIME_EPOCH; + ft -= -Span::new(); + assert_eq!(ft, FileTime::NT_TIME_EPOCH); + } + { + let mut ft = FileTime::NT_TIME_EPOCH; + ft -= (-1).nanosecond(); + assert_eq!(ft, FileTime::NT_TIME_EPOCH); + } + { + let mut ft = FileTime::NT_TIME_EPOCH; + ft -= (-99).nanoseconds(); + assert_eq!(ft, FileTime::NT_TIME_EPOCH); + } + { + let mut ft = FileTime::NT_TIME_EPOCH; + ft -= (-100).nanoseconds(); + assert_eq!(ft, FileTime::new(1)); + } + + { + let mut ft = FileTime::MAX; + ft -= -Span::new(); + assert_eq!(ft, FileTime::MAX); + } + { + let mut ft = FileTime::MAX; + ft -= (-1).nanosecond(); + assert_eq!(ft, FileTime::MAX); + } + { + let mut ft = FileTime::MAX; + ft -= (-99).nanoseconds(); + assert_eq!(ft, FileTime::MAX); + } + } + + #[cfg(feature = "jiff")] + #[test] + #[should_panic(expected = "overflow when adding duration to date and time")] + fn sub_assign_negative_jiff_span_with_overflow() { + use jiff::ToSpan; + + let mut ft = FileTime::MAX; + ft -= (-100).nanoseconds(); + } } diff --git a/src/file_time/rand.rs b/src/file_time/rand.rs index 6ba0310..4e5d150 100644 --- a/src/file_time/rand.rs +++ b/src/file_time/rand.rs @@ -5,36 +5,40 @@ //! Implementations of [`rand`] for [`FileTime`]. use rand::{ - distributions::{Distribution, Standard}, Rng, + distr::{Distribution, StandardUniform}, }; use super::FileTime; -impl Distribution for Standard { +impl Distribution for StandardUniform { #[inline] fn sample(&self, rng: &mut R) -> FileTime { - FileTime::new(rng.gen()) + FileTime::new(rng.random()) } } #[cfg(test)] mod tests { - use rand::rngs::mock::StepRng; + use rand_pcg::{Pcg64Mcg, rand_core::SeedableRng}; use super::*; #[test] fn sample() { - let mut rng = StepRng::new(0, 1); - let buf: [FileTime; 4] = rng.gen(); + let mut rng = Pcg64Mcg::from_seed(Default::default()); + let buf: [FileTime; 8] = rng.random(); assert_eq!( buf, [ - FileTime::new(0), - FileTime::new(1), - FileTime::new(2), - FileTime::new(3) + FileTime::new(0xe160_e532_6180_0aab), + FileTime::new(0x2a29_11d5_87fc_4ed5), + FileTime::new(0xdfe7_5554_bbd3_4d0d), + FileTime::new(0x2a4c_f66b_2879_6f51), + FileTime::new(0x500e_b6de_08bd_473b), + FileTime::new(0x8660_66c5_0dab_6374), + FileTime::new(0xe8e3_3086_f142_3eff), + FileTime::new(0x7d67_17b2_e579_844f) ] ); } diff --git a/src/file_time/serde.rs b/src/file_time/serde.rs index 0a9db8f..90bda7b 100644 --- a/src/file_time/serde.rs +++ b/src/file_time/serde.rs @@ -8,7 +8,7 @@ use core::fmt; -use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor}; use super::FileTime; @@ -132,7 +132,7 @@ mod tests { #[test] fn serde() { - use serde_test::{assert_tokens, Token}; + use serde_test::{Token, assert_tokens}; assert_tokens( &Test { @@ -183,7 +183,7 @@ mod tests { #[test] fn deserialize_error() { - use serde_test::{assert_de_tokens_error, Token}; + use serde_test::{Token, assert_de_tokens_error}; assert_de_tokens_error::( &[ @@ -224,7 +224,7 @@ mod tests { #[test] fn serde_optional() { - use serde_test::{assert_tokens, Token}; + use serde_test::{Token, assert_tokens}; assert_tokens( &TestOption { @@ -290,7 +290,7 @@ mod tests { #[test] fn deserialize_optional_error() { - use serde_test::{assert_de_tokens_error, Token}; + use serde_test::{Token, assert_de_tokens_error}; assert_de_tokens_error::( &[ diff --git a/src/file_time/str.rs b/src/file_time/str.rs new file mode 100644 index 0000000..10b37d6 --- /dev/null +++ b/src/file_time/str.rs @@ -0,0 +1,778 @@ +// SPDX-FileCopyrightText: 2023 Shun Sakai +// +// SPDX-License-Identifier: Apache-2.0 OR MIT + +//! Utilities for string related operations. + +use core::str::FromStr; + +use super::FileTime; +use crate::error::ParseFileTimeError; + +impl FileTime { + /// Parses a `FileTime` from a string slice with digits in a given base. + /// + /// The string is expected to be an optional `+` sign followed by only + /// digits. Leading and trailing non-digit characters (including whitespace) + /// represent an error. Underscores (which are accepted in Rust literals) + /// also represent an error. + /// + /// Digits are a subset of these characters, depending on `radix`: + /// + /// - `0-9` + /// - `a-z` + /// - `A-Z` + /// + /// # Errors + /// + /// Returns [`Err`] if [`u64::from_str_radix`] returns an error. + /// + /// # Panics + /// + /// Panics if `radix` is not in the range from 2 to 36. + /// + /// # Examples + /// + /// ``` + /// # use nt_time::FileTime; + /// # + /// assert_eq!( + /// FileTime::from_str_radix("0", 2), + /// Ok(FileTime::NT_TIME_EPOCH) + /// ); + /// assert_eq!( + /// FileTime::from_str_radix("6355435732517500000", 8), + /// Ok(FileTime::UNIX_EPOCH) + /// ); + /// assert_eq!( + /// FileTime::from_str_radix("+9223372036854775807", 10), + /// Ok(FileTime::SIGNED_MAX) + /// ); + /// assert_eq!( + /// FileTime::from_str_radix("+ffffffffffffffff", 16), + /// Ok(FileTime::MAX) + /// ); + /// + /// assert!(FileTime::from_str_radix("8", 8).is_err()); + /// + /// assert!(FileTime::from_str_radix("", 16).is_err()); + /// + /// assert!(FileTime::from_str_radix("Z", 16).is_err()); + /// assert!(FileTime::from_str_radix("_", 16).is_err()); + /// assert!(FileTime::from_str_radix("-1", 16).is_err()); + /// assert!(FileTime::from_str_radix("+", 16).is_err()); + /// assert!(FileTime::from_str_radix("0 ", 16).is_err()); + /// + /// assert!(FileTime::from_str_radix("3W5E11264SGSG", 36).is_err()); + /// ``` + /// + /// `radix` must be greater than or equal to 2: + /// + /// ```should_panic + /// # use nt_time::FileTime; + /// # + /// let _ = FileTime::from_str_radix("0", 1); + /// ``` + /// + /// `radix` must be less than or equal to 36: + /// + /// ```should_panic + /// # use nt_time::FileTime; + /// # + /// let _ = FileTime::from_str_radix("0", 37); + /// ``` + #[inline] + pub const fn from_str_radix(src: &str, radix: u32) -> Result { + match u64::from_str_radix(src, radix) { + Ok(ft) => Ok(Self::new(ft)), + Err(err) => Err(ParseFileTimeError::new(err)), + } + } +} + +impl FromStr for FileTime { + type Err = ParseFileTimeError; + + /// Parses a string `src` to return a value of `FileTime`. + /// + ///
+ /// + /// The string is expected to be a decimal non-negative integer. If the + /// string is not a decimal integer, use [`FileTime::from_str_radix`] + /// instead. + /// + ///
+ /// + /// # Errors + /// + /// Returns [`Err`] if [`u64::from_str`] returns an error. + /// + /// # Examples + /// + /// ``` + /// # use core::str::FromStr; + /// # + /// # use nt_time::FileTime; + /// # + /// assert_eq!(FileTime::from_str("0"), Ok(FileTime::NT_TIME_EPOCH)); + /// assert_eq!( + /// FileTime::from_str("116444736000000000"), + /// Ok(FileTime::UNIX_EPOCH) + /// ); + /// assert_eq!( + /// FileTime::from_str("+9223372036854775807"), + /// Ok(FileTime::SIGNED_MAX) + /// ); + /// assert_eq!( + /// FileTime::from_str("+18446744073709551615"), + /// Ok(FileTime::MAX) + /// ); + /// + /// assert!(FileTime::from_str("").is_err()); + /// + /// assert!(FileTime::from_str("a").is_err()); + /// assert!(FileTime::from_str("_").is_err()); + /// assert!(FileTime::from_str("-1").is_err()); + /// assert!(FileTime::from_str("+").is_err()); + /// assert!(FileTime::from_str("0 ").is_err()); + /// + /// assert!(FileTime::from_str("18446744073709551616").is_err()); + /// ``` + #[inline] + fn from_str(src: &str) -> Result { + Self::from_str_radix(src, 10) + } +} + +#[cfg(test)] +mod tests { + use core::{ + error::Error, + num::{IntErrorKind, ParseIntError}, + }; + + use super::*; + + #[test] + fn from_str_radix() { + assert_eq!( + FileTime::from_str_radix("0", 2).unwrap(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("+0", 2).unwrap(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::from_str_radix( + "110011101101100011101111011010101001111101000000000000000", + 2 + ) + .unwrap(), + FileTime::UNIX_EPOCH + ); + assert_eq!( + FileTime::from_str_radix( + "+110011101101100011101111011010101001111101000000000000000", + 2 + ) + .unwrap(), + FileTime::UNIX_EPOCH + ); + assert_eq!( + FileTime::from_str_radix( + "111111111111111111111111111111111111111111111111111111111111111", + 2 + ) + .unwrap(), + FileTime::SIGNED_MAX + ); + assert_eq!( + FileTime::from_str_radix( + "+111111111111111111111111111111111111111111111111111111111111111", + 2 + ) + .unwrap(), + FileTime::SIGNED_MAX + ); + assert_eq!( + FileTime::from_str_radix( + "1111111111111111111111111111111111111111111111111111111111111111", + 2 + ) + .unwrap(), + FileTime::MAX + ); + assert_eq!( + FileTime::from_str_radix( + "+1111111111111111111111111111111111111111111111111111111111111111", + 2 + ) + .unwrap(), + FileTime::MAX + ); + assert_eq!( + FileTime::from_str_radix("0", 8).unwrap(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("+0", 8).unwrap(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("6355435732517500000", 8).unwrap(), + FileTime::UNIX_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("+6355435732517500000", 8).unwrap(), + FileTime::UNIX_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("777777777777777777777", 8).unwrap(), + FileTime::SIGNED_MAX + ); + assert_eq!( + FileTime::from_str_radix("+777777777777777777777", 8).unwrap(), + FileTime::SIGNED_MAX + ); + assert_eq!( + FileTime::from_str_radix("1777777777777777777777", 8).unwrap(), + FileTime::MAX + ); + assert_eq!( + FileTime::from_str_radix("+1777777777777777777777", 8).unwrap(), + FileTime::MAX + ); + assert_eq!( + FileTime::from_str_radix("0", 10).unwrap(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("+0", 10).unwrap(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("116444736000000000", 10).unwrap(), + FileTime::UNIX_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("+116444736000000000", 10).unwrap(), + FileTime::UNIX_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("9223372036854775807", 10).unwrap(), + FileTime::SIGNED_MAX + ); + assert_eq!( + FileTime::from_str_radix("+9223372036854775807", 10).unwrap(), + FileTime::SIGNED_MAX + ); + assert_eq!( + FileTime::from_str_radix("18446744073709551615", 10).unwrap(), + FileTime::MAX + ); + assert_eq!( + FileTime::from_str_radix("+18446744073709551615", 10).unwrap(), + FileTime::MAX + ); + assert_eq!( + FileTime::from_str_radix("0", 16).unwrap(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("+0", 16).unwrap(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("19db1ded53e8000", 16).unwrap(), + FileTime::UNIX_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("19DB1DED53E8000", 16).unwrap(), + FileTime::UNIX_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("+19db1ded53e8000", 16).unwrap(), + FileTime::UNIX_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("+19DB1DED53E8000", 16).unwrap(), + FileTime::UNIX_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("7fffffffffffffff", 16).unwrap(), + FileTime::SIGNED_MAX + ); + assert_eq!( + FileTime::from_str_radix("7FFFFFFFFFFFFFFF", 16).unwrap(), + FileTime::SIGNED_MAX + ); + assert_eq!( + FileTime::from_str_radix("+7fffffffffffffff", 16).unwrap(), + FileTime::SIGNED_MAX + ); + assert_eq!( + FileTime::from_str_radix("+7FFFFFFFFFFFFFFF", 16).unwrap(), + FileTime::SIGNED_MAX + ); + assert_eq!( + FileTime::from_str_radix("ffffffffffffffff", 16).unwrap(), + FileTime::MAX + ); + assert_eq!( + FileTime::from_str_radix("FFFFFFFFFFFFFFFF", 16).unwrap(), + FileTime::MAX + ); + assert_eq!( + FileTime::from_str_radix("+ffffffffffffffff", 16).unwrap(), + FileTime::MAX + ); + assert_eq!( + FileTime::from_str_radix("+FFFFFFFFFFFFFFFF", 16).unwrap(), + FileTime::MAX + ); + assert_eq!( + FileTime::from_str_radix("0", 36).unwrap(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("+0", 36).unwrap(), + FileTime::NT_TIME_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("vuk7p84etc0", 36).unwrap(), + FileTime::UNIX_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("VUK7P84ETC0", 36).unwrap(), + FileTime::UNIX_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("+vuk7p84etc0", 36).unwrap(), + FileTime::UNIX_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("+VUK7P84ETC0", 36).unwrap(), + FileTime::UNIX_EPOCH + ); + assert_eq!( + FileTime::from_str_radix("1y2p0ij32e8e7", 36).unwrap(), + FileTime::SIGNED_MAX + ); + assert_eq!( + FileTime::from_str_radix("1Y2P0IJ32E8E7", 36).unwrap(), + FileTime::SIGNED_MAX + ); + assert_eq!( + FileTime::from_str_radix("+1y2p0ij32e8e7", 36).unwrap(), + FileTime::SIGNED_MAX + ); + assert_eq!( + FileTime::from_str_radix("+1Y2P0IJ32E8E7", 36).unwrap(), + FileTime::SIGNED_MAX + ); + assert_eq!( + FileTime::from_str_radix("3w5e11264sgsf", 36).unwrap(), + FileTime::MAX + ); + assert_eq!( + FileTime::from_str_radix("3W5E11264SGSF", 36).unwrap(), + FileTime::MAX + ); + assert_eq!( + FileTime::from_str_radix("+3w5e11264sgsf", 36).unwrap(), + FileTime::MAX + ); + assert_eq!( + FileTime::from_str_radix("+3W5E11264SGSF", 36).unwrap(), + FileTime::MAX + ); + } + + #[test] + fn from_str_radix_with_invalid_digit_radix() { + assert_eq!( + FileTime::from_str_radix("2", 2) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + assert_eq!( + FileTime::from_str_radix("8", 8) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + assert_eq!( + FileTime::from_str_radix("a", 10) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + assert_eq!( + FileTime::from_str_radix("A", 10) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + assert_eq!( + FileTime::from_str_radix("g", 16) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + assert_eq!( + FileTime::from_str_radix("G", 16) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + } + + #[test] + fn from_str_radix_when_empty() { + assert_eq!( + FileTime::from_str_radix("", 16) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::Empty + ); + } + + #[test] + fn from_str_radix_with_invalid_digit() { + assert_eq!( + FileTime::from_str_radix("Z", 16) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + assert_eq!( + FileTime::from_str_radix("_", 16) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + assert_eq!( + FileTime::from_str_radix("-1", 16) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + assert_eq!( + FileTime::from_str_radix("+", 16) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + assert_eq!( + FileTime::from_str_radix("-", 16) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + assert_eq!( + FileTime::from_str_radix(" 0", 16) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + assert_eq!( + FileTime::from_str_radix("0 ", 16) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + } + + #[test] + fn from_str_radix_when_positive_overflow() { + assert_eq!( + FileTime::from_str_radix( + "10000000000000000000000000000000000000000000000000000000000000000", + 2 + ) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::PosOverflow + ); + assert_eq!( + FileTime::from_str_radix("2000000000000000000000", 8) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::PosOverflow + ); + assert_eq!( + FileTime::from_str_radix("18446744073709551616", 10) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::PosOverflow + ); + assert_eq!( + FileTime::from_str_radix("10000000000000000", 16) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::PosOverflow + ); + assert_eq!( + FileTime::from_str_radix("3w5e11264sgsg", 36) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::PosOverflow + ); + assert_eq!( + FileTime::from_str_radix("3W5E11264SGSG", 36) + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::PosOverflow + ); + } + + #[test] + #[should_panic] + fn from_str_radix_when_radix_is_less_than_2() { + let _ = FileTime::from_str_radix("0", 1); + } + + #[test] + #[should_panic] + fn from_str_radix_when_radix_is_greater_than_36() { + let _ = FileTime::from_str_radix("0", 37); + } + + #[test] + const fn from_str_radix_is_const_fn() { + const _: Result = FileTime::from_str_radix("0", 2); + } + + #[test] + fn from_str() { + assert_eq!(FileTime::from_str("0").unwrap(), FileTime::NT_TIME_EPOCH); + assert_eq!(FileTime::from_str("+0").unwrap(), FileTime::NT_TIME_EPOCH); + assert_eq!( + FileTime::from_str("116444736000000000").unwrap(), + FileTime::UNIX_EPOCH + ); + assert_eq!( + FileTime::from_str("+116444736000000000").unwrap(), + FileTime::UNIX_EPOCH + ); + assert_eq!( + FileTime::from_str("9223372036854775807").unwrap(), + FileTime::SIGNED_MAX + ); + assert_eq!( + FileTime::from_str("+9223372036854775807").unwrap(), + FileTime::SIGNED_MAX + ); + assert_eq!( + FileTime::from_str("18446744073709551615").unwrap(), + FileTime::MAX + ); + assert_eq!( + FileTime::from_str("+18446744073709551615").unwrap(), + FileTime::MAX + ); + } + + #[cfg(feature = "std")] + #[test_strategy::proptest] + fn from_str_roundtrip(#[strategy(r"\+?[0-9]{1,19}")] s: std::string::String) { + use proptest::prop_assert_eq; + + let ft = s.parse().unwrap(); + prop_assert_eq!(FileTime::from_str(&s).unwrap(), FileTime::new(ft)); + } + + #[test] + fn from_str_when_empty() { + assert_eq!( + FileTime::from_str("") + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::Empty + ); + } + + #[test] + fn from_str_with_invalid_digit() { + assert_eq!( + FileTime::from_str("a") + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + assert_eq!( + FileTime::from_str("_") + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + assert_eq!( + FileTime::from_str("-1") + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + assert_eq!( + FileTime::from_str("+") + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + assert_eq!( + FileTime::from_str("-") + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + assert_eq!( + FileTime::from_str(" 0") + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + assert_eq!( + FileTime::from_str("0 ") + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::InvalidDigit + ); + } + + #[cfg(feature = "std")] + #[test_strategy::proptest] + fn from_str_with_invalid_digit_roundtrip( + #[strategy(r"-[0-9]+|[^0-9]+")] s: std::string::String, + ) { + use proptest::prop_assert; + + prop_assert!(FileTime::from_str(&s).is_err()); + } + + #[test] + fn from_str_when_positive_overflow() { + assert_eq!( + FileTime::from_str("18446744073709551616") + .unwrap_err() + .source() + .unwrap() + .downcast_ref::() + .unwrap() + .kind(), + &IntErrorKind::PosOverflow + ); + } +} diff --git a/src/file_time/unix_time.rs b/src/file_time/unix_time.rs index 0c9c8b6..3fce4d7 100644 --- a/src/file_time/unix_time.rs +++ b/src/file_time/unix_time.rs @@ -8,7 +8,7 @@ use core::time::Duration; -use super::{FileTime, FILE_TIMES_PER_SEC}; +use super::{FILE_TIMES_PER_SEC, FileTime}; use crate::error::{FileTimeRangeError, FileTimeRangeErrorKind}; impl FileTime { @@ -194,26 +194,23 @@ impl FileTime { /// # use nt_time::FileTime; /// # /// assert_eq!( - /// FileTime::from_unix_time(-11_644_473_600, 0).unwrap(), - /// FileTime::NT_TIME_EPOCH + /// FileTime::from_unix_time(-11_644_473_600, 0), + /// Ok(FileTime::NT_TIME_EPOCH) /// ); + /// assert_eq!(FileTime::from_unix_time(0, 0), Ok(FileTime::UNIX_EPOCH)); /// assert_eq!( - /// FileTime::from_unix_time(0, 0).unwrap(), - /// FileTime::UNIX_EPOCH + /// FileTime::from_unix_time(910_692_730_085, 477_580_700), + /// Ok(FileTime::SIGNED_MAX) /// ); /// assert_eq!( - /// FileTime::from_unix_time(910_692_730_085, 477_580_700).unwrap(), - /// FileTime::SIGNED_MAX - /// ); - /// assert_eq!( - /// FileTime::from_unix_time(1_833_029_933_770, 955_161_500).unwrap(), - /// FileTime::MAX + /// FileTime::from_unix_time(1_833_029_933_770, 955_161_500), + /// Ok(FileTime::MAX) /// ); /// /// // The number of nanoseconds is greater than 1 billion. /// assert_eq!( - /// FileTime::from_unix_time(0, 1_000_000_000).unwrap(), - /// FileTime::from_unix_time(1, 0).unwrap() + /// FileTime::from_unix_time(0, 1_000_000_000), + /// FileTime::from_unix_time(1, 0) /// ); /// /// // Before `1601-01-01 00:00:00 UTC`. @@ -246,20 +243,17 @@ impl FileTime { /// # use nt_time::FileTime; /// # /// assert_eq!( - /// FileTime::from_unix_time_secs(-11_644_473_600).unwrap(), - /// FileTime::NT_TIME_EPOCH + /// FileTime::from_unix_time_secs(-11_644_473_600), + /// Ok(FileTime::NT_TIME_EPOCH) /// ); + /// assert_eq!(FileTime::from_unix_time_secs(0), Ok(FileTime::UNIX_EPOCH)); /// assert_eq!( - /// FileTime::from_unix_time_secs(0).unwrap(), - /// FileTime::UNIX_EPOCH + /// FileTime::from_unix_time_secs(910_692_730_085), + /// Ok(FileTime::SIGNED_MAX - Duration::from_nanos(477_580_700)) /// ); /// assert_eq!( - /// FileTime::from_unix_time_secs(910_692_730_085).unwrap(), - /// FileTime::SIGNED_MAX - Duration::from_nanos(477_580_700) - /// ); - /// assert_eq!( - /// FileTime::from_unix_time_secs(1_833_029_933_770).unwrap(), - /// FileTime::MAX - Duration::from_nanos(955_161_500) + /// FileTime::from_unix_time_secs(1_833_029_933_770), + /// Ok(FileTime::MAX - Duration::from_nanos(955_161_500)) /// ); /// /// // Before `1601-01-01 00:00:00 UTC`. @@ -295,20 +289,17 @@ impl FileTime { /// # use nt_time::FileTime; /// # /// assert_eq!( - /// FileTime::from_unix_time_millis(-11_644_473_600_000).unwrap(), - /// FileTime::NT_TIME_EPOCH - /// ); - /// assert_eq!( - /// FileTime::from_unix_time_millis(0).unwrap(), - /// FileTime::UNIX_EPOCH + /// FileTime::from_unix_time_millis(-11_644_473_600_000), + /// Ok(FileTime::NT_TIME_EPOCH) /// ); + /// assert_eq!(FileTime::from_unix_time_millis(0), Ok(FileTime::UNIX_EPOCH)); /// assert_eq!( - /// FileTime::from_unix_time_millis(910_692_730_085_477).unwrap(), - /// FileTime::SIGNED_MAX - Duration::from_nanos(580_700) + /// FileTime::from_unix_time_millis(910_692_730_085_477), + /// Ok(FileTime::SIGNED_MAX - Duration::from_nanos(580_700)) /// ); /// assert_eq!( - /// FileTime::from_unix_time_millis(1_833_029_933_770_955).unwrap(), - /// FileTime::MAX - Duration::from_nanos(161_500) + /// FileTime::from_unix_time_millis(1_833_029_933_770_955), + /// Ok(FileTime::MAX - Duration::from_nanos(161_500)) /// ); /// /// // Before `1601-01-01 00:00:00 UTC`. @@ -338,20 +329,17 @@ impl FileTime { /// # use nt_time::FileTime; /// # /// assert_eq!( - /// FileTime::from_unix_time_micros(-11_644_473_600_000_000).unwrap(), - /// FileTime::NT_TIME_EPOCH - /// ); - /// assert_eq!( - /// FileTime::from_unix_time_micros(0).unwrap(), - /// FileTime::UNIX_EPOCH + /// FileTime::from_unix_time_micros(-11_644_473_600_000_000), + /// Ok(FileTime::NT_TIME_EPOCH) /// ); + /// assert_eq!(FileTime::from_unix_time_micros(0), Ok(FileTime::UNIX_EPOCH)); /// assert_eq!( - /// FileTime::from_unix_time_micros(910_692_730_085_477_580).unwrap(), - /// FileTime::SIGNED_MAX - Duration::from_nanos(700) + /// FileTime::from_unix_time_micros(910_692_730_085_477_580), + /// Ok(FileTime::SIGNED_MAX - Duration::from_nanos(700)) /// ); /// assert_eq!( - /// FileTime::from_unix_time_micros(1_833_029_933_770_955_161).unwrap(), - /// FileTime::MAX - Duration::from_nanos(500) + /// FileTime::from_unix_time_micros(1_833_029_933_770_955_161), + /// Ok(FileTime::MAX - Duration::from_nanos(500)) /// ); /// /// // Before `1601-01-01 00:00:00 UTC`. @@ -379,20 +367,17 @@ impl FileTime { /// # use nt_time::FileTime; /// # /// assert_eq!( - /// FileTime::from_unix_time_nanos(-11_644_473_600_000_000_000).unwrap(), - /// FileTime::NT_TIME_EPOCH - /// ); - /// assert_eq!( - /// FileTime::from_unix_time_nanos(0).unwrap(), - /// FileTime::UNIX_EPOCH + /// FileTime::from_unix_time_nanos(-11_644_473_600_000_000_000), + /// Ok(FileTime::NT_TIME_EPOCH) /// ); + /// assert_eq!(FileTime::from_unix_time_nanos(0), Ok(FileTime::UNIX_EPOCH)); /// assert_eq!( - /// FileTime::from_unix_time_nanos(910_692_730_085_477_580_700).unwrap(), - /// FileTime::SIGNED_MAX + /// FileTime::from_unix_time_nanos(910_692_730_085_477_580_700), + /// Ok(FileTime::SIGNED_MAX) /// ); /// assert_eq!( - /// FileTime::from_unix_time_nanos(1_833_029_933_770_955_161_500).unwrap(), - /// FileTime::MAX + /// FileTime::from_unix_time_nanos(1_833_029_933_770_955_161_500), + /// Ok(FileTime::MAX) /// ); /// /// // Before `1601-01-01 00:00:00 UTC`. diff --git a/src/lib.rs b/src/lib.rs index 3a2cb9f..9405809 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,12 +10,16 @@ //! timestamps such as [NTFS] and [7z]. Windows uses a file time to record when //! an application creates, accesses, or writes to a file. //! +//!
+//! //! Note that many environments, such as the [Win32 API], may limit the largest //! value of the file time to "+30828-09-14 02:48:05.477580700 UTC", which is //! equal to [`i64::MAX`], the largest value of a 64-bit signed integer type //! when represented as an underlying integer value. This is the largest file //! time accepted by the [`FileTimeToSystemTime`] function of the Win32 API. //! +//!
+//! //! # Examples //! //! ## Basic usage @@ -27,25 +31,22 @@ //! use core::time::Duration; //! //! use nt_time::{ -//! time::{macros::datetime, OffsetDateTime}, //! FileTime, +//! time::{OffsetDateTime, macros::datetime}, //! }; //! //! let ft = FileTime::NT_TIME_EPOCH; //! assert_eq!( -//! OffsetDateTime::try_from(ft).unwrap(), -//! datetime!(1601-01-01 00:00 UTC) +//! OffsetDateTime::try_from(ft), +//! Ok(datetime!(1601-01-01 00:00 UTC)) //! ); //! //! let ft = ft + Duration::from_secs(11_644_473_600); -//! assert_eq!( -//! OffsetDateTime::try_from(ft).unwrap(), -//! OffsetDateTime::UNIX_EPOCH -//! ); +//! assert_eq!(OffsetDateTime::try_from(ft), Ok(OffsetDateTime::UNIX_EPOCH)); //! assert_eq!(ft.to_raw(), 116_444_736_000_000_000); //! //! // The practical largest file time. -//! assert_eq!(FileTime::try_from(i64::MAX).unwrap(), FileTime::SIGNED_MAX); +//! assert_eq!(FileTime::try_from(i64::MAX), Ok(FileTime::SIGNED_MAX)); //! // The theoretical largest file time. //! assert_eq!(FileTime::new(u64::MAX), FileTime::MAX); //! ``` @@ -58,25 +59,22 @@ //! ``` //! use core::time::Duration; //! -//! use nt_time::{ -//! time::{OffsetDateTime, UtcOffset}, -//! FileTime, -//! }; +//! use nt_time::{FileTime, time::OffsetDateTime}; //! //! // `1970-01-01 00:00:00 UTC`. //! let ut = i64::default(); //! assert_eq!( -//! OffsetDateTime::from_unix_timestamp(ut).unwrap(), -//! OffsetDateTime::UNIX_EPOCH +//! OffsetDateTime::from_unix_timestamp(ut), +//! Ok(OffsetDateTime::UNIX_EPOCH) //! ); //! //! let ft = FileTime::from_unix_time_secs(ut).unwrap(); //! assert_eq!(ft, FileTime::UNIX_EPOCH); //! -//! // `1980-01-01 00:00:00 UTC`. +//! // From `1980-01-01 00:00:00 UTC` to `1980-01-01 00:00:00`. //! let ft = ft + Duration::from_secs(315_532_800); -//! let dos_dt = ft.to_dos_date_time(Some(UtcOffset::UTC)).unwrap(); -//! assert_eq!(dos_dt, (0x0021, u16::MIN, u8::MIN, Some(UtcOffset::UTC))); +//! let dos_dt = ft.to_dos_date_time(); +//! assert_eq!(dos_dt, Ok((0x0021, u16::MIN))); //! ``` //! //! ## Formatting and printing the file time @@ -87,14 +85,13 @@ //! [`time::OffsetDateTime`]. //! //! ``` -//! use nt_time::{time::OffsetDateTime, FileTime}; +//! use nt_time::{FileTime, time::OffsetDateTime}; //! //! let ft = FileTime::NT_TIME_EPOCH; //! assert_eq!(format!("{ft}"), "0"); -//! assert_eq!( -//! format!("{}", OffsetDateTime::try_from(ft).unwrap()), -//! "1601-01-01 0:00:00.0 +00:00:00" -//! ); +//! +//! let dt = OffsetDateTime::try_from(ft).unwrap(); +//! assert_eq!(format!("{dt}"), "1601-01-01 0:00:00.0 +00:00:00"); //! ``` //! //! [Windows file time]: https://docs.microsoft.com/en-us/windows/win32/sysinfo/file-times @@ -105,7 +102,7 @@ //! [Unix time]: https://en.wikipedia.org/wiki/Unix_time //! [MS-DOS date and time]: https://learn.microsoft.com/en-us/windows/win32/sysinfo/ms-dos-date-and-time -#![doc(html_root_url = "https://docs.rs/nt-time/0.10.6/")] +#![doc(html_root_url = "https://docs.rs/nt-time/0.12.1/")] #![no_std] #![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg))] // Lint levels of rustc. @@ -124,6 +121,8 @@ pub mod serde_with; #[cfg(feature = "chrono")] pub use chrono; +#[cfg(feature = "jiff")] +pub use jiff; #[cfg(feature = "rand")] pub use rand; #[cfg(feature = "serde")] diff --git a/src/serde_with/iso_8601.rs b/src/serde_with/iso_8601.rs index 63f0b5a..522401b 100644 --- a/src/serde_with/iso_8601.rs +++ b/src/serde_with/iso_8601.rs @@ -7,16 +7,20 @@ //! //! Use this module in combination with Serde's [`with`] attribute. //! +//!
+//! //! If the `large-dates` feature is not enabled, the largest date and time is //! "9999-12-31 23:59:59.999999999 UTC". //! +//!
+//! //! # Examples //! //! ``` //! use nt_time::{ +//! FileTime, //! serde::{Deserialize, Serialize}, //! serde_with::iso_8601, -//! FileTime, //! }; //! //! #[derive(Deserialize, Serialize)] @@ -40,7 +44,7 @@ pub mod option; -use serde::{de::Error as _, ser::Error as _, Deserializer, Serializer}; +use serde::{Deserializer, Serializer, de::Error as _, ser::Error as _}; use time::serde::iso8601; use crate::FileTime; @@ -70,7 +74,7 @@ pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result +//! //! If the `large-dates` feature is not enabled, the largest date and time is //! "9999-12-31 23:59:59.999999999 UTC". //! +//! +//! //! # Examples //! //! ``` //! use nt_time::{ +//! FileTime, //! serde::{Deserialize, Serialize}, //! serde_with::iso_8601, -//! FileTime, //! }; //! //! #[derive(Deserialize, Serialize)] @@ -45,8 +49,8 @@ //! [ISO 8601 format]: https://www.iso.org/iso-8601-date-and-time-format.html //! [`with`]: https://serde.rs/field-attrs.html#with -use serde::{de::Error as _, ser::Error as _, Deserializer, Serializer}; -use time::{serde::iso8601, OffsetDateTime}; +use serde::{Deserializer, Serializer, de::Error as _, ser::Error as _}; +use time::{OffsetDateTime, serde::iso8601}; use crate::FileTime; @@ -86,7 +90,7 @@ pub fn deserialize<'de, D: Deserializer<'de>>( #[cfg(test)] mod tests { use serde::{Deserialize, Serialize}; - use serde_test::{assert_de_tokens_error, assert_tokens, Token}; + use serde_test::{Token, assert_de_tokens_error, assert_tokens}; use super::*; diff --git a/src/serde_with/rfc_2822.rs b/src/serde_with/rfc_2822.rs index b40ad69..0b374ce 100644 --- a/src/serde_with/rfc_2822.rs +++ b/src/serde_with/rfc_2822.rs @@ -11,9 +11,9 @@ //! //! ``` //! use nt_time::{ +//! FileTime, //! serde::{Deserialize, Serialize}, //! serde_with::rfc_2822, -//! FileTime, //! }; //! //! #[derive(Deserialize, Serialize)] @@ -37,7 +37,7 @@ pub mod option; -use serde::{de::Error as _, ser::Error as _, Deserializer, Serializer}; +use serde::{Deserializer, Serializer, de::Error as _, ser::Error as _}; use time::serde::rfc2822; use crate::FileTime; @@ -67,7 +67,7 @@ pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result>( #[cfg(test)] mod tests { use serde::{Deserialize, Serialize}; - use serde_test::{assert_ser_tokens_error, assert_tokens, Token}; + use serde_test::{Token, assert_ser_tokens_error, assert_tokens}; use super::*; diff --git a/src/serde_with/rfc_3339.rs b/src/serde_with/rfc_3339.rs index 9ba5616..d4ff56d 100644 --- a/src/serde_with/rfc_3339.rs +++ b/src/serde_with/rfc_3339.rs @@ -11,9 +11,9 @@ //! //! ``` //! use nt_time::{ +//! FileTime, //! serde::{Deserialize, Serialize}, //! serde_with::rfc_3339, -//! FileTime, //! }; //! //! #[derive(Deserialize, Serialize)] @@ -37,7 +37,7 @@ pub mod option; -use serde::{de::Error as _, ser::Error as _, Deserializer, Serializer}; +use serde::{Deserializer, Serializer, de::Error as _, ser::Error as _}; use time::serde::rfc3339; use crate::FileTime; @@ -67,7 +67,7 @@ pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result>( #[cfg(test)] mod tests { use serde::{Deserialize, Serialize}; - use serde_test::{assert_de_tokens_error, assert_ser_tokens_error, assert_tokens, Token}; + use serde_test::{Token, assert_de_tokens_error, assert_ser_tokens_error, assert_tokens}; use super::*; diff --git a/src/serde_with/unix_time.rs b/src/serde_with/unix_time.rs index f1cffa8..f435a01 100644 --- a/src/serde_with/unix_time.rs +++ b/src/serde_with/unix_time.rs @@ -10,9 +10,9 @@ //! //! ``` //! use nt_time::{ +//! FileTime, //! serde::{Deserialize, Serialize}, //! serde_with::unix_time, -//! FileTime, //! }; //! //! #[derive(Deserialize, Serialize)] @@ -39,7 +39,7 @@ pub mod milliseconds; pub mod nanoseconds; pub mod option; -use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; use crate::FileTime; @@ -70,7 +70,7 @@ mod tests { use core::time::Duration; use serde_test::{ - assert_de_tokens, assert_de_tokens_error, assert_ser_tokens, assert_tokens, Token, + Token, assert_de_tokens, assert_de_tokens_error, assert_ser_tokens, assert_tokens, }; use super::*; diff --git a/src/serde_with/unix_time/microseconds.rs b/src/serde_with/unix_time/microseconds.rs index 8b2ec7f..8c174da 100644 --- a/src/serde_with/unix_time/microseconds.rs +++ b/src/serde_with/unix_time/microseconds.rs @@ -11,9 +11,9 @@ //! //! ``` //! use nt_time::{ +//! FileTime, //! serde::{Deserialize, Serialize}, //! serde_with::unix_time, -//! FileTime, //! }; //! //! #[derive(Deserialize, Serialize)] @@ -37,7 +37,7 @@ pub mod option; -use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; use crate::FileTime; @@ -68,7 +68,7 @@ mod tests { use core::time::Duration; use serde_test::{ - assert_de_tokens, assert_de_tokens_error, assert_ser_tokens, assert_tokens, Token, + Token, assert_de_tokens, assert_de_tokens_error, assert_ser_tokens, assert_tokens, }; use super::*; diff --git a/src/serde_with/unix_time/microseconds/option.rs b/src/serde_with/unix_time/microseconds/option.rs index ff86aa4..cd78242 100644 --- a/src/serde_with/unix_time/microseconds/option.rs +++ b/src/serde_with/unix_time/microseconds/option.rs @@ -11,9 +11,9 @@ //! //! ``` //! use nt_time::{ +//! FileTime, //! serde::{Deserialize, Serialize}, //! serde_with::unix_time, -//! FileTime, //! }; //! //! #[derive(Deserialize, Serialize)] @@ -42,7 +42,7 @@ //! [Unix time]: https://en.wikipedia.org/wiki/Unix_time //! [`with`]: https://serde.rs/field-attrs.html#with -use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; use crate::FileTime; @@ -78,7 +78,7 @@ mod tests { use core::time::Duration; use serde_test::{ - assert_de_tokens, assert_de_tokens_error, assert_ser_tokens, assert_tokens, Token, + Token, assert_de_tokens, assert_de_tokens_error, assert_ser_tokens, assert_tokens, }; use super::*; diff --git a/src/serde_with/unix_time/milliseconds.rs b/src/serde_with/unix_time/milliseconds.rs index 6d140bd..0552fbc 100644 --- a/src/serde_with/unix_time/milliseconds.rs +++ b/src/serde_with/unix_time/milliseconds.rs @@ -11,9 +11,9 @@ //! //! ``` //! use nt_time::{ +//! FileTime, //! serde::{Deserialize, Serialize}, //! serde_with::unix_time, -//! FileTime, //! }; //! //! #[derive(Deserialize, Serialize)] @@ -37,7 +37,7 @@ pub mod option; -use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; use crate::FileTime; @@ -68,7 +68,7 @@ mod tests { use core::time::Duration; use serde_test::{ - assert_de_tokens, assert_de_tokens_error, assert_ser_tokens, assert_tokens, Token, + Token, assert_de_tokens, assert_de_tokens_error, assert_ser_tokens, assert_tokens, }; use super::*; diff --git a/src/serde_with/unix_time/milliseconds/option.rs b/src/serde_with/unix_time/milliseconds/option.rs index 0b80559..3c98a6f 100644 --- a/src/serde_with/unix_time/milliseconds/option.rs +++ b/src/serde_with/unix_time/milliseconds/option.rs @@ -11,9 +11,9 @@ //! //! ``` //! use nt_time::{ +//! FileTime, //! serde::{Deserialize, Serialize}, //! serde_with::unix_time, -//! FileTime, //! }; //! //! #[derive(Deserialize, Serialize)] @@ -42,7 +42,7 @@ //! [Unix time]: https://en.wikipedia.org/wiki/Unix_time //! [`with`]: https://serde.rs/field-attrs.html#with -use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; use crate::FileTime; @@ -78,7 +78,7 @@ mod tests { use core::time::Duration; use serde_test::{ - assert_de_tokens, assert_de_tokens_error, assert_ser_tokens, assert_tokens, Token, + Token, assert_de_tokens, assert_de_tokens_error, assert_ser_tokens, assert_tokens, }; use super::*; diff --git a/src/serde_with/unix_time/nanoseconds.rs b/src/serde_with/unix_time/nanoseconds.rs index 872c1b4..232d11c 100644 --- a/src/serde_with/unix_time/nanoseconds.rs +++ b/src/serde_with/unix_time/nanoseconds.rs @@ -11,9 +11,9 @@ //! //! ``` //! use nt_time::{ +//! FileTime, //! serde::{Deserialize, Serialize}, //! serde_with::unix_time, -//! FileTime, //! }; //! //! #[derive(Deserialize, Serialize)] @@ -37,7 +37,7 @@ pub mod option; -use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; use crate::FileTime; diff --git a/src/serde_with/unix_time/nanoseconds/option.rs b/src/serde_with/unix_time/nanoseconds/option.rs index 41d73c2..56d116b 100644 --- a/src/serde_with/unix_time/nanoseconds/option.rs +++ b/src/serde_with/unix_time/nanoseconds/option.rs @@ -11,9 +11,9 @@ //! //! ``` //! use nt_time::{ +//! FileTime, //! serde::{Deserialize, Serialize}, //! serde_with::unix_time, -//! FileTime, //! }; //! //! #[derive(Deserialize, Serialize)] @@ -42,7 +42,7 @@ //! [Unix time]: https://en.wikipedia.org/wiki/Unix_time //! [`with`]: https://serde.rs/field-attrs.html#with -use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; use crate::FileTime; diff --git a/src/serde_with/unix_time/option.rs b/src/serde_with/unix_time/option.rs index 5f84a99..8c378ab 100644 --- a/src/serde_with/unix_time/option.rs +++ b/src/serde_with/unix_time/option.rs @@ -10,9 +10,9 @@ //! //! ``` //! use nt_time::{ +//! FileTime, //! serde::{Deserialize, Serialize}, //! serde_with::unix_time, -//! FileTime, //! }; //! //! #[derive(Deserialize, Serialize)] @@ -41,7 +41,7 @@ //! [Unix time]: https://en.wikipedia.org/wiki/Unix_time //! [`with`]: https://serde.rs/field-attrs.html#with -use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; use crate::FileTime; @@ -77,7 +77,7 @@ mod tests { use core::time::Duration; use serde_test::{ - assert_de_tokens, assert_de_tokens_error, assert_ser_tokens, assert_tokens, Token, + Token, assert_de_tokens, assert_de_tokens_error, assert_ser_tokens, assert_tokens, }; use super::*;