diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 000000000..87702c79e --- /dev/null +++ b/.clippy.toml @@ -0,0 +1,21 @@ +msrv = "1.67" + +missing-docs-in-crate-items = true +disallowed-macros = [ + "std::column", + "std::env", + "std::file", + "std::include_bytes", + "std::include_str", + "std::include", + "std::line", + "std::module_path", + "std::option_env", +] +disallowed-names = ["alpha", "beta", "gamma", "delta"] +matches-for-let-else = "AllTypes" +enforced-import-renames = [ + { path = "std::time::Duration", rename = "StdDuration" }, + { path = "std::time::Instant", rename = "StdInstant" }, +] +semicolon-outside-block-ignore-multiline = true diff --git a/.deny.toml b/.deny.toml new file mode 100644 index 000000000..fd6be8eb1 --- /dev/null +++ b/.deny.toml @@ -0,0 +1,6 @@ +[licenses] +allow = [ + "MIT", + "Apache-2.0", + "Unicode-DFS-2016", +] diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..74e8d6fb9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.yaml] +indent_size = 2 diff --git a/.github/codecov.yaml b/.github/codecov.yaml new file mode 100644 index 000000000..045fa4bd1 --- /dev/null +++ b/.github/codecov.yaml @@ -0,0 +1,13 @@ +codecov: + max_report_age: 1 + require_ci_to_pass: false + notify: + wait_for_ci: false + +coverage: + precision: 1 + round: "nearest" + status: + project: + default: + informational: true diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 000000000..dca25cc65 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,341 @@ +name: Build + +concurrency: + group: ${{ github.workflow }}-${{ github.ref_name }} + cancel-in-progress: true + +env: + TYPE_CHECK_TARGETS: '{ + "no_std": [ + "thumbv7em-none-eabihf" + ], + "std_no_offset": [ + "x86_64-unknown-netbsd", + "x86_64-unknown-illumos", + "wasm32-wasi" + ], + "std_with_offset": [ + "x86_64-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-pc-windows-gnu" + ] + }' + +defaults: + run: + shell: bash + +on: + push: + branches: ["**"] + tags-ignore: ["**"] + paths-ignore: + - "**.md" + - LICENSE-Apache + - LICENSE-MIT + pull_request: + paths-ignore: + - "**.md" + - "**/LICENSE-Apache" + - "**/LICENSE-MIT" + - .github/FUNDING.yml + - .editorconfig + - .gitignore + - logo.svg + +jobs: + check-targets: + name: Type checking (${{ matrix.rust.name }}, ${{ matrix.kind.name }}) + runs-on: ubuntu-latest + if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork) || github.event_name == 'push' + strategy: + matrix: + rust: + - { version: "1.67.0", name: MSRV } + - { version: stable, name: stable } + kind: + - name: no_std + query: .no_std + .std_no_offset + .std_with_offset + exclude-features: + - std + - formatting + - serde-human-readable + - serde-well-known + - local-offset + - quickcheck + group-features: [] + - name: std_no_offset + query: .std_no_offset + .std_with_offset + exclude-features: [local-offset] + enable-features: [std] + group-features: + - [formatting, parsing] + - [serde-human-readable, serde-well-known] + - name: std_with_offset + query: .std_with_offset + enable-features: [std, local-offset] + group-features: + - [formatting, parsing] + - [serde-human-readable, serde-well-known] + + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Cache cargo output + uses: Swatinem/rust-cache@v2 + + - name: Generate target list + run: | + echo $TYPE_CHECK_TARGETS \ + | jq -r '${{ matrix.kind.query }} | join(",") | "TARGETS=" + .' >> $GITHUB_ENV + + - name: Install toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust.version }} + targets: ${{ env.TARGETS }} + + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack + + - name: Check feature powerset + env: + GROUP_FEATURES: ${{ toJSON(matrix.kind.group-features) }} + run: | + echo $GROUP_FEATURES \ + | jq -r '[.[] | join(",")] | map("--group-features " + .) | join(" ")' \ + | xargs -d" " \ + | ( \ + echo $TYPE_CHECK_TARGETS \ + | jq -r '${{ matrix.kind.query }} | map("--target " + .) | join(" ")' \ + | xargs -d" " \ + cargo hack check \ + -p time \ + --no-dev-deps \ + --feature-powerset \ + --optional-deps \ + --group-features serde,rand \ + --exclude-features default,wasm-bindgen,${{ join(matrix.kind.exclude-features) }} \ + --features macros,${{ join(matrix.kind.enable-features) }} \ + --exclude-all-features \ + ) + + check-benchmarks: + name: Type-check benchmarks + runs-on: ubuntu-latest + if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork) || github.event_name == 'push' + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Cache cargo output + uses: Swatinem/rust-cache@v2 + + - name: Install toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Type-check benchmarks + run: cargo check -p time --benches --all-features + env: + RUSTFLAGS: --cfg bench + + test: + name: Test (${{ matrix.os.name }}, ${{ matrix.rust.name }}) + runs-on: ${{ matrix.os.value }} + if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork) || github.event_name == 'push' + strategy: + matrix: + rust: + - { version: "1.67.0", name: MSRV } + - { version: stable, name: stable } + os: + - { name: Ubuntu, value: ubuntu-latest } + - { name: Windows, value: windows-latest } + - { name: MacOS, value: macOS-latest } + + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Cache cargo output + uses: Swatinem/rust-cache@v2 + + - name: Install toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust.version }} + + - name: Test + run: cargo test -p time --all-features + + miri: + name: Test (miri) + runs-on: ubuntu-latest + if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork) || github.event_name == 'push' + + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Cache cargo output + uses: Swatinem/rust-cache@v2 + + - name: Install toolchain + uses: dtolnay/rust-toolchain@nightly + with: + components: miri + + - name: Test + run: cargo miri test -p time --all-features + env: + MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-symbolic-alignment-check + QUICKCHECK_MAX_TESTS: 100 + + cross-build: + name: Cross-build + runs-on: ubuntu-latest + if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork) || github.event_name == 'push' + + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Cache cargo output + uses: Swatinem/rust-cache@v2 + + - name: Install toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: x86_64-pc-windows-gnu + + - name: Install dependencies + run: sudo apt install gcc-mingw-w64 + + # We're testing the linking, so running `cargo check` is insufficient. + - name: Cross-build tests + run: cargo build -p time --tests --all-features --target x86_64-pc-windows-gnu + + fmt: + name: Formatting + runs-on: ubuntu-latest + if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork) || github.event_name == 'push' + + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Install toolchain + uses: dtolnay/rust-toolchain@nightly + with: + components: rustfmt + + - name: Check formatting + run: cargo fmt --all -- --check + env: + RUSTFLAGS: --cfg bench + + clippy: + name: Clippy + runs-on: ubuntu-latest + if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork) || github.event_name == 'push' + + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Cache cargo output + uses: Swatinem/rust-cache@v2 + + - name: Install toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: clippy + targets: x86_64-unknown-linux-gnu,aarch64-apple-darwin,x86_64-pc-windows-gnu,x86_64-unknown-netbsd,x86_64-unknown-illumos,wasm32-wasi + + - name: Run clippy + run: | + cargo clippy \ + --all-features \ + --all-targets \ + --target x86_64-unknown-linux-gnu \ + --target aarch64-apple-darwin \ + --target x86_64-pc-windows-gnu \ + --target x86_64-unknown-netbsd \ + --target x86_64-unknown-illumos \ + --target wasm32-wasi + env: + RUSTFLAGS: --cfg bench + + documentation: + name: Documentation + runs-on: ubuntu-latest + if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork) || github.event_name == 'push' + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Cache cargo output + uses: Swatinem/rust-cache@v2 + + - name: Install toolchain + uses: dtolnay/rust-toolchain@nightly + + - name: Generate documentation + run: cargo doc --workspace --all-features --no-deps + env: + RUSTDOCFLAGS: --cfg docsrs + + publish-documentation: + name: Publish docs + needs: + - documentation + - check-targets + - check-benchmarks + - test + - cross-build + - fmt + - clippy + if: github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.master_branch) + uses: time-rs/time-rs.github.io/.github/workflows/trigger-deploy.yaml@main + secrets: inherit + permissions: + actions: write + + coverage: + name: Coverage + runs-on: ubuntu-latest + permissions: + issues: write + if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork) || github.event_name == 'push' + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Cache cargo output + uses: Swatinem/rust-cache@v2 + + - name: Install toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: Generate coverage report + run: | + cargo llvm-cov clean --workspace + cargo llvm-cov test -p time --no-report --all-features -- --test-threads=1 + cargo llvm-cov report --lcov > lcov.txt + env: + RUSTFLAGS: --cfg __ui_tests + + - name: Upload coverage report + uses: codecov/codecov-action@v4 + with: + files: ./lcov.txt diff --git a/.github/workflows/dependency-audit.yaml b/.github/workflows/dependency-audit.yaml new file mode 100644 index 000000000..52590928d --- /dev/null +++ b/.github/workflows/dependency-audit.yaml @@ -0,0 +1,17 @@ +name: Dependency audit + +on: + schedule: + - cron: "0 0 * * 1" # midnight on Monday + workflow_dispatch: + +jobs: + security-audit: + name: Dependency audit + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Audit dependencies + uses: EmbarkStudios/cargo-deny-action@v1 diff --git a/.github/workflows/github-release.yaml b/.github/workflows/github-release.yaml new file mode 100644 index 000000000..de83de5d2 --- /dev/null +++ b/.github/workflows/github-release.yaml @@ -0,0 +1,24 @@ +name: GitHub release + +on: + workflow_call: + inputs: + prerelease: + required: false + type: boolean + +jobs: + release: + name: Create release + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Create release + uses: ncipollo/release-action@v1 + with: + body: See the [changelog](https://github.com/time-rs/time/blob/main/CHANGELOG.md) for details. + prerelease: ${{ inputs.prerelease }} diff --git a/.github/workflows/powerset.yaml b/.github/workflows/powerset.yaml new file mode 100644 index 000000000..479c036bf --- /dev/null +++ b/.github/workflows/powerset.yaml @@ -0,0 +1,111 @@ +name: "Check powerset" + +env: + RUSTFLAGS: -Dwarnings + TARGETS: '{ + "no_std": [ + "thumbv7em-none-eabihf" + ], + "std_no_offset": [ + ], + "std_with_offset": [ + "aarch64-unknown-linux-gnu", + "i686-pc-windows-gnu", + "i686-pc-windows-msvc", + "i686-unknown-linux-gnu", + "x86_64-apple-darwin", + "x86_64-pc-windows-gnu", + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "aarch64-linux-android", + "wasm32-wasi", + "x86_64-linux-android", + "x86_64-unknown-netbsd", + "x86_64-unknown-illumos" + ] + }' + +concurrency: + group: powerset-${{ github.head_ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +on: + push: + tags: + - "v[0-9]+.[0-9]+.[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]+-alpha.[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+" + - "v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+" + workflow_dispatch: + +jobs: + check: + name: Type checking (${{ matrix.kind.name }}, ${{ matrix.rust.name }}) + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - { version: "1.67.0", name: MSRV } + - { version: stable, name: stable } + kind: + - name: no_std + query: .no_std + .std_no_offset + .std_with_offset + exclude_features: + - std + - local-offset + - quickcheck + - formatting + - serde-human-readable + - serde-well-known + - name: std_no_offset + query: .std_no_offset + .std_with_offset + exclude_features: [local-offset] + enable_features: [std] + - name: std_with_offset + query: .std_with_offset + enable_features: [std, local-offset] + + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Generate target list + run: | + echo $TARGETS \ + | jq -r '${{ matrix.kind.query }} | join(",") | "TARGET_LIST=" + .' >> $GITHUB_ENV + + - name: Install toolchain + uses: dtolnay/rust-toolchain@master + with: + targets: ${{ env.TARGET_LIST }} + toolchain: ${{ matrix.rust.version}} + + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack + + - name: Check feature powerset + run: | + echo $TARGETS \ + | jq -r '${{ matrix.kind.query }} | map("--target " + .) | join(" ")' \ + | xargs -d" " \ + cargo hack check \ + -p time \ + --no-dev-deps \ + --feature-powerset \ + --optional-deps \ + --exclude-features default,wasm-bindgen,${{ join(matrix.kind.exclude_features) }} ${{ + matrix.kind.enable_features && format('--features {0}', join(matrix.kind.enable_features)) }} + + release: + name: Create release + if: startsWith(github.ref, 'refs/tags') && github.run_attempt == 1 + needs: check + uses: ./.github/workflows/github-release.yaml + with: + prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc') }} + permissions: + contents: write diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.mailmap b/.mailmap new file mode 100644 index 000000000..691d5e48a --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +Jacob Pratt diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..76390cc41 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1186 @@ +# Changelog + +All notable changes to the time project will be documented in this file. + +The format is based on [Keep a Changelog]. This project adheres to [Semantic Versioning]. + +--- + +## 0.3.36 [2024-04-10] + +### # Fixed + +- `FormatItem` can be used as part of an import path. See [#675] for details. + +[#675]: https://github.com/time-rs/time/issues/675 + +## 0.3.35 [2024-04-10] + +### Added + +- `Duration::checked_neg` +- `ext::InstantExt`, which provides methods for using `time::Duration` with `std::time::Instant` + +### Changed + +- `Instant` is deprecated. It is recommended to use `std::time::Instant` directly, importing + `time::ext::InstantExt` for interoperability with `time::Duration`. +- `FormatItem` has been renamed to `BorrowedFormatItem`, avoiding confusion with `OwnedFormatItem`. + An alias has been added for backwards compatibility. + +### Fixed + +- The weekday is optional when parsing RFC2822. +- The range of sub-second values in `Duration` is documented correctly. The previous documentation + contained an off-by-one error. +- Leap seconds are now correctly handled when parsing ISO 8601. + +## 0.3.34 [2024-02-03] + +### Fixed + +Computing the local offset on Windows works again. It was broken in some cases in v0.3.32 and +v0.3.33. + +## 0.3.33 [2024-02-03] + +### Fixed + +Builds targeting `wasm32-unknown-unknown` now work again. + +## 0.3.32 [2024-02-01] + +### Added + +- Methods to replace the day of the year. + - `Date::replace_ordinal` + - `PrimitiveDateTime::replace_ordinal` + - `OffsetDateTime::replace_ordinal` +- Modules to treat an `OffsetDateTime` as a Unix timestamp with subsecond precision for serde. + - `time::serde::timestamp::milliseconds` + - `time::serde::timestamp::microseconds` + - `time::serde::timestamp::nanoseconds` + +### Changed + +- `Duration::time_fn` is deprecated. + +## 0.3.31 [2023-12-19] + +### Added + +- `OffsetDateTime::new_in_offset` +- `OffsetDateTime::new_utc` + +### Changed + +- The valid range of `UtcOffset` has been expanded from ±23:59:59 to ±25:59:59. This is to support + the full POSIX range while permitting infallible negation. + +## 0.3.30 [2023-10-13] + +### Added + +- `powerfmt::smart_display::SmartDisplay` has been added for the main types in the library. These + implementations ensure that values follow the requested fill, width, and alignment when using + `format!` or similar macros. `Display` is implemented in terms of `SmartDisplay`. + +### Fixed + +- Large values no longer wrap around in release mode when using `NumericalDuration` or + `NumericalStdDuration`. + +## 0.3.29 [2023-09-24] + +### Added + +- Niche value optimization for `Date` has been added. Both `Date` and `Option` are four bytes. +- Unit conversions have been added. It is now possible to write `Second::per(Day)`, which returns + the number of seconds in one day. See the types in the [`time::convert` module] for more + information. + + [`time::convert` module]: https://time-rs.github.io/api/time/convert/index.html + +### Changed + +- The diagnostic for `--cfg unsound_local_offset` has been removed. +- `#![feature(no_coverage)]` was previously used internally for code coverage. It is no longer used, + so it has been removed. +- The default value for `modifier::OffsetHour` has been changed. This was unintentionally changed in + v0.3.17 and went unnoticed until now. The sign is now only present if needed by default, as was + the case previously. This does not affect any situation where `format_description!` or + `format_description::parse` is used. + +### Fixed + +- Adding or subtracting a `std::time::Duration` to/from an `OffsetDateTime` will not result in + integer overflow internally. It will still panic if the result is out of range. + +## 0.3.28 [2023-08-27] + +### Added + +- More additional constants for the well-known `Iso8601` format description have been added. This + avoids the need to manually configure the format. +- An `[end]` component has been added. This is ignored during formatting, but is used to indicate + the end of input when parsing. If any input remains after this component, an error is returned. + This is useful when using the `[first]` component, as it avoids the need to reorder variants. + +### Changed + +- The exemption for MacOS introduced in 0.3.20 has been removed. This is because some supported + versions of MacOS do not meet the requirements for the exemption. +- The `UnexpectedTrailingCharacters` error variant has been moved to `ParseFromDescription`. All + previously-existing locations of this variant have been deprecated and will no longer be returned. + +## 0.3.27 [2023-08-22] + +This sets the `serde` dependency requirement to `>= 1.0.184` where the binaries have been removed. + +## 0.3.26 [2023-08-18] + +This release contains only a single change. `serde` is required to be a version prior to 1.0.171. +This is due to the decision by the maintainer of `serde` to include pre-built binaries that are +executed without the end user's knowledge. As of the time of publishing, the included binary has not +even been reproduced. This is a security risk, and the `time` project strongly opposes this +decision. While this may break some users' builds due to conflicting versions, it is a necessary +step to ensure the security. + +## 0.3.25 [2023-08-02] + +### Fixed + +- Methods such as `Time::replace_milliseconds` would panic on some out-of-range values. This has + been fixed. + +## 0.3.24 [2023-07-30] + +### Added + +- The `subsecond` component is taken into account when parsing the `unix_timestamp` component. If + data is conflicting, the `subsecond` value takes precedence. +- Parsing a `Time` with only the `hour` component is now supported. The `minute` and `second`, and + `subsecond` components are assumed to be zero. + +### Changed + +- The minimum supported Rust version is now 1.67.0. +- The debug output for `Parsed` has been improved. +- When parsing, invalid values are now rejected sooner. Previously, the entire input would be parsed + before being rejected in the final step. Now, invalid values are rejected as soon as they are + encountered. This affects the error variant returned, which may cause minor breakage for any code + (incorrectly) relying on the exact error variant. +- When parsing a `Time`, an error is returned if components are present but not consecutive. For + example, if `hours` and `seconds` are present, `minutes` will not be assumed to be zero. + +### Fixed + +- The implementation of `Duration::checked_div` could return a slightly incorrect result in some + cases. This has been fixed. + +## 0.3.23 [2023-07-08] + +### Added + +- `Date::next_occurrence` +- `Date::prev_occurrence` +- `Date::nth_next_occurrence` +- `Date::nth_prev_occurrence` +- `Weekday::nth_prev` +- `Month::nth_next` +- `Month::nth_prev` + +### Changed + +**The minimum supported Rust version policy has been updated.** See [the README][msrv-policy] for +details. + +[msrv-policy]: https://github.com/time-rs/time#minimum-rust-version-policy + +### Fixed + +- `Duration::abs` correctly returns `Duration::MAX` when near the minimum value. The nanoseconds + value was previously incorrect. +- Compliance with ISO 8601 has been improved. Previously, a UTC offset would be incorrectly rejected + in some cases. + +## 0.3.22 [2023-06-07] + +### Added + +- `OffsetDateTime::checked_to_offset` + +## 0.3.21 [2023-05-05] + +### Added + +- Any formattable/parsable type can now be used with the `time::serde::format_description!` macro. +- `Weekday::nth_next` + +### Changed + +- The minimum supported Rust version is now 1.65.0. + +## 0.3.20 [2023-02-24] + +### Changed + +- The minimum supported Rust version is now 1.63.0. +- On Unix-based operating systems with known thread-safe environments, functions obtaining the local + offset no longer require a check that the program is single-threaded. This currently includes + MacOS, illumos, and NetBSD. + +### Added + +- `[ignore]` component in format descriptions. A `count` modifier is required, indicating the number + of bytes to ignore when parsing. +- `[unix_timestamp]` component in format descriptions. This is currently only usable with + `OffsetDateTime`. Users can choose between seconds, milliseconds, microseconds, and nanoseconds, + and whether the sign is mandatory or optional. + +### Fixed + +- The API for declaring soundness now uses stricter atomic orderings internally. + +## 0.3.19 [2023-02-16] + +### Fixed + +This includes the update to the `format_description!` macro, which was supposed to be included in +0.3.18. + +## 0.3.18 [2023-02-16] + +### Changed + +- The minimum supported Rust version is now 1.62.0. + +### Added + +- `[first]` and `[optional]` items can now be included in format descriptions. To parse this at + runtime, you must use the `format_description::parse_owned` method. +- `format_description::parse_borrowed` +- An API has been added to opt out of soundness checks for obtaining the local offset. This replaces + the previous, officially unsupported `RUSTFLAGS="--cfg unsound_local_offset"`. End users may call + `time::util::local_offset::set_soundness(time::util::local_offset::Soundness::Unsound)`. This + method is `unsafe` because it enables undefined behavior if its safety requirements are not + upheld. Note that libraries **must not** set this to `Unsound`, as it is impossible for a library + to guarantee end users uphold the required invariants. + +### Fixed + +- Correctly parse offset sign when hour is zero. The parse was previously unconditionally positive, + even if the sign indicated otherwise. +- Compilation is fixed for WebAssembly. + +## 0.3.17 [2022-11-06] + +### Changed + +- The amount of code generated by `time::serde::format_description!` is reduced if not all feature + flags are active. +- `cargo test --tests` works with any configuration of feature flags. This occurs by spawning a + subprocess that passes `--all-features`. `cargo test --doc` works with most combinations of + feature flags, including the default. The combination of these changes means that crater will now + run on `time`. +- `libc` and `num_threads` are only included as dependencies when needed. They were previously + unconditionally included. + +### Added + +- `time::format_description::parse_owned`, which returns an `OwnedFormatItem`. This avoids "lifetime + hell", where all your structs now need a lifetime because a single field has one. Note that when + possible, the borrowed format item (just called `FormatItem`) is still preferred, as it has + significantly fewer allocations. The new `OwnedFormatItem` is usable for both formatting and + parsing, as you would expect. + +### Compatibility + +- The parser for runtime format descriptions has been rewritten. A side effect of this is that some + errors have slightly changed. No existing API has been altered, so this is not a breaking change. + However, you may notice different errors, which are hopefully better! The parser for compile-time + format descriptions has not yet been swapped out. If you notice any bugs, please file an issue. + +## 0.3.16 [2022-10-24] + +### Changed + +- The minimum supported Rust version is now 1.60.0. +- The `serde-well-known` feature flag is deprecated. The necessary features for an item to be + enabled are indicated in documentation. +- Feature gates have been loosened for well-known formats. + +### Added + +- `const`s can now be provided as the format description for `time::serde::format_description!`. The + `const` must be of type `&[FormatItem<'_>]`, which is what is returned by the + `time::macros::format_description!` macro. + + ```rust + const TIME_FORMAT_ALT: &[FormatItem<'_>] = time::macros::format_description!("[hour]:[minute]"); + time::serde::format_description!(time_format_alt, Time, TIME_FORMAT_ALT); + ``` + +### Compatibility + +- Some feature flags have been removed. None of these have ever been documented as flags, so any use + was unsupported. These flags are: + - `js-sys` + - `quickcheck-dep` + - `itoa` + - `time-macros` + +## 0.3.15 [2022-10-03] + +### Changed + +- Better gating for `tm_gmtoff` extension. This should eliminate build failures on some untested + platforms. +- `Debug` output for types are now human-readable. While this should not be relied upon, it is + currently the same as the output with `Display`. +- Eliminate overflows in the constructors for `Duration. When there is an overflow, the methods now + panic. This was previously only the case in debug mode. +- Panic if `NaN` is passed to `Duration::from_secs_f32` or `Duration::from_secs_f64`. + +### Fixed + +- Fix error when deserializing data types from bytes. This affects formats such as JSON. +- Eliminate a panic in an edge case when converting `OffsetDateTime` to another `UtcOffset`. This + occurred due to an old assumption in code that was no longer the case. + +## 0.3.14 [2022-08-24] + +### Changed + +- The minimum supported Rust version is now 1.59.0. +- `Duration::unsigned_abs` is now `const fn`. +- The const parameter for `time::format_description::well_known::Iso8601` now has a default. This + means `Iso8601` is the same as `Iso8601::DEFAULT`. +- The `Parsed` struct has been reduced in size from 56 to 32 bytes (a 43% reduction). + +## 0.3.13 [2022-08-09] + +### Fixed + +- wasm builds other than those using `wasm-bindgen` work again. + +## 0.3.12 [2022-08-01] + +### Added + +- `js-sys` now supports obtaining the system's local UTC offset. + +### Changed + +- Performance of many `Date` operations has improved when using the `large-dates` feature. +- While an internal change, `OffsetDateTime` now stores the value in the attached `UtcOffset`, not + UTC. This leads to significant performance gains on nearly all `OffsetDateTime` methods. + +### Fixed + +- Subtracting two `Time`s can no longer panic. This previously occurred in some situations where the + result was invalid. +- ISO 8601 parsing rounds the subseconds to avoid incorrectly truncating the value. + +## 0.3.11 [2022-06-21] + +### Fixed + +- [#479]: regression when parsing optional values with `serde` +- [#481]: `Time` subtracted from `Time` can panic. This was caused by a bug that has always existed, + in that an internal invariant was not upheld. Memory safety was not violated. + +[#479]: https://github.com/time-rs/time/issues/479 +[#481]: https://github.com/time-rs/time/issues/481 + +## 0.3.10 [2022-06-19] + +### Added + +- Serde support for non-self-describing formats +- `Duration::unsigned_abs`, which returns a `std::time::Duration` +- ISO 8601 well-known format +- `Duration` can now be formatted with a `.N` specifier, providing a shorter representation when + using `Display`. +- Parse `null` as `None` on serde structs + +### Fixed + +- Fix incorrect parsing of UTC offset in `Rfc3339`. + +### Changed + +- The minimum supported Rust version is now 1.57.0. +- Performance for `Rfc2822` has been improved. +- Debug assertions have been added in a few places. This should have no user facing impact, as it + only serves to catch bugs and is disabled in release mode. + +## 0.3.9 [2022-03-22] + +### Added + +- `time::serde::format_description!` + - This macro is similar to `time::format_description!`, but it generates a module that can be used + in `#[serde(with = "foo")]`. This makes it far easier to serialize/deserialize a custom format. +- `Date::replace_year` +- `Date::replace_month` +- `Date::replace_day` +- `Time::replace_hour` +- `Time::replace_minute` +- `Time::replace_second` +- `Time::replace_millisecond` +- `Time::replace_microsecond` +- `Time::replace_nanosecond` +- `PrimitiveDateTime::replace_year` +- `PrimitiveDateTime::replace_month` +- `PrimitiveDateTime::replace_day` +- `PrimitiveDateTime::replace_hour` +- `PrimitiveDateTime::replace_minute` +- `PrimitiveDateTime::replace_second` +- `PrimitiveDateTime::replace_millisecond` +- `PrimitiveDateTime::replace_microsecond` +- `PrimitiveDateTime::replace_nanosecond` +- `OffsetDateTime::replace_year` +- `OffsetDateTime::replace_month` +- `OffsetDateTime::replace_day` +- `OffsetDateTime::replace_hour` +- `OffsetDateTime::replace_minute` +- `OffsetDateTime::replace_second` +- `OffsetDateTime::replace_millisecond` +- `OffsetDateTime::replace_microsecond` +- `OffsetDateTime::replace_nanosecond` +- `Parsed::offset_minute_signed` +- `Parsed::offset_second_signed` +- `Parsed::set_offset_minute_signed` +- `Parsed::set_offset_second_signed` +- `Parsed::with_offset_minute_signed` +- `Parsed::with_offset_second_signed` +- `error::InvalidVariant` +- `impl FromStr` for `Weekday` +- `impl FromStr` for `Month` +- `impl Display for Duration` + +### Deprecated + +The following methods have been deprecated in favor of the new, signed equivalent methods. The +pre-existing methods + +- `Parsed::offset_minute` +- `Parsed::offset_second` +- `Parsed::set_offset_minute` +- `Parsed::set_offset_second` +- `Parsed::with_offset_minute` +- `Parsed::with_offset_second` + +### Changed + +- Well-known formats that support leap seconds now return the `TryFromParsed::ComponentRange` error + variant if the leap second could not occur at that given moment. + +## 0.3.8 [2022-02-22] [YANKED] + +This release is broken and has been yanked. + +## 0.3.7 [2022-01-26] + +### Fixed + +Solaris and Illumos build again. + +## 0.3.6 [2022-01-20] + +### Added + +- `Date::saturating_add` +- `Date::saturating_sub` +- `PrimitiveDateTime::saturating_add` +- `PrimitiveDateTime::saturating_sub` +- `OffsetDateTime::saturating_add` +- `OffsetDateTime::saturating_sub` +- `PrimitiveDatetime::MIN` +- `PrimitiveDatetime::MAX` +- `Rfc2822` format description +- Serde support for well-known formats + - This is placed behind the new `serde-well-known` feature flag. + +### Changed + +- MacOS and FreeBSD are supported obtaining the local offset when single-threaded. + - Starting with this version, this is delegated to the `num_threads` crate. +- Leap seconds are now parsed as the final nanosecond of the preceding second. +- The minimum supported Rust version is now 1.53.0. +- Deserializers for human readable formats will fall back to the binary format if the human readable + format fails to deserialize. + +### Fixed + +- Deserialization will no longer fail when given a non-borrowed string. + +## 0.3.5 [2021-11-12] + +### Added + +- `Date::checked_add` +- `Date::checked_sub` +- `PrimitiveDateTime::checked_add` +- `PrimitiveDateTime::checked_sub` +- `OffsetDateTime::checked_add` +- `OffsetDateTime::checked_sub` + +### Changed + +- Attempts to obtain the local UTC offset will now succeed on Linux if the process is + single-threaded. This does not affect other Unix platforms. As a reminder, the relevant methods + are fallible and may return an `Err` value for any reason. + +## 0.3.4 [2021-10-26] + +### Added + +- `error::DifferentVariant` and `Error::DifferentVariant` +- `impl From for FormatItem<'_>` +- `impl TryFrom> for Component` +- `impl<'a> From<&'a [FormatItem<'_>]> for FormatItem<'a>` +- `impl<'a> TryFrom> for &[FormatItem<'a>]` +- `impl PartialEq for FormatItem<'_>` +- `impl PartialEq> for Component` +- `impl PartialEq<&[FormatItem<'_>]> for FormatItem<'_>` +- `impl PartialEq> for &[FormatItem<'_>]` +- `impl TryFrom for error::TryFromParsed` +- `impl TryFrom for error::Parse` +- `impl TryFrom for error::ParseFromDescription` +- `impl TryFrom for error::InvalidFormatDescription` +- `impl TryFrom for error::IndeterminateOffset` +- `impl TryFrom for error::Format` +- `impl TryFrom for error::ConversionRange` +- `impl TryFrom for error::ComponentRange` +- `impl TryFrom for error::ComponentRange` +- `impl TryFrom for error::TryFromParsed` +- `impl TryFrom for error::ParseFromDescription` +- `impl TryFrom for std::io::Error` +- `impl Sum for Duration` +- `impl Sum<&Duration> for Duration` +- A `const fn default()` has been added to all modifiers that are `struct`s. These methods exist to + permit construction in `const` contexts and may be removed (without being considered a breaking + change) once `impl const Default` is stabilized. +- `FormatItem::Optional`, which will consume the contained value if present but still succeed + otherwise. +- `FormatItem::First`, which will consume the first successful parse, ignoring any prior errors. + +### Fixed + +- Cross-building to Windows now succeeds. +- A parse error on a `UtcOffset` component now indicates the error comes from the offset. +- Some arithmetic no longer panics in edge cases. + +## 0.3.3 [2021-09-25] + +### Added + +- `Parsed::parse_item` +- `Parsed::parse_items` +- `Parsed::parse_literal` +- Builder methods for `Parsed` +- The `format_description!` macro now supports the `case_sensitive` modifier. + +### Changed + +The minimum supported version is now 1.51.0. + +## 0.3.2 [2021-08-25] + +### Added + +- `Instant` is now `#[repr(transparent)]` + +### Fixed + +- Constructing a `Date` from its ISO year, week, and weekday now returns the correct value in all + circumstances. Previously, dates with an ISO year less than zero may have returned incorrect + values. This affects both the `Date::from_iso_week_date` method and the `date!` macro. + +## 0.3.1 [2021-08-06] + +### Added + +- `Month` now implements `TryFrom` +- `From` is now implemented for `u8` +- The parser now accepts "5 PM" and similar as a valid time. Only the 12-hour clock hour and the + AM/PM suffix may be present; any other components representing a time will cause an error (this + exact behavior is not guaranteed). +- The `time!` macro accepts the "5 PM" syntax with the same restriction. + +### Fixed + +- Macros that have a time component (`time!` and `datetime!`) no longer accept "0:00 AM" and + similar. This was previously erroneously accepted. + +## 0.3.0 [2021-07-30] + +### Added + +- `datetime!` macro, which allows the construction of a statically verified `PrimitiveDateTime` or + `OffsetDateTime`. +- `PrimitiveDateTime::replace_time` +- `PrimitiveDateTime::replace_date` +- `OffsetDateTime::replace_time` +- `OffsetDateTime::replace_date` +- `OffsetDateTime::replace_date_time` +- `OffsetDateTime::replace_offset` +- `#![no_alloc]` support +- `Date::to_iso_week_date`, replacing `Date::iso_year_week` +- `Date::MIN` +- `Date::MAX` +- `UtcOffset::from_hms` +- `UtcOffset::from_whole_seconds` +- `UtcOffset::as_hms` +- `UtcOffset::whole_hours` +- `UtcOffset::whole_minutes` +- `UtcOffset::minutes_past_hour` +- `UtcOffset::seconds_past_minute` +- `UtcOffset::is_utc` +- `UtcOffset::is_positive` +- `UtcOffset::is_negative` +- `OffsetDateTime::sunday_based_week` +- `OffsetDateTime::monday_based_week` +- `PrimitiveDateTime::to_calendar_date` +- `PrimitiveDateTime::to_ordinal_date` +- `PrimitiveDateTime::to_iso_week_date` +- `PrimitiveDateTime::to_julian_day` +- `OffsetDateTime::to_calendar_date` +- `OffsetDateTime::to_ordinal_date` +- `OffsetDateTime::to_iso_week_date` +- `OffsetDateTime::to_julian_day` +- `Time::as_hms` +- `Time::as_hms_milli` +- `Time::as_hms_micro` +- `Time::as_hms_nano` +- `PrimitiveDateTime::as_hms` +- `PrimitiveDateTime::as_hms_milli` +- `PrimitiveDateTime::as_hms_micro` +- `PrimitiveDateTime::as_hms_nano` +- `OffsetDateTime::to_hms` +- `OffsetDateTime::to_hms_milli` +- `OffsetDateTime::to_hms_micro` +- `OffsetDateTime::to_hms_nano` +- `Duration::saturating_add` +- `Duration::saturating_sub` +- `Duration::saturating_mul` +- `util::days_in_year_month` +- `Month` +- `Instant::into_inner` +- `impl AsRef` and `impl Borrow` for `Instant` +- Support for obtaining the local UTC offset on Unix-like systems has been re-added under a + user-provided flag. This functionality is not tested in any way and is not guaranteed to work. + Library authors are unable to enable this feature, as it must be passed via `RUSTFLAGS`. Further + information is available in the documentation. + +### Changed + +- The minimum supported Rust version is now 1.48.0. Per the policy in the README, this may be bumped + within the 0.3 series without being a breaking change. +- rand has been updated to 0.8. +- quickcheck has been updated to 1.0. +- Macros are placed behind the `macros` feature flag. +- Renamed + - `OffsetDatetime::timestamp` → `OffsetDateTime::unix_timestamp` + - `OffsetDatetime::timestamp_nanos` → `OffsetDateTime::unix_timestamp_nanos` + - `Date::try_from_ymd` → `Date::from_calendar_date` + - `Date::try_from_yo` → `Date::from_ordinal_date` + - `Date::try_from_iso_ywd` → `Date::from_iso_week_date` + - `Date::as_ymd` → `Date::to_calendar_date` + - `Date::as_yo` → `Date::to_ordinal_date` + - `Date::try_with_hms` → `Date::with_hms` + - `Date::try_with_hms_milli` → `Date::with_hms_milli` + - `Date::try_with_hms_micro` → `Date::with_hms_micro` + - `Date::try_with_hms_nano` → `Date::with_hms_nano` + - `Time::try_from_hms` → `Time::from_hms` + - `Time::try_from_hms_milli` → `Time::from_hms_milli` + - `Time::try_from_hms_micro` → `Time::from_hms_micro` + - `Time::try_from_hms_nano` → `Time::from_hms_nano` + - `UtcOffset::try_local_offset_at` → `UtcOffset::local_offset_at` + - `UtcOffset::as_seconds` → `UtcOffset::whole_seconds` + - `OffsetDateTime::try_now_local` → `OffsetDateTime::now_local` + - `Date::week` → `Date::iso_week` + - `PrimitiveDateTime::week` → `PrimitiveDateTime::iso_week` + - `OffsetDateTime::week` → `OffsetDateTime::iso_week` + - `Date::julian_day` → `Date::to_julian_day` + - All `Duration` unit values, as well as the minimum and maximum, are now associated constants. + - `OffsetDateTime::unix_epoch()` → `OffsetDateTime::UNIX_EPOCH` + - `Time::midnight()` → `Time::MIDNIGHT` +- Now `const fn` (on at least newer compilers) + - `Date::weekday` + - `Date::next_day` + - `Date::previous_day` + - `PrimitiveDateTime::assume_offset` + - `PrimitiveDateTime::weekday` + - `Duration::checked_add` + - `Duration::checked_sub` + - `Duration::checked_mul` + - `OffsetDateTime::from_unix_timestamp` + - `OffsetDateTime::from_unix_timestamp_nanos` + - `OffsetDateTime::date` + - `OffsetDateTime::time` + - `OffsetDateTime::year` + - `OffsetDateTime::month` + - `OffsetDateTime::day` + - `OffsetDateTime::ordinal` + - `OffsetDateTime::to_iso_week_date` + - `OffsetDateTime::week` + - `OffsetDateTime::weekday` + - `OffsetDateTime::hour` + - `OffsetDateTime::minute` + - `OffsetDateTime::second` + - `OffsetDateTime::millisecond` + - `OffsetDateTime::microsecond` + - `OffsetDateTime::nanosecond` + - `OffsetDateTime::unix_timestamp` + - `OffsetDateTime::unix_timestamp_nanos` +- The following functions now return a `Result`: + - `Date::from_julian_day` + - `OffsetDateTime::from_unix_timestamp` + - `OffsetDateTime::from_unix_timestamp_nanos` +- The following functions now return an `Option`: + - `Date::next_day` + - `Date::previous_day` +- The range of valid years has changed. By default, it is ±9999. When using the `large-dates` + feature, this is increased to ±999,999. Enabling the feature has performance implications and + introduces ambiguities when parsing. +- The following are now gated under the `local-offset` feature: + - `UtcOffset::local_offset_at` + - `OffsetDateTime::now_local` +- `Instant` is now guaranteed to be represented as a tuple struct containing a `std::time::Instant`. +- Macros are guaranteed to be evaluated at compile time. +- `Date::to_julian_day` now returns an `i32` (was `i64`). +- `Date::from_julian_day` now accepts an `i32` (was `i64`). +- Extension traits are only implemented for some types and are now sealed. As they are intended to + be used with value literals, the breakage caused by this should be minimal. +- The new `Month` enum is used instead of numerical values where appropriate. + +### Removed + +- v0.1 APIs, previously behind an enabled-by-default feature flag + - `PreciseTime` + - `SteadyTime` + - `precise_time_ns` + - `precise_time_s` + - `Instant::to` + - `Duration::num_weeks` + - `Duration::num_days` + - `Duration::num_hours` + - `Duration::num_minutes` + - `Duration::num_seconds` + - `Duration::num_milliseconds` + - `Duration::num_microseconds` + - `Duration::num_nanoseconds` + - `Duration::span` + - `Duration::from_std` + - `Duration::to_std` +- Panicking APIs, previously behind a non-default feature flag + - `Date::from_ymd` + - `Date::from_yo` + - `Date::from_iso_ywd` + - `Date::with_hms` + - `Date::with_hms_milli` + - `Date::with_hms_micro` + - `Date::with_hms_nano` + - `Time::from_hms` + - `Time::from_hms_milli` + - `Time::from_hms_micro` + - `Time::from_hms_nano` +- APIs that assumed an offset of UTC, previously enabled unconditionally + - `Date::today` + - `Time::now` + - `PrimitiveDateTime::now` + - `PrimitiveDateTime::unix_epoch` + - `PrimitiveDateTime::from_unix_timestamp` + - `PrimitiveDateTime::timestamp` + - `OffsetDateTime::now` + - `impl Sub for PrimitiveDateTime` + - `impl Sub for SystemTime` + - `impl PartialEq for PrimitiveDateTime` + - `impl PartialEq for SystemTime` + - `impl PartialOrd for PrimitiveDateTime` + - `impl PartialOrd for SystemTime` + - `impl From for PrimitiveDateTime` + - `impl From for SystemTime` + - `UtcOffset::local_offset_at` — assumed UTC if unable to determine local offset + - `OffsetDateTime::now_local` — assumed UTC if unable to determine local offset +- Other APIs deprecated during the course of 0.2, previously enabled unconditionally + - `Duration::sign` + - `PrimitiveDateTime::using_offset` + - `Sign` +- Re-exports of APIs moved during the course of 0.2 + - `days_in_year` + - `is_leap_year` + - `validate_format_string` + - `weeks_in_year` + - `ComponentRangeError` + - `ConversionRangeError` + - `IndeterminateOffsetError` + - `ParseError` + - `NumericalDuration` + - `NumericalStdDuration` + - `NumericalStdDurationShort` + - All top-level macros +- Lazy formatting, which was unidiomatic as a failure would have returned `fmt::Error`, indicating + an error unrelated to the time crate. + - `Time::lazy_format` + - `Date::lazy_format` + - `UtcOffset::lazy_format` + - `PrimitiveDateTime::lazy_format` + - `OffsetDateTime::lazy_format` +- Support for stdweb has been removed, as the crate is unmaintained. +- The `prelude` module has been removed in its entirety. +- `Date::iso_year_week` in favor of `Date::to_iso_week_date` +- `PrimitiveDateTime::iso_year_week` +- `OffsetDateTime::iso_year_week` +- `UtcOffset::east_hours` +- `UtcOffset::west_hours` +- `UtcOffset::hours` +- `UtcOffset::east_minutes` +- `UtcOffset::west_minutes` +- `UtcOffset::minutes` +- `UtcOffset::east_seconds` +- `UtcOffset::west_seconds` +- `UtcOffset::seconds` +- `Date::month_day` +- `PrimitiveDateTime::month_day` +- `OffsetDateTime::month_day` +- `Weekday::iso_weekday_number` (identical to `Weekday::number_from_monday`) +- `ext::NumericalStdDurationShort` + +## 0.2.26 [2021-03-16] + +### Fixed + +- #316, where the build script was wrongly unable to determine the correct compiler version +- Dependencies have been bumped to the latest patch version, ensuring compatibility. + +## 0.2.25 [2021-01-24] + +### Fixed + +- Fix #309, which can cause panics in certain situations. + +## 0.2.24 [2021-01-08] + +### Fixed + +- The implementation of `OffsetDateTime::timestamp`, `OffsetDateTime::unix_timestamp`, + `PrimitiveDatetime::timestamp`, and `OffsetDateTime::unix_timestamp` have been corrected. This + affects all negative timestamps with a nonzero subsecond value. + +## 0.2.23 [2020-11-17] + +### Compatibility notes + +Due to #293, any method that requires knowledge of the local offset will now +_fail_ on Linux. For `try_` methods, this means returning an error. For others, +it means assuming UTC. + +### Deprecated + +- `UtcOffset::timestamp` (moved to `UtcOffset::unix_timestamp`) +- `UtcOffset::timestamp_nanos` (moved to `UtcOffset::unix_timestamp_nanos`) +- `date` (moved to `macros::date`) +- `time` (moved to `macros::time`) +- `offset` (moved to `macros::offset`) +- `OffsetDateTime::now_local` (assumes UTC if unable to be determined) +- `UtcOffset::local_offset_at` (assumes UTC if unable to be determined) +- `UtcOffset::current_local_offset` (assumes UTC if unable to be determined) + +## 0.2.22 [2020-09-25] + +### Fixed + +- Solaris & Illumos now successfully build. +- `Duration::new` could previously result in an inconsistent internal state. This led to some odd + situations where a `Duration` could be both positive and negative. This has been fixed such that + the internal state maintains its invariants. + +## 0.2.21 [2020-09-20] + +### Changed + +- Implementation details of some error types have been exposed. This means that data about a + component being out of range can be directly obtained, while an invalid offset or conversion error + is guaranteed to be a zero-sized type. +- The following functions are `const fn` on rustc ≥ 1.46: + - `Date::try_from_iso_ywd` + - `Date::iso_year_week` + - `Date::week` + - `Date::sunday_based_week` + - `Date::monday_based_week` + - `Date::try_with_hms` + - `Date::try_with_hms_milli` + - `Date::try_with_hms_micro` + - `Date::try_with_hms_nano` + - `PrimitiveDateTime::iso_year_week` + - `PrimitiveDateTime::week` + - `PrimitiveDateTime::sunday_based_week` + - `PrimitiveDateTime::monday_based_week` + - `util::weeks_in_year` + +## 0.2.20 [2020-09-16] + +### Added + +- `OffsetDateTime::timestamp_nanos` +- `OffsetDateTime::from_unix_timestamp_nanos` + +### Fixed + +A bug with far-reaching consequences has been fixed. See #276 for complete details, but the gist is +that the constructing a `Date` from a valid Julian day may result in an invalid value or even panic. +As a consequence of implementation details, this affects nearly all arithmetic with `Date`s (and as +a result also `PrimitiveDateTime`s and `OffsetDateTime`s). + +### Improvements + +- Document how to construct an `OffsetDateTime` from a timestamp-nanosecond pair + +## 0.2.19 [2020-09-12] + +### Fixed + +- The build script now declares a dependency on the `COMPILING_UNDER_CARGO_WEB` environment + variable. +- Parsing the `%D` specifier no longer requires padding on the month. Previously, + `Err(InvalidMonth)` was incorrectly returned. +- A `std::time::Duration` that is larger than `time::Duration::max_value()` now correctly returns + `Ordering::Greater` when compared. +- Multiplying and assigning an integer by `Sign::Zero` now sets the integer to be zero. This + previously left the integer unmodified. + +## 0.2.18 [2020-09-08] + +### Changed + +- The following functions are `const fn` on rustc ≥ 1.46: + - `Date::try_from_ymd` + - `Date::try_from_yo` + - `Time::try_from_hms` + - `Time::try_from_hms_milli` + - `Time::try_from_hms_micro` + - `Time::try_from_hms_nano` +- An `error` module has been created where all existing error types are contained. The `Error` + suffix has been dropped from these types. +- An `ext` module has been created where extension traits are contained. +- A `util` module has been created where utility functions are contained. +- `error::ComponentRange` now implements `Copy`. + +For back-compatibility, all items that were moved to newly-contained modules have been re-exported +from their previous locations (and in the case of the `error` module, with their previous name). + +### Fixes + +Parsing `format::Rfc3339` now correctly handles the UTC offset (#274). + +## 0.2.17 [2020-09-01] + +### Changed + +The following functions are `const fn` on rustc ≥ 1.46: + +- `Date::year` +- `Date::month` +- `Date::day` +- `Date::month_day` +- `Date::ordinal` +- `Date::as_ymd` +- `Date::as_yo` +- `Date::julian_day` +- `Duration::checked_div` +- `PrimitiveDateTime::year` +- `PrimitiveDateTime::month` +- `PrimitiveDateTime::day` +- `PrimitiveDateTime::month_day` +- `PrimitiveDateTime::ordinal` +- `Weekday::previous` +- `Weekday::next` + +### Improvements + +- `size_of::()` has been reduced from 8 to 4. As a consequence, + `size_of::()` went from 16 to 12 and `size_of::()` from 20 + to 16. This change also results in a performance improvement of approximately 30% on the + `Date::year` and `Date::ordinal` methods. +- `cfg-if` has been removed as a dependency. + +### Fixed + +- `cfg` flags passed to rustc will no longer collide with other crates (at least unless they're + doing something very stupid). +- The crate will successfully compile with any combination of feature flags. Previously, some + combinations would fail. + +## 0.2.16 [2020-05-12] + +### Added + +`OffsetDateTime`s can now be represented as Unix timestamps with serde. To do this, you can use the +`time::serde::timestamp` and `time::serde::timestamp::option` modules. + +## 0.2.15 [2020-05-04] + +### Fixed + +`cargo-web` support works, and is now explicitly checked in CI. A previous change was made that made +a method call ambiguous. + +## 0.2.14 [2020-05-02] + +### Fixed + +Adding/subtracting a `core::time::Duration` now correctly takes subsecond values into account. This +also affects `PrimitiveDateTime` and `OffsetDateTime`. + +## 0.2.13 [2020-05-01] + +### Fixed + +Panicking APIs are re-exposed. + +## 0.2.12 [2020-04-30] + +### Fixed + +Subtracting `Instant`s can correctly result in a negative duration, rather than resulting in the +absolute value of it. + +## 0.2.11 [2020-04-27] + +### Added + +- `OffsetDateTime::now_utc` + +### Deprecated + +- `OffsetDateTime::now` due to the offset not being clear from the method name alone. + +### Fixed + +`Date`s are now uniformly random when using the `rand` crate. Previously, both the year and day +within the year were uniform, but this meant that any given day in a leap year was slightly less +likely to be chosen than a day in a non-leap year. + +### Changed + +- MSRV is lowered to 1.32.0. + +## 0.2.10 [2020-04-19] + +### Added + +- Support for formatting and parsing `OffsetDateTime`s as RFC3339. +- Lazy formatting. To avoid exposing implementation details, we're just returning `impl Display`, + rather than a concrete type. +- Add support for Illumos. + +### Fixed + +- Deprecated APIs from time v0.1 are public again. They were previously hidden by accident in 0.2.9. + +## 0.2.9 [2020-03-13] + +### Fixed + +`cfg-if` now has a mandatory minimum of 0.1.10, rather than just 0.1. This is because compilation +fails when using 0.1.9. + +## 0.2.8 [2020-03-12] + +### Added + +- `cargo_web` support has been added for getting a local offset. A general catch-all defaulting to + UTC has also been added. +- `Error::source` has been implemented for the wrapper `time::Error`. +- `UtcOffset::try_local_offset`, `UtcOffset::try_current_local_offset`, + `OffsetDateTime::try_now_local()` provide fallible alternatives when the default of UTC is not + desired. To facilitate this change, `IndeterminateOffsetError` has been added. +- Support for parsing and formatting subsecond nanoseconds. + +### Changed + +- `#[non_exhaustive]` is simulated on compilers prior to 1.40.0. + +## 0.2.7 [2020-02-22] + +### Added + +- `Display` has been implemented for `Date`, `OffsetDateTime`, `PrimitiveDateTime`, `Time`, + `UtcOffset`, and `Weekday`. +- `Hash` is now derived for `Duration`. +- `SystemTime` can be converted to and from `OffsetDateTime`. The following trait implementations + have been made for interoperability: + - `impl Sub for OffsetDateTime` + - `impl Sub for SystemTime` + - `impl PartialEq for OffsetDateTime` + - `impl PartialEq for SystemTime` + - `impl PartialOrd for OffsetDateTime` + - `impl PartialOrd for SystemTime` + - `impl From for OffsetDateTime` + - `impl From for SystemTime` +- All structs now `impl Duration for Standard`, allowing usage with the `rand` crate. This is + gated behind the `rand` feature flag. +- Documentation can now be built on stable. Some annotations will be missing if you do this. +- `NumericalDuration` has been implemented for `f32` and `f64`. `NumericalStdDuration` and + `NumericalStdDurationShort` have been implemented for `f64` only. +- `UtcOffset::local_offset_at(OffsetDateTime)`, which will obtain the system's local offset at the + provided moment in time. + - `OffsetDateTime::now_local()` is equivalent to calling + `OffsetDateTime::now().to_offset(UtcOffset::local_offset_at(OffsetDateTime::now()))` (but more + efficient). + - `UtcOffset::current_local_offset()` will return the equivalent of + `OffsetDateTime::now_local().offset()`. + +### Changed + +- All formatting and parsing methods now accept `impl AsRef` as parameters, rather than just + `&str`. `time::validate_format_string` does this as well. +- The requirement of a `Date` being between the years -100,000 and +100,000 (inclusive) is now + strictly enforced. +- Overflow checks for `Duration` are now enabled by default. This behavior is the identical to what + the standard library does. +- The `time`, `date`, and `offset` macros have been added to the prelude. + +### Deprecated + +- `Sign` has been deprecated in its entirety, along with `Duration::sign`. + + To obtain the sign of a `Duration`, you can use the `Duration::is_positive`, + `Duration::is_negative`, and `Duration::is_zero` methods. + +- A number of functions and trait implementations that implicitly assumed a timezone (generally UTC) + have been deprecated. These are: + - `Date::today` + - `Time::now` + - `PrimitiveDateTime::now` + - `PrimitiveDateTime::unix_epoch` + - `PrimitiveDateTime::from_unix_timestamp` + - `PrimitiveDateTime::timestamp` + - `impl Sub for PrimitiveDateTime` + - `impl Sub for SystemTime` + - `impl PartialEq for PrimitiveDateTime` + - `impl PartialEq for SystemTime>` + - `impl PartialOrd for PrimitiveDateTime` + - `impl PartialOrd for SystemTime>` + - `impl From for PrimitiveDateTime` + - `impl From for SystemTime` + +### Fixed + +- Avoid panics when parsing an empty string (#215). +- The nanoseconds component of a `Duration` is now always in range. Previously, it was possible (via + addition and/or subtraction) to obtain a value that was not internally consistent. +- `Time::parse` erroneously returned an `InvalidMinute` error when it was actually the second that + was invalid. +- `Date::parse("0000-01-01", "%Y-%m-%d")` incorrectly returned an `Err` (#221). + +## Pre-0.2.7 + +Prior to v0.2.7, changes were listed in GitHub releases. + +[keep a changelog]: https://keepachangelog.com/en/1.0.0/ +[semantic versioning]: https://semver.org/spec/v2.0.0.html diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..fac6b886d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,814 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "basic-toml" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2db21524cad41c5591204d22d75e1970a2d1f71060214ca931dc7d5afe2c14e5" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "quickcheck", + "rand", + "serde", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "half" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hermit-abi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" + +[[package]] +name = "is-terminal" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" +dependencies = [ + "hermit-abi", + "rustix", + "windows-sys", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quickcheck" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "rand", +] + +[[package]] +name = "quickcheck_macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "relative-path" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" + +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.48", + "unicode-ident", +] + +[[package]] +name = "rstest_reuse" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88530b681abe67924d42cca181d070e3ac20e0740569441a9e35a7cedd2b34a4" +dependencies = [ + "quote", + "rand", + "rustc_version", + "syn 2.0.48", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "semver" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" + +[[package]] +name = "serde" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.196" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "serde_json" +version = "1.0.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d1bd37ce2324cf3bf85e5a25f96eb4baf0d5aa6eba43e7ae8958870c4ec48ed" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_test" +version = "1.0.176" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a2f49ace1498612d14f7e0b8245519584db8299541dfe31a06374a828d620ab" +dependencies = [ + "serde", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "time" +version = "0.3.36" +dependencies = [ + "criterion", + "deranged", + "itoa", + "js-sys", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "quickcheck", + "quickcheck_macros", + "rand", + "rstest", + "rstest_reuse", + "serde", + "serde_json", + "serde_test", + "time-core", + "time-macros", + "trybuild", +] + +[[package]] +name = "time-core" +version = "0.1.2" + +[[package]] +name = "time-macros" +version = "0.2.18" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "trybuild" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a9d3ba662913483d6722303f619e75ea10b7855b0f8e0d72799cf8621bb488f" +dependencies = [ + "basic-toml", + "glob", + "once_cell", + "serde", + "serde_derive", + "serde_json", + "termcolor", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..a9adaa426 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,126 @@ +[workspace] +default-members = ["time"] +members = ["time", "time-core", "time-macros"] +resolver = "2" + +[workspace.dependencies] +time-core = { path = "time-core", version = "=0.1.2" } +time-macros = { path = "time-macros", version = "=0.2.18" } + +criterion = { version = "0.5.1", default-features = false } +deranged = { version = "0.3.9", default-features = false, features = [ + "powerfmt", +] } +itoa = "1.0.1" +js-sys = "0.3.58" +libc = "0.2.98" +num-conv = "0.1.0" +num_threads = "0.1.2" +powerfmt = { version = "0.2.0", default-features = false } +quickcheck = { version = "1.0.3", default-features = false } +quickcheck_macros = "1.0.0" +rand = { version = "0.8.4", default-features = false } +rstest = { version = "0.18.2", default-features = false } +rstest_reuse = "0.6.0" +# ^1.0.184 due to serde-rs/serde#2538 +serde = { version = "1.0.184", default-features = false } +serde_json = "1.0.68" +serde_test = "1.0.126" +trybuild = "1.0.68" + +[profile.dev] +debug = 0 + +[profile.test] +debug = 2 + +# Deny-by-default lints: These are lints that indicate a lack of compiler guarantees, future +# incompatibility (with no guarantees in the meantime) introduce surprising behavior, or are likely +# to cause undesired behavior. Code that trigger these lints should only be allowed with a +# compelling reason that is clearly documented. + +# Warn-by-default lints: These are lints that indicate possible errors, future incompatibility +# (with guaranteed behavior in the meantime), or other stylistic issues (including idioms). Code +# that trigger these lints should not cause undesired behavior and may be allowed as necessary. + +# All overrides need higher priority. Any overrides that are for a specific crate need to be done +# the "traditional" way of using attributes at the crate root. + +[workspace.lints.rust] +ambiguous-glob-reexports = "deny" +clashing-extern-declarations = "deny" +const-item-mutation = "deny" +deref-nullptr = "deny" +drop-bounds = "deny" +future-incompatible = "deny" +hidden-glob-reexports = "deny" +improper-ctypes = "deny" +improper-ctypes-definitions = "deny" +invalid-from-utf8 = "deny" +invalid-macro-export-arguments = "deny" +invalid-nan-comparisons = "deny" +invalid-reference-casting = "deny" +invalid-value = "deny" +named-arguments-used-positionally = "deny" +non-ascii-idents = "deny" +opaque-hidden-inferred-bound = "deny" +overlapping-range-endpoints = "deny" +suspicious-double-ref-op = "deny" +temporary-cstring-as-ptr = "deny" +unconditional-recursion = "deny" +unnameable-test-items = "deny" +unsafe-op-in-unsafe-fn = "deny" +unstable-syntax-pre-expansion = "deny" + +keyword-idents = "warn" +let-underscore = "warn" +macro-use-extern-crate = "warn" +meta-variable-misuse = "warn" +missing-abi = "warn" +missing-copy-implementations = "warn" +missing-debug-implementations = "warn" +missing-docs = "warn" +noop-method-call = "warn" +single-use-lifetimes = "warn" +trivial-casts = "warn" +trivial-numeric-casts = "warn" +unreachable-pub = "warn" +unused = { level = "warn", priority = -1 } +unused-import-braces = "warn" +unused-lifetimes = "warn" +unused-qualifications = "warn" +variant-size-differences = "warn" + +unstable-name-collisions = { level = "allow", priority = 1 } # overrides #![deny(future_incompatible)], temporary while `.cast_{un}signed()` is unstable + +[workspace.lints.clippy] +alloc-instead-of-core = "deny" +std-instead-of-core = "deny" +undocumented-unsafe-blocks = "deny" + +missing-docs-in-private-items = "warn" +all = { level = "warn", priority = -1 } +dbg-macro = "warn" +decimal-literal-representation = "warn" +explicit-auto-deref = "warn" +get-unwrap = "warn" +manual-let-else = "warn" +missing-enforced-import-renames = "warn" +nursery = { level = "warn", priority = -1 } +obfuscated-if-else = "warn" +print-stdout = "warn" +semicolon-outside-block = "warn" +todo = "warn" +unimplemented = "warn" +uninlined-format-args = "warn" +unnested-or-patterns = "warn" +unwrap-in-result = "warn" +unwrap-used = "warn" +use-debug = "warn" + +option-if-let-else = { level = "allow", priority = 1 } # suggests terrible code, overrides #![warn(clippy::nursery)] +redundant-pub-crate = { level = "allow", priority = 1 } # rust-lang/rust-clippy#5369, overrides #![warn(clippy::nursery)] + +[workspace.lints.rustdoc] +private-doc-tests = "warn" +unescaped-backticks = "warn" diff --git a/LICENSE-Apache b/LICENSE-Apache new file mode 100644 index 000000000..c763a0c9d --- /dev/null +++ b/LICENSE-Apache @@ -0,0 +1,202 @@ + + 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 2024 Jacob Pratt et al. + + 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 000000000..5cc097f1c --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) 2024 Jacob Pratt et al. + +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 new file mode 100644 index 000000000..b2991f93d --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# time + +[![minimum rustc: 1.67](https://img.shields.io/badge/minimum%20rustc-1.67-yellowgreen?logo=rust&style=flat-square)](https://www.whatrustisit.com) +[![version](https://img.shields.io/crates/v/time?color=blue&logo=rust&style=flat-square)](https://crates.io/crates/time) +[![build status](https://img.shields.io/github/actions/workflow/status/time-rs/time/build.yaml?branch=main&style=flat-square)](https://github.com/time-rs/time/actions) +[![codecov](https://codecov.io/gh/time-rs/time/branch/main/graph/badge.svg?token=yt4XSmQNKQ)](https://codecov.io/gh/time-rs/time) + +Documentation: + +- [latest release](https://docs.rs/time) +- [main branch](https://time-rs.github.io/api/time) +- [book](https://time-rs.github.io/book) + +## Minimum Rust version policy + +`time` is guaranteed to compile with the latest stable release of Rust in addition to the two prior +minor releases. For example, if the latest stable Rust release is 1.70, then `time` is guaranteed to +compile with Rust 1.68, 1.69, and 1.70. + +The minimum supported Rust version may be increased to one of the aforementioned versions if doing +so provides the end user a benefit. However, the minimum supported Rust version may also be bumped +to a version four minor releases prior to the most recent stable release if doing so improves code +quality or maintainability. + +For interoperability with third-party crates, it is guaranteed that there exists a version of that +crate that supports the minimum supported Rust version of `time`. This does not mean that the latest +version of the third-party crate supports the minimum supported Rust version of `time`. + +## Contributing + +Contributions are always welcome! If you have an idea, it's best to float it by me before working on +it to ensure no effort is wasted. If there's already an open issue for it, knock yourself out. +Internal documentation can be viewed [here](https://time-rs.github.io/internal-api/time). + +If you have any questions, feel free to use [Discussions]. Don't hesitate to ask questions — that's +what I'm here for! + +[Discussions]: https://github.com/time-rs/time/discussions + +## License + +This project is licensed under either of + +- [Apache License, Version 2.0](https://github.com/time-rs/time/blob/main/LICENSE-Apache) +- [MIT license](https://github.com/time-rs/time/blob/main/LICENSE-MIT) + +at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in +time by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. diff --git a/benchmarks/date.rs b/benchmarks/date.rs new file mode 100644 index 000000000..fc1f45015 --- /dev/null +++ b/benchmarks/date.rs @@ -0,0 +1,330 @@ +use criterion::Bencher; +use time::ext::{NumericalDuration, NumericalStdDuration}; +use time::macros::date; +use time::{Date, Month, Time, Weekday}; + +setup_benchmark! { + "Date", + + // region: constructors + fn from_calendar_date(ben: &mut Bencher<'_>) { + ben.iter(|| Date::from_calendar_date(2019, Month::January, 1)); + ben.iter(|| Date::from_calendar_date(2019, Month::December, 31)); + ben.iter(|| Date::from_calendar_date(2020, Month::January, 1)); + ben.iter(|| Date::from_calendar_date(2020, Month::December, 31)); + } + + fn from_ordinal_date(ben: &mut Bencher<'_>) { + ben.iter(|| Date::from_ordinal_date(2019, 1)); + ben.iter(|| Date::from_ordinal_date(2019, 365)); + ben.iter(|| Date::from_ordinal_date(2020, 1)); + ben.iter(|| Date::from_ordinal_date(2020, 366)); + } + + fn from_iso_week_date(ben: &mut Bencher<'_>) { + use Weekday::*; + ben.iter(|| Date::from_iso_week_date(2019, 1, Tuesday)); + ben.iter(|| Date::from_iso_week_date(2020, 1, Tuesday)); + ben.iter(|| Date::from_iso_week_date(2020, 1, Wednesday)); + ben.iter(|| Date::from_iso_week_date(2020, 53, Thursday)); + } + + fn from_julian_day(ben: &mut Bencher<'_>) { + ben.iter(|| Date::from_julian_day(-34_803_190)); + ben.iter(|| Date::from_julian_day(0)); + ben.iter(|| Date::from_julian_day(2_459_753)); + } + // endregion constructors + + // region: getters + fn year(ben: &mut Bencher<'_>) { + let d = date!(2019-002); + ben.iter(|| d.year()); + } + + fn month(ben: &mut Bencher<'_>) { + ben.iter(|| date!(2019-01-01).month()); + ben.iter(|| date!(2019-02-01).month()); + ben.iter(|| date!(2019-03-01).month()); + ben.iter(|| date!(2019-04-01).month()); + ben.iter(|| date!(2019-05-01).month()); + ben.iter(|| date!(2019-06-01).month()); + ben.iter(|| date!(2019-07-01).month()); + ben.iter(|| date!(2019-08-01).month()); + ben.iter(|| date!(2019-09-01).month()); + ben.iter(|| date!(2019-10-01).month()); + ben.iter(|| date!(2019-11-01).month()); + ben.iter(|| date!(2019-12-01).month()); + } + + fn day(ben: &mut Bencher<'_>) { + ben.iter(|| date!(2019-01-01).day()); + ben.iter(|| date!(2019-02-01).day()); + ben.iter(|| date!(2019-03-01).day()); + ben.iter(|| date!(2019-04-01).day()); + ben.iter(|| date!(2019-05-01).day()); + ben.iter(|| date!(2019-06-01).day()); + ben.iter(|| date!(2019-07-01).day()); + ben.iter(|| date!(2019-08-01).day()); + ben.iter(|| date!(2019-09-01).day()); + ben.iter(|| date!(2019-10-01).day()); + ben.iter(|| date!(2019-11-01).day()); + ben.iter(|| date!(2019-12-01).day()); + } + + fn ordinal(ben: &mut Bencher<'_>) { + ben.iter(|| date!(2019-01-01).ordinal()); + ben.iter(|| date!(2019-02-01).ordinal()); + ben.iter(|| date!(2019-03-01).ordinal()); + ben.iter(|| date!(2019-04-01).ordinal()); + ben.iter(|| date!(2019-05-01).ordinal()); + ben.iter(|| date!(2019-06-01).ordinal()); + ben.iter(|| date!(2019-07-01).ordinal()); + ben.iter(|| date!(2019-08-01).ordinal()); + ben.iter(|| date!(2019-09-01).ordinal()); + ben.iter(|| date!(2019-10-01).ordinal()); + ben.iter(|| date!(2019-11-01).ordinal()); + ben.iter(|| date!(2019-12-01).ordinal()); + } + + fn iso_week(ben: &mut Bencher<'_>) { + ben.iter(|| date!(2019-01-01).iso_week()); + ben.iter(|| date!(2019-02-01).iso_week()); + ben.iter(|| date!(2019-03-01).iso_week()); + ben.iter(|| date!(2019-04-01).iso_week()); + ben.iter(|| date!(2019-05-01).iso_week()); + ben.iter(|| date!(2019-06-01).iso_week()); + ben.iter(|| date!(2019-07-01).iso_week()); + ben.iter(|| date!(2019-08-01).iso_week()); + ben.iter(|| date!(2019-09-01).iso_week()); + ben.iter(|| date!(2019-10-01).iso_week()); + ben.iter(|| date!(2019-11-01).iso_week()); + ben.iter(|| date!(2019-12-01).iso_week()); + } + + fn sunday_based_week(ben: &mut Bencher<'_>) { + ben.iter(|| date!(2019-01-01).sunday_based_week()); + ben.iter(|| date!(2019-02-01).sunday_based_week()); + ben.iter(|| date!(2019-03-01).sunday_based_week()); + ben.iter(|| date!(2019-04-01).sunday_based_week()); + ben.iter(|| date!(2019-05-01).sunday_based_week()); + ben.iter(|| date!(2019-06-01).sunday_based_week()); + ben.iter(|| date!(2019-07-01).sunday_based_week()); + ben.iter(|| date!(2019-08-01).sunday_based_week()); + ben.iter(|| date!(2019-09-01).sunday_based_week()); + ben.iter(|| date!(2019-10-01).sunday_based_week()); + ben.iter(|| date!(2019-11-01).sunday_based_week()); + ben.iter(|| date!(2019-12-01).sunday_based_week()); + } + + fn monday_based_week(ben: &mut Bencher<'_>) { + ben.iter(|| date!(2019-01-01).monday_based_week()); + ben.iter(|| date!(2019-02-01).monday_based_week()); + ben.iter(|| date!(2019-03-01).monday_based_week()); + ben.iter(|| date!(2019-04-01).monday_based_week()); + ben.iter(|| date!(2019-05-01).monday_based_week()); + ben.iter(|| date!(2019-06-01).monday_based_week()); + ben.iter(|| date!(2019-07-01).monday_based_week()); + ben.iter(|| date!(2019-08-01).monday_based_week()); + ben.iter(|| date!(2019-09-01).monday_based_week()); + ben.iter(|| date!(2019-10-01).monday_based_week()); + ben.iter(|| date!(2019-11-01).monday_based_week()); + ben.iter(|| date!(2019-12-01).monday_based_week()); + } + + fn to_calendar_date(ben: &mut Bencher<'_>) { + ben.iter(|| date!(2019-01-01).to_calendar_date()); + ben.iter(|| date!(2019-02-01).to_calendar_date()); + ben.iter(|| date!(2019-03-01).to_calendar_date()); + ben.iter(|| date!(2019-04-01).to_calendar_date()); + ben.iter(|| date!(2019-05-01).to_calendar_date()); + ben.iter(|| date!(2019-06-01).to_calendar_date()); + ben.iter(|| date!(2019-07-01).to_calendar_date()); + ben.iter(|| date!(2019-08-01).to_calendar_date()); + ben.iter(|| date!(2019-09-01).to_calendar_date()); + ben.iter(|| date!(2019-10-01).to_calendar_date()); + ben.iter(|| date!(2019-11-01).to_calendar_date()); + ben.iter(|| date!(2019-12-01).to_calendar_date()); + } + + fn to_ordinal_date(ben: &mut Bencher<'_>) { + ben.iter(|| date!(2019-01-01).to_ordinal_date()); + ben.iter(|| date!(2019-02-01).to_ordinal_date()); + ben.iter(|| date!(2019-03-01).to_ordinal_date()); + ben.iter(|| date!(2019-04-01).to_ordinal_date()); + ben.iter(|| date!(2019-05-01).to_ordinal_date()); + ben.iter(|| date!(2019-06-01).to_ordinal_date()); + ben.iter(|| date!(2019-07-01).to_ordinal_date()); + ben.iter(|| date!(2019-08-01).to_ordinal_date()); + ben.iter(|| date!(2019-09-01).to_ordinal_date()); + ben.iter(|| date!(2019-10-01).to_ordinal_date()); + ben.iter(|| date!(2019-11-01).to_ordinal_date()); + ben.iter(|| date!(2019-12-01).to_ordinal_date()); + } + + fn to_iso_week_date(ben: &mut Bencher<'_>) { + ben.iter(|| date!(2019-01-01).to_iso_week_date()); + ben.iter(|| date!(2019-02-01).to_iso_week_date()); + ben.iter(|| date!(2019-03-01).to_iso_week_date()); + ben.iter(|| date!(2019-04-01).to_iso_week_date()); + ben.iter(|| date!(2019-05-01).to_iso_week_date()); + ben.iter(|| date!(2019-06-01).to_iso_week_date()); + ben.iter(|| date!(2019-07-01).to_iso_week_date()); + ben.iter(|| date!(2019-08-01).to_iso_week_date()); + ben.iter(|| date!(2019-09-01).to_iso_week_date()); + ben.iter(|| date!(2019-10-01).to_iso_week_date()); + ben.iter(|| date!(2019-11-01).to_iso_week_date()); + ben.iter(|| date!(2019-12-01).to_iso_week_date()); + } + + fn weekday(ben: &mut Bencher<'_>) { + ben.iter(|| date!(2019-01-01).weekday()); + ben.iter(|| date!(2019-02-01).weekday()); + ben.iter(|| date!(2019-03-01).weekday()); + ben.iter(|| date!(2019-04-01).weekday()); + ben.iter(|| date!(2019-05-01).weekday()); + ben.iter(|| date!(2019-06-01).weekday()); + ben.iter(|| date!(2019-07-01).weekday()); + ben.iter(|| date!(2019-08-01).weekday()); + ben.iter(|| date!(2019-09-01).weekday()); + ben.iter(|| date!(2019-10-01).weekday()); + ben.iter(|| date!(2019-11-01).weekday()); + ben.iter(|| date!(2019-12-01).weekday()); + } + + fn next_day(ben: &mut Bencher<'_>) { + ben.iter(|| date!(2019-01-01).next_day()); + ben.iter(|| date!(2019-02-01).next_day()); + ben.iter(|| date!(2019-12-31).next_day()); + ben.iter(|| date!(2020-12-31).next_day()); + ben.iter(|| Date::MAX.next_day()); + } + + fn previous_day(ben: &mut Bencher<'_>) { + ben.iter(|| date!(2019-01-02).previous_day()); + ben.iter(|| date!(2019-02-01).previous_day()); + ben.iter(|| date!(2020-01-01).previous_day()); + ben.iter(|| date!(2021-01-01).previous_day()); + ben.iter(|| Date::MIN.previous_day()); + } + + fn to_julian_day(ben: &mut Bencher<'_>) { + ben.iter(|| date!(2019-01-01).to_julian_day()); + ben.iter(|| date!(2019-02-01).to_julian_day()); + ben.iter(|| date!(2019-03-01).to_julian_day()); + ben.iter(|| date!(2019-04-01).to_julian_day()); + ben.iter(|| date!(2019-05-01).to_julian_day()); + ben.iter(|| date!(2019-06-01).to_julian_day()); + ben.iter(|| date!(2019-07-01).to_julian_day()); + ben.iter(|| date!(2019-08-01).to_julian_day()); + ben.iter(|| date!(2019-09-01).to_julian_day()); + ben.iter(|| date!(2019-10-01).to_julian_day()); + ben.iter(|| date!(2019-11-01).to_julian_day()); + ben.iter(|| date!(2019-12-01).to_julian_day()); + } + // endregion getters + + // region: attach time + fn midnight(ben: &mut Bencher<'_>) { + ben.iter(|| date!(1970-01-01).midnight()); + } + + fn with_time(ben: &mut Bencher<'_>) { + ben.iter(|| date!(1970-01-01).with_time(Time::MIDNIGHT)); + } + + fn with_hms(ben: &mut Bencher<'_>) { + ben.iter(|| date!(1970-01-01).with_hms(0, 0, 0)); + } + + fn with_hms_milli(ben: &mut Bencher<'_>) { + ben.iter(|| date!(1970-01-01).with_hms_milli(0, 0, 0, 0)); + } + + fn with_hms_micro(ben: &mut Bencher<'_>) { + ben.iter(|| date!(1970-01-01).with_hms_micro(0, 0, 0, 0)); + } + + fn with_hms_nano(ben: &mut Bencher<'_>) { + ben.iter(|| date!(1970-01-01).with_hms_nano(0, 0, 0, 0)); + } + // endregion attach time + + // region: trait impls + fn add(ben: &mut Bencher<'_>) { + let dt = 5.days(); + ben.iter(|| date!(2019-01-01) + dt); + } + + fn add_std(ben: &mut Bencher<'_>) { + let dt = 5.std_days(); + ben.iter(|| date!(2019-01-01) + dt); + } + + fn add_assign(ben: &mut Bencher<'_>) { + let dt = 1.days(); + iter_batched_ref!( + ben, + || date!(2019-12-31), + [|date| *date += dt] + ); + } + + fn add_assign_std(ben: &mut Bencher<'_>) { + let dt = 1.std_days(); + iter_batched_ref!( + ben, + || date!(2019-12-31), + [|date| *date += dt] + ); + } + + fn sub(ben: &mut Bencher<'_>) { + let dt = 5.days(); + ben.iter(|| date!(2019-01-06) - dt); + } + + fn sub_std(ben: &mut Bencher<'_>) { + let dt = 5.std_days(); + ben.iter(|| date!(2019-01-06) - dt); + } + + fn sub_assign(ben: &mut Bencher<'_>) { + let dt = 1.days(); + iter_batched_ref!( + ben, + || date!(2019-12-31), + [|date| *date -= dt] + ); + } + + fn sub_assign_std(ben: &mut Bencher<'_>) { + let dt = 1.std_days(); + iter_batched_ref!( + ben, + || date!(2019-12-31), + [|date| *date -= dt] + ); + } + + fn sub_self(ben: &mut Bencher<'_>) { + ben.iter(|| date!(2019-01-02) - date!(2019-01-01)); + } + + fn partial_ord(ben: &mut Bencher<'_>) { + let first = date!(2019-01-01); + let second = date!(2019-01-02); + ben.iter(|| first.partial_cmp(&first)); + ben.iter(|| first.partial_cmp(&second)); + ben.iter(|| second.partial_cmp(&first)); + } + + fn ord(ben: &mut Bencher<'_>) { + let first = date!(2019-01-01); + let second = date!(2019-01-02); + ben.iter(|| first.cmp(&first)); + ben.iter(|| first.cmp(&second)); + ben.iter(|| second.cmp(&first)); + } + // endregion trait impls +} diff --git a/benchmarks/duration.rs b/benchmarks/duration.rs new file mode 100644 index 000000000..558eba782 --- /dev/null +++ b/benchmarks/duration.rs @@ -0,0 +1,728 @@ +use std::time::Duration as StdDuration; + +use criterion::Bencher; +use time::ext::{NumericalDuration, NumericalStdDuration}; +use time::Duration; + +setup_benchmark! { + "Duration", + + // region: is_{sign} + fn is_zero(ben: &mut Bencher<'_>) { + let a = (-1).nanoseconds(); + let b = 0.seconds(); + let c = 1.nanoseconds(); + ben.iter(|| a.is_zero()); + ben.iter(|| b.is_zero()); + ben.iter(|| c.is_zero()); + } + + fn is_negative(ben: &mut Bencher<'_>) { + let a = (-1).seconds(); + let b = 0.seconds(); + let c = 1.seconds(); + ben.iter(|| a.is_negative()); + ben.iter(|| b.is_negative()); + ben.iter(|| c.is_negative()); + } + + fn is_positive(ben: &mut Bencher<'_>) { + let a = (-1).seconds(); + let b = 0.seconds(); + let c = 1.seconds(); + ben.iter(|| a.is_positive()); + ben.iter(|| b.is_positive()); + ben.iter(|| c.is_positive()); + } + // endregion is_{sign} + + // region: abs + fn abs(ben: &mut Bencher<'_>) { + let a = 1.seconds(); + let b = 0.seconds(); + let c = (-1).seconds(); + ben.iter(|| a.abs()); + ben.iter(|| b.abs()); + ben.iter(|| c.abs()); + } + + fn unsigned_abs(ben: &mut Bencher<'_>) { + let a = 1.seconds(); + let b = 0.seconds(); + let c = (-1).seconds(); + ben.iter(|| a.unsigned_abs()); + ben.iter(|| b.unsigned_abs()); + ben.iter(|| c.unsigned_abs()); + } + // endregion abs + + // region: constructors + fn new(ben: &mut Bencher<'_>) { + ben.iter(|| Duration::new(1, 0)); + ben.iter(|| Duration::new(-1, 0)); + ben.iter(|| Duration::new(1, 2_000_000_000)); + + ben.iter(|| Duration::new(0, 0)); + ben.iter(|| Duration::new(0, 1_000_000_000)); + ben.iter(|| Duration::new(-1, 1_000_000_000)); + ben.iter(|| Duration::new(-2, 1_000_000_000)); + + ben.iter(|| Duration::new(1, -1)); + ben.iter(|| Duration::new(-1, 1)); + ben.iter(|| Duration::new(1, 1)); + ben.iter(|| Duration::new(-1, -1)); + ben.iter(|| Duration::new(0, 1)); + ben.iter(|| Duration::new(0, -1)); + + ben.iter(|| Duration::new(-1, 1_400_000_000)); + ben.iter(|| Duration::new(-2, 1_400_000_000)); + ben.iter(|| Duration::new(-3, 1_400_000_000)); + ben.iter(|| Duration::new(1, -1_400_000_000)); + ben.iter(|| Duration::new(2, -1_400_000_000)); + ben.iter(|| Duration::new(3, -1_400_000_000)); + } + + fn weeks(ben: &mut Bencher<'_>) { + ben.iter(|| Duration::weeks(1)); + ben.iter(|| Duration::weeks(2)); + ben.iter(|| Duration::weeks(-1)); + ben.iter(|| Duration::weeks(-2)); + } + + fn days(ben: &mut Bencher<'_>) { + ben.iter(|| Duration::days(1)); + ben.iter(|| Duration::days(2)); + ben.iter(|| Duration::days(-1)); + ben.iter(|| Duration::days(-2)); + } + + fn hours(ben: &mut Bencher<'_>) { + ben.iter(|| Duration::hours(1)); + ben.iter(|| Duration::hours(2)); + ben.iter(|| Duration::hours(-1)); + ben.iter(|| Duration::hours(-2)); + } + + fn minutes(ben: &mut Bencher<'_>) { + ben.iter(|| Duration::minutes(1)); + ben.iter(|| Duration::minutes(2)); + ben.iter(|| Duration::minutes(-1)); + ben.iter(|| Duration::minutes(-2)); + } + + fn seconds(ben: &mut Bencher<'_>) { + ben.iter(|| Duration::seconds(1)); + ben.iter(|| Duration::seconds(2)); + ben.iter(|| Duration::seconds(-1)); + ben.iter(|| Duration::seconds(-2)); + } + + fn seconds_f64(ben: &mut Bencher<'_>) { + ben.iter(|| Duration::seconds_f64(0.5)); + ben.iter(|| Duration::seconds_f64(-0.5)); + } + + fn seconds_f32(ben: &mut Bencher<'_>) { + ben.iter(|| Duration::seconds_f32(0.5)); + ben.iter(|| Duration::seconds_f32(-0.5)); + } + + fn saturating_seconds_f64(ben: &mut Bencher<'_>) { + ben.iter(|| Duration::saturating_seconds_f64(0.5)); + ben.iter(|| Duration::saturating_seconds_f64(-0.5)); + } + + fn saturating_seconds_f32(ben: &mut Bencher<'_>) { + ben.iter(|| Duration::saturating_seconds_f32(0.5)); + ben.iter(|| Duration::saturating_seconds_f32(-0.5)); + } + + fn checked_seconds_f64(ben: &mut Bencher<'_>) { + ben.iter(|| Duration::checked_seconds_f64(0.5)); + ben.iter(|| Duration::checked_seconds_f64(-0.5)); + } + + fn checked_seconds_f32(ben: &mut Bencher<'_>) { + ben.iter(|| Duration::checked_seconds_f32(0.5)); + ben.iter(|| Duration::checked_seconds_f32(-0.5)); + } + + fn milliseconds(ben: &mut Bencher<'_>) { + ben.iter(|| Duration::milliseconds(1)); + ben.iter(|| Duration::milliseconds(-1)); + } + + fn microseconds(ben: &mut Bencher<'_>) { + ben.iter(|| Duration::microseconds(1)); + ben.iter(|| Duration::microseconds(-1)); + } + + fn nanoseconds(ben: &mut Bencher<'_>) { + ben.iter(|| Duration::nanoseconds(1)); + ben.iter(|| Duration::nanoseconds(-1)); + } + + fn whole_weeks(ben: &mut Bencher<'_>) { + let a = Duration::weeks(1); + let b = Duration::weeks(-1); + let c = Duration::days(6); + let d = Duration::days(-6); + ben.iter(|| a.whole_weeks()); + ben.iter(|| b.whole_weeks()); + ben.iter(|| c.whole_weeks()); + ben.iter(|| d.whole_weeks()); + } + // endregion constructors + + // region: getters + fn whole_days(ben: &mut Bencher<'_>) { + let a = Duration::days(1); + let b = Duration::days(-1); + let c = Duration::hours(23); + let d = Duration::hours(-23); + ben.iter(|| a.whole_days()); + ben.iter(|| b.whole_days()); + ben.iter(|| c.whole_days()); + ben.iter(|| d.whole_days()); + } + + fn whole_hours(ben: &mut Bencher<'_>) { + let a = Duration::hours(1); + let b = Duration::hours(-1); + let c = Duration::minutes(59); + let d = Duration::minutes(-59); + ben.iter(|| a.whole_hours()); + ben.iter(|| b.whole_hours()); + ben.iter(|| c.whole_hours()); + ben.iter(|| d.whole_hours()); + } + + fn whole_minutes(ben: &mut Bencher<'_>) { + let a = 1.minutes(); + let b = (-1).minutes(); + let c = 59.seconds(); + let d = (-59).seconds(); + ben.iter(|| a.whole_minutes()); + ben.iter(|| b.whole_minutes()); + ben.iter(|| c.whole_minutes()); + ben.iter(|| d.whole_minutes()); + } + + fn whole_seconds(ben: &mut Bencher<'_>) { + let a = 1.seconds(); + let b = (-1).seconds(); + let c = 1.minutes(); + let d = (-1).minutes(); + ben.iter(|| a.whole_seconds()); + ben.iter(|| b.whole_seconds()); + ben.iter(|| c.whole_seconds()); + ben.iter(|| d.whole_seconds()); + } + + fn as_seconds_f64(ben: &mut Bencher<'_>) { + let a = 1.seconds(); + let b = (-1).seconds(); + let c = 1.minutes(); + let d = (-1).minutes(); + let e = 1.5.seconds(); + let f = (-1.5).seconds(); + ben.iter(|| a.as_seconds_f64()); + ben.iter(|| b.as_seconds_f64()); + ben.iter(|| c.as_seconds_f64()); + ben.iter(|| d.as_seconds_f64()); + ben.iter(|| e.as_seconds_f64()); + ben.iter(|| f.as_seconds_f64()); + } + + fn as_seconds_f32(ben: &mut Bencher<'_>) { + let a = 1.seconds(); + let b = (-1).seconds(); + let c = 1.minutes(); + let d = (-1).minutes(); + let e = 1.5.seconds(); + let f = (-1.5).seconds(); + ben.iter(|| a.as_seconds_f32()); + ben.iter(|| b.as_seconds_f32()); + ben.iter(|| c.as_seconds_f32()); + ben.iter(|| d.as_seconds_f32()); + ben.iter(|| e.as_seconds_f32()); + ben.iter(|| f.as_seconds_f32()); + } + + fn whole_milliseconds(ben: &mut Bencher<'_>) { + let a = 1.seconds(); + let b = (-1).seconds(); + let c = 1.milliseconds(); + let d = (-1).milliseconds(); + ben.iter(|| a.whole_milliseconds()); + ben.iter(|| b.whole_milliseconds()); + ben.iter(|| c.whole_milliseconds()); + ben.iter(|| d.whole_milliseconds()); + } + + fn subsec_milliseconds(ben: &mut Bencher<'_>) { + let a = 1.4.seconds(); + let b = (-1.4).seconds(); + ben.iter(|| a.subsec_milliseconds()); + ben.iter(|| b.subsec_milliseconds()); + } + + fn whole_microseconds(ben: &mut Bencher<'_>) { + let a = 1.milliseconds(); + let b = (-1).milliseconds(); + let c = 1.microseconds(); + let d = (-1).microseconds(); + ben.iter(|| a.whole_microseconds()); + ben.iter(|| b.whole_microseconds()); + ben.iter(|| c.whole_microseconds()); + ben.iter(|| d.whole_microseconds()); + } + + fn subsec_microseconds(ben: &mut Bencher<'_>) { + let a = 1.0004.seconds(); + let b = (-1.0004).seconds(); + ben.iter(|| a.subsec_microseconds()); + ben.iter(|| b.subsec_microseconds()); + } + + fn whole_nanoseconds(ben: &mut Bencher<'_>) { + let a = 1.microseconds(); + let b = (-1).microseconds(); + let c = 1.nanoseconds(); + let d = (-1).nanoseconds(); + ben.iter(|| a.whole_nanoseconds()); + ben.iter(|| b.whole_nanoseconds()); + ben.iter(|| c.whole_nanoseconds()); + ben.iter(|| d.whole_nanoseconds()); + } + + fn subsec_nanoseconds(ben: &mut Bencher<'_>) { + let a = 1.000_000_4.seconds(); + let b = (-1.000_000_4).seconds(); + ben.iter(|| a.subsec_nanoseconds()); + ben.iter(|| b.subsec_nanoseconds()); + } + // endregion getters + + // region: checked arithmetic + fn checked_add(ben: &mut Bencher<'_>) { + let a = 5.seconds(); + let b = Duration::MAX; + let c = (-5).seconds(); + + let a2 = 5.seconds(); + let b2 = 1.nanoseconds(); + let c2 = 5.seconds(); + + ben.iter(|| a.checked_add(a2)); + ben.iter(|| b.checked_add(b2)); + ben.iter(|| c.checked_add(c2)); + } + + fn checked_sub(ben: &mut Bencher<'_>) { + let a = 5.seconds(); + let b = Duration::MIN; + let c = 5.seconds(); + + let a2 = 5.seconds(); + let b2 = 1.nanoseconds(); + let c2 = 10.seconds(); + + ben.iter(|| a.checked_sub(a2)); + ben.iter(|| b.checked_sub(b2)); + ben.iter(|| c.checked_sub(c2)); + } + + fn checked_mul(ben: &mut Bencher<'_>) { + let a = 5.seconds(); + let b = Duration::MAX; + ben.iter(|| a.checked_mul(2)); + ben.iter(|| b.checked_mul(2)); + } + + fn checked_div(ben: &mut Bencher<'_>) { + let a = 10.seconds(); + ben.iter(|| a.checked_div(2)); + ben.iter(|| a.checked_div(0)); + } + // endregion checked arithmetic + + // region: saturating arithmetic + fn saturating_add(ben: &mut Bencher<'_>) { + let a = 5.seconds(); + let b = Duration::MAX; + let c = Duration::MIN; + let d = (-5).seconds(); + + let a2 = 5.seconds(); + let b2 = 1.nanoseconds(); + let c2 = (-1).nanoseconds(); + let d2 = 5.seconds(); + + ben.iter(|| a.saturating_add(a2)); + ben.iter(|| b.saturating_add(b2)); + ben.iter(|| c.saturating_add(c2)); + ben.iter(|| d.saturating_add(d2)); + } + + fn saturating_sub(ben: &mut Bencher<'_>) { + let a = 5.seconds(); + let b = Duration::MIN; + let c = Duration::MAX; + let d = 5.seconds(); + + let a2 = 5.seconds(); + let b2 = 1.nanoseconds(); + let c2 = (-1).nanoseconds(); + let d2 = 10.seconds(); + + ben.iter(|| a.saturating_sub(a2)); + ben.iter(|| b.saturating_sub(b2)); + ben.iter(|| c.saturating_sub(c2)); + ben.iter(|| d.saturating_sub(d2)); + } + + fn saturating_mul(ben: &mut Bencher<'_>) { + let a = 5.seconds(); + let b = 5.seconds(); + let c = 5.seconds(); + let d = Duration::MAX; + let e = Duration::MIN; + let f = Duration::MAX; + let g = Duration::MIN; + + ben.iter(|| a.saturating_mul(2)); + ben.iter(|| b.saturating_mul(-2)); + ben.iter(|| c.saturating_mul(0)); + ben.iter(|| d.saturating_mul(2)); + ben.iter(|| e.saturating_mul(2)); + ben.iter(|| f.saturating_mul(-2)); + ben.iter(|| g.saturating_mul(-2)); + } + // endregion saturating arithmetic + + // region: trait impls + fn try_from_std_duration(ben: &mut Bencher<'_>) { + let a = 0.std_seconds(); + let b = 1.std_seconds(); + ben.iter(|| Duration::try_from(a)); + ben.iter(|| Duration::try_from(b)); + } + + fn try_to_std_duration(ben: &mut Bencher<'_>) { + let a = 0.seconds(); + let b = 1.seconds(); + let c = (-1).seconds(); + ben.iter(|| StdDuration::try_from(a)); + ben.iter(|| StdDuration::try_from(b)); + ben.iter(|| StdDuration::try_from(c)); + } + + fn add(ben: &mut Bencher<'_>) { + let a = 1.seconds(); + let b = 2.seconds(); + let c = 500.milliseconds(); + let d = (-1).seconds(); + ben.iter(|| a + b + c + d); + } + + fn add_std(ben: &mut Bencher<'_>) { + let a = 1.seconds(); + let b = 2.std_seconds(); + ben.iter(|| a + b); + } + + fn std_add(ben: &mut Bencher<'_>) { + let a = 1.std_seconds(); + let b = 2.seconds(); + ben.iter(|| a + b); + } + + fn add_assign(ben: &mut Bencher<'_>) { + let a = 1.seconds(); + let b = 500.milliseconds(); + let c = (-1).seconds(); + iter_batched_ref!( + ben, + || 1.seconds(), + [ + |duration| *duration += a, + |duration| *duration += b, + |duration| *duration += c, + ] + ); + } + + fn add_assign_std(ben: &mut Bencher<'_>) { + let a = 1.std_seconds(); + let b = 500.std_milliseconds(); + iter_batched_ref!( + ben, + || 1.seconds(), + [ + |duration| *duration += a, + |duration| *duration += b, + ] + ); + } + + fn neg(ben: &mut Bencher<'_>) { + let a = 1.seconds(); + let b = (-1).seconds(); + let c = 0.seconds(); + ben.iter(|| -a); + ben.iter(|| -b); + ben.iter(|| -c); + } + + fn sub(ben: &mut Bencher<'_>) { + let a = 1.seconds(); + let b = 1.seconds(); + let c = 1_500.milliseconds(); + let d = 500.milliseconds(); + let e = 1.seconds(); + let f = (-1).seconds(); + ben.iter(|| a - b); + ben.iter(|| b - c); + ben.iter(|| c - d); + ben.iter(|| d - e); + ben.iter(|| e - f); + ben.iter(|| f - a); + } + + fn sub_std(ben: &mut Bencher<'_>) { + let a = 1.seconds(); + let b = 2.std_seconds(); + ben.iter(|| a - b); + } + + fn std_sub(ben: &mut Bencher<'_>) { + let a = 1.std_seconds(); + let b = 2.seconds(); + ben.iter(|| a - b); + } + + fn sub_assign(ben: &mut Bencher<'_>) { + let a = 1.seconds(); + let b = 500.milliseconds(); + let c = (-1).seconds(); + iter_batched_ref!( + ben, + || 1.seconds(), + [ + |duration| *duration -= a, + |duration| *duration -= b, + |duration| *duration -= c, + ] + ); + } + + fn mul_int(ben: &mut Bencher<'_>) { + let d = 1.seconds(); + ben.iter(|| d * 2); + ben.iter(|| d * -2); + } + + fn mul_int_assign(ben: &mut Bencher<'_>) { + iter_batched_ref!( + ben, + || 1.seconds(), + [ + |duration| *duration *= 2, + |duration| *duration *= -2, + ] + ); + } + + fn int_mul(ben: &mut Bencher<'_>) { + let d = 1.seconds(); + ben.iter(|| 2 * d); + ben.iter(|| -2 * d); + } + + fn div_int(ben: &mut Bencher<'_>) { + let d = 1.seconds(); + ben.iter(|| d / 2); + ben.iter(|| d / -2); + } + + fn div_int_assign(ben: &mut Bencher<'_>) { + iter_batched_ref!( + ben, + || 1.seconds(), + [ + |duration| *duration /= 2, + |duration| *duration /= -2, + ] + ); + } + + fn div(ben: &mut Bencher<'_>) { + let a = 1.seconds(); + let b = 0.5.seconds(); + ben.iter(|| a / b); + } + + fn mul_float(ben: &mut Bencher<'_>) { + let d = 1.seconds(); + ben.iter(|| d * 1.5_f32); + ben.iter(|| d * 2.5_f32); + ben.iter(|| d * -1.5_f32); + ben.iter(|| d * 0_f32); + ben.iter(|| d * 1.5_f64); + ben.iter(|| d * 2.5_f64); + ben.iter(|| d * -1.5_f64); + ben.iter(|| d * 0_f64); + } + + fn float_mul(ben: &mut Bencher<'_>) { + let d = 1.seconds(); + ben.iter(|| 1.5_f32 * d); + ben.iter(|| 2.5_f32 * d); + ben.iter(|| -1.5_f32 * d); + ben.iter(|| 0_f32 * d); + ben.iter(|| 1.5_f64 * d); + ben.iter(|| 2.5_f64 * d); + ben.iter(|| -1.5_f64 * d); + ben.iter(|| 0_f64 * d); + } + + fn mul_float_assign(ben: &mut Bencher<'_>) { + iter_batched_ref!( + ben, + || 1.seconds(), + [ + |duration| *duration *= 1.5_f32, + |duration| *duration *= 2.5_f32, + |duration| *duration *= -1.5_f32, + |duration| *duration *= 3.15_f32, + |duration| *duration *= 1.5_f64, + |duration| *duration *= 2.5_f64, + |duration| *duration *= -1.5_f64, + |duration| *duration *= 0_f64, + ] + ); + } + + fn div_float(ben: &mut Bencher<'_>) { + let d = 1.seconds(); + ben.iter(|| d / 1_f32); + ben.iter(|| d / 2_f32); + ben.iter(|| d / -1_f32); + ben.iter(|| d / 1_f64); + ben.iter(|| d / 2_f64); + ben.iter(|| d / -1_f64); + } + + fn div_float_assign(ben: &mut Bencher<'_>) { + iter_batched_ref!( + ben, + || 10.seconds(), + [ + |duration| *duration /= 1_f32, + |duration| *duration /= 2_f32, + |duration| *duration /= -1_f32, + |duration| *duration /= 1_f64, + |duration| *duration /= 2_f64, + |duration| *duration /= -1_f64, + ] + ); + } + + fn partial_eq(ben: &mut Bencher<'_>) { + let a = 1.minutes(); + let b = (-1).minutes(); + let c = 40.seconds(); + ben.iter(|| a == b); + ben.iter(|| c == a); + } + + fn partial_eq_std(ben: &mut Bencher<'_>) { + let a = (-1).seconds(); + let b = 1.std_seconds(); + let c = (-1).minutes(); + let d = 1.std_minutes(); + let e = 40.seconds(); + ben.iter(|| a == b); + ben.iter(|| c == d); + ben.iter(|| e == d); + } + + fn std_partial_eq(ben: &mut Bencher<'_>) { + let a = 1.std_seconds(); + let b = (-1).seconds(); + let c = 1.std_minutes(); + let d = (-1).minutes(); + let e = 40.std_seconds(); + let f = 1.minutes(); + ben.iter(|| a == b); + ben.iter(|| c == d); + ben.iter(|| e == f); + } + + fn partial_ord(ben: &mut Bencher<'_>) { + let a = 0.seconds(); + let b = 1.seconds(); + let c = (-1).seconds(); + let d = 1.minutes(); + let e = (-1).minutes(); + ben.iter(|| a.partial_cmp(&a)); + ben.iter(|| b.partial_cmp(&a)); + ben.iter(|| b.partial_cmp(&c)); + ben.iter(|| c.partial_cmp(&b)); + ben.iter(|| a.partial_cmp(&c)); + ben.iter(|| a.partial_cmp(&b)); + ben.iter(|| c.partial_cmp(&a)); + ben.iter(|| d.partial_cmp(&b)); + ben.iter(|| e.partial_cmp(&c)); + } + + fn partial_ord_std(ben: &mut Bencher<'_>) { + let a = 0.seconds(); + let b = 0.std_seconds(); + let c = 1.seconds(); + let d = (-1).seconds(); + let e = 1.std_seconds(); + let f = 1.minutes(); + let g = u64::MAX.std_seconds(); + ben.iter(|| a.partial_cmp(&b)); + ben.iter(|| c.partial_cmp(&b)); + ben.iter(|| d.partial_cmp(&e)); + ben.iter(|| a.partial_cmp(&e)); + ben.iter(|| d.partial_cmp(&b)); + ben.iter(|| f.partial_cmp(&e)); + ben.iter(|| a.partial_cmp(&g)); + } + + fn std_partial_ord(ben: &mut Bencher<'_>) { + let a = 0.std_seconds(); + let b = 0.seconds(); + let c = 1.std_seconds(); + let d = (-1).seconds(); + let e = 1.seconds(); + let f = 1.std_minutes(); + ben.iter(|| a.partial_cmp(&b)); + ben.iter(|| c.partial_cmp(&b)); + ben.iter(|| c.partial_cmp(&d)); + ben.iter(|| a.partial_cmp(&d)); + ben.iter(|| a.partial_cmp(&e)); + ben.iter(|| f.partial_cmp(&e)); + } + + fn ord(ben: &mut Bencher<'_>) { + let a = 1.seconds(); + let b = 0.seconds(); + let c = (-1).seconds(); + let d = 1.minutes(); + let e = (-1).minutes(); + ben.iter(|| a > b); + ben.iter(|| a > c); + ben.iter(|| c < a); + ben.iter(|| b > c); + ben.iter(|| b < a); + ben.iter(|| c < b); + ben.iter(|| d > a); + ben.iter(|| e < c); + } + // endregion trait impls +} diff --git a/benchmarks/formatting.rs b/benchmarks/formatting.rs new file mode 100644 index 000000000..66dc5d179 --- /dev/null +++ b/benchmarks/formatting.rs @@ -0,0 +1,191 @@ +use std::io; + +use criterion::Bencher; +use time::format_description; +use time::format_description::well_known::{Rfc2822, Rfc3339}; +use time::macros::{date, datetime, format_description as fd, offset, time}; + +setup_benchmark! { + "Formatting", + + fn format_rfc3339(ben: &mut Bencher<'_>) { + macro_rules! item { + ($value:expr) => { + $value.format_into(&mut io::sink(), &Rfc3339) + } + } + + ben.iter(|| item!(datetime!(2021-01-02 03:04:05 UTC))); + ben.iter(|| item!(datetime!(2021-01-02 03:04:05.1 UTC))); + ben.iter(|| item!(datetime!(2021-01-02 03:04:05.12 UTC))); + ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123 UTC))); + ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123_4 UTC))); + ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123_45 UTC))); + ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123_456 UTC))); + ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123_456_7 UTC))); + ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123_456_78 UTC))); + ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123_456_789 UTC))); + ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123_456_789 -01:02))); + ben.iter(|| item!(datetime!(2021-01-02 03:04:05.123_456_789 +01:02))); + } + + fn format_rfc2822(ben: &mut Bencher<'_>) { + macro_rules! item { + ($value:expr) => { + $value.format_into(&mut io::sink(), &Rfc2822) + } + } + + ben.iter(|| item!(datetime!(2021-01-02 03:04:05 UTC))); + ben.iter(|| item!(datetime!(2021-01-02 03:04:05 +06:07))); + ben.iter(|| item!(datetime!(2021-01-02 03:04:05 -06:07))); + } + + + fn format_time(ben: &mut Bencher<'_>) { + macro_rules! item { + ($format:expr) => { + time!(13:02:03.456_789_012).format_into( + &mut io::sink(), + &$format, + ) + } + } + + ben.iter(|| item!(fd!("[hour]"))); + ben.iter(|| item!(fd!("[hour repr:12]"))); + ben.iter(|| item!(fd!("[hour repr:12 padding:none]"))); + ben.iter(|| item!(fd!("[hour repr:12 padding:space]"))); + ben.iter(|| item!(fd!("[hour repr:24]"))); + ben.iter(|| item!(fd!("[hour repr:24]"))); + ben.iter(|| item!(fd!("[hour repr:24 padding:none]"))); + ben.iter(|| item!(fd!("[hour repr:24 padding:space]"))); + ben.iter(|| item!(fd!("[minute]"))); + ben.iter(|| item!(fd!("[minute padding:none]"))); + ben.iter(|| item!(fd!("[minute padding:space]"))); + ben.iter(|| item!(fd!("[minute padding:zero]"))); + ben.iter(|| item!(fd!("[period]"))); + ben.iter(|| item!(fd!("[period case:upper]"))); + ben.iter(|| item!(fd!("[period case:lower]"))); + ben.iter(|| item!(fd!("[second]"))); + ben.iter(|| item!(fd!("[second padding:none]"))); + ben.iter(|| item!(fd!("[second padding:space]"))); + ben.iter(|| item!(fd!("[second padding:zero]"))); + ben.iter(|| item!(fd!("[subsecond]"))); + ben.iter(|| item!(fd!("[subsecond digits:1]"))); + ben.iter(|| item!(fd!("[subsecond digits:2]"))); + ben.iter(|| item!(fd!("[subsecond digits:3]"))); + ben.iter(|| item!(fd!("[subsecond digits:4]"))); + ben.iter(|| item!(fd!("[subsecond digits:5]"))); + ben.iter(|| item!(fd!("[subsecond digits:6]"))); + ben.iter(|| item!(fd!("[subsecond digits:7]"))); + ben.iter(|| item!(fd!("[subsecond digits:8]"))); + ben.iter(|| item!(fd!("[subsecond digits:9]"))); + ben.iter(|| item!(fd!("[subsecond digits:1+]"))); + } + + fn display_time(ben: &mut Bencher<'_>) { + ben.iter(|| time!(0:00).to_string()); + ben.iter(|| time!(23:59).to_string()); + ben.iter(|| time!(23:59:59).to_string()); + ben.iter(|| time!(0:00:01).to_string()); + ben.iter(|| time!(0:00:00.001).to_string()); + ben.iter(|| time!(0:00:00.000_001).to_string()); + ben.iter(|| time!(0:00:00.000_000_001).to_string()); + } + + fn format_date(ben: &mut Bencher<'_>) { + macro_rules! item { + ($format:expr) => { + date!(2019-12-31).format_into(&mut io::sink(), &$format) + } + } + + ben.iter(|| item!(fd!("[day]"))); + ben.iter(|| item!(fd!("[month]"))); + ben.iter(|| item!(fd!("[month repr:short]"))); + ben.iter(|| item!(fd!("[month repr:long]"))); + ben.iter(|| item!(fd!("[ordinal]"))); + ben.iter(|| item!(fd!("[weekday]"))); + ben.iter(|| item!(fd!("[weekday repr:short]"))); + ben.iter(|| item!(fd!("[weekday repr:sunday]"))); + ben.iter(|| item!(fd!("[weekday repr:sunday one_indexed:false]"))); + ben.iter(|| item!(fd!("[weekday repr:monday]"))); + ben.iter(|| item!(fd!("[weekday repr:monday one_indexed:false]"))); + ben.iter(|| item!(fd!("[week_number]"))); + ben.iter(|| item!(fd!("[week_number padding:none]"))); + ben.iter(|| item!(fd!("[week_number padding:space]"))); + ben.iter(|| item!(fd!("[week_number repr:sunday]"))); + ben.iter(|| item!(fd!("[week_number repr:monday]"))); + ben.iter(|| item!(fd!("[year]"))); + ben.iter(|| item!(fd!("[year base:iso_week]"))); + ben.iter(|| item!(fd!("[year sign:mandatory]"))); + ben.iter(|| item!(fd!("[year base:iso_week sign:mandatory]"))); + ben.iter(|| item!(fd!("[year repr:last_two]"))); + ben.iter(|| item!(fd!("[year base:iso_week repr:last_two]"))); + } + + fn display_date(ben: &mut Bencher<'_>) { + ben.iter(|| date!(2019-01-01).to_string()); + ben.iter(|| date!(2019-12-31).to_string()); + ben.iter(|| date!(-4713-11-24).to_string()); + ben.iter(|| date!(-0001-01-01).to_string()); + } + + fn format_offset(ben: &mut Bencher<'_>) { + macro_rules! item { + ($value:expr, $format:expr) => { + $value.format_into(&mut io::sink(), &$format) + } + } + + ben.iter(|| item!(offset!(+01:02:03), fd!("[offset_hour sign:automatic]"))); + ben.iter(|| item!(offset!(+01:02:03), fd!("[offset_hour sign:mandatory]"))); + ben.iter(|| item!(offset!(-01:02:03), fd!("[offset_hour sign:automatic]"))); + ben.iter(|| item!(offset!(-01:02:03), fd!("[offset_hour sign:mandatory]"))); + ben.iter(|| item!(offset!(+01:02:03), fd!("[offset_minute]"))); + ben.iter(|| item!(offset!(+01:02:03), fd!("[offset_second]"))); + } + + fn display_offset(ben: &mut Bencher<'_>) { + ben.iter(|| offset!(UTC).to_string()); + ben.iter(|| offset!(+0:00:01).to_string()); + ben.iter(|| offset!(-0:00:01).to_string()); + ben.iter(|| offset!(+1).to_string()); + ben.iter(|| offset!(-1).to_string()); + ben.iter(|| offset!(+23:59).to_string()); + ben.iter(|| offset!(-23:59).to_string()); + ben.iter(|| offset!(+23:59:59).to_string()); + ben.iter(|| offset!(-23:59:59).to_string()); + } + + fn format_pdt(ben: &mut Bencher<'_>) { + ben.iter(|| { + datetime!(1970-01-01 0:00).format_into( + &mut io::sink(), + fd!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"), + ) + }); + } + + fn display_pdt(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(1970-01-01 0:00).to_string()); + ben.iter(|| datetime!(1970-01-01 0:00:01).to_string()); + } + + fn format_odt(ben: &mut Bencher<'_>) { + // We can't currently handle escaped line breaks in the format description macro. + let format_description = format_description::parse( + "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond] [offset_hour \ + sign:mandatory]:[offset_minute]:[offset_second]", + ).expect("invalid format description"); + + ben.iter(|| { + datetime!(1970-01-01 0:00 UTC).format_into(&mut io::sink(), &format_description) + }); + } + + fn display_odt(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(1970-01-01 0:00 UTC).to_string()); + } +} diff --git a/benchmarks/instant.rs b/benchmarks/instant.rs new file mode 100644 index 000000000..f05af3487 --- /dev/null +++ b/benchmarks/instant.rs @@ -0,0 +1,93 @@ +#![allow(deprecated)] + +use std::time::Instant as StdInstant; + +use criterion::Bencher; +use time::ext::NumericalDuration; +use time::{Duration, Instant}; + +setup_benchmark! { + "Instant", + + // region: checked arithmetic + fn checked_add(ben: &mut Bencher<'_>) { + let instant = Instant::now(); + let dt = 5.seconds(); + ben.iter(|| instant.checked_add(dt)); + } + + fn checked_sub(ben: &mut Bencher<'_>) { + let instant = Instant::now(); + let dt = 5.seconds(); + ben.iter(|| instant.checked_sub(dt)); + } + // endregion checked arithmetic + + // region: trait impls + fn sub(ben: &mut Bencher<'_>) { + let start: Instant = Instant::now(); + let end: Instant = start + 1.milliseconds(); + ben.iter(|| end - start); + } + + fn add_duration(ben: &mut Bencher<'_>) { + let start = Instant::now(); + let dt: Duration = 1.seconds(); + ben.iter(|| start + dt); + } + + fn std_add_duration(ben: &mut Bencher<'_>) { + let start = StdInstant::now(); + let dt: Duration = 1.milliseconds(); + ben.iter(|| start + dt); + } + + fn add_assign_duration(ben: &mut Bencher<'_>) { + let dt: Duration = 1.milliseconds(); + iter_batched_ref!( + ben, + Instant::now, + [|start| *start += dt] + ); + } + + fn std_add_assign_duration(ben: &mut Bencher<'_>) { + let dt: Duration = 1.milliseconds(); + iter_batched_ref!( + ben, + StdInstant::now, + [|start| *start += dt] + ); + } + + fn sub_duration(ben: &mut Bencher<'_>) { + let instant = Instant::now(); + let dt: Duration = 100.milliseconds(); + ben.iter(|| instant - dt); + } + + fn std_sub_duration(ben: &mut Bencher<'_>) { + let instant = StdInstant::now(); + let dt: Duration = 100.milliseconds(); + ben.iter(|| instant - dt); + } + + fn sub_assign_duration(ben: &mut Bencher<'_>) { + let dt: Duration = 100.milliseconds(); + iter_batched_ref!( + ben, + Instant::now, + [|instant| *instant -= dt] + ); + } + + fn std_sub_assign_duration(ben: &mut Bencher<'_>) { + let dt: Duration = 100.milliseconds(); + iter_batched_ref!( + ben, + StdInstant::now, + [|instant| *instant -= dt] + ); + } + // endregion trait impls +} diff --git a/benchmarks/main.rs b/benchmarks/main.rs new file mode 100644 index 000000000..c9cb497fa --- /dev/null +++ b/benchmarks/main.rs @@ -0,0 +1,104 @@ +//! Benchmarks for `time`. +//! +//! These benchmarks are not very precise, but they're good enough to catch major performance +//! regressions. Run them if you think that may be the case. CI **does not** run benchmarks. + +#![allow( + clippy::missing_docs_in_private_items, + clippy::std_instead_of_core, // irrelevant for benchmarks + clippy::std_instead_of_alloc, // irrelevant for benchmarks + clippy::alloc_instead_of_core, // irrelevant for benchmarks +)] + +#[cfg(not(all( + feature = "default", + feature = "alloc", + feature = "formatting", + feature = "large-dates", + feature = "local-offset", + feature = "macros", + feature = "parsing", + feature = "quickcheck", + feature = "serde-human-readable", + feature = "serde-well-known", + feature = "std", + feature = "rand", + feature = "serde", + bench, +)))] +compile_error!( + "benchmarks must be run as `RUSTFLAGS=\"--cfg bench\" cargo criterion --all-features`" +); + +macro_rules! setup_benchmark { + ( + $group_prefix:literal, + $( + $(#[$fn_attr:meta])* + fn $fn_name:ident ($bencher:ident : $bencher_type:ty) + $code:block + )* + ) => { + $( + $(#[$fn_attr])* + fn $fn_name( + c: &mut ::criterion::Criterion + ) { + c.bench_function( + concat!($group_prefix, ": ", stringify!($fn_name)), + |$bencher: $bencher_type| $code + ); + } + )* + + ::criterion::criterion_group! { + name = benches; + config = ::criterion::Criterion::default() + // Set a stricter statistical significance threshold ("p-value") + // for deciding what's an actual performance change vs. noise. + // The more benchmarks, the lower this needs to be in order to + // not get lots of false positives. + .significance_level(0.0001) + // Ignore any performance change less than this (0.05 = 5%) as + // noise, regardless of statistical significance. + .noise_threshold(0.05) + // Reduce the time taken to run each benchmark + .warm_up_time(::std::time::Duration::from_millis(100)) + .measurement_time(::std::time::Duration::from_millis(400)); + targets = $($fn_name,)* + } + }; +} + +macro_rules! iter_batched_ref { + ($ben:ident, $initializer:expr,[$($routine:expr),+ $(,)?]) => {$( + $ben.iter_batched_ref( + $initializer, + $routine, + ::criterion::BatchSize::SmallInput, + ); + )+}; +} + +macro_rules! mods { + ($(mod $mod:ident;)+) => { + $(mod $mod;)+ + ::criterion::criterion_main!($($mod::benches),+); + } +} + +mods![ + mod date; + mod duration; + mod formatting; + mod instant; + mod month; + mod offset_date_time; + mod parsing; + mod primitive_date_time; + mod rand; + mod time; + mod utc_offset; + mod util; + mod weekday; +]; diff --git a/benchmarks/month.rs b/benchmarks/month.rs new file mode 100644 index 000000000..5bf4d10ee --- /dev/null +++ b/benchmarks/month.rs @@ -0,0 +1,36 @@ +use criterion::Bencher; +use time::Month::*; + +setup_benchmark! { + "Month", + + fn previous(ben: &mut Bencher<'_>) { + ben.iter(|| January.previous()); + ben.iter(|| February.previous()); + ben.iter(|| March.previous()); + ben.iter(|| April.previous()); + ben.iter(|| May.previous()); + ben.iter(|| June.previous()); + ben.iter(|| July.previous()); + ben.iter(|| August.previous()); + ben.iter(|| September.previous()); + ben.iter(|| October.previous()); + ben.iter(|| November.previous()); + ben.iter(|| December.previous()); + } + + fn next(ben: &mut Bencher<'_>) { + ben.iter(|| January.next()); + ben.iter(|| February.next()); + ben.iter(|| March.next()); + ben.iter(|| April.next()); + ben.iter(|| May.next()); + ben.iter(|| June.next()); + ben.iter(|| July.next()); + ben.iter(|| August.next()); + ben.iter(|| September.next()); + ben.iter(|| October.next()); + ben.iter(|| November.next()); + ben.iter(|| December.next()); + } +} diff --git a/benchmarks/offset_date_time.rs b/benchmarks/offset_date_time.rs new file mode 100644 index 000000000..92a9695eb --- /dev/null +++ b/benchmarks/offset_date_time.rs @@ -0,0 +1,429 @@ +use std::time::SystemTime; + +use criterion::Bencher; +use time::ext::{NumericalDuration, NumericalStdDuration}; +use time::macros::{date, datetime, offset, time}; +use time::OffsetDateTime; + +setup_benchmark! { + "OffsetDateTime", + + // region: now + fn now_utc(ben: &mut Bencher<'_>) { + ben.iter(OffsetDateTime::now_utc); + } + + fn now_local(ben: &mut Bencher<'_>) { + ben.iter(OffsetDateTime::now_local); + } + // endregion now + + fn to_offset(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2000-01-01 0:00 +11).to_offset(offset!(-5))); + ben.iter(|| datetime!(2000-01-01 0:00 +11).to_offset(offset!(-8))); + } + + // region: constructors + fn from_unix_timestamp(ben: &mut Bencher<'_>) { + ben.iter(|| OffsetDateTime::from_unix_timestamp(0)); + ben.iter(|| OffsetDateTime::from_unix_timestamp(1_546_300_800)); + } + + fn from_unix_timestamp_nanos(ben: &mut Bencher<'_>) { + ben.iter(|| OffsetDateTime::from_unix_timestamp_nanos(0)); + ben.iter(|| OffsetDateTime::from_unix_timestamp_nanos(1_546_300_800_000_000_000)); + } + // endregion constructors + + // region: getters + fn offset(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2019-01-01 0:00 UTC).offset()); + ben.iter(|| datetime!(2019-01-01 0:00 +1).offset()); + ben.iter(|| datetime!(2019-01-01 1:00 +1).offset()); + } + + fn unix_timestamp(ben: &mut Bencher<'_>) { + ben.iter(|| OffsetDateTime::UNIX_EPOCH.unix_timestamp()); + ben.iter(|| datetime!(1970-01-01 1:00 +1).unix_timestamp()); + ben.iter(|| datetime!(1970-01-01 0:00 -1).unix_timestamp()); + } + + fn unix_timestamp_nanos(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(1970-01-01 0:00 UTC).unix_timestamp_nanos()); + ben.iter(|| datetime!(1970-01-01 1:00 +1).unix_timestamp_nanos()); + ben.iter(|| datetime!(1970-01-01 0:00 -1).unix_timestamp_nanos()); + } + + fn date(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2019-01-01 0:00 UTC).date()); + ben.iter(|| datetime!(2018-12-31 23:00 -1).date()); + } + + fn time(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2019-01-01 0:00 UTC).time()); + ben.iter(|| datetime!(2018-12-31 23:00 -1).time()); + } + + fn year(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2019-01-01 0:00 UTC).year()); + ben.iter(|| datetime!(2018-12-31 23:00 -1).year()); + } + + fn ordinal(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2019-01-01 0:00 UTC).ordinal()); + ben.iter(|| datetime!(2018-12-31 23:00 -1).ordinal()); + } + + fn hour(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2019-01-01 0:00 UTC).hour()); + ben.iter(|| datetime!(2018-12-31 23:00 -1).hour()); + } + + fn minute(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2019-01-01 0:00 UTC).minute()); + ben.iter(|| datetime!(2018-12-31 23:00 -1).minute()); + } + + fn second(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2019-01-01 0:00 UTC).second()); + ben.iter(|| datetime!(2018-12-31 23:00 -1).second()); + } + // endregion getters + + // region: replacement + fn replace_time(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2020-01-01 5:00 UTC).replace_time(time!(12:00))); + ben.iter(|| datetime!(2020-01-01 12:00 -5).replace_time(time!(7:00))); + ben.iter(|| datetime!(2020-01-01 0:00 +1).replace_time(time!(12:00))); + } + + fn replace_date(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2020-01-01 12:00 UTC).replace_date(date!(2020-01-30))); + ben.iter(|| datetime!(2020-01-01 0:00 +1).replace_date(date!(2020-01-30))); + } + + fn replace_date_time(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2020-01-01 12:00 UTC).replace_date_time(datetime!(2020-01-30 16:00))); + ben.iter(|| datetime!(2020-01-01 12:00 +1).replace_date_time(datetime!(2020-01-30 0:00))); + } + + fn replace_offset(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2020-01-01 0:00 UTC).replace_offset(offset!(-5))); + } + // endregion replacement + + // region: trait impls + fn partial_eq(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(1999-12-31 23:00 -1) == datetime!(2000-01-01 0:00 UTC)); + } + + fn partial_ord(ben: &mut Bencher<'_>) { + ben.iter(|| + datetime!(2019-01-01 0:00 UTC).partial_cmp(&datetime!(1999-12-31 23:00 -1)) + ); + } + + fn ord(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2019-01-01 0:00 UTC) == datetime!(2018-12-31 23:00 -1)); + ben.iter(|| datetime!(2019-01-01 0:00:00.000_000_001 UTC) > datetime!(2019-01-01 0:00 UTC)); + } + + fn hash(ben: &mut Bencher<'_>) { + use std::collections::hash_map::DefaultHasher; + use std::hash::Hash; + + iter_batched_ref!( + ben, + DefaultHasher::new, + [ + |hasher| datetime!(2019-01-01 0:00 UTC).hash(hasher), + |hasher| datetime!(2018-12-31 23:00 -1).hash(hasher), + ] + ); + } + + fn add_duration(ben: &mut Bencher<'_>) { + let a = 5.days(); + let b = 1.days(); + let c = 2.seconds(); + let d = (-2).seconds(); + let e = 1.hours(); + + ben.iter(|| datetime!(2019-01-01 0:00 UTC) + a); + ben.iter(|| datetime!(2019-12-31 0:00 UTC) + b); + ben.iter(|| datetime!(2019-12-31 23:59:59 UTC) + c); + ben.iter(|| datetime!(2020-01-01 0:00:01 UTC) + d); + ben.iter(|| datetime!(1999-12-31 23:00 UTC) + e); + } + + fn add_std_duration(ben: &mut Bencher<'_>) { + let a = 5.std_days(); + let b = 1.std_days(); + let c = 2.std_seconds(); + + ben.iter(|| datetime!(2019-01-01 0:00 UTC) + a); + ben.iter(|| datetime!(2019-12-31 0:00 UTC) + b); + ben.iter(|| datetime!(2019-12-31 23:59:59 UTC) + c); + } + + fn add_assign_duration(ben: &mut Bencher<'_>) { + let a = 1.days(); + let b = 1.seconds(); + iter_batched_ref!( + ben, + || datetime!(2019-01-01 0:00 UTC), + [ + |datetime| *datetime += a, + |datetime| *datetime += b, + ] + ); + } + + fn add_assign_std_duration(ben: &mut Bencher<'_>) { + let a = 1.std_days(); + let b = 1.std_seconds(); + iter_batched_ref!( + ben, + || datetime!(2019-01-01 0:00 UTC), + [ + |datetime| *datetime += a, + |datetime| *datetime += b, + ] + ); + } + + fn sub_duration(ben: &mut Bencher<'_>) { + let a = 5.days(); + let b = 1.days(); + let c = 2.seconds(); + + ben.iter(|| datetime!(2019-01-06 0:00 UTC) - a); + ben.iter(|| datetime!(2020-01-01 0:00 UTC) - b); + ben.iter(|| datetime!(2020-01-01 0:00:01 UTC) - c); + } + + fn sub_std_duration(ben: &mut Bencher<'_>) { + let a = 5.std_days(); + let b = 1.std_days(); + let c = 2.std_seconds(); + + ben.iter(|| datetime!(2019-01-06 0:00 UTC) - a); + ben.iter(|| datetime!(2020-01-01 0:00 UTC) - b); + ben.iter(|| datetime!(2020-01-01 0:00:01 UTC) - c); + } + + fn sub_assign_duration(ben: &mut Bencher<'_>) { + let a = 1.days(); + let b = 1.seconds(); + iter_batched_ref!( + ben, + || datetime!(2019-01-01 0:00 UTC), + [ + |datetime| *datetime -= a, + |datetime| *datetime -= b, + ] + ); + } + + fn sub_assign_std_duration(ben: &mut Bencher<'_>) { + let a = 1.std_days(); + let b = 1.std_seconds(); + iter_batched_ref!( + ben, + || datetime!(2019-01-01 0:00 UTC), + [ + |datetime| *datetime -= a, + |datetime| *datetime -= b, + ] + ); + } + + fn std_add_duration(ben: &mut Bencher<'_>) { + let a1 = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let a2 = 0.seconds(); + let b1 = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let b2 = 5.days(); + let c1 = SystemTime::from(datetime!(2019-12-31 0:00 UTC)); + let c2 = 1.days(); + let d1 = SystemTime::from(datetime!(2019-12-31 23:59:59 UTC)); + let d2 = 2.seconds(); + let e1 = SystemTime::from(datetime!(2020-01-01 0:00:01 UTC)); + let e2 = (-2).seconds(); + ben.iter(|| a1 + a2); + ben.iter(|| b1 + b2); + ben.iter(|| c1 + c2); + ben.iter(|| d1 + d2); + ben.iter(|| e1 + e2); + } + + fn std_add_assign_duration(ben: &mut Bencher<'_>) { + let a = 1.days(); + let b = 1.seconds(); + iter_batched_ref!( + ben, + || SystemTime::from(datetime!(2019-01-01 0:00 UTC)), + [ + |datetime| *datetime += a, + |datetime| *datetime += b, + ] + ); + } + + fn std_sub_duration(ben: &mut Bencher<'_>) { + let a1 = SystemTime::from(datetime!(2019-01-06 0:00 UTC)); + let a2 = 5.days(); + let b1 = SystemTime::from(datetime!(2020-01-01 0:00 UTC)); + let b2 = 1.days(); + let c1 = SystemTime::from(datetime!(2020-01-01 0:00:01 UTC)); + let c2 = 2.seconds(); + let d1 = SystemTime::from(datetime!(2019-12-31 23:59:59 UTC)); + let d2 = (-2).seconds(); + ben.iter(|| a1 - a2); + ben.iter(|| b1 - b2); + ben.iter(|| c1 - c2); + ben.iter(|| d1 - d2); + } + + fn std_sub_assign_duration(ben: &mut Bencher<'_>) { + let a = 1.days(); + let b = 1.seconds(); + iter_batched_ref!( + ben, + || SystemTime::from(datetime!(2019-01-01 0:00 UTC)), + [ + |datetime| *datetime -= a, + |datetime| *datetime -= b, + ] + ); + } + + fn sub_self(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2019-01-02 0:00 UTC) - datetime!(2019-01-01 0:00 UTC)); + ben.iter(|| datetime!(2019-01-01 0:00 UTC) - datetime!(2019-01-02 0:00 UTC)); + ben.iter(|| datetime!(2020-01-01 0:00 UTC) - datetime!(2019-12-31 0:00 UTC)); + ben.iter(|| datetime!(2019-12-31 0:00 UTC) - datetime!(2020-01-01 0:00 UTC)); + } + + fn std_sub(ben: &mut Bencher<'_>) { + let a = SystemTime::from(datetime!(2019-01-02 0:00 UTC)); + let b = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let c = SystemTime::from(datetime!(2020-01-01 0:00 UTC)); + let d = SystemTime::from(datetime!(2019-12-31 0:00 UTC)); + + ben.iter(|| a - datetime!(2019-01-01 0:00 UTC)); + ben.iter(|| b - datetime!(2019-01-02 0:00 UTC)); + ben.iter(|| c - datetime!(2019-12-31 0:00 UTC)); + ben.iter(|| d - datetime!(2020-01-01 0:00 UTC)); + } + + fn sub_std(ben: &mut Bencher<'_>) { + let a = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let b = SystemTime::from(datetime!(2019-01-02 0:00 UTC)); + let c = SystemTime::from(datetime!(2019-12-31 0:00 UTC)); + let d = SystemTime::from(datetime!(2020-01-01 0:00 UTC)); + + ben.iter(|| datetime!(2019-01-02 0:00 UTC) - a); + ben.iter(|| datetime!(2019-01-01 0:00 UTC) - b); + ben.iter(|| datetime!(2020-01-01 0:00 UTC) - c); + ben.iter(|| datetime!(2019-12-31 0:00 UTC) - d); + } + + fn eq_std(ben: &mut Bencher<'_>) { + let a = OffsetDateTime::now_utc(); + let b = SystemTime::from(a); + ben.iter(|| a == b); + } + + fn std_eq(ben: &mut Bencher<'_>) { + let a = OffsetDateTime::now_utc(); + let b = SystemTime::from(a); + ben.iter(|| b == a); + } + + fn ord_std(ben: &mut Bencher<'_>) { + let a = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let b = SystemTime::from(datetime!(2020-01-01 0:00 UTC)); + let c = SystemTime::from(datetime!(2019-02-01 0:00 UTC)); + let d = SystemTime::from(datetime!(2019-01-02 0:00 UTC)); + let e = SystemTime::from(datetime!(2019-01-01 1:00:00 UTC)); + let f = SystemTime::from(datetime!(2019-01-01 0:01:00 UTC)); + let g = SystemTime::from(datetime!(2019-01-01 0:00:01 UTC)); + let h = SystemTime::from(datetime!(2019-01-01 0:00:00.001 UTC)); + let i = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let j = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let k = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let l = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let m = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let n = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let o = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + + ben.iter(|| datetime!(2019-01-01 0:00 UTC) == a); + ben.iter(|| datetime!(2019-01-01 0:00 UTC) < b); + ben.iter(|| datetime!(2019-01-01 0:00 UTC) < c); + ben.iter(|| datetime!(2019-01-01 0:00 UTC) < d); + ben.iter(|| datetime!(2019-01-01 0:00 UTC) < e); + ben.iter(|| datetime!(2019-01-01 0:00 UTC) < f); + ben.iter(|| datetime!(2019-01-01 0:00 UTC) < g); + ben.iter(|| datetime!(2019-01-01 0:00 UTC) < h); + ben.iter(|| datetime!(2020-01-01 0:00 UTC) > i); + ben.iter(|| datetime!(2019-02-01 0:00 UTC) > j); + ben.iter(|| datetime!(2019-01-02 0:00 UTC) > k); + ben.iter(|| datetime!(2019-01-01 1:00:00 UTC) > l); + ben.iter(|| datetime!(2019-01-01 0:01:00 UTC) > m); + ben.iter(|| datetime!(2019-01-01 0:00:01 UTC) > n); + ben.iter(|| datetime!(2019-01-01 0:00:00.000_000_001 UTC) > o); + } + + fn std_ord(ben: &mut Bencher<'_>) { + let a = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let b = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let c = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let d = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let e = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let f = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let g = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let h = SystemTime::from(datetime!(2019-01-01 0:00 UTC)); + let i = SystemTime::from(datetime!(2020-01-01 0:00 UTC)); + let j = SystemTime::from(datetime!(2019-02-01 0:00 UTC)); + let k = SystemTime::from(datetime!(2019-01-02 0:00 UTC)); + let l = SystemTime::from(datetime!(2019-01-01 1:00:00 UTC)); + let m = SystemTime::from(datetime!(2019-01-01 0:01:00 UTC)); + let n = SystemTime::from(datetime!(2019-01-01 0:00:01 UTC)); + let o = SystemTime::from(datetime!(2019-01-01 0:00:00.001 UTC)); + + ben.iter(|| a == datetime!(2019-01-01 0:00 UTC)); + ben.iter(|| b < datetime!(2020-01-01 0:00 UTC)); + ben.iter(|| c < datetime!(2019-02-01 0:00 UTC)); + ben.iter(|| d < datetime!(2019-01-02 0:00 UTC)); + ben.iter(|| e < datetime!(2019-01-01 1:00:00 UTC)); + ben.iter(|| f < datetime!(2019-01-01 0:01:00 UTC)); + ben.iter(|| g < datetime!(2019-01-01 0:00:01 UTC)); + ben.iter(|| h < datetime!(2019-01-01 0:00:00.000_000_001 UTC)); + ben.iter(|| i > datetime!(2019-01-01 0:00 UTC)); + ben.iter(|| j > datetime!(2019-01-01 0:00 UTC)); + ben.iter(|| k > datetime!(2019-01-01 0:00 UTC)); + ben.iter(|| l > datetime!(2019-01-01 0:00 UTC)); + ben.iter(|| m > datetime!(2019-01-01 0:00 UTC)); + ben.iter(|| n > datetime!(2019-01-01 0:00 UTC)); + ben.iter(|| o > datetime!(2019-01-01 0:00 UTC)); + } + + fn from_std(ben: &mut Bencher<'_>) { + let a = SystemTime::UNIX_EPOCH; + let b = SystemTime::UNIX_EPOCH - 1.std_days(); + let c = SystemTime::UNIX_EPOCH + 1.std_days(); + ben.iter(|| OffsetDateTime::from(a)); + ben.iter(|| OffsetDateTime::from(b)); + ben.iter(|| OffsetDateTime::from(c)); + } + + fn to_std(ben: &mut Bencher<'_>) { + let a = OffsetDateTime::UNIX_EPOCH; + let b = OffsetDateTime::UNIX_EPOCH + 1.days(); + let c = OffsetDateTime::UNIX_EPOCH - 1.days(); + ben.iter(|| SystemTime::from(a)); + ben.iter(|| SystemTime::from(b)); + ben.iter(|| SystemTime::from(c)); + } + // endregion trait impls +} diff --git a/benchmarks/parsing.rs b/benchmarks/parsing.rs new file mode 100644 index 000000000..da5dde9f5 --- /dev/null +++ b/benchmarks/parsing.rs @@ -0,0 +1,224 @@ +use criterion::Bencher; +use time::format_description::well_known::{Rfc2822, Rfc3339}; +use time::format_description::{modifier, Component}; +use time::parsing::Parsed; +use time::OffsetDateTime; + +macro_rules! component { + ($name:ident {$($field:ident : $value:expr),+ $(,)? }) => {{ + const COMPONENT: Component = Component::$name({ + let mut modifier = modifier::$name::default(); + $(modifier.$field = $value;)+ + modifier + }); + COMPONENT + }}; +} + +setup_benchmark! { + "Parsing", + + fn parse_component_year(ben: &mut Bencher<'_>) { + let mut parsed = Parsed::new(); + ben.iter(|| { + parsed.parse_component(b"2021", component!(Year { + padding: modifier::Padding::Zero, + repr: modifier::YearRepr::Full, + iso_week_based: false, + sign_is_mandatory: false, + })) + }); + ben.iter(|| { + parsed.parse_component(b"21", component!(Year { + padding: modifier::Padding::Zero, + repr: modifier::YearRepr::LastTwo, + iso_week_based: false, + sign_is_mandatory: false, + })) + }); + ben.iter(|| { + parsed.parse_component(b"2021", component!(Year { + padding: modifier::Padding::Zero, + repr: modifier::YearRepr::Full, + iso_week_based: true, + sign_is_mandatory: false, + })) + }); + ben.iter(|| { + parsed.parse_component(b"21", component!(Year { + padding: modifier::Padding::Zero, + repr: modifier::YearRepr::LastTwo, + iso_week_based: true, + sign_is_mandatory: false, + })) + }); + } + + fn parse_component_month(ben: &mut Bencher<'_>) { + let mut parsed = Parsed::new(); + ben.iter(|| { + parsed.parse_component(b" 1", component!(Month { + padding: modifier::Padding::Space, + repr: modifier::MonthRepr::Numerical, + })) + }); + ben.iter(|| { + parsed.parse_component(b"Jan", component!(Month { + padding: modifier::Padding::None, + repr: modifier::MonthRepr::Short, + })) + }); + ben.iter(|| { + parsed.parse_component(b"January", component!(Month { + padding: modifier::Padding::None, + repr: modifier::MonthRepr::Long, + })) + }); + } + + fn parse_component_ordinal(ben: &mut Bencher<'_>) { + let mut parsed = Parsed::new(); + ben.iter(|| { + parsed.parse_component(b"012", component!(Ordinal { + padding: modifier::Padding::Zero, + })) + }); + } + + fn parse_component_weekday(ben: &mut Bencher<'_>) { + let mut parsed = Parsed::new(); + ben.iter(|| { + parsed.parse_component(b"Sun", component!(Weekday { + repr: modifier::WeekdayRepr::Short, + one_indexed: false, + })) + }); + ben.iter(|| { + parsed.parse_component(b"Sunday", component!(Weekday { + repr: modifier::WeekdayRepr::Long, + one_indexed: false, + })) + }); + ben.iter(|| { + parsed.parse_component(b"0", component!(Weekday { + repr: modifier::WeekdayRepr::Sunday, + one_indexed: false, + })) + }); + ben.iter(|| { + parsed.parse_component(b"1", component!(Weekday { + repr: modifier::WeekdayRepr::Sunday, + one_indexed: true, + })) + }); + ben.iter(|| { + parsed.parse_component(b"6", component!(Weekday { + repr: modifier::WeekdayRepr::Monday, + one_indexed: false, + })) + }); + ben.iter(|| { + parsed.parse_component(b"7", component!(Weekday { + repr: modifier::WeekdayRepr::Monday, + one_indexed: true, + })) + }); + } + + fn parse_component_week_number(ben: &mut Bencher<'_>) { + let mut parsed = Parsed::new(); + ben.iter(|| { + parsed.parse_component(b"2", component!(WeekNumber { + padding: modifier::Padding::None, + repr: modifier::WeekNumberRepr::Sunday, + })) + }); + ben.iter(|| { + parsed.parse_component(b"2", component!(WeekNumber { + padding: modifier::Padding::None, + repr: modifier::WeekNumberRepr::Monday, + })) + }); + ben.iter(|| { + parsed.parse_component(b"2", component!(WeekNumber { + padding: modifier::Padding::None, + repr: modifier::WeekNumberRepr::Iso, + })) + }); + } + + fn parse_component_subsecond(ben: &mut Bencher<'_>) { + let mut parsed = Parsed::new(); + ben.iter(|| { + parsed.parse_component(b"1", component!(Subsecond { + digits: modifier::SubsecondDigits::One, + })) + }); + ben.iter(|| { + parsed.parse_component(b"12", component!(Subsecond { + digits: modifier::SubsecondDigits::Two, + })) + }); + ben.iter(|| { + parsed.parse_component(b"123", component!(Subsecond { + digits: modifier::SubsecondDigits::Three, + })) + }); + ben.iter(|| { + parsed.parse_component(b"1234", component!(Subsecond { + digits: modifier::SubsecondDigits::Four, + })) + }); + ben.iter(|| { + parsed.parse_component(b"12345", component!(Subsecond { + digits: modifier::SubsecondDigits::Five, + })) + }); + ben.iter(|| { + parsed.parse_component(b"123456", component!(Subsecond { + digits: modifier::SubsecondDigits::Six, + })) + }); + ben.iter(|| { + parsed.parse_component(b"1234567", component!(Subsecond { + digits: modifier::SubsecondDigits::Seven, + })) + }); + ben.iter(|| { + parsed.parse_component(b"12345678", component!(Subsecond { + digits: modifier::SubsecondDigits::Eight, + })) + }); + ben.iter(|| { + parsed.parse_component(b"123456789", component!(Subsecond { + digits: modifier::SubsecondDigits::Nine, + })) + }); + ben.iter(|| { + parsed.parse_component(b"123456789", component!(Subsecond { + digits: modifier::SubsecondDigits::OneOrMore, + })) + }); + } + + fn parse_rfc3339(ben: &mut Bencher<'_>) { + ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05Z", &Rfc3339)); + ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.1Z", &Rfc3339)); + ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.12Z", &Rfc3339)); + ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.123Z", &Rfc3339)); + ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.1234Z", &Rfc3339)); + ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.12345Z", &Rfc3339)); + ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.123456Z", &Rfc3339)); + ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.1234567Z", &Rfc3339)); + ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.12345678Z", &Rfc3339)); + ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.123456789Z", &Rfc3339)); + ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.123456789-01:02", &Rfc3339)); + ben.iter(|| OffsetDateTime::parse("2021-01-02T03:04:05.123456789+01:02", &Rfc3339)); + } + + fn parse_rfc2822(ben: &mut Bencher<'_>) { + ben.iter(|| OffsetDateTime::parse("Sat, 02 Jan 2021 03:04:05 +0000", &Rfc2822)); + ben.iter(|| OffsetDateTime::parse("Sat, 02 Jan 2021 03:04:05 +0607", &Rfc2822)); + ben.iter(|| OffsetDateTime::parse("Sat, 02 Jan 2021 03:04:05 -0607", &Rfc2822)); + } +} diff --git a/benchmarks/primitive_date_time.rs b/benchmarks/primitive_date_time.rs new file mode 100644 index 000000000..a45130ee2 --- /dev/null +++ b/benchmarks/primitive_date_time.rs @@ -0,0 +1,148 @@ +use criterion::Bencher; +use time::ext::{NumericalDuration, NumericalStdDuration}; +use time::macros::{datetime, offset}; + +setup_benchmark! { + "PrimitiveDateTime", + + // All getters are trivially dispatched to the relevant field, and do not need to be benchmarked + // a second time. + + // region: attach offset + fn assume_offset(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2019-01-01 0:00).assume_offset(offset!(UTC))); + ben.iter(|| datetime!(2019-01-01 0:00).assume_offset(offset!(-1))); + } + + fn assume_utc(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2019-01-01 0:00).assume_utc()); + } + // endregion attach offset + + // region: trait impls + fn add_duration(ben: &mut Bencher<'_>) { + let a = 5.days(); + let b = 1.days(); + let c = 2.seconds(); + let d = (-2).seconds(); + let e = 1.hours(); + + ben.iter(|| datetime!(2019-01-01 0:00) + a); + ben.iter(|| datetime!(2019-12-31 0:00) + b); + ben.iter(|| datetime!(2019-12-31 23:59:59) + c); + ben.iter(|| datetime!(2020-01-01 0:00:01) + d); + ben.iter(|| datetime!(1999-12-31 23:00) + e); + } + + fn add_std_duration(ben: &mut Bencher<'_>) { + let a = 5.std_days(); + let b = 1.std_days(); + let c = 2.std_seconds(); + + ben.iter(|| datetime!(2019-01-01 0:00) + a); + ben.iter(|| datetime!(2019-12-31 0:00) + b); + ben.iter(|| datetime!(2019-12-31 23:59:59) + c); + } + + fn add_assign_duration(ben: &mut Bencher<'_>) { + let a = 1.days(); + let b = 1.seconds(); + iter_batched_ref!( + ben, + || datetime!(2019-01-01 0:00), + [ + |datetime| *datetime += a, + |datetime| *datetime += b, + ] + ); + } + + fn add_assign_std_duration(ben: &mut Bencher<'_>) { + let a = 1.std_days(); + let b = 1.std_seconds(); + iter_batched_ref!( + ben, + || datetime!(2019-01-01 0:00), + [ + |datetime| *datetime += a, + |datetime| *datetime += b, + ] + ); + } + + fn sub_duration(ben: &mut Bencher<'_>) { + let a = 5.days(); + let b = 1.days(); + let c = 2.seconds(); + let d = (-2).seconds(); + let e = (-1).hours(); + + ben.iter(|| datetime!(2019-01-06 0:00) - a); + ben.iter(|| datetime!(2020-01-01 0:00) - b); + ben.iter(|| datetime!(2020-01-01 0:00:01) - c); + ben.iter(|| datetime!(2019-12-31 23:59:59) - d); + ben.iter(|| datetime!(1999-12-31 23:00) - e); + } + + fn sub_std_duration(ben: &mut Bencher<'_>) { + let a = 5.std_days(); + let b = 1.std_days(); + let c = 2.std_seconds(); + + ben.iter(|| datetime!(2019-01-06 0:00) - a); + ben.iter(|| datetime!(2020-01-01 0:00) - b); + ben.iter(|| datetime!(2020-01-01 0:00:01) - c); + } + + fn sub_assign_duration(ben: &mut Bencher<'_>) { + let a = 1.days(); + let b = 1.seconds(); + iter_batched_ref!( + ben, + || datetime!(2019-01-01 0:00), + [ + |datetime| *datetime -= a, + |datetime| *datetime -= b, + ] + ); + } + + fn sub_assign_std_duration(ben: &mut Bencher<'_>) { + let a = 1.std_days(); + let b = 1.std_seconds(); + iter_batched_ref!( + ben, + || datetime!(2019-01-01 0:00), + [ + |datetime| *datetime -= a, + |datetime| *datetime -= b, + ] + ); + } + + fn sub_datetime(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2019-01-02 0:00) - datetime!(2019-01-01 0:00)); + ben.iter(|| datetime!(2019-01-01 0:00) - datetime!(2019-01-02 0:00)); + ben.iter(|| datetime!(2020-01-01 0:00) - datetime!(2019-12-31 0:00)); + ben.iter(|| datetime!(2019-12-31 0:00) - datetime!(2020-01-01 0:00)); + } + + fn ord(ben: &mut Bencher<'_>) { + ben.iter(|| datetime!(2019-01-01 0:00).partial_cmp(&datetime!(2019-01-01 0:00))); + ben.iter(|| datetime!(2019-01-01 0:00).partial_cmp(&datetime!(2020-01-01 0:00))); + ben.iter(|| datetime!(2019-01-01 0:00).partial_cmp(&datetime!(2019-02-01 0:00))); + ben.iter(|| datetime!(2019-01-01 0:00).partial_cmp(&datetime!(2019-01-02 0:00))); + ben.iter(|| datetime!(2019-01-01 0:00).partial_cmp(&datetime!(2019-01-01 1:00))); + ben.iter(|| datetime!(2019-01-01 0:00).partial_cmp(&datetime!(2019-01-01 0:01))); + ben.iter(|| datetime!(2019-01-01 0:00).partial_cmp(&datetime!(2019-01-01 0:00:01))); + ben.iter(|| datetime!(2019-01-01 0:00).partial_cmp(&datetime!(2019-01-01 0:00:00.000_000_001))); + ben.iter(|| datetime!(2020-01-01 0:00).partial_cmp(&datetime!(2019-01-01 0:00))); + ben.iter(|| datetime!(2019-02-01 0:00).partial_cmp(&datetime!(2019-01-01 0:00))); + ben.iter(|| datetime!(2019-01-02 0:00).partial_cmp(&datetime!(2019-01-01 0:00))); + ben.iter(|| datetime!(2019-01-01 1:00).partial_cmp(&datetime!(2019-01-01 0:00))); + ben.iter(|| datetime!(2019-01-01 0:01).partial_cmp(&datetime!(2019-01-01 0:00))); + ben.iter(|| datetime!(2019-01-01 0:00:01).partial_cmp(&datetime!(2019-01-01 0:00))); + ben.iter(|| datetime!(2019-01-01 0:00:00.000_000_001).partial_cmp(&datetime!(2019-01-01 0:00))); + } + // endregion trait impls +} diff --git a/benchmarks/rand.rs b/benchmarks/rand.rs new file mode 100644 index 000000000..37a8df0e9 --- /dev/null +++ b/benchmarks/rand.rs @@ -0,0 +1,30 @@ +use criterion::Bencher; +use rand::rngs::mock::StepRng; +use rand::Rng; +use time::{Date, Duration, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, Weekday}; + +macro_rules! bench_rand { + ($($name:ident : $type:ty),* $(,)?) => { + setup_benchmark! { + "Random", + $(fn $name(ben: &mut Bencher<'_>) { + iter_batched_ref!( + ben, + || StepRng::new(0, 1), + [|rng| rng.r#gen::<$type>()] + ); + })* + } + } +} + +bench_rand![ + time: Time, + date: Date, + utc_offset: UtcOffset, + primitive_date_time: PrimitiveDateTime, + offset_date_time: OffsetDateTime, + duration: Duration, + weekday: Weekday, + month: Month, +]; diff --git a/benchmarks/time.rs b/benchmarks/time.rs new file mode 100644 index 000000000..884e173e8 --- /dev/null +++ b/benchmarks/time.rs @@ -0,0 +1,271 @@ +use std::hint::black_box; + +use criterion::Bencher; +use time::ext::{NumericalDuration, NumericalStdDuration}; +use time::macros::time; +use time::Time; + +setup_benchmark! { + "Time", + + // region: constructors + fn from_hms(ben: &mut Bencher<'_>) { + ben.iter(|| Time::from_hms(1, 2, 3)); + } + + fn from_hms_milli(ben: &mut Bencher<'_>) { + ben.iter(|| Time::from_hms_milli(1, 2, 3, 4)); + } + + fn from_hms_micro(ben: &mut Bencher<'_>) { + ben.iter(|| Time::from_hms_micro(1, 2, 3, 4)); + } + + fn from_hms_nano(ben: &mut Bencher<'_>) { + ben.iter(|| Time::from_hms_nano(1, 2, 3, 4)); + } + // endregion constructors + + // region: getters + fn as_hms(ben: &mut Bencher<'_>) { + ben.iter(|| Time::MIDNIGHT.as_hms()); + } + + fn as_hms_milli(ben: &mut Bencher<'_>) { + ben.iter(|| Time::MIDNIGHT.as_hms_milli()); + } + + fn as_hms_micro(ben: &mut Bencher<'_>) { + ben.iter(|| Time::MIDNIGHT.as_hms_micro()); + } + + fn as_hms_nano(ben: &mut Bencher<'_>) { + ben.iter(|| Time::MIDNIGHT.as_hms_nano()); + } + + fn hour(ben: &mut Bencher<'_>) { + ben.iter(|| Time::MIDNIGHT.hour()); + } + + fn minute(ben: &mut Bencher<'_>) { + ben.iter(|| Time::MIDNIGHT.minute()); + } + + fn second(ben: &mut Bencher<'_>) { + ben.iter(|| Time::MIDNIGHT.second()); + } + + fn millisecond(ben: &mut Bencher<'_>) { + ben.iter(|| Time::MIDNIGHT.millisecond()); + } + + fn microsecond(ben: &mut Bencher<'_>) { + ben.iter(|| Time::MIDNIGHT.microsecond()); + } + + fn nanosecond(ben: &mut Bencher<'_>) { + ben.iter(|| Time::MIDNIGHT.nanosecond()); + } + // endregion getters + + // region: trait impls + fn add_duration(ben: &mut Bencher<'_>) { + let a = 1.milliseconds(); + let b = 1.seconds(); + let c = 1.minutes(); + let d = 1.hours(); + let e = 1.days(); + ben.iter(|| Time::MIDNIGHT + a); + ben.iter(|| Time::MIDNIGHT + b); + ben.iter(|| Time::MIDNIGHT + c); + ben.iter(|| Time::MIDNIGHT + d); + ben.iter(|| Time::MIDNIGHT + e); + } + + fn add_assign_duration(ben: &mut Bencher<'_>) { + let a = 1.milliseconds(); + let b = 1.seconds(); + let c = 1.minutes(); + let d = 1.hours(); + let e = 1.days(); + iter_batched_ref!( + ben, + || Time::MIDNIGHT, + [ + |time| *time += a, + |time| *time += b, + |time| *time += c, + |time| *time += d, + |time| *time += e, + ] + ); + } + + fn sub_duration(ben: &mut Bencher<'_>) { + let a = 1.milliseconds(); + let b = 1.seconds(); + let c = 1.minutes(); + let d = 1.hours(); + let e = 1.days(); + ben.iter(|| Time::MIDNIGHT - a); + ben.iter(|| Time::MIDNIGHT - b); + ben.iter(|| Time::MIDNIGHT - c); + ben.iter(|| Time::MIDNIGHT - d); + ben.iter(|| Time::MIDNIGHT - e); + } + + fn sub_assign_duration(ben: &mut Bencher<'_>) { + let a = 1.milliseconds(); + let b = 1.seconds(); + let c = 1.minutes(); + let d = 1.hours(); + let e = 1.days(); + iter_batched_ref!( + ben, + || Time::MIDNIGHT, + [ + |time| *time -= a, + |time| *time -= b, + |time| *time -= c, + |time| *time -= d, + |time| *time -= e, + ] + ); + } + + fn add_std_duration(ben: &mut Bencher<'_>) { + let a = 1.std_milliseconds(); + let b = 1.std_seconds(); + let c = 1.std_minutes(); + let d = 1.std_hours(); + let e = 1.std_days(); + ben.iter(|| Time::MIDNIGHT + a); + ben.iter(|| Time::MIDNIGHT + b); + ben.iter(|| Time::MIDNIGHT + c); + ben.iter(|| Time::MIDNIGHT + d); + ben.iter(|| Time::MIDNIGHT + e); + } + + fn add_assign_std_duration(ben: &mut Bencher<'_>) { + let a = 1.std_milliseconds(); + let b = 1.std_seconds(); + let c = 1.std_minutes(); + let d = 1.std_hours(); + let e = 1.std_days(); + iter_batched_ref!( + ben, + || Time::MIDNIGHT, + [ + |time| *time += a, + |time| *time += b, + |time| *time += c, + |time| *time += d, + |time| *time += e, + ] + ); + } + + fn sub_std_duration(ben: &mut Bencher<'_>) { + let a = 1.std_milliseconds(); + let b = 1.std_seconds(); + let c = 1.std_minutes(); + let d = 1.std_hours(); + let e = 1.std_days(); + ben.iter(|| Time::MIDNIGHT - a); + ben.iter(|| Time::MIDNIGHT - b); + ben.iter(|| Time::MIDNIGHT - c); + ben.iter(|| Time::MIDNIGHT - d); + ben.iter(|| Time::MIDNIGHT - e); + } + + fn sub_assign_std_duration(ben: &mut Bencher<'_>) { + let a = 1.std_milliseconds(); + let b = 1.std_seconds(); + let c = 1.std_minutes(); + let d = 1.std_hours(); + let e = 1.std_days(); + iter_batched_ref!( + ben, + || Time::MIDNIGHT, + [ + |time| *time -= a, + |time| *time -= b, + |time| *time -= c, + |time| *time -= d, + |time| *time -= e, + ] + ); + } + + fn sub_time(ben: &mut Bencher<'_>) { + ben.iter(|| Time::MIDNIGHT - time!(0:00:01)); + ben.iter(|| time!(1:00) - Time::MIDNIGHT); + ben.iter(|| time!(1:00) - time!(0:00:01)); + } + + fn ordering(ben: &mut Bencher<'_>) { + ben.iter(|| Time::MIDNIGHT < time!(0:00:00.000_000_001)); + ben.iter(|| Time::MIDNIGHT < time!(0:00:01)); + ben.iter(|| time!(12:00) > time!(11:00)); + ben.iter(|| Time::MIDNIGHT == time!(0:00:00.000_000_001)); + } + + fn sort_align_8(ben: &mut Bencher<'_>) { + ben.iter_batched_ref( + || { + #[repr(C,align(8))] + struct Padder { + arr: [Time;4096], + } + let mut res = Padder { + arr: [Time::MIDNIGHT;4096] + }; + let mut last = Time::MIDNIGHT; + let mut last_hour = 0; + for t in &mut res.arr { + *t = last; + t.replace_hour(last_hour).expect("failed to replace hour"); + last += 997.std_milliseconds(); + last_hour = (last_hour + 5) % 24; + } + res.arr.sort_unstable_by_key(|t| + (t.nanosecond(),t.second(),t.minute(),t.hour()) + ); + res + }, + |v| black_box(v).arr.sort_unstable(), + criterion::BatchSize::SmallInput + ) + } + + fn sort_align_4(ben: &mut Bencher<'_>) { + ben.iter_batched_ref( + || { + #[repr(C,align(8))] + struct Padder { + pad: u32, + arr: [Time;4096], + } + let mut res = Padder { + pad: 0, + arr: [Time::MIDNIGHT;4096] + }; + let mut last = Time::MIDNIGHT; + let mut last_hour = 0; + for t in &mut res.arr { + *t = last; + t.replace_hour(last_hour).expect("failed to replace hour"); + last += 997.std_milliseconds(); + last_hour = (last_hour + 5) % 24; + } + res.arr.sort_unstable_by_key(|t| + (t.nanosecond(),t.second(),t.minute(),t.hour()) + ); + res + }, + |v| black_box(v).arr.sort_unstable(), + criterion::BatchSize::SmallInput + ) + } + // endregion trait impls +} diff --git a/benchmarks/utc_offset.rs b/benchmarks/utc_offset.rs new file mode 100644 index 000000000..e96f7175d --- /dev/null +++ b/benchmarks/utc_offset.rs @@ -0,0 +1,64 @@ +use criterion::Bencher; +use time::{OffsetDateTime, UtcOffset}; + +setup_benchmark! { + "UtcOffset", + + // region: constructors + fn from_hms(ben: &mut Bencher<'_>) { + ben.iter(|| UtcOffset::from_hms(0, 0, 0)); + } + + fn from_whole_seconds(ben: &mut Bencher<'_>) { + ben.iter(|| UtcOffset::from_whole_seconds(0)); + } + // endregion constructors + + // region: getters + fn as_hms(ben: &mut Bencher<'_>) { + ben.iter(|| UtcOffset::UTC.as_hms()); + } + + fn whole_hours(ben: &mut Bencher<'_>) { + ben.iter(|| UtcOffset::UTC.whole_hours()); + } + + fn whole_minutes(ben: &mut Bencher<'_>) { + ben.iter(|| UtcOffset::UTC.whole_minutes()); + } + + fn minutes_past_hour(ben: &mut Bencher<'_>) { + ben.iter(|| UtcOffset::UTC.minutes_past_hour()); + } + + fn whole_seconds(ben: &mut Bencher<'_>) { + ben.iter(|| UtcOffset::UTC.whole_seconds()); + } + + fn seconds_past_minute(ben: &mut Bencher<'_>) { + ben.iter(|| UtcOffset::UTC.seconds_past_minute()); + } + // endregion getters + + // region: is_{sign} + fn is_utc(ben: &mut Bencher<'_>) { + ben.iter(|| UtcOffset::UTC.is_utc()); + } + + fn is_positive(ben: &mut Bencher<'_>) { + ben.iter(|| UtcOffset::UTC.is_positive()); + } + + fn is_negative(ben: &mut Bencher<'_>) { + ben.iter(|| UtcOffset::UTC.is_negative()); + } + // endregion is_{sign} + + fn local_offset_at(ben: &mut Bencher<'_>) { + ben.iter(|| UtcOffset::local_offset_at(OffsetDateTime::UNIX_EPOCH)); + } + + fn current_local_offset(ben: &mut Bencher<'_>) { + ben.iter(UtcOffset::current_local_offset); + } +} diff --git a/benchmarks/util.rs b/benchmarks/util.rs new file mode 100644 index 000000000..b02bf6096 --- /dev/null +++ b/benchmarks/util.rs @@ -0,0 +1,60 @@ +use criterion::{black_box, Bencher}; +use time::{util, Month}; + +setup_benchmark! { + "Utils", + + fn days_in_year_month(ben: &mut Bencher<'_>) { + // Common year + ben.iter(|| util::days_in_year_month(2019, Month::January)); + ben.iter(|| util::days_in_year_month(2019, Month::February)); + ben.iter(|| util::days_in_year_month(2019, Month::March)); + ben.iter(|| util::days_in_year_month(2019, Month::April)); + ben.iter(|| util::days_in_year_month(2019, Month::May)); + ben.iter(|| util::days_in_year_month(2019, Month::June)); + ben.iter(|| util::days_in_year_month(2019, Month::July)); + ben.iter(|| util::days_in_year_month(2019, Month::August)); + ben.iter(|| util::days_in_year_month(2019, Month::September)); + ben.iter(|| util::days_in_year_month(2019, Month::October)); + ben.iter(|| util::days_in_year_month(2019, Month::November)); + ben.iter(|| util::days_in_year_month(2019, Month::December)); + + // Leap year + ben.iter(|| util::days_in_year_month(2020, Month::January)); + ben.iter(|| util::days_in_year_month(2020, Month::February)); + ben.iter(|| util::days_in_year_month(2020, Month::March)); + ben.iter(|| util::days_in_year_month(2020, Month::April)); + ben.iter(|| util::days_in_year_month(2020, Month::May)); + ben.iter(|| util::days_in_year_month(2020, Month::June)); + ben.iter(|| util::days_in_year_month(2020, Month::July)); + ben.iter(|| util::days_in_year_month(2020, Month::August)); + ben.iter(|| util::days_in_year_month(2020, Month::September)); + ben.iter(|| util::days_in_year_month(2020, Month::October)); + ben.iter(|| util::days_in_year_month(2020, Month::November)); + ben.iter(|| util::days_in_year_month(2020, Month::December)); + } + + fn is_leap_year(ben: &mut Bencher<'_>) { + ben.iter(|| { + for year in 0..400 { + black_box(util::is_leap_year(year)); + } + }); + } + + fn days_in_year(ben: &mut Bencher<'_>) { + ben.iter(|| { + for year in 0..400 { + black_box(util::days_in_year(year)); + } + }); + } + + fn weeks_in_year(ben: &mut Bencher<'_>) { + ben.iter(|| { + for year in 0..400 { + black_box(util::weeks_in_year(year)); + } + }); + } +} diff --git a/benchmarks/weekday.rs b/benchmarks/weekday.rs new file mode 100644 index 000000000..e992d4d8c --- /dev/null +++ b/benchmarks/weekday.rs @@ -0,0 +1,81 @@ +use criterion::Bencher; +use time::Weekday::*; + +setup_benchmark! { + "Weekday", + + fn previous(ben: &mut Bencher<'_>) { + ben.iter(|| Sunday.previous()); + ben.iter(|| Monday.previous()); + ben.iter(|| Tuesday.previous()); + ben.iter(|| Wednesday.previous()); + ben.iter(|| Thursday.previous()); + ben.iter(|| Friday.previous()); + ben.iter(|| Saturday.previous()); + } + + fn next(ben: &mut Bencher<'_>) { + ben.iter(|| Sunday.next()); + ben.iter(|| Monday.next()); + ben.iter(|| Tuesday.next()); + ben.iter(|| Wednesday.next()); + ben.iter(|| Thursday.next()); + ben.iter(|| Friday.next()); + ben.iter(|| Saturday.next()); + } + + fn nth(ben: &mut Bencher<'_>) { + ben.iter(|| Sunday.nth_next(0)); + ben.iter(|| Sunday.nth_next(1)); + ben.iter(|| Sunday.nth_next(2)); + ben.iter(|| Sunday.nth_next(3)); + ben.iter(|| Sunday.nth_next(4)); + ben.iter(|| Sunday.nth_next(5)); + ben.iter(|| Sunday.nth_next(6)); + + ben.iter(|| Sunday.nth_next(7)); + ben.iter(|| Sunday.nth_next(u8::MAX)); + ben.iter(|| Monday.nth_next(7)); + ben.iter(|| Monday.nth_next(u8::MAX)); + } + + fn number_from_monday(ben: &mut Bencher<'_>) { + ben.iter(|| Monday.number_from_monday()); + ben.iter(|| Tuesday.number_from_monday()); + ben.iter(|| Wednesday.number_from_monday()); + ben.iter(|| Thursday.number_from_monday()); + ben.iter(|| Friday.number_from_monday()); + ben.iter(|| Saturday.number_from_monday()); + ben.iter(|| Sunday.number_from_monday()); + } + + fn number_from_sunday(ben: &mut Bencher<'_>) { + ben.iter(|| Sunday.number_from_sunday()); + ben.iter(|| Monday.number_from_sunday()); + ben.iter(|| Tuesday.number_from_sunday()); + ben.iter(|| Wednesday.number_from_sunday()); + ben.iter(|| Thursday.number_from_sunday()); + ben.iter(|| Friday.number_from_sunday()); + ben.iter(|| Saturday.number_from_sunday()); + } + + fn number_days_from_monday(ben: &mut Bencher<'_>) { + ben.iter(|| Monday.number_days_from_monday()); + ben.iter(|| Tuesday.number_days_from_monday()); + ben.iter(|| Wednesday.number_days_from_monday()); + ben.iter(|| Thursday.number_days_from_monday()); + ben.iter(|| Friday.number_days_from_monday()); + ben.iter(|| Saturday.number_days_from_monday()); + ben.iter(|| Sunday.number_days_from_monday()); + } + + fn number_days_from_sunday(ben: &mut Bencher<'_>) { + ben.iter(|| Sunday.number_days_from_sunday()); + ben.iter(|| Monday.number_days_from_sunday()); + ben.iter(|| Tuesday.number_days_from_sunday()); + ben.iter(|| Wednesday.number_days_from_sunday()); + ben.iter(|| Thursday.number_days_from_sunday()); + ben.iter(|| Friday.number_days_from_sunday()); + ben.iter(|| Saturday.number_days_from_sunday()); + } +} diff --git a/logo.svg b/logo.svg new file mode 100644 index 000000000..9c1ae5442 --- /dev/null +++ b/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000..a4528c2ac --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,17 @@ +newline_style = "unix" +use_field_init_shorthand = true +use_try_shorthand = true + +# Unstable features below +unstable_features = true +version = "Two" +comment_width = 100 +error_on_line_overflow = true +format_code_in_doc_comments = true +format_macro_bodies = true +format_macro_matchers = true +format_strings = true +imports_granularity = "Module" +group_imports = "StdExternalCrate" +normalize_doc_attributes = true +wrap_comments = true diff --git a/tests/compile-fail/invalid_date.rs b/tests/compile-fail/invalid_date.rs new file mode 100644 index 000000000..3605ae80b --- /dev/null +++ b/tests/compile-fail/invalid_date.rs @@ -0,0 +1,22 @@ +use time::macros::date; + +fn main() { + let _ = date!(+1_000_000-01-01); + let _ = date!(10_000 - 01 - 01); + let _ = date!(2021-W 60-1); + let _ = date!(2021-W 01-0); + let _ = date!(2021-W 01-8); + let _ = date!(2021 - 00 - 01); + let _ = date!(2021 - 13 - 01); + let _ = date!(2021 - 01 - 00); + let _ = date!(2021 - 01 - 32); + let _ = date!(2021 - 000); + let _ = date!(2021 - 366); + let _ = date!(0a); + let _ = date!(2021:); + let _ = date!(2021-W 0a); + let _ = date!(2021-W 01:); + let _ = date!(2021-W 01-0a); + let _ = date!(2021-0a); + let _ = date!(2021-01-0a); +} diff --git a/tests/compile-fail/invalid_date.stderr b/tests/compile-fail/invalid_date.stderr new file mode 100644 index 000000000..3bf9d34eb --- /dev/null +++ b/tests/compile-fail/invalid_date.stderr @@ -0,0 +1,107 @@ +error: invalid component: year was 1000000 + --> $DIR/invalid_date.rs:4:19 + | +4 | let _ = date!(+1_000_000-01-01); + | ^^^^^^^^^^ + +error: years with more than four digits must have an explicit sign + --> $DIR/invalid_date.rs:5:19 + | +5 | let _ = date!(10_000 - 01 - 01); + | ^^^^^^ + +error: invalid component: week was 60 + --> $DIR/invalid_date.rs:6:24 + | +6 | let _ = date!(2021-W 60-1); + | ^^^^ + +error: invalid component: day was 0 + --> $DIR/invalid_date.rs:7:29 + | +7 | let _ = date!(2021-W 01-0); + | ^ + +error: invalid component: day was 8 + --> $DIR/invalid_date.rs:8:29 + | +8 | let _ = date!(2021-W 01-8); + | ^ + +error: invalid component: month was 0 + --> $DIR/invalid_date.rs:9:26 + | +9 | let _ = date!(2021 - 00 - 01); + | ^^ + +error: invalid component: month was 13 + --> $DIR/invalid_date.rs:10:26 + | +10 | let _ = date!(2021 - 13 - 01); + | ^^ + +error: invalid component: day was 0 + --> $DIR/invalid_date.rs:11:31 + | +11 | let _ = date!(2021 - 01 - 00); + | ^^ + +error: invalid component: day was 32 + --> $DIR/invalid_date.rs:12:31 + | +12 | let _ = date!(2021 - 01 - 32); + | ^^ + +error: invalid component: ordinal was 0 + --> $DIR/invalid_date.rs:13:26 + | +13 | let _ = date!(2021 - 000); + | ^^^ + +error: invalid component: ordinal was 366 + --> $DIR/invalid_date.rs:14:26 + | +14 | let _ = date!(2021 - 366); + | ^^^ + +error: invalid component: year was 0a + --> $DIR/invalid_date.rs:15:19 + | +15 | let _ = date!(0a); + | ^^ + +error: unexpected token: : + --> $DIR/invalid_date.rs:16:23 + | +16 | let _ = date!(2021:); + | ^ + +error: invalid component: week was 0a + --> $DIR/invalid_date.rs:17:26 + | +17 | let _ = date!(2021-W 0a); + | ^^ + +error: unexpected token: : + --> $DIR/invalid_date.rs:18:28 + | +18 | let _ = date!(2021-W 01:); + | ^ + +error: invalid component: day was 0a + --> $DIR/invalid_date.rs:19:29 + | +19 | let _ = date!(2021-W 01-0a); + | ^^ + +error: invalid component: month or ordinal was 0a + --> $DIR/invalid_date.rs:20:24 + | +20 | let _ = date!(2021-0a); + | ^^ + +error: invalid component: day was 0a + --> $DIR/invalid_date.rs:21:27 + | +21 | let _ = date!(2021-01-0a); + | ^^ diff --git a/tests/compile-fail/invalid_datetime.rs b/tests/compile-fail/invalid_datetime.rs new file mode 100644 index 000000000..5fe4138ee --- /dev/null +++ b/tests/compile-fail/invalid_datetime.rs @@ -0,0 +1,8 @@ +use time::macros::datetime; + +fn main() { + let _ = datetime!(2021 - 000 0:00); + let _ = datetime!(2021 - 001 24:00); + let _ = datetime!(2021 - 001 0:00 0); + let _ = datetime!(2021 - 001 0:00 UTC x); +} diff --git a/tests/compile-fail/invalid_datetime.stderr b/tests/compile-fail/invalid_datetime.stderr new file mode 100644 index 000000000..d65e42760 --- /dev/null +++ b/tests/compile-fail/invalid_datetime.stderr @@ -0,0 +1,23 @@ +error: invalid component: ordinal was 0 + --> $DIR/invalid_datetime.rs:4:30 + | +4 | let _ = datetime!(2021 - 000 0:00); + | ^^^ + +error: invalid component: hour was 24 + --> $DIR/invalid_datetime.rs:5:34 + | +5 | let _ = datetime!(2021 - 001 24:00); + | ^^ + +error: unexpected token: 0 + --> $DIR/invalid_datetime.rs:6:39 + | +6 | let _ = datetime!(2021 - 001 0:00 0); + | ^ + +error: unexpected token: x + --> $DIR/invalid_datetime.rs:7:43 + | +7 | let _ = datetime!(2021 - 001 0:00 UTC x); + | ^ diff --git a/tests/compile-fail/invalid_format_description.rs b/tests/compile-fail/invalid_format_description.rs new file mode 100644 index 000000000..a77a31808 --- /dev/null +++ b/tests/compile-fail/invalid_format_description.rs @@ -0,0 +1,46 @@ +use time::macros::format_description; + +fn main() { + let _ = format_description!(); + let _ = format_description!("[]"); + let _ = format_description!("[foo]"); + let _ = format_description!("["); + let _ = format_description!("[hour foo]"); + let _ = format_description!("" x); + let _ = format_description!(x); + let _ = format_description!(0); + let _ = format_description!({}); + + let _ = format_description!("[ invalid ]"); + let _ = format_description!("["); + let _ = format_description!("[ "); + let _ = format_description!("[]"); + let _ = format_description!("[day sign:mandatory]"); + let _ = format_description!("[day sign:]"); + let _ = format_description!("[day :mandatory]"); + let _ = format_description!("[day sign:mandatory"); + let _ = format_description!("[day padding:invalid]"); + + let _ = format_description!(version); + let _ = format_description!(version ""); + let _ = format_description!(version =); + let _ = format_description!(version = 0); + let _ = format_description!(version = 1); + let _ = format_description!(version = 3); + let _ = format_description!(version = two); + + let _ = format_description!(version = 2, r"\a"); + let _ = format_description!(version = 2, r"\"); + + let _ = format_description!(version = 2, "[year [month]]"); + let _ = format_description!(version = 2, "[optional[]]"); + let _ = format_description!(version = 2, "[first[]]"); + let _ = format_description!(version = 2, "[optional []"); + let _ = format_description!(version = 2, "[first []"); + let _ = format_description!(version = 2, "[optional ["); + let _ = format_description!(version = 2, "[optional [[year"); + let _ = format_description!(version = 2, "[optional "); + + let _ = format_description!("[ignore]"); + let _ = format_description!("[ignore count:0]"); +} diff --git a/tests/compile-fail/invalid_format_description.stderr b/tests/compile-fail/invalid_format_description.stderr new file mode 100644 index 000000000..156665a38 --- /dev/null +++ b/tests/compile-fail/invalid_format_description.stderr @@ -0,0 +1,229 @@ +error: expected string literal + --> ../tests/compile-fail/invalid_format_description.rs:4:13 + | +4 | let _ = format_description!(); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `format_description` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: expected component name + --> ../tests/compile-fail/invalid_format_description.rs:5:33 + | +5 | let _ = format_description!("[]"); + | ^^^^ + +error: invalid component + --> ../tests/compile-fail/invalid_format_description.rs:6:33 + | +6 | let _ = format_description!("[foo]"); + | ^^^^^^^ + +error: expected component name + --> ../tests/compile-fail/invalid_format_description.rs:7:33 + | +7 | let _ = format_description!("["); + | ^^^ + +error: modifier must be of the form `key:value` + --> ../tests/compile-fail/invalid_format_description.rs:8:33 + | +8 | let _ = format_description!("[hour foo]"); + | ^^^^^^^^^^^^ + +error: unexpected token: x + --> ../tests/compile-fail/invalid_format_description.rs:9:36 + | +9 | let _ = format_description!("" x); + | ^ + +error: expected string literal + --> ../tests/compile-fail/invalid_format_description.rs:10:33 + | +10 | let _ = format_description!(x); + | ^ + +error: expected string literal + --> ../tests/compile-fail/invalid_format_description.rs:11:33 + | +11 | let _ = format_description!(0); + | ^ + +error: expected string literal + --> ../tests/compile-fail/invalid_format_description.rs:12:33 + | +12 | let _ = format_description!({}); + | ^^ + +error: invalid component + --> ../tests/compile-fail/invalid_format_description.rs:14:33 + | +14 | let _ = format_description!("[ invalid ]"); + | ^^^^^^^^^^^^^ + +error: expected component name + --> ../tests/compile-fail/invalid_format_description.rs:15:33 + | +15 | let _ = format_description!("["); + | ^^^ + +error: expected component name + --> ../tests/compile-fail/invalid_format_description.rs:16:33 + | +16 | let _ = format_description!("[ "); + | ^^^^ + +error: expected component name + --> ../tests/compile-fail/invalid_format_description.rs:17:33 + | +17 | let _ = format_description!("[]"); + | ^^^^ + +error: invalid modifier key + --> ../tests/compile-fail/invalid_format_description.rs:18:33 + | +18 | let _ = format_description!("[day sign:mandatory]"); + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: expected modifier value + --> ../tests/compile-fail/invalid_format_description.rs:19:33 + | +19 | let _ = format_description!("[day sign:]"); + | ^^^^^^^^^^^^^ + +error: expected modifier key + --> ../tests/compile-fail/invalid_format_description.rs:20:33 + | +20 | let _ = format_description!("[day :mandatory]"); + | ^^^^^^^^^^^^^^^^^^ + +error: unclosed bracket + --> ../tests/compile-fail/invalid_format_description.rs:21:33 + | +21 | let _ = format_description!("[day sign:mandatory"); + | ^^^^^^^^^^^^^^^^^^^^^ + +error: invalid modifier value + --> ../tests/compile-fail/invalid_format_description.rs:22:33 + | +22 | let _ = format_description!("[day padding:invalid]"); + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected `=` + --> ../tests/compile-fail/invalid_format_description.rs:24:13 + | +24 | let _ = format_description!(version); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `format_description` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: expected `=` + --> ../tests/compile-fail/invalid_format_description.rs:25:41 + | +25 | let _ = format_description!(version ""); + | ^^ + +error: expected 1 or 2 + --> ../tests/compile-fail/invalid_format_description.rs:26:13 + | +26 | let _ = format_description!(version =); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `format_description` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid format description version + --> ../tests/compile-fail/invalid_format_description.rs:27:43 + | +27 | let _ = format_description!(version = 0); + | ^ + +error: unexpected end of input + --> ../tests/compile-fail/invalid_format_description.rs:28:13 + | +28 | let _ = format_description!(version = 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `format_description` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid format description version + --> ../tests/compile-fail/invalid_format_description.rs:29:43 + | +29 | let _ = format_description!(version = 3); + | ^ + +error: expected 1 or 2 + --> ../tests/compile-fail/invalid_format_description.rs:30:43 + | +30 | let _ = format_description!(version = two); + | ^^^ + +error: invalid escape sequence + --> ../tests/compile-fail/invalid_format_description.rs:32:46 + | +32 | let _ = format_description!(version = 2, r"\a"); + | ^^^^^ + +error: unexpected end of input + --> ../tests/compile-fail/invalid_format_description.rs:33:46 + | +33 | let _ = format_description!(version = 2, r"\"); + | ^^^^ + +error: modifier must be of the form `key:value` + --> ../tests/compile-fail/invalid_format_description.rs:35:46 + | +35 | let _ = format_description!(version = 2, "[year [month]]"); + | ^^^^^^^^^^^^^^^^ + +error: expected whitespace after `optional` + --> ../tests/compile-fail/invalid_format_description.rs:36:46 + | +36 | let _ = format_description!(version = 2, "[optional[]]"); + | ^^^^^^^^^^^^^^ + +error: expected whitespace after `first` + --> ../tests/compile-fail/invalid_format_description.rs:37:46 + | +37 | let _ = format_description!(version = 2, "[first[]]"); + | ^^^^^^^^^^^ + +error: unclosed bracket + --> ../tests/compile-fail/invalid_format_description.rs:38:46 + | +38 | let _ = format_description!(version = 2, "[optional []"); + | ^^^^^^^^^^^^^^ + +error: unclosed bracket + --> ../tests/compile-fail/invalid_format_description.rs:39:46 + | +39 | let _ = format_description!(version = 2, "[first []"); + | ^^^^^^^^^^^ + +error: unclosed bracket + --> ../tests/compile-fail/invalid_format_description.rs:40:46 + | +40 | let _ = format_description!(version = 2, "[optional ["); + | ^^^^^^^^^^^^^ + +error: unclosed bracket + --> ../tests/compile-fail/invalid_format_description.rs:41:46 + | +41 | let _ = format_description!(version = 2, "[optional [[year"); + | ^^^^^^^^^^^^^^^^^^ + +error: expected opening bracket + --> ../tests/compile-fail/invalid_format_description.rs:42:46 + | +42 | let _ = format_description!(version = 2, "[optional "); + | ^^^^^^^^^^^^ + +error: missing required modifier + --> ../tests/compile-fail/invalid_format_description.rs:44:33 + | +44 | let _ = format_description!("[ignore]"); + | ^^^^^^^^^^ + +error: invalid modifier value + --> ../tests/compile-fail/invalid_format_description.rs:45:33 + | +45 | let _ = format_description!("[ignore count:0]"); + | ^^^^^^^^^^^^^^^^^^ diff --git a/tests/compile-fail/invalid_offset.rs b/tests/compile-fail/invalid_offset.rs new file mode 100644 index 000000000..b33cf498b --- /dev/null +++ b/tests/compile-fail/invalid_offset.rs @@ -0,0 +1,12 @@ +use time::macros::offset; + +fn main() { + let _ = offset!(+26); + let _ = offset!(+0:60); + let _ = offset!(+0:00:60); + let _ = offset!(0); + let _ = offset!(); + let _ = offset!(+0a); + let _ = offset!(+0:0a); + let _ = offset!(+0:00:0a); +} diff --git a/tests/compile-fail/invalid_offset.stderr b/tests/compile-fail/invalid_offset.stderr new file mode 100644 index 000000000..7b66393aa --- /dev/null +++ b/tests/compile-fail/invalid_offset.stderr @@ -0,0 +1,49 @@ +error: invalid component: hour was 26 + --> ../tests/compile-fail/invalid_offset.rs:4:22 + | +4 | let _ = offset!(+26); + | ^^ + +error: invalid component: minute was 60 + --> ../tests/compile-fail/invalid_offset.rs:5:24 + | +5 | let _ = offset!(+0:60); + | ^^ + +error: invalid component: second was 60 + --> ../tests/compile-fail/invalid_offset.rs:6:27 + | +6 | let _ = offset!(+0:00:60); + | ^^ + +error: unexpected token: 0 + --> ../tests/compile-fail/invalid_offset.rs:7:21 + | +7 | let _ = offset!(0); + | ^ + +error: missing component: sign + --> ../tests/compile-fail/invalid_offset.rs:8:13 + | +8 | let _ = offset!(); + | ^^^^^^^^^ + | + = note: this error originates in the macro `offset` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid component: hour was 0a + --> ../tests/compile-fail/invalid_offset.rs:9:22 + | +9 | let _ = offset!(+0a); + | ^^ + +error: invalid component: minute was 0a + --> ../tests/compile-fail/invalid_offset.rs:10:24 + | +10 | let _ = offset!(+0:0a); + | ^^ + +error: invalid component: second was 0a + --> ../tests/compile-fail/invalid_offset.rs:11:27 + | +11 | let _ = offset!(+0:00:0a); + | ^^ diff --git a/tests/compile-fail/invalid_serializer.rs b/tests/compile-fail/invalid_serializer.rs new file mode 100644 index 000000000..284d0b783 --- /dev/null +++ b/tests/compile-fail/invalid_serializer.rs @@ -0,0 +1,16 @@ +use time::serde; + +serde::format_description!(); // unexpected end of input +serde::format_description!("bad string", OffsetDateTime, "[year] [month]"); // module name is not ident +serde::format_description!(my_format: OffsetDateTime, "[year] [month]"); // not a comma +serde::format_description!(my_format,); // missing formattable and string +serde::format_description!(my_format, "[year] [month]"); // missing formattable +serde::format_description!(OffsetDateTime, "[year] [month]"); // missing ident +serde::format_description!(my_format, OffsetDateTime); // missing string format +serde::format_description!(my_format, OffsetDateTime,); // missing string format +serde::format_description!(my_format, OffsetDateTime "[year] [month]"); // missing comma +serde::format_description!(my_format, OffsetDateTime : "[year] [month]"); // not a comma +serde::format_description!(my_format, OffsetDateTime, "[bad]"); // bad component name +serde::format_description!(my_format, OffsetDateTime, not_string); // string format wrong type + +fn main() {} diff --git a/tests/compile-fail/invalid_serializer.stderr b/tests/compile-fail/invalid_serializer.stderr new file mode 100644 index 000000000..3579ac33c --- /dev/null +++ b/tests/compile-fail/invalid_serializer.stderr @@ -0,0 +1,113 @@ +error: unexpected end of input + --> ../tests/compile-fail/invalid_serializer.rs:3:1 + | +3 | serde::format_description!(); // unexpected end of input + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `serde::format_description` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unexpected token: "bad string" + --> ../tests/compile-fail/invalid_serializer.rs:4:28 + | +4 | serde::format_description!("bad string", OffsetDateTime, "[year] [month]"); // module name is not ident + | ^^^^^^^^^^^^ + +error: unexpected token: : + --> ../tests/compile-fail/invalid_serializer.rs:5:37 + | +5 | serde::format_description!(my_format: OffsetDateTime, "[year] [month]"); // not a comma + | ^ + +error: unexpected end of input + --> ../tests/compile-fail/invalid_serializer.rs:6:1 + | +6 | serde::format_description!(my_format,); // missing formattable and string + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `serde::format_description` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unexpected token: "[year] [month]" + --> ../tests/compile-fail/invalid_serializer.rs:7:39 + | +7 | serde::format_description!(my_format, "[year] [month]"); // missing formattable + | ^^^^^^^^^^^^^^^^ + +error: unexpected token: "[year] [month]" + --> ../tests/compile-fail/invalid_serializer.rs:8:44 + | +8 | serde::format_description!(OffsetDateTime, "[year] [month]"); // missing ident + | ^^^^^^^^^^^^^^^^ + +error: unexpected end of input + --> ../tests/compile-fail/invalid_serializer.rs:9:1 + | +9 | serde::format_description!(my_format, OffsetDateTime); // missing string format + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `serde::format_description` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unexpected end of input + --> ../tests/compile-fail/invalid_serializer.rs:10:1 + | +10 | serde::format_description!(my_format, OffsetDateTime,); // missing string format + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `serde::format_description` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unexpected token: "[year] [month]" + --> ../tests/compile-fail/invalid_serializer.rs:11:54 + | +11 | serde::format_description!(my_format, OffsetDateTime "[year] [month]"); // missing comma + | ^^^^^^^^^^^^^^^^ + +error: unexpected token: : + --> ../tests/compile-fail/invalid_serializer.rs:12:54 + | +12 | serde::format_description!(my_format, OffsetDateTime : "[year] [month]"); // not a comma + | ^ + +error: invalid component + --> ../tests/compile-fail/invalid_serializer.rs:13:55 + | +13 | serde::format_description!(my_format, OffsetDateTime, "[bad]"); // bad component name + | ^^^^^^^ + +error[E0432]: unresolved import `not_string` + --> ../tests/compile-fail/invalid_serializer.rs:14:1 + | +14 | serde::format_description!(my_format, OffsetDateTime, not_string); // string format wrong type + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no external crate `not_string` + | + = note: this error originates in the macro `serde::format_description` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0391]: cycle detected when computing type of opaque `my_format::description::{opaque#0}` + --> ../tests/compile-fail/invalid_serializer.rs:14:1 + | +14 | serde::format_description!(my_format, OffsetDateTime, not_string); // string format wrong type + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: ...which requires borrow-checking `my_format::description`... + --> ../tests/compile-fail/invalid_serializer.rs:14:1 + | +14 | serde::format_description!(my_format, OffsetDateTime, not_string); // string format wrong type + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +note: ...which requires promoting constants in MIR for `my_format::description`... + --> ../tests/compile-fail/invalid_serializer.rs:14:1 + | +14 | serde::format_description!(my_format, OffsetDateTime, not_string); // string format wrong type + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +note: ...which requires const checking `my_format::description`... + --> ../tests/compile-fail/invalid_serializer.rs:14:1 + | +14 | serde::format_description!(my_format, OffsetDateTime, not_string); // string format wrong type + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: ...which requires computing whether `my_format::description::{opaque#0}` is freeze... + = note: ...which requires evaluating trait selection obligation `my_format::description::{opaque#0}: core::marker::Freeze`... + = note: ...which again requires computing type of opaque `my_format::description::{opaque#0}`, completing the cycle +note: cycle used when computing type of `my_format::description::{opaque#0}` + --> ../tests/compile-fail/invalid_serializer.rs:14:1 + | +14 | serde::format_description!(my_format, OffsetDateTime, not_string); // string format wrong type + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information + = note: this error originates in the macro `serde::format_description` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/compile-fail/invalid_time.rs b/tests/compile-fail/invalid_time.rs new file mode 100644 index 000000000..514795245 --- /dev/null +++ b/tests/compile-fail/invalid_time.rs @@ -0,0 +1,17 @@ +use time::macros::time; + +fn main() { + let _ = time!(24:00); + let _ = time!(0:60); + let _ = time!(0:00:60); + let _ = time!(x); + let _ = time!(0:00:00 x); + let _ = time!(""); + let _ = time!(0:); + let _ = time!(0,); + let _ = time!(0:00:0a); + let _ = time!(0:00 pm); + let _ = time!(0); + let _ = time!(0 pm); + let _ = time!(1 am :); +} diff --git a/tests/compile-fail/invalid_time.stderr b/tests/compile-fail/invalid_time.stderr new file mode 100644 index 000000000..303f57327 --- /dev/null +++ b/tests/compile-fail/invalid_time.stderr @@ -0,0 +1,81 @@ +error: invalid component: hour was 24 + --> $DIR/invalid_time.rs:4:19 + | +4 | let _ = time!(24:00); + | ^^ + +error: invalid component: minute was 60 + --> $DIR/invalid_time.rs:5:21 + | +5 | let _ = time!(0:60); + | ^^ + +error: invalid component: second was 60 + --> $DIR/invalid_time.rs:6:24 + | +6 | let _ = time!(0:00:60); + | ^^ + +error: unexpected token: x + --> $DIR/invalid_time.rs:7:19 + | +7 | let _ = time!(x); + | ^ + +error: unexpected token: x + --> $DIR/invalid_time.rs:8:27 + | +8 | let _ = time!(0:00:00 x); + | ^ + +error: invalid component: hour was "" + --> $DIR/invalid_time.rs:9:19 + | +9 | let _ = time!(""); + | ^^ + +error: unexpected end of input + --> $DIR/invalid_time.rs:10:13 + | +10 | let _ = time!(0:); + | ^^^^^^^^^ + | + = note: this error originates in the macro `time` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: unexpected token: , + --> $DIR/invalid_time.rs:11:20 + | +11 | let _ = time!(0,); + | ^ + +error: invalid component: second was 0a + --> $DIR/invalid_time.rs:12:24 + | +12 | let _ = time!(0:00:0a); + | ^^ + +error: invalid component: hour was 0 + --> $DIR/invalid_time.rs:13:19 + | +13 | let _ = time!(0:00 pm); + | ^^^^^^^ + +error: unexpected end of input + --> $DIR/invalid_time.rs:14:13 + | +14 | let _ = time!(0); + | ^^^^^^^^ + | + = note: this error originates in the macro `time` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: invalid component: hour was 0 + --> $DIR/invalid_time.rs:15:19 + | +15 | let _ = time!(0 pm); + | ^^^^ + +error: unexpected token: : + --> $DIR/invalid_time.rs:16:24 + | +16 | let _ = time!(1 am :); + | ^ diff --git a/tests/date.rs b/tests/date.rs new file mode 100644 index 000000000..74ab27db0 --- /dev/null +++ b/tests/date.rs @@ -0,0 +1,1188 @@ +use std::cmp::Ordering; +use std::collections::HashSet; + +use time::ext::{NumericalDuration, NumericalStdDuration}; +use time::macros::{date, datetime, time}; +use time::{util, Date, Duration, Month, Weekday}; + +#[test] +fn debug() { + assert_eq!(format!("{:?}", date!(2020 - 02 - 03)), "2020-02-03"); +} + +#[test] +fn weeks_in_year_exhaustive() { + let years_with_53 = [ + 4, 9, 15, 20, 26, 32, 37, 43, 48, 54, 60, 65, 71, 76, 82, 88, 93, 99, 105, 111, 116, 122, + 128, 133, 139, 144, 150, 156, 161, 167, 172, 178, 184, 189, 195, 201, 207, 212, 218, 224, + 229, 235, 240, 246, 252, 257, 263, 268, 274, 280, 285, 291, 296, 303, 308, 314, 320, 325, + 331, 336, 342, 348, 353, 359, 364, 370, 376, 381, 387, 392, 398, + ] + .iter() + .copied() + .collect::>(); + + for year in 0..400 { + assert_eq!( + util::weeks_in_year(year), + if years_with_53.contains(&year) { + 53 + } else { + 52 + } + ); + } +} + +// Test all dominical letters. For leap years, check the dates immediately preceding and after the +// leap day. + +#[test] +fn test_monday_based_week_dominical_a() { + assert_eq!(date!(2023 - 01 - 01).monday_based_week(), 0); + assert_eq!(date!(2023 - 01 - 02).monday_based_week(), 1); + assert_eq!(date!(2023 - 01 - 03).monday_based_week(), 1); + assert_eq!(date!(2023 - 01 - 04).monday_based_week(), 1); + assert_eq!(date!(2023 - 01 - 05).monday_based_week(), 1); + assert_eq!(date!(2023 - 01 - 06).monday_based_week(), 1); + assert_eq!(date!(2023 - 01 - 07).monday_based_week(), 1); +} + +#[test] +fn test_monday_based_week_dominical_b() { + assert_eq!(date!(2022 - 01 - 01).monday_based_week(), 0); + assert_eq!(date!(2022 - 01 - 02).monday_based_week(), 0); + assert_eq!(date!(2022 - 01 - 03).monday_based_week(), 1); + assert_eq!(date!(2022 - 01 - 04).monday_based_week(), 1); + assert_eq!(date!(2022 - 01 - 05).monday_based_week(), 1); + assert_eq!(date!(2022 - 01 - 06).monday_based_week(), 1); + assert_eq!(date!(2022 - 01 - 07).monday_based_week(), 1); +} + +#[test] +fn test_monday_based_week_dominical_c() { + assert_eq!(date!(2021 - 01 - 01).monday_based_week(), 0); + assert_eq!(date!(2021 - 01 - 02).monday_based_week(), 0); + assert_eq!(date!(2021 - 01 - 03).monday_based_week(), 0); + assert_eq!(date!(2021 - 01 - 04).monday_based_week(), 1); + assert_eq!(date!(2021 - 01 - 05).monday_based_week(), 1); + assert_eq!(date!(2021 - 01 - 06).monday_based_week(), 1); + assert_eq!(date!(2021 - 01 - 07).monday_based_week(), 1); +} + +#[test] +fn test_monday_based_week_dominical_d() { + assert_eq!(date!(2026 - 01 - 01).monday_based_week(), 0); + assert_eq!(date!(2026 - 01 - 02).monday_based_week(), 0); + assert_eq!(date!(2026 - 01 - 03).monday_based_week(), 0); + assert_eq!(date!(2026 - 01 - 04).monday_based_week(), 0); + assert_eq!(date!(2026 - 01 - 05).monday_based_week(), 1); + assert_eq!(date!(2026 - 01 - 06).monday_based_week(), 1); + assert_eq!(date!(2026 - 01 - 07).monday_based_week(), 1); +} + +#[test] +fn test_monday_based_week_dominical_e() { + assert_eq!(date!(2025 - 01 - 01).monday_based_week(), 0); + assert_eq!(date!(2025 - 01 - 02).monday_based_week(), 0); + assert_eq!(date!(2025 - 01 - 03).monday_based_week(), 0); + assert_eq!(date!(2025 - 01 - 04).monday_based_week(), 0); + assert_eq!(date!(2025 - 01 - 05).monday_based_week(), 0); + assert_eq!(date!(2025 - 01 - 06).monday_based_week(), 1); + assert_eq!(date!(2025 - 01 - 07).monday_based_week(), 1); +} + +#[test] +fn test_monday_based_week_dominical_f() { + assert_eq!(date!(2019 - 01 - 01).monday_based_week(), 0); + assert_eq!(date!(2019 - 01 - 02).monday_based_week(), 0); + assert_eq!(date!(2019 - 01 - 03).monday_based_week(), 0); + assert_eq!(date!(2019 - 01 - 04).monday_based_week(), 0); + assert_eq!(date!(2019 - 01 - 05).monday_based_week(), 0); + assert_eq!(date!(2019 - 01 - 06).monday_based_week(), 0); + assert_eq!(date!(2019 - 01 - 07).monday_based_week(), 1); +} + +#[test] +fn test_monday_based_week_dominical_g() { + assert_eq!(date!(2018 - 01 - 01).monday_based_week(), 1); + assert_eq!(date!(2018 - 01 - 02).monday_based_week(), 1); + assert_eq!(date!(2018 - 01 - 03).monday_based_week(), 1); + assert_eq!(date!(2018 - 01 - 04).monday_based_week(), 1); + assert_eq!(date!(2018 - 01 - 05).monday_based_week(), 1); + assert_eq!(date!(2018 - 01 - 06).monday_based_week(), 1); + assert_eq!(date!(2018 - 01 - 07).monday_based_week(), 1); +} + +#[test] +fn test_monday_based_week_dominical_ag() { + assert_eq!(date!(2012 - 01 - 01).monday_based_week(), 0); + assert_eq!(date!(2012 - 01 - 02).monday_based_week(), 1); + assert_eq!(date!(2012 - 01 - 03).monday_based_week(), 1); + assert_eq!(date!(2012 - 01 - 04).monday_based_week(), 1); + assert_eq!(date!(2012 - 01 - 05).monday_based_week(), 1); + assert_eq!(date!(2012 - 01 - 06).monday_based_week(), 1); + assert_eq!(date!(2012 - 01 - 07).monday_based_week(), 1); + assert_eq!(date!(2012 - 02 - 28).monday_based_week(), 9); + assert_eq!(date!(2012 - 02 - 29).monday_based_week(), 9); + assert_eq!(date!(2012 - 03 - 01).monday_based_week(), 9); + assert_eq!(date!(2012 - 03 - 02).monday_based_week(), 9); + assert_eq!(date!(2012 - 03 - 03).monday_based_week(), 9); + assert_eq!(date!(2012 - 03 - 04).monday_based_week(), 9); + assert_eq!(date!(2012 - 03 - 05).monday_based_week(), 10); + assert_eq!(date!(2012 - 03 - 06).monday_based_week(), 10); + assert_eq!(date!(2012 - 03 - 07).monday_based_week(), 10); +} + +#[test] +fn test_monday_based_week_dominical_ba() { + assert_eq!(date!(2028 - 01 - 01).monday_based_week(), 0); + assert_eq!(date!(2028 - 01 - 02).monday_based_week(), 0); + assert_eq!(date!(2028 - 01 - 03).monday_based_week(), 1); + assert_eq!(date!(2028 - 01 - 04).monday_based_week(), 1); + assert_eq!(date!(2028 - 01 - 05).monday_based_week(), 1); + assert_eq!(date!(2028 - 01 - 06).monday_based_week(), 1); + assert_eq!(date!(2028 - 01 - 07).monday_based_week(), 1); + assert_eq!(date!(2028 - 02 - 28).monday_based_week(), 9); + assert_eq!(date!(2028 - 02 - 29).monday_based_week(), 9); + assert_eq!(date!(2028 - 03 - 01).monday_based_week(), 9); + assert_eq!(date!(2028 - 03 - 02).monday_based_week(), 9); + assert_eq!(date!(2028 - 03 - 03).monday_based_week(), 9); + assert_eq!(date!(2028 - 03 - 04).monday_based_week(), 9); + assert_eq!(date!(2028 - 03 - 05).monday_based_week(), 9); + assert_eq!(date!(2028 - 03 - 06).monday_based_week(), 10); + assert_eq!(date!(2028 - 03 - 07).monday_based_week(), 10); +} + +#[test] +fn test_monday_based_week_dominical_cb() { + assert_eq!(date!(2016 - 01 - 01).monday_based_week(), 0); + assert_eq!(date!(2016 - 01 - 02).monday_based_week(), 0); + assert_eq!(date!(2016 - 01 - 03).monday_based_week(), 0); + assert_eq!(date!(2016 - 01 - 04).monday_based_week(), 1); + assert_eq!(date!(2016 - 01 - 05).monday_based_week(), 1); + assert_eq!(date!(2016 - 01 - 06).monday_based_week(), 1); + assert_eq!(date!(2016 - 01 - 07).monday_based_week(), 1); + assert_eq!(date!(2016 - 02 - 28).monday_based_week(), 8); + assert_eq!(date!(2016 - 02 - 29).monday_based_week(), 9); + assert_eq!(date!(2016 - 03 - 01).monday_based_week(), 9); + assert_eq!(date!(2016 - 03 - 02).monday_based_week(), 9); + assert_eq!(date!(2016 - 03 - 03).monday_based_week(), 9); + assert_eq!(date!(2016 - 03 - 04).monday_based_week(), 9); + assert_eq!(date!(2016 - 03 - 05).monday_based_week(), 9); + assert_eq!(date!(2016 - 03 - 06).monday_based_week(), 9); + assert_eq!(date!(2016 - 03 - 07).monday_based_week(), 10); +} + +#[test] +fn test_monday_based_week_dominical_dc() { + assert_eq!(date!(2032 - 01 - 01).monday_based_week(), 0); + assert_eq!(date!(2032 - 01 - 02).monday_based_week(), 0); + assert_eq!(date!(2032 - 01 - 03).monday_based_week(), 0); + assert_eq!(date!(2032 - 01 - 04).monday_based_week(), 0); + assert_eq!(date!(2032 - 01 - 05).monday_based_week(), 1); + assert_eq!(date!(2032 - 01 - 06).monday_based_week(), 1); + assert_eq!(date!(2032 - 01 - 07).monday_based_week(), 1); + assert_eq!(date!(2032 - 02 - 28).monday_based_week(), 8); + assert_eq!(date!(2032 - 02 - 29).monday_based_week(), 8); + assert_eq!(date!(2032 - 03 - 01).monday_based_week(), 9); + assert_eq!(date!(2032 - 03 - 02).monday_based_week(), 9); + assert_eq!(date!(2032 - 03 - 03).monday_based_week(), 9); + assert_eq!(date!(2032 - 03 - 04).monday_based_week(), 9); + assert_eq!(date!(2032 - 03 - 05).monday_based_week(), 9); + assert_eq!(date!(2032 - 03 - 06).monday_based_week(), 9); + assert_eq!(date!(2032 - 03 - 07).monday_based_week(), 9); +} + +#[test] +fn test_monday_based_week_dominical_ed() { + assert_eq!(date!(2020 - 01 - 01).monday_based_week(), 0); + assert_eq!(date!(2020 - 01 - 02).monday_based_week(), 0); + assert_eq!(date!(2020 - 01 - 03).monday_based_week(), 0); + assert_eq!(date!(2020 - 01 - 04).monday_based_week(), 0); + assert_eq!(date!(2020 - 01 - 05).monday_based_week(), 0); + assert_eq!(date!(2020 - 01 - 06).monday_based_week(), 1); + assert_eq!(date!(2020 - 01 - 07).monday_based_week(), 1); + assert_eq!(date!(2020 - 02 - 28).monday_based_week(), 8); + assert_eq!(date!(2020 - 02 - 29).monday_based_week(), 8); + assert_eq!(date!(2020 - 03 - 01).monday_based_week(), 8); + assert_eq!(date!(2020 - 03 - 02).monday_based_week(), 9); + assert_eq!(date!(2020 - 03 - 03).monday_based_week(), 9); + assert_eq!(date!(2020 - 03 - 04).monday_based_week(), 9); + assert_eq!(date!(2020 - 03 - 05).monday_based_week(), 9); + assert_eq!(date!(2020 - 03 - 06).monday_based_week(), 9); + assert_eq!(date!(2020 - 03 - 07).monday_based_week(), 9); +} + +#[test] +fn test_monday_based_week_dominical_fe() { + assert_eq!(date!(2036 - 01 - 01).monday_based_week(), 0); + assert_eq!(date!(2036 - 01 - 02).monday_based_week(), 0); + assert_eq!(date!(2036 - 01 - 03).monday_based_week(), 0); + assert_eq!(date!(2036 - 01 - 04).monday_based_week(), 0); + assert_eq!(date!(2036 - 01 - 05).monday_based_week(), 0); + assert_eq!(date!(2036 - 01 - 06).monday_based_week(), 0); + assert_eq!(date!(2036 - 01 - 07).monday_based_week(), 1); + assert_eq!(date!(2036 - 02 - 28).monday_based_week(), 8); + assert_eq!(date!(2036 - 02 - 29).monday_based_week(), 8); + assert_eq!(date!(2036 - 03 - 01).monday_based_week(), 8); + assert_eq!(date!(2036 - 03 - 02).monday_based_week(), 8); + assert_eq!(date!(2036 - 03 - 03).monday_based_week(), 9); + assert_eq!(date!(2036 - 03 - 04).monday_based_week(), 9); + assert_eq!(date!(2036 - 03 - 05).monday_based_week(), 9); + assert_eq!(date!(2036 - 03 - 06).monday_based_week(), 9); + assert_eq!(date!(2036 - 03 - 07).monday_based_week(), 9); +} + +#[test] +fn test_monday_based_week_dominical_gf() { + assert_eq!(date!(2024 - 01 - 01).monday_based_week(), 1); + assert_eq!(date!(2024 - 01 - 02).monday_based_week(), 1); + assert_eq!(date!(2024 - 01 - 03).monday_based_week(), 1); + assert_eq!(date!(2024 - 01 - 04).monday_based_week(), 1); + assert_eq!(date!(2024 - 01 - 05).monday_based_week(), 1); + assert_eq!(date!(2024 - 01 - 06).monday_based_week(), 1); + assert_eq!(date!(2024 - 01 - 07).monday_based_week(), 1); + assert_eq!(date!(2024 - 02 - 28).monday_based_week(), 9); + assert_eq!(date!(2024 - 02 - 29).monday_based_week(), 9); + assert_eq!(date!(2024 - 03 - 01).monday_based_week(), 9); + assert_eq!(date!(2024 - 03 - 02).monday_based_week(), 9); + assert_eq!(date!(2024 - 03 - 03).monday_based_week(), 9); + assert_eq!(date!(2024 - 03 - 04).monday_based_week(), 10); + assert_eq!(date!(2024 - 03 - 05).monday_based_week(), 10); + assert_eq!(date!(2024 - 03 - 06).monday_based_week(), 10); + assert_eq!(date!(2024 - 03 - 07).monday_based_week(), 10); +} + +#[test] +fn test_sunday_based_week_dominical_a() { + assert_eq!(date!(2023 - 01 - 01).sunday_based_week(), 1); + assert_eq!(date!(2023 - 01 - 02).sunday_based_week(), 1); + assert_eq!(date!(2023 - 01 - 03).sunday_based_week(), 1); + assert_eq!(date!(2023 - 01 - 04).sunday_based_week(), 1); + assert_eq!(date!(2023 - 01 - 05).sunday_based_week(), 1); + assert_eq!(date!(2023 - 01 - 06).sunday_based_week(), 1); + assert_eq!(date!(2023 - 01 - 07).sunday_based_week(), 1); +} + +#[test] +fn test_sunday_based_week_dominical_b() { + assert_eq!(date!(2022 - 01 - 01).sunday_based_week(), 0); + assert_eq!(date!(2022 - 01 - 02).sunday_based_week(), 1); + assert_eq!(date!(2022 - 01 - 03).sunday_based_week(), 1); + assert_eq!(date!(2022 - 01 - 04).sunday_based_week(), 1); + assert_eq!(date!(2022 - 01 - 05).sunday_based_week(), 1); + assert_eq!(date!(2022 - 01 - 06).sunday_based_week(), 1); + assert_eq!(date!(2022 - 01 - 07).sunday_based_week(), 1); +} + +#[test] +fn test_sunday_based_week_dominical_c() { + assert_eq!(date!(2021 - 01 - 01).sunday_based_week(), 0); + assert_eq!(date!(2021 - 01 - 02).sunday_based_week(), 0); + assert_eq!(date!(2021 - 01 - 03).sunday_based_week(), 1); + assert_eq!(date!(2021 - 01 - 04).sunday_based_week(), 1); + assert_eq!(date!(2021 - 01 - 05).sunday_based_week(), 1); + assert_eq!(date!(2021 - 01 - 06).sunday_based_week(), 1); + assert_eq!(date!(2021 - 01 - 07).sunday_based_week(), 1); +} + +#[test] +fn test_sunday_based_week_dominical_d() { + assert_eq!(date!(2026 - 01 - 01).sunday_based_week(), 0); + assert_eq!(date!(2026 - 01 - 02).sunday_based_week(), 0); + assert_eq!(date!(2026 - 01 - 03).sunday_based_week(), 0); + assert_eq!(date!(2026 - 01 - 04).sunday_based_week(), 1); + assert_eq!(date!(2026 - 01 - 05).sunday_based_week(), 1); + assert_eq!(date!(2026 - 01 - 06).sunday_based_week(), 1); + assert_eq!(date!(2026 - 01 - 07).sunday_based_week(), 1); +} + +#[test] +fn test_sunday_based_week_dominical_e() { + assert_eq!(date!(2025 - 01 - 01).sunday_based_week(), 0); + assert_eq!(date!(2025 - 01 - 02).sunday_based_week(), 0); + assert_eq!(date!(2025 - 01 - 03).sunday_based_week(), 0); + assert_eq!(date!(2025 - 01 - 04).sunday_based_week(), 0); + assert_eq!(date!(2025 - 01 - 05).sunday_based_week(), 1); + assert_eq!(date!(2025 - 01 - 06).sunday_based_week(), 1); + assert_eq!(date!(2025 - 01 - 07).sunday_based_week(), 1); +} + +#[test] +fn test_sunday_based_week_dominical_f() { + assert_eq!(date!(2019 - 01 - 01).sunday_based_week(), 0); + assert_eq!(date!(2019 - 01 - 02).sunday_based_week(), 0); + assert_eq!(date!(2019 - 01 - 03).sunday_based_week(), 0); + assert_eq!(date!(2019 - 01 - 04).sunday_based_week(), 0); + assert_eq!(date!(2019 - 01 - 05).sunday_based_week(), 0); + assert_eq!(date!(2019 - 01 - 06).sunday_based_week(), 1); + assert_eq!(date!(2019 - 01 - 07).sunday_based_week(), 1); +} + +#[test] +fn test_sunday_based_week_dominical_g() { + assert_eq!(date!(2018 - 01 - 01).sunday_based_week(), 0); + assert_eq!(date!(2018 - 01 - 02).sunday_based_week(), 0); + assert_eq!(date!(2018 - 01 - 03).sunday_based_week(), 0); + assert_eq!(date!(2018 - 01 - 04).sunday_based_week(), 0); + assert_eq!(date!(2018 - 01 - 05).sunday_based_week(), 0); + assert_eq!(date!(2018 - 01 - 06).sunday_based_week(), 0); + assert_eq!(date!(2018 - 01 - 07).sunday_based_week(), 1); +} + +#[test] +fn test_sunday_based_week_dominical_ag() { + assert_eq!(date!(2012 - 01 - 01).sunday_based_week(), 1); + assert_eq!(date!(2012 - 01 - 02).sunday_based_week(), 1); + assert_eq!(date!(2012 - 01 - 03).sunday_based_week(), 1); + assert_eq!(date!(2012 - 01 - 04).sunday_based_week(), 1); + assert_eq!(date!(2012 - 01 - 05).sunday_based_week(), 1); + assert_eq!(date!(2012 - 01 - 06).sunday_based_week(), 1); + assert_eq!(date!(2012 - 01 - 07).sunday_based_week(), 1); + assert_eq!(date!(2012 - 02 - 28).sunday_based_week(), 9); + assert_eq!(date!(2012 - 02 - 29).sunday_based_week(), 9); + assert_eq!(date!(2012 - 03 - 01).sunday_based_week(), 9); + assert_eq!(date!(2012 - 03 - 02).sunday_based_week(), 9); + assert_eq!(date!(2012 - 03 - 03).sunday_based_week(), 9); + assert_eq!(date!(2012 - 03 - 04).sunday_based_week(), 10); + assert_eq!(date!(2012 - 03 - 05).sunday_based_week(), 10); + assert_eq!(date!(2012 - 03 - 06).sunday_based_week(), 10); + assert_eq!(date!(2012 - 03 - 07).sunday_based_week(), 10); +} + +#[test] +fn test_sunday_based_week_dominical_ba() { + assert_eq!(date!(2028 - 01 - 01).sunday_based_week(), 0); + assert_eq!(date!(2028 - 01 - 02).sunday_based_week(), 1); + assert_eq!(date!(2028 - 01 - 03).sunday_based_week(), 1); + assert_eq!(date!(2028 - 01 - 04).sunday_based_week(), 1); + assert_eq!(date!(2028 - 01 - 05).sunday_based_week(), 1); + assert_eq!(date!(2028 - 01 - 06).sunday_based_week(), 1); + assert_eq!(date!(2028 - 01 - 07).sunday_based_week(), 1); + assert_eq!(date!(2028 - 02 - 28).sunday_based_week(), 9); + assert_eq!(date!(2028 - 02 - 29).sunday_based_week(), 9); + assert_eq!(date!(2028 - 03 - 01).sunday_based_week(), 9); + assert_eq!(date!(2028 - 03 - 02).sunday_based_week(), 9); + assert_eq!(date!(2028 - 03 - 03).sunday_based_week(), 9); + assert_eq!(date!(2028 - 03 - 04).sunday_based_week(), 9); + assert_eq!(date!(2028 - 03 - 05).sunday_based_week(), 10); + assert_eq!(date!(2028 - 03 - 06).sunday_based_week(), 10); + assert_eq!(date!(2028 - 03 - 07).sunday_based_week(), 10); +} + +#[test] +fn test_sunday_based_week_dominical_cb() { + assert_eq!(date!(2016 - 01 - 01).sunday_based_week(), 0); + assert_eq!(date!(2016 - 01 - 02).sunday_based_week(), 0); + assert_eq!(date!(2016 - 01 - 03).sunday_based_week(), 1); + assert_eq!(date!(2016 - 01 - 04).sunday_based_week(), 1); + assert_eq!(date!(2016 - 01 - 05).sunday_based_week(), 1); + assert_eq!(date!(2016 - 01 - 06).sunday_based_week(), 1); + assert_eq!(date!(2016 - 01 - 07).sunday_based_week(), 1); + assert_eq!(date!(2016 - 02 - 28).sunday_based_week(), 9); + assert_eq!(date!(2016 - 02 - 29).sunday_based_week(), 9); + assert_eq!(date!(2016 - 03 - 01).sunday_based_week(), 9); + assert_eq!(date!(2016 - 03 - 02).sunday_based_week(), 9); + assert_eq!(date!(2016 - 03 - 03).sunday_based_week(), 9); + assert_eq!(date!(2016 - 03 - 04).sunday_based_week(), 9); + assert_eq!(date!(2016 - 03 - 05).sunday_based_week(), 9); + assert_eq!(date!(2016 - 03 - 06).sunday_based_week(), 10); + assert_eq!(date!(2016 - 03 - 07).sunday_based_week(), 10); +} + +#[test] +fn test_sunday_based_week_dominical_dc() { + assert_eq!(date!(2032 - 01 - 01).sunday_based_week(), 0); + assert_eq!(date!(2032 - 01 - 02).sunday_based_week(), 0); + assert_eq!(date!(2032 - 01 - 03).sunday_based_week(), 0); + assert_eq!(date!(2032 - 01 - 04).sunday_based_week(), 1); + assert_eq!(date!(2032 - 01 - 05).sunday_based_week(), 1); + assert_eq!(date!(2032 - 01 - 06).sunday_based_week(), 1); + assert_eq!(date!(2032 - 01 - 07).sunday_based_week(), 1); + assert_eq!(date!(2032 - 02 - 28).sunday_based_week(), 8); + assert_eq!(date!(2032 - 02 - 29).sunday_based_week(), 9); + assert_eq!(date!(2032 - 03 - 01).sunday_based_week(), 9); + assert_eq!(date!(2032 - 03 - 02).sunday_based_week(), 9); + assert_eq!(date!(2032 - 03 - 03).sunday_based_week(), 9); + assert_eq!(date!(2032 - 03 - 04).sunday_based_week(), 9); + assert_eq!(date!(2032 - 03 - 05).sunday_based_week(), 9); + assert_eq!(date!(2032 - 03 - 06).sunday_based_week(), 9); + assert_eq!(date!(2032 - 03 - 07).sunday_based_week(), 10); +} + +#[test] +fn test_sunday_based_week_dominical_ed() { + assert_eq!(date!(2020 - 01 - 01).sunday_based_week(), 0); + assert_eq!(date!(2020 - 01 - 02).sunday_based_week(), 0); + assert_eq!(date!(2020 - 01 - 03).sunday_based_week(), 0); + assert_eq!(date!(2020 - 01 - 04).sunday_based_week(), 0); + assert_eq!(date!(2020 - 01 - 05).sunday_based_week(), 1); + assert_eq!(date!(2020 - 01 - 06).sunday_based_week(), 1); + assert_eq!(date!(2020 - 01 - 07).sunday_based_week(), 1); + assert_eq!(date!(2020 - 02 - 28).sunday_based_week(), 8); + assert_eq!(date!(2020 - 02 - 29).sunday_based_week(), 8); + assert_eq!(date!(2020 - 03 - 01).sunday_based_week(), 9); + assert_eq!(date!(2020 - 03 - 02).sunday_based_week(), 9); + assert_eq!(date!(2020 - 03 - 03).sunday_based_week(), 9); + assert_eq!(date!(2020 - 03 - 04).sunday_based_week(), 9); + assert_eq!(date!(2020 - 03 - 05).sunday_based_week(), 9); + assert_eq!(date!(2020 - 03 - 06).sunday_based_week(), 9); + assert_eq!(date!(2020 - 03 - 07).sunday_based_week(), 9); +} + +#[test] +fn test_sunday_based_week_dominical_fe() { + assert_eq!(date!(2036 - 01 - 01).sunday_based_week(), 0); + assert_eq!(date!(2036 - 01 - 02).sunday_based_week(), 0); + assert_eq!(date!(2036 - 01 - 03).sunday_based_week(), 0); + assert_eq!(date!(2036 - 01 - 04).sunday_based_week(), 0); + assert_eq!(date!(2036 - 01 - 05).sunday_based_week(), 0); + assert_eq!(date!(2036 - 01 - 06).sunday_based_week(), 1); + assert_eq!(date!(2036 - 01 - 07).sunday_based_week(), 1); + assert_eq!(date!(2036 - 02 - 28).sunday_based_week(), 8); + assert_eq!(date!(2036 - 02 - 29).sunday_based_week(), 8); + assert_eq!(date!(2036 - 03 - 01).sunday_based_week(), 8); + assert_eq!(date!(2036 - 03 - 02).sunday_based_week(), 9); + assert_eq!(date!(2036 - 03 - 03).sunday_based_week(), 9); + assert_eq!(date!(2036 - 03 - 04).sunday_based_week(), 9); + assert_eq!(date!(2036 - 03 - 05).sunday_based_week(), 9); + assert_eq!(date!(2036 - 03 - 06).sunday_based_week(), 9); + assert_eq!(date!(2036 - 03 - 07).sunday_based_week(), 9); +} + +#[test] +fn test_sunday_based_week_dominical_gf() { + assert_eq!(date!(2024 - 01 - 01).sunday_based_week(), 0); + assert_eq!(date!(2024 - 01 - 02).sunday_based_week(), 0); + assert_eq!(date!(2024 - 01 - 03).sunday_based_week(), 0); + assert_eq!(date!(2024 - 01 - 04).sunday_based_week(), 0); + assert_eq!(date!(2024 - 01 - 05).sunday_based_week(), 0); + assert_eq!(date!(2024 - 01 - 06).sunday_based_week(), 0); + assert_eq!(date!(2024 - 01 - 07).sunday_based_week(), 1); + assert_eq!(date!(2024 - 02 - 28).sunday_based_week(), 8); + assert_eq!(date!(2024 - 02 - 29).sunday_based_week(), 8); + assert_eq!(date!(2024 - 03 - 01).sunday_based_week(), 8); + assert_eq!(date!(2024 - 03 - 02).sunday_based_week(), 8); + assert_eq!(date!(2024 - 03 - 03).sunday_based_week(), 9); + assert_eq!(date!(2024 - 03 - 04).sunday_based_week(), 9); + assert_eq!(date!(2024 - 03 - 05).sunday_based_week(), 9); + assert_eq!(date!(2024 - 03 - 06).sunday_based_week(), 9); + assert_eq!(date!(2024 - 03 - 07).sunday_based_week(), 9); +} + +#[test] +fn from_iso_week_date() { + use Weekday::*; + assert!(Date::from_iso_week_date(2019, 1, Monday).is_ok()); + assert!(Date::from_iso_week_date(2019, 1, Tuesday).is_ok()); + assert!(Date::from_iso_week_date(2020, 53, Friday).is_ok()); + assert!(Date::from_iso_week_date(-9999, 1, Monday).is_ok()); + // 2019 doesn't have 53 weeks. + assert!(Date::from_iso_week_date(2019, 53, Monday).is_err()); + // Regression test. Year zero (1 BCE) has dominical letter BA. + assert_eq!( + Date::from_iso_week_date(-1, 52, Saturday), + Ok(date!(0000 - 01 - 01)) + ); + assert_eq!(date!(-0001-W 52-6), date!(0000 - 01 - 01)); +} + +#[test] +fn year() { + assert_eq!(date!(2019 - 002).year(), 2019); + assert_eq!(date!(2020 - 002).year(), 2020); +} + +#[test] +fn month() { + assert_eq!(date!(2019 - 002).month(), Month::January); + assert_eq!(date!(2020 - 002).month(), Month::January); + assert_eq!(date!(2019 - 060).month(), Month::March); + assert_eq!(date!(2020 - 060).month(), Month::February); +} + +#[test] +fn day() { + assert_eq!(date!(2019 - 002).day(), 2); + assert_eq!(date!(2020 - 002).day(), 2); + assert_eq!(date!(2019 - 060).day(), 1); + assert_eq!(date!(2020 - 060).day(), 29); +} + +#[test] +fn iso_week() { + assert_eq!(date!(2019 - 01 - 01).iso_week(), 1); + assert_eq!(date!(2019 - 10 - 04).iso_week(), 40); + assert_eq!(date!(2020 - 01 - 01).iso_week(), 1); + assert_eq!(date!(2020 - 12 - 31).iso_week(), 53); + assert_eq!(date!(2021 - 01 - 01).iso_week(), 53); +} + +#[test] +fn to_calendar_date() { + assert_eq!( + date!(2019 - 01 - 02).to_calendar_date(), + (2019, Month::January, 2) + ); + assert_eq!( + date!(2019 - 02 - 02).to_calendar_date(), + (2019, Month::February, 2) + ); + assert_eq!( + date!(2019 - 03 - 02).to_calendar_date(), + (2019, Month::March, 2) + ); + assert_eq!( + date!(2019 - 04 - 02).to_calendar_date(), + (2019, Month::April, 2) + ); + assert_eq!( + date!(2019 - 05 - 02).to_calendar_date(), + (2019, Month::May, 2) + ); + assert_eq!( + date!(2019 - 06 - 02).to_calendar_date(), + (2019, Month::June, 2) + ); + assert_eq!( + date!(2019 - 07 - 02).to_calendar_date(), + (2019, Month::July, 2) + ); + assert_eq!( + date!(2019 - 08 - 02).to_calendar_date(), + (2019, Month::August, 2) + ); + assert_eq!( + date!(2019 - 09 - 02).to_calendar_date(), + (2019, Month::September, 2) + ); + assert_eq!( + date!(2019 - 10 - 02).to_calendar_date(), + (2019, Month::October, 2) + ); + assert_eq!( + date!(2019 - 11 - 02).to_calendar_date(), + (2019, Month::November, 2) + ); + assert_eq!( + date!(2019 - 12 - 02).to_calendar_date(), + (2019, Month::December, 2) + ); +} + +#[test] +fn to_ordinal_date() { + assert_eq!(date!(2019 - 01 - 01).to_ordinal_date(), (2019, 1)); +} + +#[test] +fn to_iso_week_date() { + use Weekday::*; + assert_eq!(date!(2019 - 01 - 01).to_iso_week_date(), (2019, 1, Tuesday)); + assert_eq!(date!(2019 - 10 - 04).to_iso_week_date(), (2019, 40, Friday)); + assert_eq!( + date!(2020 - 01 - 01).to_iso_week_date(), + (2020, 1, Wednesday) + ); + assert_eq!( + date!(2020 - 12 - 31).to_iso_week_date(), + (2020, 53, Thursday) + ); + assert_eq!(date!(2021 - 01 - 01).to_iso_week_date(), (2020, 53, Friday)); + assert_eq!(date!(0000 - 01 - 01).to_iso_week_date(), (-1, 52, Saturday)); +} + +#[test] +fn weekday() { + use Weekday::*; + assert_eq!(date!(2019 - 01 - 01).weekday(), Tuesday); + assert_eq!(date!(2019 - 02 - 01).weekday(), Friday); + assert_eq!(date!(2019 - 03 - 01).weekday(), Friday); + assert_eq!(date!(2019 - 04 - 01).weekday(), Monday); + assert_eq!(date!(2019 - 05 - 01).weekday(), Wednesday); + assert_eq!(date!(2019 - 06 - 01).weekday(), Saturday); + assert_eq!(date!(2019 - 07 - 01).weekday(), Monday); + assert_eq!(date!(2019 - 08 - 01).weekday(), Thursday); + assert_eq!(date!(2019 - 09 - 01).weekday(), Sunday); + assert_eq!(date!(2019 - 10 - 01).weekday(), Tuesday); + assert_eq!(date!(2019 - 11 - 01).weekday(), Friday); + assert_eq!(date!(2019 - 12 - 01).weekday(), Sunday); +} + +#[test] +fn next_day() { + assert_eq!( + date!(2019 - 01 - 01).next_day(), + Some(date!(2019 - 01 - 02)) + ); + assert_eq!( + date!(2019 - 01 - 31).next_day(), + Some(date!(2019 - 02 - 01)) + ); + assert_eq!( + date!(2019 - 12 - 31).next_day(), + Some(date!(2020 - 01 - 01)) + ); + assert_eq!( + date!(2020 - 12 - 31).next_day(), + Some(date!(2021 - 01 - 01)) + ); + assert_eq!(Date::MAX.next_day(), None); +} + +#[test] +fn previous_day() { + assert_eq!( + date!(2019 - 01 - 02).previous_day(), + Some(date!(2019 - 01 - 01)) + ); + assert_eq!( + date!(2019 - 02 - 01).previous_day(), + Some(date!(2019 - 01 - 31)) + ); + assert_eq!( + date!(2020 - 01 - 01).previous_day(), + Some(date!(2019 - 12 - 31)) + ); + assert_eq!( + date!(2021 - 01 - 01).previous_day(), + Some(date!(2020 - 12 - 31)) + ); + assert_eq!(Date::MIN.previous_day(), None); +} + +#[test] +fn to_julian_day() { + assert_eq!(date!(-999_999 - 01 - 01).to_julian_day(), -363_521_074); + assert_eq!(date!(-9999 - 01 - 01).to_julian_day(), -1_930_999); + assert_eq!(date!(-4713 - 11 - 24).to_julian_day(), 0); + assert_eq!(date!(2000 - 01 - 01).to_julian_day(), 2_451_545); + assert_eq!(date!(2019 - 01 - 01).to_julian_day(), 2_458_485); + assert_eq!(date!(2019 - 12 - 31).to_julian_day(), 2_458_849); +} + +#[test] +fn from_julian_day() { + assert_eq!( + Date::from_julian_day(-363_521_074), + Ok(date!(-999_999 - 01 - 01)) + ); + assert_eq!( + Date::from_julian_day(-1_930_999), + Ok(date!(-9999 - 01 - 01)) + ); + assert_eq!(Date::from_julian_day(0), Ok(date!(-4713 - 11 - 24))); + assert_eq!(Date::from_julian_day(2_451_545), Ok(date!(2000 - 01 - 01))); + assert_eq!(Date::from_julian_day(2_458_485), Ok(date!(2019 - 01 - 01))); + assert_eq!(Date::from_julian_day(2_458_849), Ok(date!(2019 - 12 - 31))); + assert!(Date::from_julian_day(i32::MAX).is_err()); +} + +#[test] +fn midnight() { + assert_eq!(date!(1970 - 01 - 01).midnight(), datetime!(1970-01-01 0:00)); +} + +#[test] +fn with_time() { + assert_eq!( + date!(1970 - 01 - 01).with_time(time!(0:00)), + datetime!(1970-01-01 0:00), + ); +} + +#[test] +fn with_hms() { + assert_eq!( + date!(1970 - 01 - 01).with_hms(0, 0, 0), + Ok(datetime!(1970-01-01 0:00)), + ); + assert!(date!(1970 - 01 - 01).with_hms(24, 0, 0).is_err()); +} + +#[test] +fn with_hms_milli() { + assert_eq!( + date!(1970 - 01 - 01).with_hms_milli(0, 0, 0, 0), + Ok(datetime!(1970-01-01 0:00)), + ); + assert!(date!(1970 - 01 - 01).with_hms_milli(24, 0, 0, 0).is_err()); +} + +#[test] +fn with_hms_micro() { + assert_eq!( + date!(1970 - 01 - 01).with_hms_micro(0, 0, 0, 0), + Ok(datetime!(1970-01-01 0:00)), + ); + assert!(date!(1970 - 01 - 01).with_hms_micro(24, 0, 0, 0).is_err()); +} + +#[test] +fn with_hms_nano() { + assert_eq!( + date!(1970 - 01 - 01).with_hms_nano(0, 0, 0, 0), + Ok(datetime!(1970-01-01 0:00)), + ); + assert!(date!(1970 - 01 - 01).with_hms_nano(24, 0, 0, 0).is_err()); +} + +#[test] +fn add() { + assert_eq!(date!(2019 - 01 - 01) + 5.days(), date!(2019 - 01 - 06)); + assert_eq!(date!(2019 - 12 - 31) + 1.days(), date!(2020 - 01 - 01)); +} + +#[test] +fn add_std() { + assert_eq!(date!(2019 - 01 - 01) + 5.std_days(), date!(2019 - 01 - 06)); + assert_eq!(date!(2019 - 12 - 31) + 1.std_days(), date!(2020 - 01 - 01)); +} + +#[test] +fn add_assign() { + let mut date = date!(2019 - 12 - 31); + date += 1.days(); + assert_eq!(date, date!(2020 - 01 - 01)); +} + +#[test] +fn add_assign_std() { + let mut date = date!(2019 - 12 - 31); + date += 1.std_days(); + assert_eq!(date, date!(2020 - 01 - 01)); +} + +#[test] +fn sub() { + assert_eq!(date!(2019 - 01 - 06) - 5.days(), date!(2019 - 01 - 01)); + assert_eq!(date!(2020 - 01 - 01) - 1.days(), date!(2019 - 12 - 31)); +} + +#[test] +fn sub_std() { + assert_eq!(date!(2019 - 01 - 06) - 5.std_days(), date!(2019 - 01 - 01)); + assert_eq!(date!(2020 - 01 - 01) - 1.std_days(), date!(2019 - 12 - 31)); +} + +#[test] +fn sub_assign() { + let mut date = date!(2020 - 01 - 01); + date -= 1.days(); + assert_eq!(date, date!(2019 - 12 - 31)); +} + +#[test] +fn sub_assign_std() { + let mut date = date!(2020 - 01 - 01); + date -= 1.std_days(); + assert_eq!(date, date!(2019 - 12 - 31)); +} + +#[test] +fn sub_self() { + assert_eq!(date!(2019 - 01 - 06) - date!(2019 - 01 - 01), 5.days()); + assert_eq!(date!(2020 - 01 - 01) - date!(2019 - 12 - 31), 1.days()); +} + +#[test] +fn partial_ord() { + let first = date!(2019 - 01 - 01); + let second = date!(2019 - 01 - 02); + + assert_eq!(first.partial_cmp(&first), Some(Ordering::Equal)); + assert_eq!(first.partial_cmp(&second), Some(Ordering::Less)); + assert_eq!(second.partial_cmp(&first), Some(Ordering::Greater)); +} + +#[test] +fn ord() { + let first = date!(2019 - 01 - 01); + let second = date!(2019 - 01 - 02); + + assert_eq!(first.cmp(&first), Ordering::Equal); + assert_eq!(first.cmp(&second), Ordering::Less); + assert_eq!(second.cmp(&first), Ordering::Greater); +} + +#[test] +fn regression_check() { + let (year, week, weekday) = (date!(0063 - 365)).to_iso_week_date(); + assert_eq!(year, 64); + assert_eq!(week, 1); + assert_eq!(weekday, Weekday::Monday); +} + +#[test] +fn checked_add_duration() { + // Adding subday duration + assert_eq!( + Date::MIN.checked_add(Duration::new(86_399, 999_999_999)), + Some(Date::MIN) + ); + assert_eq!( + Date::MIN.checked_add(Duration::new(-86_399, -999_999_999)), + Some(Date::MIN) + ); + + assert_eq!( + date!(2021 - 10 - 25).checked_add(Duration::new(86_399, 999_999_999)), + Some(date!(2021 - 10 - 25)) + ); + assert_eq!( + date!(2021 - 10 - 25).checked_add(Duration::new(-86_399, -999_999_999)), + Some(date!(2021 - 10 - 25)) + ); + + assert_eq!( + Date::MAX.checked_add(Duration::new(86_399, 999_999_999)), + Some(Date::MAX) + ); + assert_eq!( + Date::MAX.checked_add(Duration::new(-86_399, -999_999_999)), + Some(Date::MAX) + ); + + // Adding 1 day duration + assert_eq!(Date::MIN.checked_add(Duration::DAY), Date::MIN.next_day()); + assert_eq!(Date::MIN.checked_add(-Duration::DAY), None); + + assert_eq!( + date!(2021 - 10 - 25).checked_add(Duration::DAY), + Some(date!(2021 - 10 - 26)) + ); + assert_eq!( + date!(2021 - 10 - 25).checked_add(-Duration::DAY), + Some(date!(2021 - 10 - 24)) + ); + + assert_eq!(Date::MAX.checked_add(Duration::DAY), None); + assert_eq!( + Date::MAX.checked_add(-Duration::DAY), + Date::MAX.previous_day() + ); + + // Adding MIN/MAX duration + assert_eq!(Date::MIN.checked_add(Duration::MIN), None); + assert_eq!(Date::MAX.checked_add(Duration::MAX), None); +} + +#[test] +fn checked_sub_duration() { + // Subtracting subday duration + assert_eq!( + Date::MIN.checked_sub(Duration::new(86_399, 999_999_999)), + Some(Date::MIN) + ); + assert_eq!( + Date::MIN.checked_sub(Duration::new(-86_399, -999_999_999)), + Some(Date::MIN) + ); + + assert_eq!( + date!(2021 - 10 - 25).checked_sub(Duration::new(86_399, 999_999_999)), + Some(date!(2021 - 10 - 25)) + ); + assert_eq!( + date!(2021 - 10 - 25).checked_sub(Duration::new(-86_399, -999_999_999)), + Some(date!(2021 - 10 - 25)) + ); + + assert_eq!( + Date::MAX.checked_sub(Duration::new(86_399, 999_999_999)), + Some(Date::MAX) + ); + assert_eq!( + Date::MAX.checked_sub(Duration::new(-86_399, -999_999_999)), + Some(Date::MAX) + ); + + // Subtracting 1 day duration + assert_eq!(Date::MIN.checked_sub(Duration::DAY), None); + assert_eq!(Date::MIN.checked_sub(-Duration::DAY), Date::MIN.next_day()); + + assert_eq!( + date!(2021 - 10 - 25).checked_sub(Duration::DAY), + Some(date!(2021 - 10 - 24)) + ); + assert_eq!( + date!(2021 - 10 - 25).checked_sub(-Duration::DAY), + Some(date!(2021 - 10 - 26)) + ); + + assert_eq!( + Date::MAX.checked_sub(Duration::DAY), + Date::MAX.previous_day() + ); + assert_eq!(Date::MAX.checked_sub(-Duration::DAY), None); + + // Subtracting MIN/MAX duration + assert_eq!(Date::MIN.checked_sub(Duration::MAX), None); + assert_eq!(Date::MAX.checked_sub(Duration::MIN), None); +} + +#[test] +fn saturating_add_duration() { + assert_eq!( + date!(2021 - 11 - 05).saturating_add(2.days()), + date!(2021 - 11 - 07) + ); + assert_eq!( + date!(2021 - 11 - 05).saturating_add((-2).days()), + date!(2021 - 11 - 03) + ); + + // Adding with underflow + assert_eq!(Date::MIN.saturating_add((-10).days()), Date::MIN); + + // Adding with overflow + assert_eq!(Date::MAX.saturating_add(10.days()), Date::MAX); + + // Adding zero duration at boundaries + assert_eq!(Date::MIN.saturating_add(Duration::ZERO), Date::MIN); + assert_eq!(Date::MAX.saturating_add(Duration::ZERO), Date::MAX); +} + +#[test] +fn saturating_sub_duration() { + assert_eq!( + date!(2021 - 11 - 05).saturating_sub(2.days()), + date!(2021 - 11 - 03) + ); + assert_eq!( + date!(2021 - 11 - 05).saturating_sub((-2).days()), + date!(2021 - 11 - 07) + ); + + // Subtracting with underflow + assert_eq!(Date::MIN.saturating_sub(10.days()), Date::MIN); + + // Subtracting with overflow + assert_eq!(Date::MAX.saturating_sub((-10).days()), Date::MAX); + + // Subtracting zero duration at boundaries + assert_eq!(Date::MIN.saturating_sub(Duration::ZERO), Date::MIN); + assert_eq!(Date::MAX.saturating_sub(Duration::ZERO), Date::MAX); +} + +#[test] +fn replace_year() { + assert_eq!( + date!(2022 - 02 - 18).replace_year(2019), + Ok(date!(2019 - 02 - 18)) + ); + assert!(date!(2022 - 02 - 18).replace_year(-1_000_000_000).is_err()); // -1_000_000_000 isn't a valid year + assert!(date!(2022 - 02 - 18).replace_year(1_000_000_000).is_err()); // 1_000_000_000 isn't a valid year + + // Common to leap year, before leap day. + assert_eq!( + date!(2022 - 01 - 01).replace_year(2024), + Ok(date!(2024 - 01 - 01)) + ); + // Common to leap year, after leap day. + assert_eq!( + date!(2022 - 12 - 01).replace_year(2024), + Ok(date!(2024 - 12 - 01)) + ); + // Leap to common year, before leap day. + assert_eq!( + date!(2024 - 01 - 01).replace_year(2022), + Ok(date!(2022 - 01 - 01)) + ); + // Leap to common year, after leap day. + assert_eq!( + date!(2024 - 12 - 01).replace_year(2022), + Ok(date!(2022 - 12 - 01)) + ); + // Leap to common year, leap day. + assert!(date!(2024 - 02 - 29).replace_year(2022).is_err()); + // Common to common year. + assert_eq!( + date!(2022 - 12 - 01).replace_year(2023), + Ok(date!(2023 - 12 - 01)) + ); + // Leap to leap year. + assert_eq!( + date!(2024 - 12 - 01).replace_year(2028), + Ok(date!(2028 - 12 - 01)) + ); +} + +#[test] +fn replace_month() { + assert_eq!( + date!(2022 - 02 - 18).replace_month(Month::January), + Ok(date!(2022 - 01 - 18)) + ); + assert!( + date!(2022 - 01 - 30) + .replace_month(Month::February) + .is_err() + ); // 30 isn't a valid day in February +} + +#[test] +fn replace_day() { + assert_eq!( + date!(2022 - 02 - 18).replace_day(1), + Ok(date!(2022 - 02 - 01)) + ); + assert!(date!(2022 - 02 - 18).replace_day(0).is_err()); // 0 isn't a valid day + assert!(date!(2022 - 02 - 18).replace_day(30).is_err()); // 30 isn't a valid day in February +} + +#[test] +fn replace_ordinal() { + assert_eq!( + date!(2022 - 02 - 18).replace_ordinal(1), + Ok(date!(2022 - 001)) + ); + assert_eq!( + date!(2024 - 02 - 29).replace_ordinal(366), + Ok(date!(2024 - 366)) + ); + assert!(date!(2022 - 049).replace_ordinal(0).is_err()); // 0 isn't a valid day + assert!(date!(2022 - 049).replace_ordinal(366).is_err()); // 2022 isn't a leap year + assert!(date!(2022 - 049).replace_ordinal(367).is_err()); // 367 isn't a valid day +} + +#[test] +fn next_occurrence_test() { + assert_eq!( + date!(2023 - 06 - 25).next_occurrence(Weekday::Monday), + date!(2023 - 06 - 26) + ); + assert_eq!( + date!(2023 - 06 - 26).next_occurrence(Weekday::Monday), + date!(2023 - 07 - 03) + ); + assert_eq!( + date!(2023 - 06 - 27).next_occurrence(Weekday::Monday), + date!(2023 - 07 - 03) + ); + assert_eq!( + date!(2023 - 06 - 28).next_occurrence(Weekday::Monday), + date!(2023 - 07 - 03) + ); + assert_eq!( + date!(2023 - 06 - 29).next_occurrence(Weekday::Monday), + date!(2023 - 07 - 03) + ); + assert_eq!( + date!(2023 - 06 - 30).next_occurrence(Weekday::Monday), + date!(2023 - 07 - 03) + ); + assert_eq!( + date!(2023 - 07 - 01).next_occurrence(Weekday::Monday), + date!(2023 - 07 - 03) + ); + assert_eq!( + date!(2023 - 07 - 02).next_occurrence(Weekday::Monday), + date!(2023 - 07 - 03) + ); + assert_eq!( + date!(2023 - 07 - 03).next_occurrence(Weekday::Monday), + date!(2023 - 07 - 10) + ); +} + +#[test] +fn prev_occurrence_test() { + assert_eq!( + date!(2023 - 07 - 07).prev_occurrence(Weekday::Thursday), + date!(2023 - 07 - 06) + ); + assert_eq!( + date!(2023 - 07 - 06).prev_occurrence(Weekday::Thursday), + date!(2023 - 06 - 29) + ); + assert_eq!( + date!(2023 - 07 - 05).prev_occurrence(Weekday::Thursday), + date!(2023 - 06 - 29) + ); + assert_eq!( + date!(2023 - 07 - 04).prev_occurrence(Weekday::Thursday), + date!(2023 - 06 - 29) + ); + assert_eq!( + date!(2023 - 07 - 03).prev_occurrence(Weekday::Thursday), + date!(2023 - 06 - 29) + ); + assert_eq!( + date!(2023 - 07 - 02).prev_occurrence(Weekday::Thursday), + date!(2023 - 06 - 29) + ); + assert_eq!( + date!(2023 - 07 - 01).prev_occurrence(Weekday::Thursday), + date!(2023 - 06 - 29) + ); + assert_eq!( + date!(2023 - 06 - 30).prev_occurrence(Weekday::Thursday), + date!(2023 - 06 - 29) + ); + assert_eq!( + date!(2023 - 06 - 29).prev_occurrence(Weekday::Thursday), + date!(2023 - 06 - 22) + ); +} + +#[test] +fn nth_next_occurrence_test() { + assert_eq!( + date!(2023 - 06 - 25).nth_next_occurrence(Weekday::Monday, 5), + date!(2023 - 07 - 24) + ); + assert_eq!( + date!(2023 - 06 - 26).nth_next_occurrence(Weekday::Monday, 5), + date!(2023 - 07 - 31) + ); +} + +#[test] +fn nth_prev_occurrence_test() { + assert_eq!( + date!(2023 - 06 - 27).nth_prev_occurrence(Weekday::Monday, 3), + date!(2023 - 06 - 12) + ); + assert_eq!( + date!(2023 - 06 - 26).nth_prev_occurrence(Weekday::Monday, 3), + date!(2023 - 06 - 05) + ); +} + +#[test] +#[should_panic] +fn next_occurrence_overflow_test() { + date!(+999999 - 12 - 25).next_occurrence(Weekday::Saturday); +} + +#[test] +#[should_panic] +fn prev_occurrence_overflow_test() { + date!(-999999 - 01 - 07).prev_occurrence(Weekday::Sunday); +} + +#[test] +#[should_panic] +fn nth_next_occurrence_overflow_test() { + date!(+999999 - 12 - 25).nth_next_occurrence(Weekday::Saturday, 1); +} + +#[test] +#[should_panic] +fn nth_next_occurence_zeroth_occurence_test() { + date!(2023 - 06 - 25).nth_next_occurrence(Weekday::Monday, 0); +} + +#[test] +#[should_panic] +fn nth_prev_occurence_zeroth_occurence_test() { + date!(2023 - 06 - 27).nth_prev_occurrence(Weekday::Monday, 0); +} + +#[test] +#[should_panic] +fn nth_prev_occurrence_overflow_test() { + date!(-999999 - 01 - 07).nth_prev_occurrence(Weekday::Sunday, 1); +} diff --git a/tests/derives.rs b/tests/derives.rs new file mode 100644 index 000000000..0165c0377 --- /dev/null +++ b/tests/derives.rs @@ -0,0 +1,201 @@ +use std::cmp::Ordering; +use std::collections::hash_map::DefaultHasher; +use std::hash::Hash; + +use time::error::{self, ConversionRange, IndeterminateOffset, TryFromParsed}; +use time::ext::NumericalDuration; +use time::format_description::{self, modifier, well_known, Component, BorrowedFormatItem, OwnedFormatItem}; +use time::macros::{date, offset, time}; +use time::parsing::Parsed; +use time::{Duration, Error, Month, Time, Weekday}; +#[allow(deprecated)] +use time::Instant; +use time_macros::datetime; + +macro_rules! assert_cloned_eq { + ($x:expr) => { + assert_eq!($x.clone(), $x) + }; +} + +fn component_range_error() -> error::ComponentRange { + Time::from_hms(24, 0, 0).expect_err("24 is not a valid hour") +} + +fn invalid_format_description() -> error::InvalidFormatDescription { + format_description::parse("[").expect_err("format description is invalid") +} + +#[allow(clippy::cognitive_complexity)] // all test the same thing +#[test] +fn clone() { + #[allow(deprecated)] + let instant = Instant::now(); + assert_cloned_eq!(date!(2021 - 001)); + assert_cloned_eq!(time!(0:00)); + assert_cloned_eq!(offset!(UTC)); + assert_cloned_eq!(datetime!(2021-001 0:00)); + assert_cloned_eq!(datetime!(2021-001 0:00 UTC)); + assert_cloned_eq!(Weekday::Monday); + assert_cloned_eq!(Month::January); + assert_cloned_eq!(Duration::ZERO); + assert_cloned_eq!(instant); + assert_cloned_eq!(IndeterminateOffset); + assert_cloned_eq!(ConversionRange); + assert_cloned_eq!(invalid_format_description()); + assert_cloned_eq!(TryFromParsed::InsufficientInformation); + #[allow(clippy::clone_on_copy)] // needed for coverage + let _ = Parsed::new().clone(); + assert_cloned_eq!(error::Parse::ParseFromDescription( + error::ParseFromDescription::InvalidComponent("foo") + )); + assert_cloned_eq!(error::DifferentVariant); + assert_cloned_eq!(error::InvalidVariant); + assert_cloned_eq!(error::ParseFromDescription::InvalidComponent("foo")); + assert_cloned_eq!(Component::OffsetSecond(modifier::OffsetSecond::default())); + assert_cloned_eq!(well_known::Rfc2822); + assert_cloned_eq!(well_known::Rfc3339); + assert_cloned_eq!(well_known::Iso8601::DEFAULT); + assert_cloned_eq!(well_known::iso8601::FormattedComponents::None); + assert_cloned_eq!(well_known::iso8601::DateKind::Calendar); + assert_cloned_eq!(well_known::iso8601::TimePrecision::Hour { + decimal_digits: None + }); + assert_cloned_eq!(well_known::iso8601::OffsetPrecision::Hour); + assert_cloned_eq!(well_known::iso8601::FormattedComponents::None); + assert_cloned_eq!(component_range_error()); + assert_cloned_eq!(BorrowedFormatItem::Literal(b"")); + assert_cloned_eq!(time::util::local_offset::Soundness::Sound); + + assert_cloned_eq!(modifier::Day::default()); + assert_cloned_eq!(modifier::MonthRepr::default()); + assert_cloned_eq!(modifier::Month::default()); + assert_cloned_eq!(modifier::Ordinal::default()); + assert_cloned_eq!(modifier::WeekdayRepr::default()); + assert_cloned_eq!(modifier::Weekday::default()); + assert_cloned_eq!(modifier::WeekNumberRepr::default()); + assert_cloned_eq!(modifier::WeekNumber::default()); + assert_cloned_eq!(modifier::YearRepr::default()); + assert_cloned_eq!(modifier::Year::default()); + assert_cloned_eq!(modifier::Hour::default()); + assert_cloned_eq!(modifier::Minute::default()); + assert_cloned_eq!(modifier::Period::default()); + assert_cloned_eq!(modifier::Second::default()); + assert_cloned_eq!(modifier::SubsecondDigits::default()); + assert_cloned_eq!(modifier::Subsecond::default()); + assert_cloned_eq!(modifier::OffsetHour::default()); + assert_cloned_eq!(modifier::OffsetMinute::default()); + assert_cloned_eq!(modifier::OffsetSecond::default()); + assert_cloned_eq!(modifier::Padding::default()); +} + +#[test] +fn hash() { + let mut hasher = DefaultHasher::new(); + date!(2021 - 001).hash(&mut hasher); + time!(0:00).hash(&mut hasher); + offset!(UTC).hash(&mut hasher); + datetime!(2021-001 0:00).hash(&mut hasher); + datetime!(2021-001 0:00 UTC).hash(&mut hasher); + Weekday::Monday.hash(&mut hasher); + Month::January.hash(&mut hasher); + #[allow(deprecated)] + Instant::now().hash(&mut hasher); + Duration::ZERO.hash(&mut hasher); + component_range_error().hash(&mut hasher); +} + +#[test] +fn partial_ord() { + #[allow(deprecated)] + let instant = Instant::now(); + assert_eq!(offset!(UTC).partial_cmp(&offset!(+1)), Some(Ordering::Less)); + assert_eq!( + offset!(+1).partial_cmp(&offset!(UTC)), + Some(Ordering::Greater) + ); + assert_eq!( + (instant - 1.seconds()).partial_cmp(&instant), + Some(Ordering::Less) + ); + assert_eq!( + (instant + 1.seconds()).partial_cmp(&instant), + Some(Ordering::Greater) + ); +} + +#[test] +fn ord() { + assert_eq!(offset!(UTC).cmp(&offset!(+1)), Ordering::Less); + assert_eq!(offset!(+1).cmp(&offset!(UTC)), Ordering::Greater); + assert_eq!(offset!(UTC).cmp(&offset!(UTC)), Ordering::Equal); +} + +#[test] +fn debug() { + macro_rules! debug_all { + () => {}; + (#[$meta:meta] $x:expr; $($rest:tt)*) => { + #[$meta] + let _unused = format!("{:?}", $x); + debug_all!($($rest)*); + }; + ($x:expr; $($rest:tt)*) => { + let _unused = format!("{:?}", $x); + debug_all!($($rest)*); + }; + } + + debug_all! { + Duration::ZERO; + IndeterminateOffset; + ConversionRange; + TryFromParsed::InsufficientInformation; + Parsed::new(); + #[allow(deprecated)] + Instant::now(); + error::ParseFromDescription::InvalidComponent("foo"); + error::Format::InvalidComponent("foo"); + well_known::Rfc2822; + well_known::Rfc3339; + well_known::Iso8601::DEFAULT; + well_known::iso8601::FormattedComponents::None; + well_known::iso8601::DateKind::Calendar; + well_known::iso8601::TimePrecision::Hour { decimal_digits: None }; + well_known::iso8601::OffsetPrecision::Hour; + well_known::iso8601::Config::DEFAULT; + component_range_error(); + Error::ConversionRange(ConversionRange); + time::util::local_offset::Soundness::Sound; + + modifier::Day::default(); + modifier::MonthRepr::default(); + modifier::Month::default(); + modifier::Ordinal::default(); + modifier::WeekdayRepr::default(); + modifier::Weekday::default(); + modifier::WeekNumberRepr::default(); + modifier::WeekNumber::default(); + modifier::YearRepr::default(); + modifier::Year::default(); + modifier::Hour::default(); + modifier::Minute::default(); + modifier::Period::default(); + modifier::Second::default(); + modifier::SubsecondDigits::default(); + modifier::Subsecond::default(); + modifier::OffsetHour::default(); + modifier::OffsetMinute::default(); + modifier::OffsetSecond::default(); + modifier::Padding::default(); + + BorrowedFormatItem::Literal(b"abcdef"); + BorrowedFormatItem::Compound(&[BorrowedFormatItem::Component(Component::Day(modifier::Day::default()))]); + BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(&[])); + BorrowedFormatItem::First(&[]); + OwnedFormatItem::from(BorrowedFormatItem::Literal(b"abcdef")); + OwnedFormatItem::from(BorrowedFormatItem::Compound(&[BorrowedFormatItem::Component(Component::Day(modifier::Day::default()))])); + OwnedFormatItem::from(BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(&[]))); + OwnedFormatItem::from(BorrowedFormatItem::First(&[])); + } +} diff --git a/tests/duration.rs b/tests/duration.rs new file mode 100644 index 000000000..e1f46e25f --- /dev/null +++ b/tests/duration.rs @@ -0,0 +1,1050 @@ +use std::cmp::Ordering; +use std::cmp::Ordering::{Equal, Greater, Less}; +use std::time::Duration as StdDuration; + +use rstest::rstest; +use time::ext::{NumericalDuration, NumericalStdDuration}; +use time::{error, Duration}; + +#[rstest] +#[case(Duration::ZERO, 0.seconds())] +#[case(Duration::NANOSECOND, 1.nanoseconds())] +#[case(Duration::MICROSECOND, 1.microseconds())] +#[case(Duration::MILLISECOND, 1.milliseconds())] +#[case(Duration::SECOND, 1.seconds())] +#[case(Duration::MINUTE, 60.seconds())] +#[case(Duration::HOUR, 3_600.seconds())] +#[case(Duration::DAY, 86_400.seconds())] +#[case(Duration::WEEK, 604_800.seconds())] +fn unit_values(#[case] input: Duration, #[case] expected: Duration) { + assert_eq!(input, expected); +} + +#[rstest] +fn default() { + assert_eq!(Duration::default(), Duration::ZERO); +} + +#[rstest] +#[case((-1).seconds(), false)] +#[case(0.seconds(), true)] +#[case(1.seconds(), false)] +fn is_zero(#[case] input: Duration, #[case] expected: bool) { + assert_eq!(input.is_zero(), expected); +} + +#[rstest] +#[case((-1).seconds(), true)] +#[case(0.seconds(), false)] +#[case(1.seconds(), false)] +fn is_negative(#[case] input: Duration, #[case] expected: bool) { + assert_eq!(input.is_negative(), expected); +} + +#[rstest] +#[case((-1).seconds(), false)] +#[case(0.seconds(), false)] +#[case(1.seconds(), true)] +fn is_positive(#[case] input: Duration, #[case] expected: bool) { + assert_eq!(input.is_positive(), expected); +} + +#[rstest] +#[case(1.seconds(), 1.seconds())] +#[case(0.seconds(), 0.seconds())] +#[case((-1).seconds(), 1.seconds())] +#[case(Duration::new(i64::MIN, 0), Duration::MAX)] +fn abs(#[case] input: Duration, #[case] expected: Duration) { + assert_eq!(input.abs(), expected); +} + +#[rstest] +#[case(1.seconds(), 1.std_seconds())] +#[case(0.seconds(), 0.std_seconds())] +#[case((-1).seconds(), 1.std_seconds())] +fn unsigned_abs(#[case] input: Duration, #[case] expected: StdDuration) { + assert_eq!(input.unsigned_abs(), expected); +} + +#[rstest] +#[case(1, 0, 1.seconds())] +#[case(-1, 0, (-1).seconds())] +#[case(1, 2_000_000_000, 3.seconds())] +#[case(0, 0, 0.seconds())] +#[case(0, 1_000_000_000, 1.seconds())] +#[case(-1, 1_000_000_000, 0.seconds())] +#[case(-2, 1_000_000_000, (-1).seconds())] +#[case(1, -1, 999_999_999.nanoseconds())] +#[case(-1, 1, (-999_999_999).nanoseconds())] +#[case(1, 1, 1_000_000_001.nanoseconds())] +#[case(-1, -1, (-1_000_000_001).nanoseconds())] +#[case(0, 1, 1.nanoseconds())] +#[case(0, -1, (-1).nanoseconds())] +#[case(-1, 1_400_000_000, 400.milliseconds())] +#[case(-2, 1_400_000_000, (-600).milliseconds())] +#[case(-3, 1_400_000_000, (-1_600).milliseconds())] +#[case(1, -1_400_000_000, (-400).milliseconds())] +#[case(2, -1_400_000_000, 600.milliseconds())] +#[case(3, -1_400_000_000, 1_600.milliseconds())] +fn new(#[case] secs: i64, #[case] nanos: i32, #[case] expected: Duration) { + assert_eq!(Duration::new(secs, nanos), expected); +} + +#[rstest] +#[case(i64::MAX, 1_000_000_000)] +#[case(i64::MIN, -1_000_000_000)] +#[should_panic] +fn new_panic(#[case] secs: i64, #[case] nanos: i32) { + let _ = Duration::new(secs, nanos); +} + +#[rstest] +#[case(1, 604_800)] +#[case(2, 2 * 604_800)] +#[case(-1, -604_800)] +#[case(-2, -2 * 604_800)] +fn weeks(#[case] weeks_: i64, #[case] expected: i64) { + assert_eq!(Duration::weeks(weeks_), expected.seconds()); +} + +#[rstest] +#[case(i64::MAX)] +#[case(i64::MIN)] +#[should_panic] +fn weeks_panic(#[case] weeks: i64) { + let _ = Duration::weeks(weeks); +} + +#[rstest] +#[case(7, 1)] +#[case(-7, -1)] +#[case(6, 0)] +#[case(-6, 0)] +fn whole_weeks(#[case] days: i64, #[case] expected: i64) { + assert_eq!(Duration::days(days).whole_weeks(), expected); +} + +#[rstest] +#[case(1, 86_400)] +#[case(2, 2 * 86_400)] +#[case(-1, -86_400)] +#[case(-2, -2 * 86_400)] +fn days(#[case] days_: i64, #[case] expected: i64) { + assert_eq!(Duration::days(days_), expected.seconds()); +} + +#[rstest] +#[case(i64::MAX)] +#[case(i64::MIN)] +#[should_panic] +fn days_panic(#[case] days: i64) { + let _ = Duration::days(days); +} + +#[rstest] +#[case(24, 1)] +#[case(-24, -1)] +#[case(23, 0)] +#[case(-23, 0)] +fn whole_days(#[case] hours: i64, #[case] expected: i64) { + assert_eq!(Duration::hours(hours).whole_days(), expected); +} + +#[rstest] +#[case(1, 3_600)] +#[case(2, 2 * 3_600)] +#[case(-1, -3_600)] +#[case(-2, -2 * 3_600)] +fn hours(#[case] hours_: i64, #[case] expected: i64) { + assert_eq!(Duration::hours(hours_), expected.seconds()); +} + +#[rstest] +#[case(i64::MAX)] +#[case(i64::MIN)] +#[should_panic] +fn hours_panic(#[case] hours: i64) { + let _ = Duration::hours(hours); +} + +#[rstest] +#[case(60, 1)] +#[case(-60, -1)] +#[case(59, 0)] +#[case(-59, 0)] +fn whole_hours(#[case] minutes: i64, #[case] expected: i64) { + assert_eq!(Duration::minutes(minutes).whole_hours(), expected); +} + +#[rstest] +#[case(1, 60)] +#[case(2, 2 * 60)] +#[case(-1, -60)] +#[case(-2, -2 * 60)] +fn minutes(#[case] minutes_: i64, #[case] expected: i64) { + assert_eq!(Duration::minutes(minutes_), expected.seconds()); +} + +#[rstest] +#[case(i64::MAX)] +#[case(i64::MIN)] +#[should_panic] +fn minutes_panic(#[case] minutes: i64) { + let _ = Duration::minutes(minutes); +} + +#[rstest] +#[case(60, 1)] +#[case(-60, -1)] +#[case(59, 0)] +#[case(-59, 0)] +fn whole_minutes(#[case] seconds: i64, #[case] expected: i64) { + assert_eq!(Duration::seconds(seconds).whole_minutes(), expected); +} + +#[rstest] +#[case(1, 1_000)] +#[case(2, 2 * 1_000)] +#[case(-1, -1_000)] +#[case(-2, -2 * 1_000)] +fn seconds(#[case] seconds_: i64, #[case] expected: i64) { + assert_eq!(Duration::seconds(seconds_), expected.milliseconds()); +} + +#[rstest] +#[case(1)] +#[case(-1)] +#[case(60)] +#[case(-60)] +fn whole_seconds(#[case] seconds: i64) { + assert_eq!(Duration::seconds(seconds).whole_seconds(), seconds); +} + +#[rstest] +#[case(0.5, Duration::milliseconds(500))] +#[case(-0.5, Duration::milliseconds(-500))] +#[case(123.250, Duration::milliseconds(123_250))] +#[case(0.000_000_000_012, Duration::ZERO)] +fn seconds_f64(#[case] seconds: f64, #[case] expected: Duration) { + assert_eq!(Duration::seconds_f64(seconds), expected); +} + +#[rstest] +#[case(f64::MAX)] +#[case(f64::MIN)] +#[case(f64::NAN)] +#[should_panic] +fn seconds_f64_panic(#[case] seconds: f64) { + let _ = Duration::seconds_f64(seconds); +} + +#[rstest] +#[case(0.5, Duration::milliseconds(500))] +#[case(-0.5, Duration::milliseconds(-500))] +#[case(123.250, Duration::milliseconds(123_250))] +#[case(0.000_000_000_012, Duration::ZERO)] +#[case(f64::MAX, Duration::MAX)] +#[case(f64::MIN, Duration::MIN)] +#[case(f64::NAN, Duration::ZERO)] +fn saturating_seconds_f64(#[case] seconds: f64, #[case] expected: Duration) { + assert_eq!(Duration::saturating_seconds_f64(seconds), expected); +} + +#[rstest] +#[case(0.5, 0.5)] +#[case(-0.5, -0.5)] +#[case(123.250, 123.250)] +#[case(0.000_000_000_012, 0.)] +fn checked_seconds_f64_success(#[case] seconds: f64, #[case] expected: f64) { + assert_eq!( + Duration::checked_seconds_f64(seconds), + Some(expected.seconds()) + ); +} + +#[rstest] +#[case(f64::MAX)] +#[case(f64::MIN)] +#[case(f64::NAN)] +fn checked_seconds_f64_edge_cases(#[case] seconds: f64) { + assert_eq!(Duration::checked_seconds_f64(seconds), None); +} + +#[rstest] +#[case(1.)] +#[case(-1.)] +#[case(60.)] +#[case(-60.)] +#[case(1.5)] +#[case(-1.5)] +fn as_seconds_f64(#[case] seconds: f64) { + assert_eq!(Duration::seconds_f64(seconds).as_seconds_f64(), seconds); +} + +#[rstest] +#[case(0.5, Duration::milliseconds(500))] +#[case(-0.5, Duration::milliseconds(-500))] +#[case(123.250, Duration::milliseconds(123_250))] +#[case(0.000_000_000_012, Duration::ZERO)] +fn seconds_f32_success(#[case] seconds: f32, #[case] expected: Duration) { + assert_eq!(Duration::seconds_f32(seconds), expected); +} + +#[rstest] +#[case(f32::MAX)] +#[case(f32::MIN)] +#[case(f32::NAN)] +#[should_panic] +fn seconds_f32_panic(#[case] seconds: f32) { + let _ = Duration::seconds_f32(seconds); +} + +#[rstest] +#[case(0.5, Duration::milliseconds(500))] +#[case(-0.5, Duration::milliseconds(-500))] +#[case(123.250, Duration::milliseconds(123_250))] +#[case(0.000_000_000_012, Duration::ZERO)] +#[case(f32::MAX, Duration::MAX)] +#[case(f32::MIN, Duration::MIN)] +#[case(f32::NAN, Duration::ZERO)] +fn saturating_seconds_f32(#[case] seconds: f32, #[case] expected: Duration) { + assert_eq!(Duration::saturating_seconds_f32(seconds), expected); +} + +#[rstest] +#[case(0.5, 0.5)] +#[case(-0.5, -0.5)] +#[case(123.250, 123.250)] +#[case(0.000_000_000_012, 0.0)] +fn checked_seconds_f32_success(#[case] seconds: f32, #[case] expected: f64) { + assert_eq!( + Duration::checked_seconds_f32(seconds), + Some(expected.seconds()) + ); +} + +#[rstest] +#[case(f32::MAX)] +#[case(f32::MIN)] +#[case(f32::NAN)] +fn checked_seconds_f32_none(#[case] seconds: f32) { + assert_eq!(Duration::checked_seconds_f32(seconds), None); +} + +#[rstest] +#[case(1.0, 1.0)] +#[case(-1.0, -1.0)] +#[case(60.0, 60.0)] +#[case(-60.0, -60.0)] +#[case(1.5, 1.5)] +#[case(-1.5, -1.5)] +fn as_seconds_f32(#[case] seconds: f32, #[case] expected: f32) { + assert_eq!(Duration::seconds_f32(seconds).as_seconds_f32(), expected); +} + +#[rstest] +#[case(1, 1_000)] +#[case(-1, -1_000)] +fn milliseconds(#[case] input: i64, #[case] expected: i64) { + assert_eq!(Duration::milliseconds(input), expected.microseconds()); +} + +#[rstest] +#[case(1.seconds(), 1_000)] +#[case((-1).seconds(), -1_000)] +#[case(1.milliseconds(), 1)] +#[case((-1).milliseconds(), -1)] +fn whole_milliseconds(#[case] input: Duration, #[case] expected: i128) { + assert_eq!(input.whole_milliseconds(), expected); +} + +#[rstest] +#[case(1.4.seconds(), 400)] +#[case((-1.4).seconds(), -400)] +fn subsec_milliseconds(#[case] duration: Duration, #[case] expected: i16) { + assert_eq!(duration.subsec_milliseconds(), expected); +} + +#[rstest] +#[case(1, 1_000)] +#[case(-1, -1_000)] +fn microseconds(#[case] input: i64, #[case] expected: i64) { + assert_eq!(Duration::microseconds(input), expected.nanoseconds()); +} + +#[rstest] +#[case(1.milliseconds(), 1_000)] +#[case((-1).milliseconds(), -1_000)] +#[case(1.microseconds(), 1)] +#[case((-1).microseconds(), -1)] +fn whole_microseconds(#[case] input: Duration, #[case] expected: i128) { + assert_eq!(input.whole_microseconds(), expected); +} + +#[rstest] +#[case(1.0004.seconds(), 400)] +#[case((-1.0004).seconds(), -400)] +fn subsec_microseconds(#[case] duration: Duration, #[case] expected: i32) { + assert_eq!(duration.subsec_microseconds(), expected); +} + +#[rstest] +#[case(1, 1.microseconds() / 1_000)] +#[case(-1, (-1).microseconds() / 1_000)] +fn nanoseconds(#[case] input: i64, #[case] expected: Duration) { + assert_eq!(Duration::nanoseconds(input), expected); +} + +#[rstest] +#[case(1.microseconds(), 1_000)] +#[case((-1).microseconds(), -1_000)] +#[case(1.nanoseconds(), 1)] +#[case((-1).nanoseconds(), -1)] +fn whole_nanoseconds(#[case] input: Duration, #[case] expected: i128) { + assert_eq!(input.whole_nanoseconds(), expected); +} + +#[rstest] +#[case(1.000_000_4.seconds(), 400)] +#[case((-1.000_000_4).seconds(), -400)] +fn subsec_nanoseconds(#[case] duration: Duration, #[case] expected: i32) { + assert_eq!(duration.subsec_nanoseconds(), expected); +} + +#[rstest] +#[case(5.seconds(), 5.seconds(), 10.seconds())] +#[case((-5).seconds(), 5.seconds(), 0.seconds())] +#[case(1.seconds(), (-1).milliseconds(), 999.milliseconds())] +fn checked_add_some(#[case] a: Duration, #[case] b: Duration, #[case] expected: Duration) { + assert_eq!(a.checked_add(b), Some(expected)); +} + +#[rstest] +#[case(Duration::MAX, 1.nanoseconds())] +#[case(5.seconds(), Duration::MAX)] +#[case(Duration::MIN, Duration::MIN)] +fn checked_add_none(#[case] a: Duration, #[case] b: Duration) { + assert_eq!(a.checked_add(b), None); +} + +#[rstest] +#[case(5.seconds(), 5.seconds(), 0.seconds())] +#[case(5.seconds(), 10.seconds(), (-5).seconds())] +fn checked_sub_some(#[case] a: Duration, #[case] b: Duration, #[case] expected: Duration) { + assert_eq!(a.checked_sub(b), Some(expected)); +} + +#[rstest] +#[case(Duration::MIN, 1.nanoseconds())] +#[case(5.seconds(), Duration::MIN)] +#[case(Duration::MAX, Duration::MIN)] +fn checked_sub_none(#[case] a: Duration, #[case] b: Duration) { + assert_eq!(a.checked_sub(b), None); +} + +#[rstest] +#[case(5.seconds(), 2, 10.seconds())] +#[case(5.seconds(), -2, (-10).seconds())] +#[case(5.seconds(), 0, Duration::ZERO)] +fn checked_mul_some(#[case] duration: Duration, #[case] rhs: i32, #[case] expected: Duration) { + assert_eq!(duration.checked_mul(rhs), Some(expected)); +} + +#[rstest] +#[case(Duration::MIN, -1)] +#[case(Duration::MAX, 2)] +#[case(Duration::MIN, 2)] +fn checked_mul_none(#[case] duration: Duration, #[case] rhs: i32) { + assert_eq!(duration.checked_mul(rhs), None); +} + +#[rstest] +#[case(10.seconds(), 2, 5.seconds())] +#[case(10.seconds(), -2, (-5).seconds())] +fn checked_div_some(#[case] duration: Duration, #[case] rhs: i32, #[case] expected: Duration) { + assert_eq!(duration.checked_div(rhs), Some(expected)); +} + +#[rstest] +#[case(1.seconds(), 0)] +#[case(Duration::MIN, -1)] +fn checked_div_none(#[case] duration: Duration, #[case] rhs: i32) { + assert_eq!(duration.checked_div(rhs), None); +} + +#[rstest] +fn checked_div_regression() { + assert_eq!( + Duration::new(1, 1).checked_div(7), + Some(Duration::new(0, 142_857_143)) // manually verified + ); +} + +#[rstest] +#[case(5.seconds(), Some((-5).seconds()))] +#[case((-5).seconds(), Some(5.seconds()))] +#[case(Duration::MIN, None)] +fn checked_neg(#[case] duration: Duration, #[case] expected: Option) { + assert_eq!(duration.checked_neg(), expected); +} + +#[rstest] +#[case(5.seconds(), 5.seconds(), 10.seconds())] +#[case(Duration::MAX, 1.nanoseconds(), Duration::MAX)] +#[case(Duration::MAX, 1.seconds(), Duration::MAX)] +#[case(Duration::MIN, (-1).nanoseconds(), Duration::MIN)] +#[case(Duration::MIN, (-1).seconds(), Duration::MIN)] +#[case((-5).seconds(), 5.seconds(), Duration::ZERO)] +#[case(1_600.milliseconds(), 1_600.milliseconds(), 3_200.milliseconds())] +#[case(1.seconds(), (-1).milliseconds(), 999.milliseconds())] +fn saturating_add(#[case] a: Duration, #[case] b: Duration, #[case] expected: Duration) { + assert_eq!(a.saturating_add(b), expected); +} + +#[rstest] +#[case(5.seconds(), 5.seconds(), Duration::ZERO)] +#[case(Duration::MIN, 1.nanoseconds(), Duration::MIN)] +#[case(Duration::MAX, (-1).nanoseconds(), Duration::MAX)] +#[case(Duration::MAX, (-1).seconds(), Duration::MAX)] +#[case(5.seconds(), 10.seconds(), (-5).seconds())] +#[case((-1_600).milliseconds(), 1_600.milliseconds(), (-3_200).milliseconds())] +#[case(0.seconds(), Duration::MIN, Duration::MIN)] +#[case(Duration::MIN, 5.seconds(), Duration::MIN)] +#[case(1_200.milliseconds(), 600.milliseconds(), 600.milliseconds())] +#[case((-1_200).milliseconds(), (-600).milliseconds(), (-600).milliseconds())] +fn saturating_sub(#[case] a: Duration, #[case] b: Duration, #[case] expected: Duration) { + assert_eq!(a.saturating_sub(b), expected); +} + +#[rstest] +#[case(5.seconds(), 2, 10.seconds())] +#[case(5.seconds(), -2, (-10).seconds())] +#[case(5.seconds(), 0, Duration::ZERO)] +#[case(Duration::MAX, 2, Duration::MAX)] +#[case(Duration::MIN, 2, Duration::MIN)] +#[case(Duration::MAX, -2, Duration::MIN)] +#[case(Duration::MIN, -2, Duration::MAX)] +#[case( + Duration::new(1_844_674_407_370_955_161, 600_000_000), + 5, + Duration::MAX +)] +#[case(Duration::new(1_844_674_407_370_955_161, 800_000_000), -5, Duration::MIN)] +fn saturating_mul(#[case] duration: Duration, #[case] rhs: i32, #[case] expected: Duration) { + assert_eq!(duration.saturating_mul(rhs), expected); +} + +#[rstest] +#[timeout(StdDuration::from_millis(100))] +fn time_fn() { + #[allow(deprecated)] + let (time, value) = Duration::time_fn(|| { + std::thread::sleep(1.std_milliseconds()); + 0 + }); + + assert!(time >= 1.milliseconds()); + assert_eq!(value, 0); +} + +#[rstest] +#[case(0.seconds(), "0s")] +#[case(60.days(), "60d")] +#[case((-48).hours(), "-2d")] +#[case(48.hours(), "2d")] +#[case(1.minutes(), "1m")] +#[case(10.minutes(), "10m")] +#[case(1.seconds(), "1s")] +#[case(10.seconds(), "10s")] +#[case(1.milliseconds(), "1ms")] +#[case(10.milliseconds(), "10ms")] +#[case(100.milliseconds(), "100ms")] +#[case(1.microseconds(), "1µs")] +#[case(10.microseconds(), "10µs")] +#[case(100.microseconds(), "100µs")] +#[case(1.nanoseconds(), "1ns")] +#[case(10.nanoseconds(), "10ns")] +#[case(100.nanoseconds(), "100ns")] +fn display_basic(#[case] duration: Duration, #[case] expected: &str) { + assert_eq!(duration.to_string(), expected); +} + +#[rstest] +#[case(1.days(), "1d")] +#[case(26.hours(), "1d2h")] +#[case(1_563.minutes(), "1d2h3m")] +#[case(93_784.seconds(), "1d2h3m4s")] +#[case(93_784_005.milliseconds(), "1d2h3m4s5ms")] +#[case(93_784_005_006.microseconds(), "1d2h3m4s5ms6µs")] +#[case(93_784_005_006_007.nanoseconds(), "1d2h3m4s5ms6µs7ns")] +fn display_compound(#[case] duration: Duration, #[case] expected: &str) { + assert_eq!(duration.to_string(), expected); +} + +#[rstest] +#[case(0.seconds(), 3, "0.000s")] +#[case(60.days(), 3, "60.000d")] +#[case((-48).hours(), 3, "-2.000d")] +#[case(48.hours(), 3, "2.000d")] +#[case(1.minutes(), 3, "1.000m")] +#[case(10.minutes(), 3, "10.000m")] +#[case(1.seconds(), 3, "1.000s")] +#[case(10.seconds(), 3, "10.000s")] +#[case(1.milliseconds(), 3, "1.000ms")] +#[case(10.milliseconds(), 3, "10.000ms")] +#[case(100.milliseconds(), 3, "100.000ms")] +#[case(1.microseconds(), 3, "1.000µs")] +#[case(10.microseconds(), 3, "10.000µs")] +#[case(100.microseconds(), 3, "100.000µs")] +#[case(1.nanoseconds(), 3, "1.000ns")] +#[case(10.nanoseconds(), 3, "10.000ns")] +#[case(100.nanoseconds(), 3, "100.000ns")] +#[case(1.days(), 3, "1.000d")] +#[case(26.hours(), 3, "1.083d")] +#[case(1_563.minutes(), 4, "1.0854d")] +#[case(93_784.seconds(), 5, "1.08546d")] +#[case(93_784_005.milliseconds(), 6, "1.085463d")] +#[case(93_784_005_006.microseconds(), 9, "1.085463021d")] +#[case(93_784_005_006_007.nanoseconds(), 12, "1.085463020903d")] +fn display_precision(#[case] duration: Duration, #[case] precision: usize, #[case] expected: &str) { + assert_eq!(format!("{duration:.precision$}"), expected); +} + +#[rstest] +#[case(0.std_seconds(), 0.seconds())] +#[case(1.std_seconds(), 1.seconds())] +fn try_from_std_duration_success(#[case] std_duration: StdDuration, #[case] expected: Duration) { + assert_eq!(Duration::try_from(std_duration), Ok(expected)); +} + +#[rstest] +#[case(u64::MAX.std_seconds(), error::ConversionRange)] +fn try_from_std_duration_error( + #[case] std_duration: StdDuration, + #[case] expected: error::ConversionRange, +) { + assert_eq!(Duration::try_from(std_duration), Err(expected)); +} + +#[rstest] +#[case(0.seconds(), 0.std_seconds())] +#[case(1.seconds(), 1.std_seconds())] +fn try_to_std_duration_success(#[case] duration: Duration, #[case] expected: StdDuration) { + assert_eq!(StdDuration::try_from(duration), Ok(expected)); +} + +#[rstest] +#[case((-1).seconds())] +#[case((-500).milliseconds())] +fn try_to_std_duration_error(#[case] duration: Duration) { + assert_eq!(StdDuration::try_from(duration), Err(error::ConversionRange)); +} + +#[rstest] +#[case(1.seconds(), 1.seconds(), 2.seconds())] +#[case(500.milliseconds(), 500.milliseconds(), 1.seconds())] +#[case(1.seconds(), (-1).seconds(), 0.seconds())] +fn add(#[case] lhs: Duration, #[case] rhs: Duration, #[case] expected: Duration) { + assert_eq!(lhs + rhs, expected); +} + +#[rstest] +#[case(1.seconds(), 1.std_seconds(), 2.seconds())] +#[case(500.milliseconds(), 500.std_milliseconds(), 1.seconds())] +#[case((-1).seconds(), 1.std_seconds(), 0.seconds())] +fn add_std(#[case] lhs: Duration, #[case] rhs: StdDuration, #[case] expected: Duration) { + assert_eq!(lhs + rhs, expected); +} + +#[rstest] +#[case(1.std_seconds(), 1.seconds(), 2.seconds())] +#[case(500.std_milliseconds(), 500.milliseconds(), 1.seconds())] +#[case(1.std_seconds(), (-1).seconds(), 0.seconds())] +fn std_add(#[case] lhs: StdDuration, #[case] rhs: Duration, #[case] expected: Duration) { + assert_eq!(lhs + rhs, expected); +} + +#[rstest] +#[case(1.seconds(), 1.seconds(), 2.seconds())] +#[case(500.milliseconds(), 500.milliseconds(), 1.seconds())] +#[case(1.seconds(), (-1).seconds(), 0.seconds())] +fn add_assign(#[case] mut duration: Duration, #[case] other: Duration, #[case] expected: Duration) { + duration += other; + assert_eq!(duration, expected); +} + +#[rstest] +#[case(1.seconds(), 1.std_seconds(), 2.seconds())] +#[case(500.milliseconds(), 500.std_milliseconds(), 1.seconds())] +#[case((-1).seconds(), 1.std_seconds(), 0.seconds())] +fn add_assign_std( + #[case] mut duration: Duration, + #[case] other: StdDuration, + #[case] expected: Duration, +) { + duration += other; + assert_eq!(duration, expected); +} + +#[rstest] +#[case(1.std_seconds(), 1.seconds(), 2.seconds())] +#[case(500.std_milliseconds(), 500.milliseconds(), 1.seconds())] +#[case(1.std_seconds(), (-1).seconds(), 0.seconds())] +fn std_add_assign( + #[case] mut duration: StdDuration, + #[case] other: Duration, + #[case] expected: Duration, +) { + duration += other; + assert_eq!(duration, expected); +} + +#[rstest] +#[case(1.seconds(), (-1).seconds())] +#[case((-1).seconds(), 1.seconds())] +#[case(0.seconds(), 0.seconds())] +fn neg(#[case] duration: Duration, #[case] expected: Duration) { + assert_eq!(-duration, expected); +} + +#[rstest] +#[case(1.seconds(), 1.seconds(), 0.seconds())] +#[case(1_500.milliseconds(), 500.milliseconds(), 1.seconds())] +#[case(1.seconds(), (-1).seconds(), 2.seconds())] +fn sub(#[case] lhs: Duration, #[case] rhs: Duration, #[case] expected: Duration) { + assert_eq!(lhs - rhs, expected); +} + +#[rstest] +#[case(1.seconds(), 1.std_seconds(), 0.seconds())] +#[case(1_500.milliseconds(), 500.std_milliseconds(), 1.seconds())] +#[case((-1).seconds(), 1.std_seconds(), -(2.seconds()))] +fn sub_std(#[case] lhs: Duration, #[case] rhs: StdDuration, #[case] expected: Duration) { + assert_eq!(lhs - rhs, expected); +} + +#[rstest] +#[case(1.std_seconds(), 1.seconds(), 0.seconds())] +#[case(1_500.std_milliseconds(), 500.milliseconds(), 1.seconds())] +#[case(1.std_seconds(), (-1).seconds(), 2.seconds())] +fn std_sub(#[case] lhs: StdDuration, #[case] rhs: Duration, #[case] expected: Duration) { + assert_eq!(lhs - rhs, expected); +} + +#[rstest] +#[case(1.seconds(), 1.seconds(), 0.seconds())] +#[case(1_500.milliseconds(), 500.milliseconds(), 1.seconds())] +#[case(1.seconds(), (-1).seconds(), 2.seconds())] +fn sub_assign(#[case] mut duration: Duration, #[case] other: Duration, #[case] expected: Duration) { + duration -= other; + assert_eq!(duration, expected); +} + +#[rstest] +#[case(1.seconds(), 1.std_seconds(), 0.seconds())] +#[case(1_500.milliseconds(), 500.std_milliseconds(), 1.seconds())] +#[case((-1).seconds(), 1.std_seconds(), -(2.seconds()))] +fn sub_assign_std( + #[case] mut duration: Duration, + #[case] other: StdDuration, + #[case] expected: Duration, +) { + duration -= other; + assert_eq!(duration, expected); +} + +#[rstest] +#[case(1.std_seconds(), 1.seconds(), 0.seconds())] +#[case(1_500.std_milliseconds(), 500.milliseconds(), 1.seconds())] +#[case(1.std_seconds(), (-1).seconds(), 2.seconds())] +fn std_sub_assign( + #[case] mut duration: StdDuration, + #[case] other: Duration, + #[case] expected: Duration, +) { + duration -= other; + assert_eq!(duration, expected); +} + +#[rstest] +#[should_panic] +fn std_sub_assign_overflow() { + let mut duration = 1.std_seconds(); + duration -= 2.seconds(); +} + +#[rstest] +#[case(1.seconds(), 2, 2.seconds())] +#[case(1.seconds(), -2, (-2).seconds())] +fn mul_int_success(#[case] duration: Duration, #[case] rhs: i32, #[case] expected: Duration) { + assert_eq!(duration * rhs, expected); +} + +#[rstest] +#[case(Duration::MAX, 2)] +#[case(Duration::MIN, 2)] +#[should_panic] +fn mul_int_panic(#[case] duration: Duration, #[case] rhs: i32) { + let _ = duration * rhs; +} + +#[rstest] +#[case(1.seconds(), 2, 2.seconds())] +#[case(1.seconds(), -2, (-2).seconds())] +fn mul_int_assign(#[case] mut duration: Duration, #[case] rhs: i32, #[case] expected: Duration) { + duration *= rhs; + assert_eq!(duration, expected); +} + +#[rstest] +#[case(2, 1.seconds(), 2.seconds())] +#[case(-2, 1.seconds(), (-2).seconds())] +fn int_mul(#[case] lhs: i32, #[case] duration: Duration, #[case] expected: Duration) { + assert_eq!(lhs * duration, expected); +} + +#[rstest] +#[case(1.seconds(), 2, 500.milliseconds())] +#[case(1.seconds(), -2, (-500).milliseconds())] +fn div_int(#[case] duration: Duration, #[case] rhs: i32, #[case] expected: Duration) { + assert_eq!(duration / rhs, expected); +} + +#[rstest] +#[case(1.seconds(), 2, 500.milliseconds())] +#[case(1.seconds(), -2, (-500).milliseconds())] +fn div_int_assign(#[case] mut duration: Duration, #[case] rhs: i32, #[case] expected: Duration) { + duration /= rhs; + assert_eq!(duration, expected); +} + +#[rstest] +#[case(1.seconds(), 0.5.seconds(), 2.)] +#[case(2.seconds(), 0.25.seconds(), 8.)] +#[allow(clippy::float_cmp)] +fn div(#[case] lhs: Duration, #[case] rhs: Duration, #[case] expected: f64) { + assert_eq!(lhs / rhs, expected); +} + +#[rstest] +#[case(1.seconds(), 0.5.std_seconds(), 2.)] +#[case(2.seconds(), 0.25.std_seconds(), 8.)] +#[allow(clippy::float_cmp)] +fn div_std(#[case] lhs: Duration, #[case] rhs: StdDuration, #[case] expected: f64) { + assert_eq!(lhs / rhs, expected); +} + +#[rstest] +#[case(1.std_seconds(), 0.5.seconds(), 2.)] +#[case(2.std_seconds(), 0.25.seconds(), 8.)] +#[allow(clippy::float_cmp)] +fn std_div(#[case] lhs: StdDuration, #[case] rhs: Duration, #[case] expected: f64) { + assert_eq!(lhs / rhs, expected); +} + +#[rstest] +#[case(1.seconds(), 1.5, 1_500.milliseconds())] +#[case(1.seconds(), 2.5, 2_500.milliseconds())] +#[case(1.seconds(), -1.5, (-1_500).milliseconds())] +#[case(1.seconds(), 0., 0.seconds())] +fn mul_f32(#[case] duration: Duration, #[case] rhs: f32, #[case] expected: Duration) { + assert_eq!(duration * rhs, expected); +} + +#[rstest] +#[case(1.seconds(), 1.5, 1_500.milliseconds())] +#[case(1.seconds(), 2.5, 2_500.milliseconds())] +#[case(1.seconds(), -1.5, (-1_500).milliseconds())] +#[case(1.seconds(), 0., 0.seconds())] +fn mul_f64(#[case] duration: Duration, #[case] rhs: f64, #[case] expected: Duration) { + assert_eq!(duration * rhs, expected); +} + +#[rstest] +#[case(1.5, 1.seconds(), 1_500.milliseconds())] +#[case(2.5, 1.seconds(), 2_500.milliseconds())] +#[case(-1.5, 1.seconds(), (-1_500).milliseconds())] +#[case(0., 1.seconds(), 0.seconds())] +fn f32_mul(#[case] lhs: f32, #[case] duration: Duration, #[case] expected: Duration) { + assert_eq!(lhs * duration, expected); +} + +#[rstest] +#[case(1.5, 1.seconds(), 1_500.milliseconds())] +#[case(2.5, 1.seconds(), 2_500.milliseconds())] +#[case(-1.5, 1.seconds(), (-1_500).milliseconds())] +#[case(0., 1.seconds(), 0.seconds())] +fn f64_mul(#[case] lhs: f64, #[case] duration: Duration, #[case] expected: Duration) { + assert_eq!(lhs * duration, expected); +} + +#[rstest] +#[case(1.seconds(), 1.5, 1_500.milliseconds())] +#[case(1.seconds(), 2.5, 2_500.milliseconds())] +#[case(1.seconds(), -1.5, (-1_500).milliseconds())] +#[case(1.seconds(), 0., 0.seconds())] +fn mul_f32_assign(#[case] mut duration: Duration, #[case] rhs: f32, #[case] expected: Duration) { + duration *= rhs; + assert_eq!(duration, expected); +} + +#[rstest] +#[case(1.seconds(), 1.5, 1_500.milliseconds())] +#[case(1.seconds(), 2.5, 2_500.milliseconds())] +#[case(1.seconds(), -1.5, (-1_500).milliseconds())] +#[case(1.seconds(), 0., 0.seconds())] +fn mul_f64_assign(#[case] mut duration: Duration, #[case] rhs: f64, #[case] expected: Duration) { + duration *= rhs; + assert_eq!(duration, expected); +} + +#[rstest] +#[case(1.seconds(), 1., 1.seconds())] +#[case(1.seconds(), 2., 500.milliseconds())] +#[case(1.seconds(), 4., 250.milliseconds())] +#[case(1.seconds(), 0.25, 4.seconds())] +#[case(1.seconds(), -1., (-1).seconds())] +fn div_f32(#[case] duration: Duration, #[case] rhs: f32, #[case] expected: Duration) { + assert_eq!(duration / rhs, expected); +} + +#[rstest] +#[case(1.seconds(), 1., 1.seconds())] +#[case(1.seconds(), 2., 500.milliseconds())] +#[case(1.seconds(), 4., 250.milliseconds())] +#[case(1.seconds(), 0.25, 4.seconds())] +#[case(1.seconds(), -1., (-1).seconds())] +fn div_f64(#[case] duration: Duration, #[case] rhs: f64, #[case] expected: Duration) { + assert_eq!(duration / rhs, expected); +} + +#[rstest] +#[case(1.seconds(), 1., 1.seconds())] +#[case(1.seconds(), 2., 500.milliseconds())] +#[case(1.seconds(), 4., 250.milliseconds())] +#[case(1.seconds(), 0.25, 4.seconds())] +#[case(1.seconds(), -1., (-1).seconds())] +fn div_f32_assign(#[case] mut duration: Duration, #[case] rhs: f32, #[case] expected: Duration) { + duration /= rhs; + assert_eq!(duration, expected); +} + +#[rstest] +#[case(1.seconds(), 1., 1.seconds())] +#[case(1.seconds(), 2., 500.milliseconds())] +#[case(1.seconds(), 4., 250.milliseconds())] +#[case(1.seconds(), 0.25, 4.seconds())] +#[case(1.seconds(), -1., (-1).seconds())] +fn div_f64_assign(#[case] mut duration: Duration, #[case] rhs: f64, #[case] expected: Duration) { + duration /= rhs; + assert_eq!(duration, expected); +} + +#[rstest] +#[case(1.seconds(), 1.seconds(), true)] +#[case(0.seconds(), 0.seconds(), true)] +#[case(1.seconds(), 2.seconds(), false)] +#[case(1.seconds(), (-1).seconds(), false)] +#[case((-1).seconds(), (-1).seconds(), true)] +#[case(40.seconds(), 1.minutes(), false)] +fn partial_eq(#[case] lhs: Duration, #[case] rhs: Duration, #[case] expected: bool) { + assert_eq_ne!(lhs, rhs, expected); +} + +#[rstest] +#[case(1.seconds(), 1.std_seconds(), true)] +#[case(0.seconds(), 0.std_seconds(), true)] +#[case(1.seconds(), 2.std_seconds(), false)] +#[case((-1).seconds(), 1.std_seconds(), false)] +#[case(40.seconds(), 1.std_minutes(), false)] +fn partial_eq_std(#[case] lhs: Duration, #[case] rhs: StdDuration, #[case] expected: bool) { + assert_eq_ne!(lhs, rhs, expected); +} + +#[rstest] +#[case(1.std_seconds(), 1.seconds(), true)] +#[case(0.std_seconds(), 0.seconds(), true)] +#[case(2.std_seconds(), 1.seconds(), false)] +#[case(1.std_seconds(), (-1).seconds(), false)] +#[case(1.std_minutes(), 40.seconds(), false)] +fn std_partial_eq(#[case] lhs: StdDuration, #[case] rhs: Duration, #[case] expected: bool) { + assert_eq_ne!(lhs, rhs, expected); +} + +#[rstest] +#[case(0.seconds(), 0.seconds(), Equal)] +#[case(1.seconds(), 0.seconds(), Greater)] +#[case(1.seconds(), (-1).seconds(), Greater)] +#[case((-1).seconds(), 1.seconds(), Less)] +#[case(0.seconds(), (-1).seconds(), Greater)] +#[case(0.seconds(), 1.seconds(), Less)] +#[case((-1).seconds(), 0.seconds(), Less)] +#[case(1.minutes(), 1.seconds(), Greater)] +#[case((-1).minutes(), (-1).seconds(), Less)] +fn partial_ord(#[case] lhs: Duration, #[case] rhs: Duration, #[case] expected: Ordering) { + assert_eq!(lhs.partial_cmp(&rhs), Some(expected)); +} + +#[rstest] +#[case(0.seconds(), 0.std_seconds(), Equal)] +#[case(1.seconds(), 0.std_seconds(), Greater)] +#[case((-1).seconds(), 1.std_seconds(), Less)] +#[case(0.seconds(), 1.std_seconds(), Less)] +#[case((-1).seconds(), 0.std_seconds(), Less)] +#[case(1.minutes(), 1.std_seconds(), Greater)] +#[case(0.seconds(), u64::MAX.std_seconds(), Less)] +fn partial_ord_std(#[case] lhs: Duration, #[case] rhs: StdDuration, #[case] expected: Ordering) { + assert_eq!(lhs.partial_cmp(&rhs), Some(expected)); +} + +#[rstest] +#[case(0.std_seconds(), 0.seconds(), Equal)] +#[case(1.std_seconds(), 0.seconds(), Greater)] +#[case(1.std_seconds(), (-1).seconds(), Greater)] +#[case(0.std_seconds(), (-1).seconds(), Greater)] +#[case(0.std_seconds(), 1.seconds(), Less)] +#[case(1.std_minutes(), 1.seconds(), Greater)] +fn std_partial_ord(#[case] lhs: StdDuration, #[case] rhs: Duration, #[case] expected: Ordering) { + assert_eq!(lhs.partial_cmp(&rhs), Some(expected)); +} + +#[rstest] +#[case(0.seconds(), 0.seconds(), Equal)] +#[case(1.seconds(), 0.seconds(), Greater)] +#[case(1.seconds(), (-1).seconds(), Greater)] +#[case((-1).seconds(), 1.seconds(), Less)] +#[case(0.seconds(), (-1).seconds(), Greater)] +#[case(0.seconds(), 1.seconds(), Less)] +#[case((-1).seconds(), 0.seconds(), Less)] +#[case(1.minutes(), 1.seconds(), Greater)] +#[case((-1).minutes(), (-1).seconds(), Less)] +#[case(100.nanoseconds(), 200.nanoseconds(), Less)] +#[case((-100).nanoseconds(), (-200).nanoseconds(), Greater)] +fn ord(#[case] lhs: Duration, #[case] rhs: Duration, #[case] expected: Ordering) { + assert_eq!(lhs.cmp(&rhs), expected); +} + +#[rstest] +fn arithmetic_regression() { + let added = 1.6.seconds() + 1.6.seconds(); + assert_eq!(added.whole_seconds(), 3); + assert_eq!(added.subsec_milliseconds(), 200); + + let subtracted = 1.6.seconds() - (-1.6).seconds(); + assert_eq!(subtracted.whole_seconds(), 3); + assert_eq!(subtracted.subsec_milliseconds(), 200); +} + +#[rstest] +fn sum_iter_ref() { + let i = [1.6.seconds(), 1.6.seconds()]; + let sum = i.iter().sum::(); + assert_eq!(sum, 3.2.seconds()); +} + +#[rstest] +fn sum_iter() { + let i = [1.6.seconds(), 1.6.seconds()]; + let sum = i.into_iter().sum::(); + assert_eq!(sum, 3.2.seconds()); +} diff --git a/tests/error.rs b/tests/error.rs new file mode 100644 index 000000000..244aba31c --- /dev/null +++ b/tests/error.rs @@ -0,0 +1,194 @@ +use std::error::Error as _; +use std::io; + +use time::error::{ + ComponentRange, ConversionRange, DifferentVariant, Error, Format, IndeterminateOffset, + InvalidFormatDescription, InvalidVariant, Parse, ParseFromDescription, TryFromParsed, +}; +use time::macros::format_description; +use time::parsing::Parsed; +use time::{format_description, Date, Time}; + +macro_rules! assert_display_eq { + ($a:expr, $b:expr $(,)?) => { + assert_eq!($a.to_string(), $b.to_string()) + }; +} + +macro_rules! assert_dbg_reflexive { + ($a:expr) => { + assert_eq!(format!("{:?}", $a), format!("{:?}", $a)) + }; +} + +macro_rules! assert_source { + ($err:expr,None $(,)?) => { + assert!($err.source().is_none()) + }; + ($err:expr, $source:ty $(,)?) => { + assert!($err.source().unwrap().is::<$source>()) + }; +} + +fn component_range() -> ComponentRange { + Date::from_ordinal_date(0, 367).expect_err("367 is not a valid day") +} + +fn insufficient_type_information() -> Format { + Time::MIDNIGHT + .format(&format_description::well_known::Rfc3339) + .expect_err("missing date and UTC offset") +} + +fn unexpected_trailing_characters() -> Parse { + Time::parse("a", format_description!("")).expect_err("should fail to parse") +} + +fn invalid_format_description() -> InvalidFormatDescription { + format_description::parse("[").expect_err("format description is invalid") +} + +fn io_error() -> io::Error { + io::Error::last_os_error() +} + +fn invalid_literal() -> ParseFromDescription { + Parsed::parse_literal(b"a", b"b").expect_err("should fail to parse") +} + +#[test] +fn debug() { + assert_dbg_reflexive!(Parse::from(ParseFromDescription::InvalidComponent("a"))); + assert_dbg_reflexive!(invalid_format_description()); + assert_dbg_reflexive!(DifferentVariant); + assert_dbg_reflexive!(InvalidVariant); +} + +#[test] +fn display() { + assert_display_eq!(ConversionRange, Error::from(ConversionRange)); + assert_display_eq!(component_range(), Error::from(component_range())); + assert_display_eq!(component_range(), TryFromParsed::from(component_range())); + assert_display_eq!(IndeterminateOffset, Error::from(IndeterminateOffset)); + assert_display_eq!( + TryFromParsed::InsufficientInformation, + Error::from(TryFromParsed::InsufficientInformation) + ); + assert_display_eq!( + insufficient_type_information(), + Error::from(insufficient_type_information()) + ); + assert_display_eq!( + Format::InvalidComponent("a"), + Error::from(Format::InvalidComponent("a")) + ); + assert_display_eq!( + ParseFromDescription::InvalidComponent("a"), + Error::from(Parse::from(ParseFromDescription::InvalidComponent("a"))) + ); + assert_display_eq!(invalid_literal(), Parse::from(invalid_literal())); + assert_display_eq!( + component_range(), + Error::from(Parse::from(TryFromParsed::from(component_range()))) + ); + assert_display_eq!( + ParseFromDescription::InvalidComponent("a"), + Parse::from(ParseFromDescription::InvalidComponent("a")) + ); + assert_display_eq!( + component_range(), + Parse::from(TryFromParsed::from(component_range())) + ); + assert_display_eq!( + unexpected_trailing_characters(), + Error::from(unexpected_trailing_characters()), + ); + assert_display_eq!( + invalid_format_description(), + Error::from(invalid_format_description()) + ); + assert_display_eq!(io_error(), Format::from(io_error())); + assert_display_eq!(DifferentVariant, Error::from(DifferentVariant)); + assert_display_eq!(InvalidVariant, Error::from(InvalidVariant)); +} + +#[test] +fn source() { + assert_source!(Error::from(ConversionRange), ConversionRange); + assert_source!(Error::from(component_range()), ComponentRange); + assert_source!(TryFromParsed::from(component_range()), ComponentRange); + assert_source!(TryFromParsed::InsufficientInformation, None); + assert_source!(insufficient_type_information(), None); + assert_source!(Format::InvalidComponent("a"), None); + assert_source!(Error::from(insufficient_type_information()), Format); + assert_source!(Error::from(IndeterminateOffset), IndeterminateOffset); + assert_source!( + Parse::from(TryFromParsed::InsufficientInformation), + TryFromParsed + ); + assert_source!( + Error::from(TryFromParsed::InsufficientInformation), + TryFromParsed + ); + assert_source!( + Parse::from(ParseFromDescription::InvalidComponent("a")), + ParseFromDescription + ); + assert_source!( + Error::from(ParseFromDescription::InvalidComponent("a")), + ParseFromDescription + ); + assert_source!(unexpected_trailing_characters(), ParseFromDescription); + assert_source!( + Error::from(unexpected_trailing_characters()), + ParseFromDescription + ); + assert_source!( + Error::from(invalid_format_description()), + InvalidFormatDescription + ); + assert_source!(Format::from(io_error()), io::Error); + assert_source!(Error::from(DifferentVariant), DifferentVariant); + assert_source!(Error::from(InvalidVariant), InvalidVariant); +} + +#[test] +fn component_name() { + assert_eq!(component_range().name(), "ordinal"); +} + +#[allow(clippy::cognitive_complexity)] // all test the same thing +#[test] +fn conversion() { + assert!(ComponentRange::try_from(Error::from(component_range())).is_ok()); + assert!(ConversionRange::try_from(Error::from(ConversionRange)).is_ok()); + assert!(Format::try_from(Error::from(insufficient_type_information())).is_ok()); + assert!(IndeterminateOffset::try_from(Error::from(IndeterminateOffset)).is_ok()); + assert!(InvalidFormatDescription::try_from(Error::from(invalid_format_description())).is_ok()); + assert!(ParseFromDescription::try_from(Error::from(invalid_literal())).is_ok()); + assert!(ParseFromDescription::try_from(Parse::from(invalid_literal())).is_ok()); + assert!(ParseFromDescription::try_from(unexpected_trailing_characters()).is_ok()); + assert!(Parse::try_from(Error::from(unexpected_trailing_characters())).is_ok()); + assert!(Parse::try_from(Error::from(invalid_literal())).is_ok()); + assert!(Parse::try_from(Error::from(TryFromParsed::InsufficientInformation)).is_ok()); + assert!(DifferentVariant::try_from(Error::from(DifferentVariant)).is_ok()); + assert!(InvalidVariant::try_from(Error::from(InvalidVariant)).is_ok()); + assert!(ComponentRange::try_from(TryFromParsed::ComponentRange(component_range())).is_ok()); + assert!(TryFromParsed::try_from(Error::from(TryFromParsed::InsufficientInformation)).is_ok()); + assert!(TryFromParsed::try_from(Parse::from(TryFromParsed::InsufficientInformation)).is_ok()); + assert!(io::Error::try_from(Format::from(io_error())).is_ok()); + + assert!(ComponentRange::try_from(Error::from(IndeterminateOffset)).is_err()); + assert!(ConversionRange::try_from(Error::from(IndeterminateOffset)).is_err()); + assert!(Format::try_from(Error::from(IndeterminateOffset)).is_err()); + assert!(IndeterminateOffset::try_from(Error::from(ConversionRange)).is_err()); + assert!(InvalidFormatDescription::try_from(Error::from(IndeterminateOffset)).is_err()); + assert!(ParseFromDescription::try_from(Error::from(IndeterminateOffset)).is_err()); + assert!(Parse::try_from(Error::from(IndeterminateOffset)).is_err()); + assert!(DifferentVariant::try_from(Error::from(IndeterminateOffset)).is_err()); + assert!(InvalidVariant::try_from(Error::from(IndeterminateOffset)).is_err()); + assert!(ComponentRange::try_from(TryFromParsed::InsufficientInformation).is_err()); + assert!(TryFromParsed::try_from(Error::from(IndeterminateOffset)).is_err()); + assert!(TryFromParsed::try_from(unexpected_trailing_characters()).is_err()); + assert!(io::Error::try_from(insufficient_type_information()).is_err()); +} diff --git a/tests/ext.rs b/tests/ext.rs new file mode 100644 index 000000000..e01ea6890 --- /dev/null +++ b/tests/ext.rs @@ -0,0 +1,112 @@ +mod numerical_duration { + use time::ext::NumericalDuration; + use time::Duration; + + #[test] + fn unsigned() { + assert_eq!(5.nanoseconds(), Duration::nanoseconds(5)); + assert_eq!(5.microseconds(), Duration::microseconds(5)); + assert_eq!(5.milliseconds(), Duration::milliseconds(5)); + assert_eq!(5.seconds(), Duration::seconds(5)); + assert_eq!(5.minutes(), Duration::minutes(5)); + assert_eq!(5.hours(), Duration::hours(5)); + assert_eq!(5.days(), Duration::days(5)); + assert_eq!(5.weeks(), Duration::weeks(5)); + } + + #[test] + fn signed() { + assert_eq!((-5).nanoseconds(), Duration::nanoseconds(-5)); + assert_eq!((-5).microseconds(), Duration::microseconds(-5)); + assert_eq!((-5).milliseconds(), Duration::milliseconds(-5)); + assert_eq!((-5).seconds(), Duration::seconds(-5)); + assert_eq!((-5).minutes(), Duration::minutes(-5)); + assert_eq!((-5).hours(), Duration::hours(-5)); + assert_eq!((-5).days(), Duration::days(-5)); + assert_eq!((-5).weeks(), Duration::weeks(-5)); + } + + #[test] + fn float() { + // Ensure values truncate rather than round. + assert_eq!(1.9.nanoseconds(), Duration::nanoseconds(1)); + + assert_eq!(1.0.nanoseconds(), Duration::nanoseconds(1)); + assert_eq!(1.0.microseconds(), Duration::microseconds(1)); + assert_eq!(1.0.milliseconds(), Duration::milliseconds(1)); + assert_eq!(1.0.seconds(), Duration::seconds(1)); + assert_eq!(1.0.minutes(), Duration::minutes(1)); + assert_eq!(1.0.hours(), Duration::hours(1)); + assert_eq!(1.0.days(), Duration::days(1)); + assert_eq!(1.0.weeks(), Duration::weeks(1)); + + assert_eq!(1.5.nanoseconds(), Duration::nanoseconds(1)); + assert_eq!(1.5.microseconds(), Duration::nanoseconds(1_500)); + assert_eq!(1.5.milliseconds(), Duration::microseconds(1_500)); + assert_eq!(1.5.seconds(), Duration::milliseconds(1_500)); + assert_eq!(1.5.minutes(), Duration::seconds(90)); + assert_eq!(1.5.hours(), Duration::minutes(90)); + assert_eq!(1.5.days(), Duration::hours(36)); + assert_eq!(1.5.weeks(), Duration::hours(252)); + } + + #[test] + fn arithmetic() { + assert_eq!(2.seconds() + 500.milliseconds(), 2_500.milliseconds()); + assert_eq!(2.seconds() - 500.milliseconds(), 1_500.milliseconds()); + } +} + +mod numerical_std_duration { + use std::time::Duration as StdDuration; + + use time::ext::NumericalStdDuration; + + #[test] + fn unsigned() { + assert_eq!(5.std_nanoseconds(), StdDuration::from_nanos(5)); + assert_eq!(5.std_microseconds(), StdDuration::from_micros(5)); + assert_eq!(5.std_milliseconds(), StdDuration::from_millis(5)); + assert_eq!(5.std_seconds(), StdDuration::from_secs(5)); + assert_eq!(5.std_minutes(), StdDuration::from_secs(5 * 60)); + assert_eq!(5.std_hours(), StdDuration::from_secs(5 * 3_600)); + assert_eq!(5.std_days(), StdDuration::from_secs(5 * 86_400)); + assert_eq!(5.std_weeks(), StdDuration::from_secs(5 * 604_800)); + } + + #[test] + fn float() { + // Ensure values truncate rather than round. + assert_eq!(1.9.std_nanoseconds(), StdDuration::from_nanos(1)); + + assert_eq!(1.0.std_nanoseconds(), StdDuration::from_nanos(1)); + assert_eq!(1.0.std_microseconds(), StdDuration::from_micros(1)); + assert_eq!(1.0.std_milliseconds(), StdDuration::from_millis(1)); + assert_eq!(1.0.std_seconds(), StdDuration::from_secs(1)); + assert_eq!(1.0.std_minutes(), StdDuration::from_secs(60)); + assert_eq!(1.0.std_hours(), StdDuration::from_secs(3_600)); + assert_eq!(1.0.std_days(), StdDuration::from_secs(86_400)); + assert_eq!(1.0.std_weeks(), StdDuration::from_secs(604_800)); + + assert_eq!(1.5.std_nanoseconds(), StdDuration::from_nanos(1)); + assert_eq!(1.5.std_microseconds(), StdDuration::from_nanos(1_500)); + assert_eq!(1.5.std_milliseconds(), StdDuration::from_micros(1_500)); + assert_eq!(1.5.std_seconds(), StdDuration::from_millis(1_500)); + assert_eq!(1.5.std_minutes(), StdDuration::from_secs(90)); + assert_eq!(1.5.std_hours(), StdDuration::from_secs(90 * 60)); + assert_eq!(1.5.std_days(), StdDuration::from_secs(36 * 3_600)); + assert_eq!(1.5.std_weeks(), StdDuration::from_secs(252 * 3_600)); + } + + #[test] + fn arithmetic() { + assert_eq!( + 2.std_seconds() + 500.std_milliseconds(), + 2_500.std_milliseconds() + ); + assert_eq!( + 2.std_seconds() - 500.std_milliseconds(), + 1_500.std_milliseconds() + ); + } +} diff --git a/tests/format_description.rs b/tests/format_description.rs new file mode 100644 index 000000000..bcbeed32e --- /dev/null +++ b/tests/format_description.rs @@ -0,0 +1,68 @@ +use time::format_description::{modifier, BorrowedFormatItem, Component, OwnedFormatItem}; + +#[test] +fn borrowed_format_item_component_conversions() { + let component = Component::Year(modifier::Year::default()); + let item = BorrowedFormatItem::from(component); + assert!(matches!(item, BorrowedFormatItem::Component(inner) if inner == component)); + assert_eq!(Component::try_from(item), Ok(component)); + assert!(Component::try_from(BorrowedFormatItem::Literal(b"")).is_err()); + assert!(<&[BorrowedFormatItem<'_>]>::try_from(BorrowedFormatItem::Literal(b"")).is_err()); +} + +#[test] +fn borrowed_format_item_compound_conversions() { + let compound = [BorrowedFormatItem::Literal(b"")].as_slice(); + let item = BorrowedFormatItem::from(compound); + assert!(matches!(item, BorrowedFormatItem::Compound(inner) if inner == compound)); + assert_eq!(<&[BorrowedFormatItem<'_>]>::try_from(item), Ok(compound)); +} + +#[test] +fn borrowed_format_item_equality() { + let component = Component::Year(modifier::Year::default()); + let compound = [BorrowedFormatItem::Literal(b"")].as_slice(); + let component_item = BorrowedFormatItem::from(component); + let compound_item = BorrowedFormatItem::from(compound); + + assert_eq!(component, component_item); + assert_eq!(component_item, component); + assert_eq!(compound, compound_item); + assert_eq!(compound_item, compound); +} + +#[test] +fn owned_format_item_component_conversions() { + let component = Component::Year(modifier::Year::default()); + let item = OwnedFormatItem::from(component); + assert!(matches!(item, OwnedFormatItem::Component(inner) if inner == component)); + assert_eq!(Component::try_from(item), Ok(component)); + assert!(Component::try_from(OwnedFormatItem::Literal(Box::new([]))).is_err()); + assert!(Vec::::try_from(OwnedFormatItem::Literal(Box::new([]))).is_err()); +} + +#[test] +fn owned_format_item_compound_conversions() { + let compound = vec![OwnedFormatItem::Literal(Box::new([]))]; + let item = OwnedFormatItem::from(compound.clone()); + assert!(matches!(item.clone(), OwnedFormatItem::Compound(inner) if inner.to_vec() == compound)); + assert_eq!(Vec::::try_from(item), Ok(compound)); +} + +#[test] +fn owned_format_item_equality() { + let component = Component::Year(modifier::Year::default()); + let compound = OwnedFormatItem::from([BorrowedFormatItem::Literal(b"")].as_slice()); + let component_item = OwnedFormatItem::from(component); + + assert_eq!(component, component_item); + assert_eq!(component_item, component); + assert_eq!( + compound, + [OwnedFormatItem::Literal(Box::new([]))].as_slice() + ); + assert_eq!( + [OwnedFormatItem::Literal(Box::new([]))].as_slice(), + compound + ); +} diff --git a/tests/formatting.rs b/tests/formatting.rs new file mode 100644 index 000000000..6cb403b74 --- /dev/null +++ b/tests/formatting.rs @@ -0,0 +1,808 @@ +use std::io; +use std::num::NonZeroU8; + +use time::format_description::well_known::iso8601::{DateKind, OffsetPrecision, TimePrecision}; +use time::format_description::well_known::{iso8601, Iso8601, Rfc2822, Rfc3339}; +use time::format_description::{self, BorrowedFormatItem, OwnedFormatItem}; +use time::macros::{date, datetime, format_description as fd, offset, time}; +use time::{OffsetDateTime, Time}; + +#[test] +fn rfc_2822() -> time::Result<()> { + assert_eq!( + datetime!(2021-01-02 03:04:05 UTC).format(&Rfc2822)?, + "Sat, 02 Jan 2021 03:04:05 +0000" + ); + assert_eq!( + datetime!(2021-01-02 03:04:05 +06:07).format(&Rfc2822)?, + "Sat, 02 Jan 2021 03:04:05 +0607" + ); + assert_eq!( + datetime!(2021-01-02 03:04:05 -06:07).format(&Rfc2822)?, + "Sat, 02 Jan 2021 03:04:05 -0607" + ); + + assert!(matches!( + datetime!(1885-01-01 01:01:01 UTC).format(&Rfc2822), + Err(time::error::Format::InvalidComponent("year")) + )); + assert!(matches!( + datetime!(2000-01-01 00:00:00 +00:00:01).format(&Rfc2822), + Err(time::error::Format::InvalidComponent("offset_second")) + )); + + Ok(()) +} + +#[test] +fn rfc_3339() -> time::Result<()> { + assert_eq!( + datetime!(2021-01-02 03:04:05 UTC).format(&Rfc3339)?, + "2021-01-02T03:04:05Z" + ); + assert_eq!( + datetime!(2021-01-02 03:04:05.1 UTC).format(&Rfc3339)?, + "2021-01-02T03:04:05.1Z" + ); + assert_eq!( + datetime!(2021-01-02 03:04:05.12 UTC).format(&Rfc3339)?, + "2021-01-02T03:04:05.12Z" + ); + assert_eq!( + datetime!(2021-01-02 03:04:05.123 UTC).format(&Rfc3339)?, + "2021-01-02T03:04:05.123Z" + ); + assert_eq!( + datetime!(2021-01-02 03:04:05.123_4 UTC).format(&Rfc3339)?, + "2021-01-02T03:04:05.1234Z" + ); + assert_eq!( + datetime!(2021-01-02 03:04:05.123_45 UTC).format(&Rfc3339)?, + "2021-01-02T03:04:05.12345Z" + ); + assert_eq!( + datetime!(2021-01-02 03:04:05.123_456 UTC).format(&Rfc3339)?, + "2021-01-02T03:04:05.123456Z" + ); + assert_eq!( + datetime!(2021-01-02 03:04:05.123_456_7 UTC).format(&Rfc3339)?, + "2021-01-02T03:04:05.1234567Z" + ); + assert_eq!( + datetime!(2021-01-02 03:04:05.123_456_78 UTC).format(&Rfc3339)?, + "2021-01-02T03:04:05.12345678Z" + ); + assert_eq!( + datetime!(2021-01-02 03:04:05.123_456_789 UTC).format(&Rfc3339)?, + "2021-01-02T03:04:05.123456789Z" + ); + assert_eq!( + datetime!(2021-01-02 03:04:05.123_456_789 -01:02).format(&Rfc3339)?, + "2021-01-02T03:04:05.123456789-01:02" + ); + assert_eq!( + datetime!(2021-01-02 03:04:05.123_456_789 +01:02).format(&Rfc3339)?, + "2021-01-02T03:04:05.123456789+01:02" + ); + + assert!(matches!( + datetime!(-0001-01-01 0:00 UTC).format(&Rfc3339), + Err(time::error::Format::InvalidComponent("year")) + )); + assert!(matches!( + datetime!(0000-01-01 0:00 +00:00:01).format(&Rfc3339), + Err(time::error::Format::InvalidComponent("offset_second")) + )); + + Ok(()) +} + +#[test] +fn iso_8601() -> time::Result<()> { + macro_rules! assert_format_config { + ($formatted:literal $(, $($config:tt)+)?) => { + assert_eq!( + datetime!(2021-01-02 03:04:05 UTC).format( + &Iso8601::<{ iso8601::Config::DEFAULT$($($config)+)?.encode() }> + )?, + $formatted + ); + }; + } + + assert!( + std::panic::catch_unwind(|| { + let _unused = datetime!(2021-01-02 03:04:05 UTC).format(&Iso8601::PARSING); + }) + .is_err() + ); + assert_eq!( + datetime!(-123_456-01-02 03:04:05 UTC).format( + &Iso8601::< + { + iso8601::Config::DEFAULT + .set_year_is_six_digits(true) + .encode() + }, + > + )?, + "-123456-01-02T03:04:05.000000000Z" + ); + assert_eq!( + datetime!(-123_456-01-02 03:04:05 UTC).format( + &Iso8601::< + { + iso8601::Config::DEFAULT + .set_date_kind(DateKind::Ordinal) + .set_year_is_six_digits(true) + .encode() + }, + > + )?, + "-123456-002T03:04:05.000000000Z" + ); + assert_eq!( + datetime!(-123_456-01-02 03:04:05 UTC).format( + &Iso8601::< + { + iso8601::Config::DEFAULT + .set_date_kind(DateKind::Week) + .set_year_is_six_digits(true) + .encode() + }, + > + )?, + "-123456-W01-4T03:04:05.000000000Z" + ); + assert_eq!( + datetime!(2021-01-02 03:04:05+1:00).format(&Iso8601::DEFAULT)?, + "2021-01-02T03:04:05.000000000+01:00" + ); + assert_eq!( + datetime!(2021-01-02 03:04:05+1:00).format( + &Iso8601::< + { + iso8601::Config::DEFAULT + .set_offset_precision(OffsetPrecision::Hour) + .encode() + }, + > + )?, + "2021-01-02T03:04:05.000000000+01" + ); + assert_format_config!("2021-01-02T03:04:05.000000000Z"); + assert_format_config!("20210102T030405.000000000Z", .set_use_separators(false)); + assert_format_config!("+002021-01-02T03:04:05.000000000Z", .set_year_is_six_digits(true)); + assert_format_config!("2021-01-02T03Z", .set_time_precision(TimePrecision::Hour { decimal_digits: None })); + assert_format_config!("2021-01-02T03:04Z", .set_time_precision(TimePrecision::Minute { decimal_digits: None })); + assert_format_config!("2021-01-02T03:04:05Z", .set_time_precision(TimePrecision::Second { decimal_digits: None })); + assert_format_config!("2021-002T03:04:05.000000000Z", .set_date_kind(DateKind::Ordinal)); + assert_format_config!("2020-W53-6T03:04:05.000000000Z", .set_date_kind(DateKind::Week)); + + assert!(matches!( + datetime!(+10_000-01-01 0:00 UTC).format(&Iso8601::DEFAULT), + Err(time::error::Format::InvalidComponent("year")) + )); + assert!(matches!( + datetime!(+10_000-W 01-1 0:00 UTC).format( + &Iso8601::< + { + iso8601::Config::DEFAULT + .set_date_kind(DateKind::Week) + .encode() + }, + > + ), + Err(time::error::Format::InvalidComponent("year")) + )); + assert!(matches!( + datetime!(+10_000-001 0:00 UTC).format( + &Iso8601::< + { + iso8601::Config::DEFAULT + .set_date_kind(DateKind::Ordinal) + .encode() + }, + > + ), + Err(time::error::Format::InvalidComponent("year")) + )); + assert!(matches!( + datetime!(2021-01-02 03:04:05 +0:00:01).format(&Iso8601::DEFAULT), + Err(time::error::Format::InvalidComponent("offset_second")) + )); + assert!(matches!( + datetime!(2021-01-02 03:04:05 +0:01).format( + &Iso8601::< + { + iso8601::Config::DEFAULT + .set_offset_precision(OffsetPrecision::Hour) + .encode() + }, + > + ), + Err(time::error::Format::InvalidComponent("offset_minute")) + )); + + Ok(()) +} + +#[test] +fn iso_8601_issue_678() -> time::Result<()> { + macro_rules! assert_format_config { + ($formatted:literal $(, $($config:tt)+)?) => { + assert_eq!( + datetime!(2021-01-02 03:04:05.999_999_999 UTC).format( + &Iso8601::<{ iso8601::Config::DEFAULT$($($config)+)?.encode() }> + )?, + $formatted + ); + }; + } + + assert_format_config!("2021-01-02T03:04:05.999999999Z", .set_time_precision(TimePrecision::Second { decimal_digits: NonZeroU8::new(9) })); + assert_format_config!("2021-01-02T03:04:05.999999Z", .set_time_precision(TimePrecision::Second { decimal_digits: NonZeroU8::new(6) })); + assert_format_config!("2021-01-02T03:04:05.999Z", .set_time_precision(TimePrecision::Second { decimal_digits: NonZeroU8::new(3) })); + + Ok(()) +} + +#[test] +fn format_time() -> time::Result<()> { + let format_output = [ + (fd!("[hour]"), "13"), + (fd!("[hour repr:12]"), "01"), + (fd!("[hour repr:12 padding:none]"), "1"), + (fd!("[hour repr:12 padding:space]"), " 1"), + (fd!("[hour repr:24]"), "13"), + (fd!("[hour repr:24]"), "13"), + (fd!("[hour repr:24 padding:none]"), "13"), + (fd!("[hour repr:24 padding:space]"), "13"), + (fd!("[minute]"), "02"), + (fd!("[minute padding:none]"), "2"), + (fd!("[minute padding:space]"), " 2"), + (fd!("[minute padding:zero]"), "02"), + (fd!("[period]"), "PM"), + (fd!("[period case:upper]"), "PM"), + (fd!("[period case:lower]"), "pm"), + (fd!("[second]"), "03"), + (fd!("[second padding:none]"), "3"), + (fd!("[second padding:space]"), " 3"), + (fd!("[second padding:zero]"), "03"), + (fd!("[subsecond]"), "456789012"), + (fd!("[subsecond digits:1]"), "4"), + (fd!("[subsecond digits:2]"), "45"), + (fd!("[subsecond digits:3]"), "456"), + (fd!("[subsecond digits:4]"), "4567"), + (fd!("[subsecond digits:5]"), "45678"), + (fd!("[subsecond digits:6]"), "456789"), + (fd!("[subsecond digits:7]"), "4567890"), + (fd!("[subsecond digits:8]"), "45678901"), + (fd!("[subsecond digits:9]"), "456789012"), + (fd!("[subsecond digits:1+]"), "456789012"), + ]; + + for &(format_description, output) in &format_output { + assert_eq!( + time!(13:02:03.456_789_012).format(format_description)?, + output + ); + assert!( + time!(13:02:03.456_789_012) + .format_into(&mut io::sink(), format_description) + .is_ok() + ); + assert_eq!( + time!(13:02:03.456_789_012).format(&OwnedFormatItem::from(format_description))?, + output + ); + assert!( + time!(13:02:03.456_789_012) + .format_into(&mut io::sink(), &OwnedFormatItem::from(format_description)) + .is_ok() + ); + } + + assert_eq!( + time!(1:02:03).format(fd!("[hour repr:12][period]"))?, + "01AM" + ); + assert_eq!( + Time::MIDNIGHT.format(fd!("[hour repr:12][period case:lower]"))?, + "12am" + ); + assert_eq!(Time::MIDNIGHT.format(fd!("[subsecond digits:1+]"))?, "0"); + assert_eq!( + time!(0:00:00.01).format(fd!("[subsecond digits:1+]"))?, + "01" + ); + assert_eq!( + time!(0:00:00.001).format(fd!("[subsecond digits:1+]"))?, + "001" + ); + assert_eq!( + time!(0:00:00.0001).format(fd!("[subsecond digits:1+]"))?, + "0001" + ); + assert_eq!( + time!(0:00:00.00001).format(fd!("[subsecond digits:1+]"))?, + "00001" + ); + assert_eq!( + time!(0:00:00.000001).format(fd!("[subsecond digits:1+]"))?, + "000001" + ); + assert_eq!( + time!(0:00:00.0000001).format(fd!("[subsecond digits:1+]"))?, + "0000001" + ); + assert_eq!( + time!(0:00:00.00000001).format(fd!("[subsecond digits:1+]"))?, + "00000001" + ); + assert_eq!( + time!(0:00:00.000000001).format(fd!("[subsecond digits:1+]"))?, + "000000001" + ); + + Ok(()) +} + +#[test] +fn display_time() { + assert_eq!(time!(0:00).to_string(), "0:00:00.0"); + assert_eq!(time!(23:59).to_string(), "23:59:00.0"); + assert_eq!(time!(23:59:59).to_string(), "23:59:59.0"); + assert_eq!(time!(0:00:01).to_string(), "0:00:01.0"); + assert_eq!(time!(0:00:00.1).to_string(), "0:00:00.1"); + assert_eq!(time!(0:00:00.01).to_string(), "0:00:00.01"); + assert_eq!(time!(0:00:00.001).to_string(), "0:00:00.001"); + assert_eq!(time!(0:00:00.000_1).to_string(), "0:00:00.0001"); + assert_eq!(time!(0:00:00.000_01).to_string(), "0:00:00.00001"); + assert_eq!(time!(0:00:00.000_001).to_string(), "0:00:00.000001"); + assert_eq!(time!(0:00:00.000_000_1).to_string(), "0:00:00.0000001"); + assert_eq!(time!(0:00:00.000_000_01).to_string(), "0:00:00.00000001"); + assert_eq!(time!(0:00:00.000_000_001).to_string(), "0:00:00.000000001"); + + assert_eq!(format!("{:>12}", time!(0:00)), " 0:00:00.0"); + assert_eq!(format!("{:x^14}", time!(0:00)), "xx0:00:00.0xxx"); +} + +#[test] +fn format_date() -> time::Result<()> { + let format_output = [ + (fd!("[day]"), "31"), + (fd!("[month]"), "12"), + (fd!("[month repr:short]"), "Dec"), + (fd!("[month repr:long]"), "December"), + (fd!("[ordinal]"), "365"), + (fd!("[weekday]"), "Tuesday"), + (fd!("[weekday repr:short]"), "Tue"), + (fd!("[weekday repr:sunday]"), "3"), + (fd!("[weekday repr:sunday one_indexed:false]"), "2"), + (fd!("[weekday repr:monday]"), "2"), + (fd!("[weekday repr:monday one_indexed:false]"), "1"), + (fd!("[week_number]"), "01"), + (fd!("[week_number padding:none]"), "1"), + (fd!("[week_number padding:space]"), " 1"), + (fd!("[week_number repr:sunday]"), "52"), + (fd!("[week_number repr:monday]"), "52"), + (fd!("[year]"), "2019"), + (fd!("[year base:iso_week]"), "2020"), + (fd!("[year sign:mandatory]"), "+2019"), + (fd!("[year base:iso_week sign:mandatory]"), "+2020"), + (fd!("[year repr:last_two]"), "19"), + (fd!("[year base:iso_week repr:last_two]"), "20"), + ]; + + for &(format_description, output) in &format_output { + assert_eq!(date!(2019 - 12 - 31).format(format_description)?, output); + assert!( + date!(2019 - 12 - 31) + .format_into(&mut io::sink(), format_description) + .is_ok() + ); + assert_eq!( + date!(2019 - 12 - 31).format(&OwnedFormatItem::from(format_description))?, + output + ); + assert!( + date!(2019 - 12 - 31) + .format_into(&mut io::sink(), &OwnedFormatItem::from(format_description)) + .is_ok() + ); + } + + Ok(()) +} + +#[test] +fn display_date() { + assert_eq!(date!(2019 - 01 - 01).to_string(), "2019-01-01"); + assert_eq!(date!(2019 - 12 - 31).to_string(), "2019-12-31"); + assert_eq!(date!(-4713 - 11 - 24).to_string(), "-4713-11-24"); + assert_eq!(date!(-0001 - 01 - 01).to_string(), "-0001-01-01"); + + assert_eq!(date!(+10_000-01-01).to_string(), "+10000-01-01"); + assert_eq!(date!(+100_000-01-01).to_string(), "+100000-01-01"); + assert_eq!(date!(-10_000 - 01 - 01).to_string(), "-10000-01-01"); + assert_eq!(date!(-100_000 - 01 - 01).to_string(), "-100000-01-01"); +} + +#[test] +fn format_offset() -> time::Result<()> { + let value_format_output = [ + ( + offset!(+01:02:03), + fd!("[offset_hour sign:automatic]"), + "01", + ), + ( + offset!(+01:02:03), + fd!("[offset_hour sign:mandatory]"), + "+01", + ), + ( + offset!(-01:02:03), + fd!("[offset_hour sign:automatic]"), + "-01", + ), + ( + offset!(-01:02:03), + fd!("[offset_hour sign:mandatory]"), + "-01", + ), + (offset!(+01:02:03), fd!("[offset_minute]"), "02"), + (offset!(+01:02:03), fd!("[offset_second]"), "03"), + ]; + + for &(value, format_description, output) in &value_format_output { + assert_eq!(value.format(format_description)?, output); + assert!( + value + .format_into(&mut io::sink(), format_description) + .is_ok() + ); + assert_eq!( + value.format(&OwnedFormatItem::from(format_description))?, + output + ); + assert!( + value + .format_into(&mut io::sink(), &OwnedFormatItem::from(format_description)) + .is_ok() + ); + } + + Ok(()) +} + +#[test] +fn display_offset() { + assert_eq!(offset!(UTC).to_string(), "+00:00:00"); + assert_eq!(offset!(+0:00:01).to_string(), "+00:00:01"); + assert_eq!(offset!(-0:00:01).to_string(), "-00:00:01"); + assert_eq!(offset!(+1).to_string(), "+01:00:00"); + assert_eq!(offset!(-1).to_string(), "-01:00:00"); + assert_eq!(offset!(+23:59).to_string(), "+23:59:00"); + assert_eq!(offset!(-23:59).to_string(), "-23:59:00"); + assert_eq!(offset!(+23:59:59).to_string(), "+23:59:59"); + assert_eq!(offset!(-23:59:59).to_string(), "-23:59:59"); + + assert_eq!(format!("{:>10}", offset!(UTC)), " +00:00:00"); + assert_eq!(format!("{:x^14}", offset!(UTC)), "xx+00:00:00xxx"); +} + +#[test] +fn format_pdt() -> time::Result<()> { + let format_description = fd!("[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]"); + + assert_eq!( + datetime!(1970-01-01 0:00).format(format_description)?, + "1970-01-01 00:00:00.0" + ); + assert!( + datetime!(1970-01-01 0:00) + .format_into(&mut io::sink(), format_description) + .is_ok() + ); + assert_eq!( + datetime!(1970-01-01 0:00).format(&OwnedFormatItem::from(format_description))?, + "1970-01-01 00:00:00.0" + ); + assert!( + datetime!(1970-01-01 0:00) + .format_into(&mut io::sink(), &OwnedFormatItem::from(format_description)) + .is_ok() + ); + + Ok(()) +} + +#[test] +fn display_pdt() { + assert_eq!( + datetime!(1970-01-01 0:00).to_string(), + String::from("1970-01-01 0:00:00.0") + ); + assert_eq!( + datetime!(1970-01-01 0:00:01).to_string(), + String::from("1970-01-01 0:00:01.0") + ); +} + +#[test] +fn format_odt() -> time::Result<()> { + let format_description = format_description::parse( + "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:1+] [offset_hour \ + sign:mandatory]:[offset_minute]:[offset_second]", + )?; + + assert_eq!( + datetime!(1970-01-01 0:00 UTC).format(&format_description)?, + "1970-01-01 00:00:00.0 +00:00:00" + ); + assert!( + datetime!(1970-01-01 0:00 UTC) + .format_into(&mut io::sink(), &format_description) + .is_ok() + ); + assert_eq!( + datetime!(1970-01-01 0:00 UTC).format(&OwnedFormatItem::from(&format_description))?, + "1970-01-01 00:00:00.0 +00:00:00" + ); + assert!( + datetime!(1970-01-01 0:00 UTC) + .format_into(&mut io::sink(), &OwnedFormatItem::from(format_description)) + .is_ok() + ); + + Ok(()) +} + +#[test] +fn display_odt() { + assert_eq!( + datetime!(1970-01-01 0:00 UTC).to_string(), + "1970-01-01 0:00:00.0 +00:00:00" + ); +} + +#[test] +fn insufficient_type_information() { + let assert_insufficient_type_information = |res| { + assert!(matches!( + res, + Err(time::error::Format::InsufficientTypeInformation { .. }) + )); + }; + assert_insufficient_type_information(Time::MIDNIGHT.format(fd!("[year]"))); + assert_insufficient_type_information(Time::MIDNIGHT.format(&Rfc3339)); + assert_insufficient_type_information(date!(2021 - 001).format(&Rfc3339)); + assert_insufficient_type_information(datetime!(2021 - 001 0:00).format(&Rfc3339)); + assert_insufficient_type_information(Time::MIDNIGHT.format(&Rfc2822)); + assert_insufficient_type_information(date!(2021 - 001).format(&Rfc2822)); + assert_insufficient_type_information(datetime!(2021 - 001 0:00).format(&Rfc2822)); + assert_insufficient_type_information(Time::MIDNIGHT.format(&BorrowedFormatItem::First(&[ + BorrowedFormatItem::Compound(fd!("[year]")), + ]))); + assert_insufficient_type_information(Time::MIDNIGHT.format(&Iso8601::DEFAULT)); + assert_insufficient_type_information(date!(2021 - 001).format(&Iso8601::DEFAULT)); + assert_insufficient_type_information(datetime!(2021-001 0:00).format(&Iso8601::DEFAULT)); +} + +#[allow(clippy::cognitive_complexity)] // all test the same thing +#[test] +fn failed_write() -> time::Result<()> { + macro_rules! assert_err { + ($val:expr, $format:expr) => {{ + let val = $val; + let format = $format; + let success_len = val.format(&format)?.len(); + for len in 0..success_len { + let mut buf = &mut vec![0; len][..]; + let res = val.format_into(&mut buf, &format); + assert!(matches!( + res, + Err(time::error::Format::StdIo(e)) if e.kind() == io::ErrorKind::WriteZero) + ); + } + }}; + } + + assert_err!(Time::MIDNIGHT, fd!("foo")); + assert_err!(Time::MIDNIGHT, OwnedFormatItem::from(fd!("foo"))); + assert_err!(Time::MIDNIGHT, BorrowedFormatItem::Compound(fd!("foo"))); + assert_err!( + Time::MIDNIGHT, + BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound(fd!("foo"))) + ); + assert_err!( + Time::MIDNIGHT, + OwnedFormatItem::from(BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound( + fd!("foo") + ))) + ); + assert_err!(OffsetDateTime::UNIX_EPOCH, Rfc3339); + assert_err!(datetime!(2021-001 0:00:00.1 UTC), Rfc3339); + assert_err!(datetime!(2021-001 0:00 +0:01), Rfc3339); + assert_err!(OffsetDateTime::UNIX_EPOCH, Rfc2822); + assert_err!(OffsetDateTime::UNIX_EPOCH, Iso8601::DEFAULT); + assert_err!(datetime!(2021-001 0:00 +0:01), Iso8601::DEFAULT); + assert_err!( + OffsetDateTime::UNIX_EPOCH, + Iso8601::< + { + iso8601::Config::DEFAULT + .set_year_is_six_digits(true) + .encode() + }, + > + ); + assert_err!( + OffsetDateTime::UNIX_EPOCH, + Iso8601::< + { + iso8601::Config::DEFAULT + .set_date_kind(DateKind::Ordinal) + .encode() + }, + > + ); + assert_err!( + OffsetDateTime::UNIX_EPOCH, + Iso8601::< + { + iso8601::Config::DEFAULT + .set_year_is_six_digits(true) + .set_date_kind(DateKind::Ordinal) + .encode() + }, + > + ); + assert_err!( + OffsetDateTime::UNIX_EPOCH, + Iso8601::< + { + iso8601::Config::DEFAULT + .set_year_is_six_digits(true) + .set_date_kind(DateKind::Week) + .encode() + }, + > + ); + assert_err!( + OffsetDateTime::UNIX_EPOCH, + Iso8601::< + { + iso8601::Config::DEFAULT + .set_date_kind(DateKind::Week) + .encode() + }, + > + ); + assert_err!( + OffsetDateTime::UNIX_EPOCH, + Iso8601::< + { + iso8601::Config::DEFAULT + .set_time_precision(TimePrecision::Minute { + decimal_digits: None, + }) + .encode() + }, + > + ); + assert_err!( + OffsetDateTime::UNIX_EPOCH, + Iso8601::< + { + iso8601::Config::DEFAULT + .set_time_precision(TimePrecision::Hour { + decimal_digits: None, + }) + .encode() + }, + > + ); + + assert_err!(Time::MIDNIGHT, fd!("[hour padding:space]")); + assert_err!(offset!(+1), fd!("[offset_hour sign:mandatory]")); + assert_err!(offset!(-1), fd!("[offset_hour]")); + assert_err!(date!(-1 - 001), fd!("[year]")); + assert_err!(date!(2021 - 001), fd!("[year sign:mandatory]")); + assert_err!(date!(+999_999 - 001), fd!("[year]")); + assert_err!(date!(+99_999 - 001), fd!("[year]")); + + let component_names = [ + "day", + "month", + "ordinal", + "weekday", + "week_number", + "year", + "hour", + "minute", + "period", + "second", + "subsecond", + "offset_hour", + "offset_minute", + "offset_second", + ]; + for component in &component_names { + let component = format!("[{component}]"); + assert_err!( + OffsetDateTime::UNIX_EPOCH, + format_description::parse(&component)? + ); + } + + Ok(()) +} + +#[test] +fn first() -> time::Result<()> { + assert_eq!(Time::MIDNIGHT.format(&BorrowedFormatItem::First(&[]))?, ""); + assert_eq!( + Time::MIDNIGHT.format(&BorrowedFormatItem::First(&[BorrowedFormatItem::Compound( + fd!("[hour]") + )]))?, + "00" + ); + assert_eq!( + Time::MIDNIGHT.format(&OwnedFormatItem::First(Box::new([])))?, + "" + ); + assert_eq!( + Time::MIDNIGHT.format(&OwnedFormatItem::from(BorrowedFormatItem::First(&[ + BorrowedFormatItem::Compound(fd!("[hour]")) + ])))?, + "00" + ); + + Ok(()) +} + +#[test] +fn ignore() -> time::Result<()> { + assert_eq!(Time::MIDNIGHT.format(fd!("[ignore count:2]"))?, ""); + + Ok(()) +} + +#[test] +fn end() -> time::Result<()> { + assert_eq!(Time::MIDNIGHT.format(fd!("[end]"))?, ""); + + Ok(()) +} + +#[test] +fn unix_timestamp() -> time::Result<()> { + let dt = datetime!(2009-02-13 23:31:30.123456789 UTC); + + assert_eq!(dt.format(&fd!("[unix_timestamp]"))?, "1234567890"); + assert_eq!( + dt.format(&fd!("[unix_timestamp sign:mandatory]"))?, + "+1234567890" + ); + assert_eq!( + dt.format(&fd!("[unix_timestamp precision:millisecond]"))?, + "1234567890123" + ); + assert_eq!( + dt.format(&fd!("[unix_timestamp precision:microsecond]"))?, + "1234567890123456" + ); + assert_eq!( + dt.format(&fd!("[unix_timestamp precision:nanosecond]"))?, + "1234567890123456789" + ); + assert_eq!( + datetime!(1969-12-31 23:59:59 UTC).format(&fd!("[unix_timestamp]"))?, + "-1" + ); + + Ok(()) +} diff --git a/tests/instant.rs b/tests/instant.rs new file mode 100644 index 000000000..ec1405191 --- /dev/null +++ b/tests/instant.rs @@ -0,0 +1,238 @@ +#![allow(deprecated)] + +use std::cmp::Ordering; +use std::thread; +use std::time::Instant as StdInstant; + +use time::ext::{NumericalDuration, NumericalStdDuration}; +use time::{Duration, Instant}; + +#[test] +fn elapsed() { + let instant = Instant::now(); + thread::sleep(1.std_milliseconds()); + assert!(instant.elapsed() >= 1.milliseconds()); +} + +#[test] +fn checked_add() { + let now = Instant::now(); + assert_eq!(now.checked_add(0.seconds()), Some(now)); + assert_eq!(now.checked_add(5.seconds()), Some(now + 5.seconds())); + assert_eq!(now.checked_add((-5).seconds()), Some(now + (-5).seconds())); +} + +#[test] +fn checked_sub() { + let now = Instant::now(); + assert_eq!(now.checked_sub(0.seconds()), Some(now)); + assert_eq!(now.checked_sub(5.seconds()), Some(now - 5.seconds())); + assert_eq!(now.checked_sub((-5).seconds()), Some(now - (-5).seconds())); +} + +#[test] +fn into_inner() { + let now = Instant::now(); + assert_eq!(now.into_inner(), now.0); +} + +#[test] +fn std_from() { + let now_time = Instant::now(); + let now_std = StdInstant::from(now_time); + assert_eq!(now_time, now_std); +} + +#[test] +fn from_std() { + let now_std = StdInstant::now(); + let now_time = Instant::from(now_std); + assert_eq!(now_time, now_std); +} + +#[allow(clippy::eq_op)] +#[test] +fn sub() { + let start = Instant::now(); + thread::sleep(1.std_milliseconds()); + assert!(Instant::now() - start >= 1.milliseconds()); + assert_eq!(start - start, Duration::ZERO); +} + +#[test] +fn sub_std() { + let start = StdInstant::now(); + thread::sleep(1.std_milliseconds()); + assert!(Instant::now() - start >= 1.milliseconds()); +} + +#[test] +fn std_sub() { + let start = Instant::now(); + thread::sleep(1.std_milliseconds()); + assert!(StdInstant::now() - start >= 1.milliseconds()); +} + +#[test] +fn add_duration() { + let start = Instant::now(); + assert!(start + 0.seconds() <= Instant::now()); + thread::sleep(1.std_milliseconds()); + assert!(start + 1.milliseconds() <= Instant::now()); +} + +#[test] +fn std_add_duration() { + let start = StdInstant::now(); + thread::sleep(1.std_milliseconds()); + assert!(start + 1.milliseconds() <= StdInstant::now()); +} + +#[test] +fn add_std_duration() { + let start = Instant::now(); + thread::sleep(1.std_milliseconds()); + assert!(start + 1.std_milliseconds() <= Instant::now()); +} + +#[test] +fn add_assign_duration() { + let mut start = Instant::now(); + thread::sleep(1.std_milliseconds()); + start += 1.milliseconds(); + assert!(start <= Instant::now()); +} + +#[test] +fn std_add_assign_duration() { + let mut start = StdInstant::now(); + thread::sleep(1.std_milliseconds()); + start += 1.milliseconds(); + assert!(start <= StdInstant::now()); +} + +#[test] +fn add_assign_std_duration() { + let mut start = Instant::now(); + thread::sleep(1.std_milliseconds()); + start += 1.std_milliseconds(); + assert!(start <= Instant::now()); +} + +#[test] +fn sub_duration() { + let instant = Instant::now(); + assert!(instant - 100.milliseconds() <= Instant::now()); + assert_eq!(instant - Duration::ZERO, instant); +} + +#[test] +fn std_sub_duration() { + let instant = StdInstant::now(); + assert!(instant - 100.milliseconds() <= StdInstant::now()); +} + +#[test] +fn sub_std_duration() { + let instant = Instant::now(); + assert!(instant - 100.std_milliseconds() <= Instant::now()); +} + +#[test] +fn sub_assign_duration() { + let mut instant = Instant::now(); + instant -= 100.milliseconds(); + assert!(instant <= Instant::now()); +} + +#[test] +fn std_sub_assign_duration() { + let mut instant = StdInstant::now(); + instant -= 100.milliseconds(); + assert!(instant <= StdInstant::now()); +} + +#[test] +fn sub_assign_std_duration() { + let mut instant = Instant::now(); + instant -= 100.std_milliseconds(); + assert!(instant <= Instant::now()); +} + +#[test] +fn eq_std() { + let now_time = Instant::now(); + let now_std = StdInstant::from(now_time); + assert_eq!(now_time, now_std); +} + +#[test] +fn std_eq() { + let now_time = Instant::now(); + let now_std = StdInstant::from(now_time); + assert_eq!(now_std, now_time); +} + +#[test] +fn ord() { + let now_time = Instant::now(); + let now_std = now_time + 1.seconds(); + assert_eq!(now_time.cmp(&now_std), Ordering::Less); + + let now_time = Instant::now(); + let now_std = now_time - 1.seconds(); + assert_eq!(now_time.cmp(&now_std), Ordering::Greater); +} + +#[test] +fn partial_ord_std() { + let now_time = Instant::now(); + let now_std = StdInstant::from(now_time) + 1.seconds(); + assert!(now_time < now_std); + + let now_time = Instant::now(); + let now_std = StdInstant::from(now_time) - 1.seconds(); + assert!(now_time > now_std); +} + +#[test] +fn std_partial_ord() { + let now_time = Instant::now(); + let now_std = StdInstant::from(now_time) + 1.seconds(); + assert!(now_std > now_time); + + let now_time = Instant::now(); + let now_std = StdInstant::from(now_time) - 1.seconds(); + assert!(now_std < now_time); +} + +#[test] +fn sub_regression() { + let now = Instant::now(); + let future = now + Duration::seconds(5); + let past = now - Duration::seconds(5); + + assert_eq!(future - now, Duration::seconds(5)); + assert_eq!(now - past, Duration::seconds(5)); + assert_eq!(future - past, Duration::seconds(10)); + + assert_eq!(now - future, Duration::seconds(-5)); + assert_eq!(past - now, Duration::seconds(-5)); + assert_eq!(past - future, Duration::seconds(-10)); +} + +#[test] +fn as_ref() { + let now = Instant::now(); + assert_eq!(now.as_ref(), now.as_ref()); +} + +#[test] +fn borrow() { + use std::borrow::Borrow; + let now = Instant::now(); + assert_eq!( + >::borrow(&now), + >::borrow(&now) + ); +} diff --git a/tests/macros.rs b/tests/macros.rs new file mode 100644 index 000000000..855fad85c --- /dev/null +++ b/tests/macros.rs @@ -0,0 +1,367 @@ +use core::num::NonZeroU16; + +use rstest::rstest; +use time::format_description::modifier::*; +use time::format_description::{BorrowedFormatItem, Component}; +use time::macros::{date, format_description, time}; +use time::{Date, Time}; + +#[rstest] +fn nontrivial_string() { + assert!(format_description!(r"").is_empty()); + assert!(format_description!(r###""###).is_empty()); + assert!(format_description!(b"").is_empty()); + assert!(format_description!(br"").is_empty()); + assert!(format_description!(br###""###).is_empty()); + #[rustfmt::skip] + assert_eq!( + format_description!("foo\ + bar\n\r\t\\\"\'\0\x20\x4E\x4e\u{20}\u{4E}\u{4_e}"), + &[BorrowedFormatItem::Literal(b"foobar\n\r\t\\\"'\0 NN NN")] + ); + #[rustfmt::skip] + assert_eq!( + format_description!(b"foo\ + bar\n\r\t\\\"\'\0\x20\x4E\x4e"), + &[BorrowedFormatItem::Literal(b"foobar\n\r\t\\\"'\0 NN")] + ); +} + +#[rstest] +fn format_description_version() { + assert_eq!( + format_description!(version = 1, "[["), + &[BorrowedFormatItem::Literal(b"[")] + ); + assert_eq!( + format_description!(version = 1, r"\\"), + &[BorrowedFormatItem::Literal(br"\\")] + ); + assert_eq!( + format_description!(version = 2, r"\\"), + &[BorrowedFormatItem::Literal(br"\")] + ); +} + +#[rstest] +fn nested_v1() { + assert_eq!( + format_description!(version = 1, "[optional [[[]]"), + &[BorrowedFormatItem::Optional(&BorrowedFormatItem::Literal( + b"[" + ))] + ); + assert_eq!( + format_description!(version = 1, "[optional [ [[ ]]"), + &[BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound( + &[ + BorrowedFormatItem::Literal(b" "), + BorrowedFormatItem::Literal(b"["), + BorrowedFormatItem::Literal(b" "), + ] + ))] + ); + assert_eq!( + format_description!(version = 1, "[first [a][[[]]"), + &[BorrowedFormatItem::First(&[ + BorrowedFormatItem::Literal(b"a"), + BorrowedFormatItem::Literal(b"[") + ])] + ); +} + +#[rstest] +fn optional() { + assert_eq!( + format_description!(version = 2, "[optional [:[year]]]"), + &[BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound( + &[ + BorrowedFormatItem::Literal(b":"), + BorrowedFormatItem::Component(Component::Year(Default::default())) + ] + ))] + ); + assert_eq!( + format_description!(version = 2, "[optional [[year]]]"), + &[BorrowedFormatItem::Optional( + &BorrowedFormatItem::Component(Component::Year(Default::default())) + )] + ); + assert_eq!( + format_description!(version = 2, r"[optional [\[]]"), + &[BorrowedFormatItem::Optional(&BorrowedFormatItem::Literal( + b"[" + ))] + ); + assert_eq!( + format_description!(version = 2, r"[optional [ \[ ]]"), + &[BorrowedFormatItem::Optional(&BorrowedFormatItem::Compound( + &[ + BorrowedFormatItem::Literal(b" "), + BorrowedFormatItem::Literal(b"["), + BorrowedFormatItem::Literal(b" "), + ] + ))] + ); +} + +#[rstest] +fn first() { + assert_eq!( + format_description!(version = 2, "[first [a]]"), + &[BorrowedFormatItem::First(&[BorrowedFormatItem::Literal( + b"a" + )])] + ); + assert_eq!( + format_description!(version = 2, "[first [a] [b]]"), + &[BorrowedFormatItem::First(&[ + BorrowedFormatItem::Literal(b"a"), + BorrowedFormatItem::Literal(b"b"), + ])] + ); + assert_eq!( + format_description!(version = 2, "[first [a][b]]"), + &[BorrowedFormatItem::First(&[ + BorrowedFormatItem::Literal(b"a"), + BorrowedFormatItem::Literal(b"b"), + ])] + ); + assert_eq!( + format_description!(version = 2, r"[first [a][\[]]"), + &[BorrowedFormatItem::First(&[ + BorrowedFormatItem::Literal(b"a"), + BorrowedFormatItem::Literal(b"["), + ])] + ); + assert_eq!( + format_description!(version = 2, r"[first [a][\[\[]]"), + &[BorrowedFormatItem::First(&[ + BorrowedFormatItem::Literal(b"a"), + BorrowedFormatItem::Compound(&[ + BorrowedFormatItem::Literal(b"["), + BorrowedFormatItem::Literal(b"["), + ]) + ])] + ); + assert_eq!( + format_description!( + version = 2, + "[first [[period case:upper]] [[period case:lower]] ]" + ), + &[BorrowedFormatItem::First(&[ + BorrowedFormatItem::Component(Component::Period(modifier!(Period { + is_uppercase: true, + case_sensitive: true, + }))), + BorrowedFormatItem::Component(Component::Period(modifier!(Period { + is_uppercase: false, + case_sensitive: true, + }))), + ])] + ); +} + +#[rstest] +fn backslash_escape() { + assert_eq!( + format_description!(version = 2, r"[optional [\]]]"), + &[BorrowedFormatItem::Optional(&BorrowedFormatItem::Literal( + b"]" + ))] + ); + assert_eq!( + format_description!(version = 2, r"[optional [\[]]"), + &[BorrowedFormatItem::Optional(&BorrowedFormatItem::Literal( + b"[" + ))] + ); + assert_eq!( + format_description!(version = 2, r"[optional [\\]]"), + &[BorrowedFormatItem::Optional(&BorrowedFormatItem::Literal( + br"\" + ))] + ); + assert_eq!( + format_description!(version = 2, r"\\"), + &[BorrowedFormatItem::Literal(br"\")] + ); + assert_eq!( + format_description!(version = 2, r"\["), + &[BorrowedFormatItem::Literal(br"[")] + ); + assert_eq!( + format_description!(version = 2, r"\]"), + &[BorrowedFormatItem::Literal(br"]")] + ); + assert_eq!( + format_description!(version = 2, r"foo\\"), + &[ + BorrowedFormatItem::Literal(b"foo"), + BorrowedFormatItem::Literal(br"\"), + ] + ); + assert_eq!( + format_description!(version = 2, r"\\"), + &[BorrowedFormatItem::Literal(br"\")] + ); + assert_eq!( + format_description!(version = 2, r"\["), + &[BorrowedFormatItem::Literal(br"[")] + ); + assert_eq!( + format_description!(version = 2, r"\]"), + &[BorrowedFormatItem::Literal(br"]")] + ); + assert_eq!( + format_description!(version = 2, r"foo\\"), + &[ + BorrowedFormatItem::Literal(b"foo"), + BorrowedFormatItem::Literal(br"\"), + ] + ); +} + +#[rstest] +fn format_description_coverage() { + assert_eq!( + format_description!("[day padding:space][day padding:zero][day padding:none]"), + &[ + BorrowedFormatItem::Component(Component::Day(modifier!(Day { + padding: Padding::Space, + }))), + BorrowedFormatItem::Component(Component::Day(modifier!(Day { + padding: Padding::Zero, + }))), + BorrowedFormatItem::Component(Component::Day(modifier!(Day { + padding: Padding::None, + }))) + ] + ); + assert_eq!( + format_description!( + "[offset_minute padding:space][offset_minute padding:zero][offset_minute padding:none]" + ), + &[ + BorrowedFormatItem::Component(Component::OffsetMinute(modifier!(OffsetMinute { + padding: Padding::Space, + }))), + BorrowedFormatItem::Component(Component::OffsetMinute(modifier!(OffsetMinute { + padding: Padding::Zero, + }))), + BorrowedFormatItem::Component(Component::OffsetMinute(modifier!(OffsetMinute { + padding: Padding::None, + }))) + ] + ); + assert_eq!( + format_description!( + "[offset_second padding:space][offset_second padding:zero][offset_second padding:none]" + ), + &[ + BorrowedFormatItem::Component(Component::OffsetSecond(modifier!(OffsetSecond { + padding: Padding::Space, + }))), + BorrowedFormatItem::Component(Component::OffsetSecond(modifier!(OffsetSecond { + padding: Padding::Zero, + }))), + BorrowedFormatItem::Component(Component::OffsetSecond(modifier!(OffsetSecond { + padding: Padding::None, + }))), + ] + ); + assert_eq!( + format_description!("[ordinal padding:space][ordinal padding:zero][ordinal padding:none]"), + &[ + BorrowedFormatItem::Component(Component::Ordinal(modifier!(Ordinal { + padding: Padding::Space, + }))), + BorrowedFormatItem::Component(Component::Ordinal(modifier!(Ordinal { + padding: Padding::Zero, + }))), + BorrowedFormatItem::Component(Component::Ordinal(modifier!(Ordinal { + padding: Padding::None, + }))), + ] + ); + assert_eq!( + format_description!("[month repr:numerical]"), + &[BorrowedFormatItem::Component(Component::Month(modifier!( + Month { + repr: MonthRepr::Numerical, + padding: Padding::Zero, + } + )))] + ); + assert_eq!( + format_description!("[week_number repr:iso ]"), + &[BorrowedFormatItem::Component(Component::WeekNumber( + modifier!(WeekNumber { + padding: Padding::Zero, + repr: WeekNumberRepr::Iso, + }) + ))] + ); + assert_eq!( + format_description!("[weekday repr:long one_indexed:true]"), + &[BorrowedFormatItem::Component(Component::Weekday( + modifier!(Weekday { + repr: WeekdayRepr::Long, + one_indexed: true, + }) + ))] + ); + assert_eq!( + format_description!("[year repr:full base:calendar]"), + &[BorrowedFormatItem::Component(Component::Year(modifier!( + Year { + repr: YearRepr::Full, + iso_week_based: false, + padding: Padding::Zero, + sign_is_mandatory: false, + } + )))] + ); + assert_eq!( + format_description!("[[ "), + &[ + BorrowedFormatItem::Literal(b"["), + BorrowedFormatItem::Literal(b" ") + ] + ); + assert_eq!( + format_description!("[ignore count:2]"), + &[BorrowedFormatItem::Component(Component::Ignore( + Ignore::count(NonZeroU16::new(2).expect("2 is not zero")) + ))] + ); + assert_eq!( + format_description!("[unix_timestamp precision:nanosecond sign:mandatory]"), + &[BorrowedFormatItem::Component(Component::UnixTimestamp( + modifier!(UnixTimestamp { + precision: UnixTimestampPrecision::Nanosecond, + sign_is_mandatory: true, + }) + ))] + ); + assert_eq!( + format_description!("[end]"), + &[BorrowedFormatItem::Component(Component::End(modifier!( + End + )))] + ); +} + +#[rstest] +fn date_coverage() { + assert_eq!(Ok(date!(2000 - 001)), Date::from_ordinal_date(2000, 1)); + assert_eq!(Ok(date!(2019-W 01-1)), Date::from_ordinal_date(2018, 365)); + assert_eq!(Ok(date!(2021-W 52-6)), Date::from_ordinal_date(2022, 1)); + assert_eq!(Ok(date!(2021-W 34-5)), Date::from_ordinal_date(2021, 239)); +} + +#[rstest] +fn time_coverage() { + assert_eq!(time!(12 AM), Time::MIDNIGHT); + assert_eq!(Ok(time!(12 PM)), Time::from_hms(12, 0, 0)); +} diff --git a/tests/main.rs b/tests/main.rs new file mode 100644 index 000000000..bada9e1cb --- /dev/null +++ b/tests/main.rs @@ -0,0 +1,156 @@ +#![allow( + clippy::missing_const_for_fn, // irrelevant for tests + clippy::std_instead_of_core, // irrelevant for tests + clippy::std_instead_of_alloc, // irrelevant for tests + clippy::alloc_instead_of_core, // irrelevant for tests +)] + +#[cfg(not(all( + feature = "default", + feature = "alloc", + feature = "formatting", + feature = "large-dates", + feature = "local-offset", + feature = "macros", + feature = "parsing", + feature = "quickcheck", + feature = "serde-human-readable", + feature = "serde-well-known", + feature = "std", + feature = "rand", + feature = "serde", +)))] +#[test] +fn run_with_all_features() -> Result<(), Box> { + #[derive(Debug)] + struct Error(std::process::ExitStatus); + + impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } + } + + impl std::error::Error for Error {} + + let status = std::process::Command::new("cargo") + .args(["test", "--all-features"]) + .status()?; + + return if status.success() { + Ok(()) + } else { + Err(Box::new(Error(status))) + }; + + // Intentionally unreachable. This is to show the user a warning when they don't provide + // `--all-features`. + "Tests must be run with `--all-features`. Because the flag was not provided, `cargo test \ + --all-features` is run."; +} + +macro_rules! require_all_features { + ($($x:item)*) => {$( + #[cfg(all( + feature = "default", + feature = "alloc", + feature = "formatting", + feature = "large-dates", + feature = "local-offset", + feature = "macros", + feature = "parsing", + feature = "quickcheck", + feature = "serde-human-readable", + feature = "serde-well-known", + feature = "std", + feature = "rand", + feature = "serde", + ))] + $x + )*}; +} + +require_all_features! { + use std::sync::Mutex; + + /// A lock to ensure that certain tests don't run in parallel, which could lead to a test + /// unexpectedly failing. + static SOUNDNESS_LOCK: Mutex<()> = Mutex::new(()); + + // Required by the crate for technical reasons. + #[allow(clippy::single_component_path_imports)] + use rstest_reuse; + + /// Construct a non-exhaustive modifier. + macro_rules! modifier { + ($name:ident $({ + $($field:ident $(: $value:expr)?),* $(,)? + })?) => {{ + // Needed for when there are no fields. + #[allow(unused_mut)] + let mut value = ::time::format_description::modifier::$name::default(); + $($(value.$field = modifier!(@value $field $($value)?);)*)? + value + }}; + + (@value $field:ident) => ($field); + (@value $field:ident $value:expr) => ($value); + } + + /// Assert that the given expression panics. + macro_rules! assert_panic { + ($($x:tt)*) => { + assert!(std::panic::catch_unwind(|| { + $($x)* + }) + .is_err()) + } + } + + /// `assert_eq!` or `assert_ne!` depending on the value of `$is_eq`. + /// + /// This provides better diagnostics than `assert_eq!($left == $right, $is_eq)`. + macro_rules! assert_eq_ne { + ($left:expr, $right:expr, $is_eq:expr $(, $($rest:tt)*)?) => {{ + if $is_eq { + assert_eq!($left, $right $(, $($rest)*)?); + } else { + assert_ne!($left, $right $(, $($rest)*)?); + } + }} + } + + mod date; + mod derives; + mod duration; + mod error; + mod ext; + mod format_description; + mod formatting; + mod instant; + mod macros; + mod meta; + mod month; + mod offset_date_time; + mod parse_format_description; + mod parsed; + mod parsing; + mod primitive_date_time; + #[path = "quickcheck.rs"] + mod quickcheck_mod; + mod rand; + mod serde; + mod serde_helpers; + mod time; + mod utc_offset; + mod util; + mod weekday; + + #[cfg(__ui_tests)] + #[test] + fn compile_fail() { + let tests = trybuild::TestCases::new(); + // Path is relative from `time/Cargo.toml`. + tests.compile_fail("../tests/compile-fail/*.rs"); + } +} diff --git a/tests/meta.rs b/tests/meta.rs new file mode 100644 index 000000000..ded83b616 --- /dev/null +++ b/tests/meta.rs @@ -0,0 +1,1090 @@ +// Prefer runtime checks if possible, as otherwise tests can't be run at all if something is +// changed. + +use std::borrow::Borrow; +use std::error::Error as StdError; +use std::fmt::{Debug, Display}; +use std::hash::Hash; +use std::iter::Sum; +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use std::panic::{RefUnwindSafe, UnwindSafe}; +use std::time::{Duration as StdDuration, Instant as StdInstant, SystemTime}; + +use quickcheck::Arbitrary; +use rand::distributions::{Distribution, Standard}; +use serde::{Deserialize, Serialize}; +use time::format_description::well_known::iso8601; +use time::format_description::{modifier, well_known, BorrowedFormatItem, Component}; +use time::formatting::Formattable; +use time::parsing::{Parsable, Parsed}; +#[allow(deprecated)] +use time::Instant; +use time::{ + error, ext, Date, Duration, Error, Month, OffsetDateTime, PrimitiveDateTime, Time, UtcOffset, + Weekday, +}; + +#[allow(clippy::cognitive_complexity)] // all test the same thing +#[test] +fn alignment() { + macro_rules! assert_alignment { + ($t:ty, $alignment:expr) => { + let alignment = $alignment; + assert_eq!( + ::core::mem::align_of::<$t>(), + alignment, + "alignment of `{}` was {}", + stringify!($t), + alignment, + ); + }; + } + + assert_alignment!(Date, 4); + assert_alignment!(Duration, 8); + assert_alignment!(OffsetDateTime, 4); + assert_alignment!(PrimitiveDateTime, 4); + assert_alignment!(Time, 4); + assert_alignment!(UtcOffset, 1); + assert_alignment!(error::ComponentRange, 8); + assert_alignment!(error::ConversionRange, 1); + assert_alignment!(error::DifferentVariant, 1); + assert_alignment!(error::IndeterminateOffset, 1); + assert_alignment!(modifier::Day, 1); + assert_alignment!(modifier::Hour, 1); + assert_alignment!(modifier::Minute, 1); + assert_alignment!(modifier::Month, 1); + assert_alignment!(modifier::OffsetHour, 1); + assert_alignment!(modifier::OffsetMinute, 1); + assert_alignment!(modifier::OffsetSecond, 1); + assert_alignment!(modifier::Ordinal, 1); + assert_alignment!(modifier::Period, 1); + assert_alignment!(modifier::Second, 1); + assert_alignment!(modifier::Subsecond, 1); + assert_alignment!(modifier::WeekNumber, 1); + assert_alignment!(modifier::Weekday, 1); + assert_alignment!(modifier::Year, 1); + assert_alignment!(well_known::Rfc2822, 1); + assert_alignment!(well_known::Rfc3339, 1); + assert_alignment!( + well_known::Iso8601<{ iso8601::Config::DEFAULT.encode() }>, + 1 + ); + assert_alignment!(iso8601::Config, 1); + assert_alignment!(iso8601::DateKind, 1); + assert_alignment!(iso8601::FormattedComponents, 1); + assert_alignment!(iso8601::OffsetPrecision, 1); + assert_alignment!(iso8601::TimePrecision, 1); + assert_alignment!(Parsed, ::core::mem::align_of::()); + assert_alignment!(Month, 1); + assert_alignment!(Weekday, 1); + assert_alignment!(Error, 8); + assert_alignment!(error::Format, 8); + assert_alignment!(error::InvalidFormatDescription, 8); + assert_alignment!(error::Parse, 8); + assert_alignment!(error::ParseFromDescription, 8); + assert_alignment!(error::TryFromParsed, 8); + assert_alignment!(Component, 2); + assert_alignment!(BorrowedFormatItem<'_>, 8); + assert_alignment!(modifier::MonthRepr, 1); + assert_alignment!(modifier::Padding, 1); + assert_alignment!(modifier::SubsecondDigits, 1); + assert_alignment!(modifier::WeekNumberRepr, 1); + assert_alignment!(modifier::WeekdayRepr, 1); + assert_alignment!(modifier::YearRepr, 1); +} + +#[allow(clippy::cognitive_complexity)] // all test the same thing +#[test] +fn size() { + macro_rules! assert_size { + ($t:ty, $size:literal, $opt_size:literal) => { + assert!( + ::core::mem::size_of::<$t>() <= $size, + concat!("size of `{}` used to be ", $size, ", but is now {}"), + stringify!($t), + ::core::mem::size_of::<$t>(), + ); + assert!( + ::core::mem::size_of::>() <= $opt_size, + concat!( + "size of `Option<{}>` used to be ", + $opt_size, + ", but is now {}" + ), + stringify!($t), + ::core::mem::size_of::>(), + ); + }; + } + + assert_size!(Date, 4, 4); + assert_size!(Duration, 16, 16); + assert_size!(OffsetDateTime, 16, 16); + assert_size!(PrimitiveDateTime, 12, 12); + assert_size!(Time, 8, 8); + assert_size!(UtcOffset, 3, 4); + assert_size!(error::ComponentRange, 48, 48); + assert_size!(error::ConversionRange, 0, 1); + assert_size!(error::DifferentVariant, 0, 1); + assert_size!(error::IndeterminateOffset, 0, 1); + assert_size!(modifier::Day, 1, 1); + assert_size!(modifier::Hour, 2, 2); + assert_size!(modifier::Minute, 1, 1); + assert_size!(modifier::Month, 3, 3); + assert_size!(modifier::OffsetHour, 2, 2); + assert_size!(modifier::OffsetMinute, 1, 1); + assert_size!(modifier::OffsetSecond, 1, 1); + assert_size!(modifier::Ordinal, 1, 1); + assert_size!(modifier::Period, 2, 2); + assert_size!(modifier::Second, 1, 1); + assert_size!(modifier::Subsecond, 1, 1); + assert_size!(modifier::WeekNumber, 2, 2); + assert_size!(modifier::Weekday, 3, 3); + assert_size!(modifier::Year, 4, 4); + assert_size!(well_known::Rfc2822, 0, 1); + assert_size!(well_known::Rfc3339, 0, 1); + assert_size!( + well_known::Iso8601<{ iso8601::Config::DEFAULT.encode() }>, + 0, + 1 + ); + assert_size!(iso8601::Config, 7, 7); + assert_size!(iso8601::DateKind, 1, 1); + assert_size!(iso8601::FormattedComponents, 1, 1); + assert_size!(iso8601::OffsetPrecision, 1, 1); + assert_size!(iso8601::TimePrecision, 2, 2); + assert_size!(Parsed, 56, 56); + assert_size!(Month, 1, 1); + assert_size!(Weekday, 1, 1); + assert_size!(Error, 56, 56); + assert_size!(error::Format, 24, 24); + assert_size!(error::InvalidFormatDescription, 48, 48); + assert_size!(error::Parse, 48, 48); + assert_size!(error::ParseFromDescription, 24, 24); + assert_size!(error::TryFromParsed, 48, 48); + assert_size!(Component, 6, 6); // TODO Size is 4 starting with rustc 1.71. + assert_size!(BorrowedFormatItem<'_>, 24, 24); + assert_size!(modifier::MonthRepr, 1, 1); + assert_size!(modifier::Padding, 1, 1); + assert_size!(modifier::SubsecondDigits, 1, 1); + assert_size!(modifier::WeekNumberRepr, 1, 1); + assert_size!(modifier::WeekdayRepr, 1, 1); + assert_size!(modifier::YearRepr, 1, 1); +} + +macro_rules! assert_obj_safe { + ($($xs:path),+ $(,)?) => { + $(const _: Option<&dyn $xs> = None;)+ + }; +} + +assert_obj_safe!(ext::NumericalDuration); +assert_obj_safe!(ext::NumericalStdDuration); +// `Parsable` is not object safe. +// `Formattable` is not object safe. + +macro_rules! assert_impl { + ($(#[$meta:meta])* $($(@$lifetimes:lifetime),+ ;)? $type:ty: $($trait:path),+ $(,)?) => { + $(#[$meta])* + const _: fn() = || { + fn assert_impl_all<$($($lifetimes,)+)? T: ?Sized $(+ $trait)+>() {} + assert_impl_all::<$type>(); + }; + }; +} + +assert_impl! { @'a; Date: + Add, + Add, + AddAssign, + AddAssign, + Arbitrary, + Clone, + Debug, + Deserialize<'a>, + Display, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize, + Sub, + Sub, + Sub, + SubAssign, + SubAssign, + TryFrom, + Copy, + Eq, + RefUnwindSafe, + Send, + Sync, + Unpin, + UnwindSafe, +} +assert_impl! { @'a; Duration: + Add, + Add, + AddAssign, + AddAssign, + Arbitrary, + Clone, + Debug, + Default, + Deserialize<'a>, + Div, + Div, + Div, + Div, + Div, + Div, + Div, + Div, + Div, + Div, + DivAssign, + DivAssign, + DivAssign, + DivAssign, + DivAssign, + DivAssign, + DivAssign, + DivAssign, + Hash, + Mul, + Mul, + Mul, + Mul, + Mul, + Mul, + Mul, + Mul, + MulAssign, + MulAssign, + MulAssign, + MulAssign, + MulAssign, + MulAssign, + MulAssign, + MulAssign, + Neg, + Ord, + PartialEq, + PartialEq, + PartialOrd, + PartialOrd, + Serialize, + Sub, + Sub, + SubAssign, + SubAssign, + Sum<&'a Duration>, + Sum, + TryFrom, + Copy, + Eq, + RefUnwindSafe, + Send, + Sync, + Unpin, + UnwindSafe, +} +assert_impl! { #[allow(deprecated)] Instant: + Add, + Add, + AddAssign, + AddAssign, + AsRef, + Borrow, + Clone, + Debug, + From, + Hash, + Ord, + PartialEq, + PartialEq, + PartialOrd, + PartialOrd, + Sub, + Sub, + Sub, + Sub, + SubAssign, + SubAssign, + Copy, + Eq, + RefUnwindSafe, + Send, + Sync, + Unpin, + UnwindSafe, +} +assert_impl! { @'a; OffsetDateTime: + Add, + Add, + AddAssign, + AddAssign, + Arbitrary, + Clone, + Debug, + Deserialize<'a>, + Display, + From, + Hash, + Ord, + PartialEq, + PartialEq, + PartialOrd, + PartialOrd, + Serialize, + Sub, + Sub, + Sub, + Sub, + SubAssign, + SubAssign, + TryFrom, + Copy, + Eq, + RefUnwindSafe, + Send, + Sync, + Unpin, + UnwindSafe, +} +assert_impl! { @'a; PrimitiveDateTime: + Add, + Add, + AddAssign, + AddAssign, + Arbitrary, + Clone, + Debug, + Deserialize<'a>, + Display, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize, + Sub, + Sub, + Sub, + SubAssign, + SubAssign, + TryFrom, + Copy, + Eq, + RefUnwindSafe, + Send, + Sync, + Unpin, + UnwindSafe, +} +assert_impl! { @'a; Time: + Add, + Add, + AddAssign, + AddAssign, + Arbitrary, + Clone, + Debug, + Deserialize<'a>, + Display, + Hash, + Ord, + PartialEq